🤖

Claude Codeを使い倒して完成した俺流のバックエンド開発スタイル

に公開

はじめに

初めまして。EpicAIの佐藤と申します。

私は機械学習をバックグラウンドとする人間なので機械学習関連のコーディングは慣れているのですが、諸事情によりWebアプリのバックエンド開発をすることになりました。Webアプリ開発は多少は経験があるのですが、めちゃめちゃ自走力があるかというとそうではないので、Claude Codeを使って4ヶ月ほどバックエンド開発をしました。

私がClaude Codeを使い始めた頃はちょうどClaude Codeが話題になっていた頃で、流石に何かしらのコーディングエージェントは触らないといけないなあと思っていたので、Claude Code Max $200を契約し、単なる開発目的のみならず、AI駆動開発の自分なりの「型」を模索しながら開発を進めました。そしてその「型」が出来上がったのでこの記事で共有したいと思います。

前提

Claude Codeといっても使用用途やプロジェクトの性質などによって適切な使い方が異なると思うので、私が今回どのようなプロジェクト・目的でClaude Codeを使用したのかを以下に記載します。

  • Webアプリのバックエンドの初期開発フェーズ(リリース前)
  • 言語: Python
  • フレームワーク: FastAPI
  • 開発人数: 私1人
  • エディタ: VSCode

※ Claude Codeの基本的な使い方は本記事では解説しません

「型」の紹介

1. Hooksを使ってSlackに通知が来るようにする。

Claude Codeの

  • ツール使用の許可が求められた時
  • 実行が完了した時

の2ケースでSlackに通知が来るようにしました。通知先はDiscordでもLINEでも何でもいいですが、私はSlackにしました。

Claude Codeを使うならこの設定は必須級だと思っています。

具体的な設定方法は忘れてしまった&他の記事に詳細に書かれているのでそちらを参考にしていただきたいです。

Claude Codeの設定は

  • ~/.claude/settings.json: ユーザー設定
  • .claude/settings.json: プロジェクト設定
  • .claude/settings.local.json: プロジェクトの個人的な設定

という計3種類のjsonファイルに書くことができますが、通知設定はプロジェクト単位で毎回書くと面倒なので、~/.claude/settings.jsonに書くと良いです。こうすればどのプロジェクトでも通知が来るようになります。

ユーザー全体に通知を設定すると厄介になってくるのが、Claude Codeを複数のプロジェクトで同時並行で動かしていた時に、「通知は来たがどのプロジェクトからのどういう意図の通知かわからない」という問題です。

なので、Slackの通知テキストは

のように、

  • ツール使用の許可を求められているのか or 実行が完了したのかどうか
  • gitプロジェクトのあるパス(厳密にはパスのスラッシュをハイフンにreplaceしたテキスト)

がSlack通知に来るようにしました。

具体的には、~/.claude 以下に3つのファイルを作成しました。

~/.claude
  ├ notify-slack.sh
  ├ .env
  └ settings.json

具体的なファイルの中身は以下の通りです。

~/.claude/settings.json
{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/notify-slack.sh Notification"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/notify-slack.sh Stop"
          }
        ]
      }
    ]
  }
}
SLACK_WEBHOOK_URL="https://hooks.slack.com/services/hoge/hoge/hoge"

SLACK_WEBHOOK_URLはSlackの管理者画面から取得しましょう。

~/.claude/notify-slack.sh
#!/bin/bash

# スクリプトのディレクトリと設定ファイル
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
CONFIG_FILE="${SCRIPT_DIR}/.env"

# 設定ファイルの読み込み
if [ -f "$CONFIG_FILE" ]; then
    source "$CONFIG_FILE"
else
    echo "警告: 設定ファイル .env が見つかりません (${CONFIG_FILE})"
fi

# Webhook URLの確認
if [ -z "$SLACK_WEBHOOK_URL" ]; then
    echo "エラー: SLACK_WEBHOOK_URL が設定されていません" >&2
    echo "scripts/.env に SLACK_WEBHOOK_URL を設定してください" >&2
    exit 1
fi

# 初期メッセージの分岐
case "$1" in
    "Notification")
        TITLE="*ツール使用を許可してください*"
        ;;
    "Stop")
        TITLE="*実行が完了しました*"
        ;;
    *)
        TITLE="*第一引数を指定してください*"
        ;;
esac

# JSONファイルからプロジェクトパスを取得する
if [ ! -t 0 ]; then
    JSON_INPUT=$(cat)

TRANSCRIPT_PATH=$(echo "$JSON_INPUT" | jq -r '.transcript_path // empty' 2>/dev/null)
if [ -n "$TRANSCRIPT_PATH" ]; then
    PROJECT_DIR=$(basename "$(dirname "$TRANSCRIPT_PATH")")
    PROJECT_PATH_CLEANED=$(echo "$PROJECT_DIR" | sed 's/^-//')  # 先頭の - を削除
    MESSAGE="プロジェクトパス: ${PROJECT_PATH_CLEANED}"
fi

fi

# Slack通知の送信
curl -s -X POST "$SLACK_WEBHOOK_URL" \
    -H 'Content-Type: application/json' \
    -d @- <<EOF
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "${TITLE}\n${MESSAGE}"
      }
    }
  ]
}
EOF

通知先のSlackのワークスペースおよびチャンネルは固定です。もし通知先を会社のSlackにした場合、私がプライベートでClaude Codeを使用した時であっても、その通知が会社のSlackに行ってしまいます。それは嫌なので、これを機にプライベート用のSlackワークスペースを作り、そこでClaude Codeの通知専用のチャンネルを作り、そのチャンネルに通知が行くようにしました。

通知のスタイルとして、単に音が鳴るだけという方法を紹介する記事もありますが、音が鳴るだけだと、

  • 離席時に自分がそれを聞き逃すリスクがある
  • ツール使用の許可を求められているのか or 実行が完了したのかどうか がわからない
  • どのプロジェクトからの通知なのかがわからない

などの問題があるため、採用しませんでした。

Slackで通知するようにすれば、聞き慣れた通知音が鳴りますし(嫌な音だと感じる人もいるかもしれませんが)、スマホでも確認できるので、色々嬉しいことが多いです。

2. MCPの導入

私は Serena MCPだけ入れました。

https://github.com/oraios/serena

Serenaについては以下の記事で詳細に解説されているので、そもそもSerenaって何?と思った方は以下の記事を読んでいただきたいです。

https://azukiazusa.dev/blog/serena-coding-agent/

そもそも、コーディングエージェントの仕組みとして、Serenaのようなセマンティックなコード検索手法はデフォルトでサポートするべきだと考えています。

私はバックエンド開発だったのでSerenaだけで十分だと考えてそれだけ入れましたが、フロントエンド開発だとまた別のMCPが必要かもしれません。

MCPは入れすぎると無駄なcontextをLLMに入れることになってしまうので、私は必要だと感じたSerenaだけ入れました。

Serena MCPを導入すると1つだけ困ったことが起きるのですが、それは「Serenaを有効化するとClaude Code起動時に勝手にブラウザに飛ばされる」という現象です。

これを防止する方法が以下の記事に書かれているので、そちらに従って設定をしましょう。

https://zenn.dev/soramarjr/articles/c0210f128a4d2a

3. 基本的に常にultrathinkを使う

ultrathinkというワードを指示文に入れると、かなり長いthinkingトークン数が割り当てられるため、Claude Codeのoutputの質が上がります。

thinkingに使うトークン数の指定は、英語だと以下3段階あります。

  • think: 4000トークン
  • megathink, think hard: 10000トークン
  • ultrathink, think harder: 31999トークン

タスクの難易度に応じて、この3段階を適切に使い分けることが理想ですが、ケチって「think hardで指示を出したら上手くコーディングできなかったのでultrathinkで指示を出し直した」ということがちょくちょくありました。なので全部ultrathinkを使うのが気が楽でした。

単にgit commitするだけのような簡単な指示であればultrathinkは使わないですが、それ以外のシーンでは基本的にultrathinkを使うのが一番理想的です。

ただ、これをやるとrate limitに引っかかりやすくなります。私は最初Max $100プランでしたが、長時間使用するとrate limitに引っかかって作業が滞ってしまったため、$200プランに上げました。おかげでultrathinkを連発しても止まることはなくなり、開発がかなり快適になりました。$200払った人の特権だなと感じました。もちろんお金は結構かかりますが、個人的にも全然ペイすると思っていますし、ありがたいことに会社が負担してくれました(本当に感謝です)。

4. CLAUDE.mdを育成する感覚を持て

CLAUDE.mdは、Claude Codeが必ず参照するドキュメントなので非常に重要です。CLAUDE.mdに必要な情報が漏れていたり、逆に誤った情報が入っていたりすると、Claude Codeは意図しない方向に動く可能性があります。ですので、このファイルは適切に管理・更新をする必要があります。

よく言われる話ですが、最初にCLAUDE.mdを作る方法としては、以下の2ステップです。

  1. summarize this project
  2. /init 日本語で書いてください

この時にもultrathinkを使っても良いですが、多くの人がultrathinkなしでやっているので、ultrathinkを使うかどうかは気にしなくていいと思います。ここで作られるCLAUDE.mdはベースとしては十分な完成度でして、ここから適宜修正や情報追加などを行っていきます。

もちろん

  • プロジェクトの概要
  • コーディング規則
  • よく使うコマンド
  • 環境構築
  • アーキテクチャ設計

などを抜け漏れなく書くことは大前提として、中でも私がご紹介したい記載事項を紹介します。

誇張表現の抑制

コード、コメント、ドキュメント、説明文など、すべての出力において「完全」「重要」「最適」「最高」などの誇張表現を使用しないでください。事実に基づいた客観的な表現を使用してください。

を追記しましょう。Claude Codeは誇張表現を使いがちです。実装が不完全なのに「実装が完璧に完了しました!」とか平気で言ってきます。なのでこのテキストをCLAUDE.mdに書きましょう。

複雑な実装をすることへの対策

Claude Codeは勝手に余計な機能を追加したり、複雑な実装をしがちです。また、その結果、DRY原則に違反するコードを平気で書くこともあります。そのため、

DRY原則に従ってください
無駄なwrapperや複雑な抽象化をするのは避けてください

CLAUDE.mdに書きましょう。

Gitに関するルール

Claude Codeは勝手にgit commit, pushしたり、勝手にPRを作成したりすることがあり、これが結構厄介でした。なので

ユーザーからの許可がない限り、勝手にcommitしたりPRを作成したりしてはいけません

と書きましょう。これでもコンテキストが長くなってしまうと、コンテキストの圧縮時にClaude Codeがこのルールを忘れて、勝手にcommitすることがありました(許さん)。なので「勝手にcommitしてはいけません」という指示文は普段からよく使っていました。

もちろん、ブランチ戦略、ブランチの命名規則、commitメッセージに関する規則などGitに関する色々なルールもCLAUDE.mdに書きましょう。

PRのtitleとbodyの書き方に関するルール

PRのテンプレートを作成し、そのパスをCLAUDE.mdに書きましょう。PRのbody(それとtitleも)の書き方に関する指示としては、それだけでは不十分なので、以下のテキストをCLAUDE.mdに書きましょう。

PRのテンプレート文は @path/to/pr_template.md を使ってください。
PRのbodyやtitleを書く際は、PRのdiffに着目し、それに基づいて適切な粒度感で情報を書いてください。このPR内で新規作成したがその後削除した機能はdiffには現れないので、そのような情報はbodyやtitleに記載しないでください。このように、途中経過ではなくてあくまでdiffに現れるものをベースにbodyを書いてください。

なぜこれを書く必要があるのかというと、もしこれを書かない場合、

  1. Claude Codeへの最初に指示を出した時に、人間の要望+余計な実装(A)をしてしまう。
  2. Claude Codeが余計な実装(A)をしたことに人間が気づいたため、Claude Codeに余計な実装(A)を削除してもらう。
  3. Claude Codeに単に「PRを作成して」と指示すると、Claude Codeはこれまでの会話履歴や実装履歴を参考にPRを作るので、PRのbodyに「Aを削除」というテキストが入ってしまう。しかし、これは同じPR内で一旦作成されたものの、その後削除されたものなので、PRのdiffには現れない。そのため、PRのbodyではAに関する言及はしてはいけないはず。

という問題が発生するからです。どうやらClaude Codeは特段指示がない限り、PRのbodyを書く時にはdiffを見ようとはしない傾向があるようです。

また、同様の理由で、bodyに書く情報の粒度感がまちまちになるという以下の現象も発生しました。

  1. Claude CodeにPRのレビューをしてもらう
  2. レビュー結果に基づいてClaude Codeに修正をしてもらう
  3. 修正結果をcommit, pushする
  4. 必要があればPRのtitleとbodyを更新する

この時、Claude Codeは特に直近の記憶が新鮮だからなのか、たとえ軽微な修正をしたとしても、

元々PRのbodyに記載してあった事項 : 軽微な追加修正 = 8 : 2

くらいの割合でbodyを書いてきました。軽微な修正に関する言及は0~1割くらいの言及でいいはずなのに...

このケースでも、おそらくClaude CodeはPRのdiffに着目しないため、粒度感がばらばらなbodyを書いてしまったのでしょう。

ですので、上記のような問題を解決するために、「PRのdiffに着目し、それに基づいて適切な情報の粒度感でbodyを書いてください。」というテキストをCLAUDE.mdに記載することをお勧めします。

ドキュメントを参照させる

要件定義や詳細設計の段階でどのようなフォーマットでドキュメントを作成するかは個人や会社によって異なると思いますが、それをClaude Codeが参照できるようにmarkdown形式にして、./docs以下に置きましょう。そして、

プロジェクトの仕様は ./docs 以下に書かれてあるので、そちらに忠実に従って実装してください

というようにドキュメントに従って実装して欲しい旨をCLAUDE.mdに書きましょう。

PRレビュー時の観点

単に「PRをレビューしてください」とだけ指示すると、Claude Codeは本来チェックするべき観点をチェックしないことがあります。なので、レビュー時の観点は事前にCLAUDE.mdに記載しましょう。私は具体的に以下の事項を書きました。

  • もし過度に複雑な実装をしていたり、無駄なwrapperを作っていたり、DRY原則に反したりしている箇所があれば指摘してください。
  • README.mdやCLAUDE.mdも適切に更新されているかチェックしてください。
  • 修正するべきなのに修正が漏れている箇所がないか、プロジェクトコード全体を見渡してチェックしてください。
  • docs/ 以下のドキュメントに忠実に実装できているかチェックしてください。
  • PRのbodyとtitleをレビューし、必要があれば適切に修正をしてください。このとき、PRのdiffに着目し、それに基づいて適切な情報の粒度感でbodyを書いてください。このPRで新規作成したが削除した機能はdiffには現れないので、そのような情報はbodyに記載しないでください。このように、途中経過ではなくてあくまでdiffに現れるものをベースにbodyを書いてください。PRのbodyやtitleを修正する必要がなければ修正しなくていいです。

私がお勧めする、特にCLAUDE.mdに記載したい事項の紹介は以上です。

さて、私がこのセクションで「CLAUDE.mdを育成する感覚を持て」と書きましたが、その意図について説明したいと思います。

Claude Codeはよくも悪くも、入力テキストに忠実に従います。逆に、言われてないことはわかりません。言わなくても分かるよね、と思って指示文を適当に書いたり、重要なコンテキストを省いてしまったりすると、意図しない方向に実装してしまいます。なので、Claude Codeへの指示文は丁寧すぎるくらいに書くのがよいです。丁寧に書かなくても意図した通りに動いてくれることもありますが、意図しない方向に動いた時に手戻りするのが面倒です。なので、丁寧すぎるくらいに指示文を書くに越したことはありません。

CLAUDE.mdも同様です。Claude Codeに毎回教えるべき基本情報、ルールなどはしっかり抜け漏れなく書きましょう。

そして、忘れがちなのがCLAUDE.mdREADME.mdの情報更新です。何かしら新しい機能を作ったり、何かしらの変更があったりしたとき、CLAUDE.mdREADME.mdの情報更新を忘れないようにしましょう。これを忘れると、例えば実装コードとCLAUDE.mdに齟齬ができてClaude Codeが混乱する原因になります。

そんなことわかっているよ!と思うかもしれないですが、Claude Codeがうまく動いてくれない原因をたどると、だいたい「CLAUDE.mdの情報が誤っていた」や「CLAUDE.mdにその情報が載っていなかったから」というケースがほとんどでした。

単なるClaude Codeの能力不足で実装できなかった、ということはありませんでした。

結局は指示出しが悪いだけです。これでもかと丁寧丁寧丁寧に指示文を書くと意図したように動いてくれます。

簡単な指示文だけで自律的にClaude Codeが完璧に意図した通りに実装してくれることが理想的ですが、Claude Codeにはそこまでの自律性はないです。その点はCodexの方が評判が良いっぽいです。

Claude Codeの特性については以下の記事が参考になります。

https://qiita.com/MIDO-ruby7/items/177f4341af8b19984b80

Claude Codeの特性をしっかり把握すると、Claude Codeを使いこなせるようになります。

PRを作ってmergeするまでの流れとプロンプト例

基本的には以下の流れでPRを作成し、mergeまでします。今回の開発はほぼ個人開発だったので、mergeに関するルール(例えば他メンバーのapproveがないとmergeできない)はありませんでした。

  1. 最初の指示出し
  2. 必要があれば修正
  3. PRの作成

ここで/clear

  1. PRのレビュー
  2. 必要があれば修正
  3. 問題なければmerge

各ステップで用いた具体的なプロンプトと、その意図を以下に順番に紹介します。

1. 最初の指示出し

これはあまり指示文の型はなくて、Claude Codeに何をして欲しいのかを丁寧に説明します。例えば、

ultrathink 現状は〇〇ですが、実装は〇〇になっています。そこで、./docs 以下のドキュメントに忠実に従って、〇〇の機能を適切に修正してください。実装の際にはdevelopブランチからブランチを切って作業してください。勝手にcommitしてはいけません。

というような具合です。実装内容の詳細がすでにドキュメントに規定されている場合は、そのドキュメントのパスを教えてあげましょう。

この指示文の結果、Claude Codeが良さげな実装をしてくれたら、

git commit, pushしてください

と指示を出します。これは単純なコマンドの実行なのでultrathinkは不要です。まだcommitするレベルの実装ではないなと感じたら、次のステップ以降でcommitをします。

2. 必要があれば修正

最初の指示出しで80%くらいの完成度で実装してくれることが多いので、残りの細かい箇所の修正を要望します。

ultrathink 私は最初に〇〇だと要望しましたが、あなたの実装だと〇〇になっているため、私の要望を達成するように適切に修正して欲しいです。勝手にcommitしてはいけません。

というような具合です。

これを、意図した実装になるまで繰り返します。適宜git commit, pushもしましょう。

3. PRの作成

Claude Codeが意図した通りの実装をしてくれたら、PRを作成します。すでにブランチは切っているので、PRのテンプレート文に従ってPRを作成してもらいます。

使用するプロンプトは以下で固定です。

ultrathink formatterとlinterを適用し、git commit, pushしてください。そしてdevelopブランチにmergeするためのPRを作成してください。ただしCLAUDE.mdに記載した「PRのtitleとbodyの書き方に関するルール」に従ってください。

PRの作成が完了したら、実装フェーズは終わったので/clearします。

4. PRのレビュー

PRのレビューもClaude Codeが行います。Claude CodeがGitHubからPRの情報を取得できるように、事前にghコマンドをinstallしておきましょう。

PRのレビューは以下の指示文で依頼します。

ultrathink review PR #1 in detail, objectively, and very carefully in Japanese. 勝手にコードを修正してはいけません。

Claude CodeにPRをレビューさせる時のポイントは、「ほどよい詳細度でレビューをさせること」です。

どういうことかというと、細かいところまで入念に厳しくレビューして欲しい!と思ってClaude Codeにそのような指示文を出すと、Claude Codeは色々やりすぎてしまう傾向があるので、重箱の隅を突くようなレビュー結果を返してしまいます。そんなに細かいところは気にしなくて良いのにな...という感想になります。

とはいえ、雑な粒度感でレビューを依頼すると、それはそれで浅いレビュー結果しか返してくれないです。

なので、Claude Codeに程よい粒度感、詳細レベルでレビューしてもらうために指示文を作成するのに当初苦労しました。

解決策としては、指示文で「review PR #1 in detail, objectively, and very carefully in Japanese.」と言ってレビュー詳細度の要求を上げつつ、CLAUDE.mdに「もし過度に複雑な実装をしていたり、無駄なwrapperを作っていたり、DRY原則に反している箇所があればそれも指摘してください。」と言うことで「やりすぎはだめだよ!」と抑制することで、良い感じに詳細度が中和されて、意図した通りの程よいレビュー結果を返してくれるようになりました。

どこまで厳しくレビューして欲しいのかというのはプロジェクトによって異なると思うので、レビューの厳しさレベルは各自調整するのが良さそうです。この「上げ」と「下げ」を適切に調整すれば、Claude Codeは我々が意図した通りのレビューを返してくれるようになると思います。

また、「勝手にコードを修正してはいけません。」と言う指示文を加えているのもこだわりポイントです。単にレビューを依頼しただけなのに、Claude Codeが勝手にコードを修正することがあり、大変迷惑でした。

Claude Codeのレビュー結果は一旦人間が確認して、指摘事項それぞれについて

  • このPR内で修正をする
  • 別のPRで修正対応をする
  • この指摘事項は軽微なので今は無視してOK

という対処法を人間側で決めたいので、「勝手にコードを修正してはいけません。」という指示をすることで、レビュー結果のみを返してもらうようにしました。

5. 必要があれば修正

レビュー結果を踏まえて、もし修正が必要であれば、続けて修正の依頼を出します。

修正が完了したら、以下の指示文を使ってcommit, pushをします。

ultrathink formatterとlinterを適用し、git commit, pushしてください。また、PRのbodyとtitleを更新してください。ただしCLAUDE.mdに記載した「PRのtitleとbodyの書き方に関するルール」に従ってください。PRを修正する必要がなければ修正しなくていいです。

その後、/clearをして再度PRのレビューをします。これを、修正するべき事項がなくなるまで繰り返します。

6. 問題なければmerge

何度かレビューと修正を繰り返した結果、Claude Codeが「mergeしてOKだよ!」と言ってくれたら自分でapproveして、mergeしました。

以上がPRを作ってmergeするまでの流れとプロンプト例になります。

プロンプト作成時の注意点

Claude Codeは、コンテキストが長くなってしまうと、これまでのコンテキストを要約して圧縮します。

現状のClaude Codeだとこの圧縮がイマイチで、圧縮後はかなり精度が悪くなります。この圧縮アルゴリズムの最適化はClaude Codeの開発チームに委ねるしかないのですが、なるべくこの圧縮は避けたいところです。なので、私はこの圧縮に到達しないような指示出しを心がけていました。

具体的には、以下3点を気をつけました。

  • 一度に大量の指示を出しすぎない。 どうせPR単位で開発をするので、1度の指示では1つのことのみ依頼をする。

  • 会話のターン数(=消費トークン数)がなるべく増えないように、段階的に行って欲しいことは一度の指示文にまとめる。

    例) テストを全件実行し、テストが全件passすることを確認してください。その後、formatterとlinterを適用してください。その後、git commit, pushしてください。そして、必要があればPRを更新してください。

  • Claude Codeが無駄な検索をしないように、分かっている情報はなるべく全部渡す。

    例) hoge.pyにある関数aの中にある変数xに関する依頼をする時に、「xを〇〇にしてください」という指示文を出すと、Claude Codeはまずは変数xがどこにあるのかという検索をし、それに無駄にトークンを消費してしまいます。そうではなくて「hoge.pyにある関数aの中にある変数xを〇〇にしてください」という丁寧な指示を出せば、Claude Codeがxの場所を検索する必要がなくなって無駄なトークンを消費せずに済みますし、Claude Codeが意図しない挙動をするリスクを1つ減らせます。

難しいタスクの場合はどうしても圧縮に到達してしまうことがあり、それは仕方ないなと思うしかないですが、なるべく圧縮に到達しないように人間側が指示文を工夫することも重要だと考えています。

さいごに

私がClaude Codeを試行錯誤しながら使った結果、どのような型に落ち着いたのかを紹介しました。これはあくまでバックエンド開発の話なので、フロントエンド開発など別目的だとまた違った方法を模索する必要があるかもしれないです。あくまで1つの型だと理解していただけると助かります。

最後まで読んでいただき、ありがとうございました!

EpicAI Tech Blog

Discussion