Upgrade to Pro — share decks privately, control downloads, hide ads and more …

作って理解するCoding Agent 〜フレームワークに頼らないピュア Python での実装〜

作って理解するCoding Agent 〜フレームワークに頼らないピュア Python での実装〜

PyConJP 2026のDAY2の発表資料です。
https://2026.pycon.jp/ja/talks/CPFGDA

発表で利用したコードのリンク:https://github.com/takapy0210/takapy-code-pyconjp2026

Avatar for Takanobu Nozawa

Takanobu Nozawa

August 21, 2026

More Decks by Takanobu Nozawa

Other Decks in Technology

Transcript

  1. 自己紹介 名前: 野澤 哲照(Nozawa Takanobu) 所属/役割: コネヒト株式会社 / CTO :

    @takapy0210 / HN:たかぱい - ML Engineer / PdMなどをメインにデータ基盤整備などに従事 - 2026年4月からCTOに就任 - 猫とマンションとラーメンが好き - PyConJPは2021/2022/2024に登壇 PR:友人とPodcast配信してます wipfm 6
  2. 今回実装したCoding Agentの実装規模 約2,000 行 今回はOpenAI / Anthropic / Googleの3モデルに対応しているので 規模が少し大きくなっていますが、

    1つのLLM SDKだけで骨格を組むなら 200〜300行で実装できます。 ボリュームは意外と少ない 13
  3. 本日話すCoding Agentの構成要素 ① LLM APIとの対話 会話履歴とツール一覧を送り、テキストかツール呼び出しを受け取る ② Tool 実行 作業ディレクトリ内のファイルを読む/書く/編集する、コマンド実行する

    ③ 思考ループ ツール呼び出しがなくなるまで、LLM呼び出しとツール実行を交互に回す ④ ガードレール 逸脱と暴走を止めるサンドボックス、承認、ステップ上限 15
  4. 本日話すCoding Agentの構成要素 ④ ガードレール サンドボックス / 承認 / ステップ上限 ↔

    ユーザー の指示 → ① LLM API との対話 Anthropic / OpenAI / Google ③ 思考ループ agentic loop ↔ ② Tool 実行 / ファイル読み書き・コマンドの実行 readFile / writeFile / editFile / execCommand 16
  5. ディレクトリ構成は以下の通り takapy_code/ ├── types.py ① 土台:統一型(Tool / Message / LanguageModel)

    ├── providers/ ① 3 社の SDK を 1 つの共通 I/F に揃える │ ├── anthropic_provider.py │ ├── openai_provider.py │ ├── google_provider.py │ └── factory.py LanguageModel を作る ├── tools/ │ ├── read_file.py ② readFile │ ├── write_file.py ② writeFile │ ├── edit_file.py ② editFile │ ├── exec_command.py ② execCommand │ └── workspace.py ④ サンドボックスのパス検証 ├── core/ │ ├── generate_text.py ① LanguageModel への統一エントリ │ ├── approval.py ④ y/n の対話プロンプト │ └── agent.py ③ 思考ループ └── cli.py Agent を組み立てて動かす 19
  6. ディレクトリ構成は以下の通り takapy_code/ ├── types.py ① 土台:統一型(Tool / Message / LanguageModel)

    ├── providers/ ① 3 社の SDK を 1 つの共通 I/F に揃える │ ├── anthropic_provider.py │ ├── openai_provider.py │ ├── google_provider.py │ └── factory.py LanguageModel を作る ├── tools/ │ ├── read_file.py ② readFile │ ├── write_file.py ② writeFile │ ├── edit_file.py ② editFile │ ├── exec_command.py ② execCommand │ └── workspace.py ④ サンドボックスのパス検証 ├── core/ │ ├── generate_text.py ① LanguageModel への統一エントリ │ ├── approval.py ④ y/n の対話プロンプト │ └── agent.py ③ 思考ループ └── cli.py Agent を組み立てて動かす 20
  7. 各種SDKの差分をproviders/で吸収 各SDKでリクエストの形も返ってくる形もバラバラなので、 「GenerateParams を受け取って GenerateTextResult を返す」という形だけ をLanguageModelとして固定し、変換はプロバイダー層に任せている。 Anthropic messages.create OpenAI

    chat.completions Google generate_content → 呼び出し側が知っているのはこの1メソッドだけ。 LLMプロバイダーの追加は、この形に合わせる ファイルを1本足すだけで済む 21
  8. ディレクトリ構成は以下の通り takapy_code/ ├── types.py ① 土台:統一型(Tool / Message / LanguageModel)

    ├── providers/ ① 3 社の SDK を 1 つの共通 I/F に揃える │ ├── anthropic_provider.py │ ├── openai_provider.py │ ├── google_provider.py │ └── factory.py LanguageModel を作る ├── tools/ │ ├── read_file.py ② readFile │ ├── write_file.py ② writeFile │ ├── edit_file.py ② editFile │ ├── exec_command.py ② execCommand │ └── workspace.py ④ サンドボックスのパス検証 ├── core/ │ ├── generate_text.py ① LanguageModel への統一エントリ │ ├── approval.py ④ y/n の対話プロンプト │ └── agent.py ③ 思考ループ └── cli.py Agent を組み立てて動かす 22
  9. ディレクトリ構成は以下の通り takapy_code/ ├── types.py ① 土台:統一型(Tool / Message / LanguageModel)

    ├── providers/ ① 3 社の SDK を 1 つの共通 I/F に揃える │ ├── anthropic_provider.py │ ├── openai_provider.py │ ├── google_provider.py │ └── factory.py LanguageModel を作る ├── tools/ │ ├── read_file.py ② readFile │ ├── write_file.py ② writeFile │ ├── edit_file.py ② editFile │ ├── exec_command.py ② execCommand │ └── workspace.py ④ サンドボックスのパス検証 ├── core/ │ ├── generate_text.py ① LanguageModel への統一エントリ │ ├── approval.py ④ y/n の対話プロンプト │ └── agent.py ③ 思考ループ └── cli.py Agent を組み立てて動かす 23
  10. ディレクトリ構成は以下の通り takapy_code/ ├── types.py ① 土台:統一型(Tool / Message / LanguageModel)

    ├── providers/ ① 3 社の SDK を 1 つの共通 I/F に揃える │ ├── anthropic_provider.py │ ├── openai_provider.py │ ├── google_provider.py │ └── factory.py LanguageModel を作る ├── tools/ │ ├── read_file.py ② readFile │ ├── write_file.py ② writeFile │ ├── edit_file.py ② editFile │ ├── exec_command.py ② execCommand │ └── workspace.py ④ サンドボックスのパス検証 ├── core/ │ ├── generate_text.py ① LanguageModel への統一エントリ │ ├── approval.py ④ y/n の対話プロンプト │ └── agent.py ③ 思考ループ └── cli.py Agent を組み立てて動かす 24
  11. ディレクトリ構成は以下の通り takapy_code/ ├── types.py ① 土台:統一型(Tool / Message / LanguageModel)

    ├── providers/ ① 3 社の SDK を 1 つの共通 I/F に揃える │ ├── anthropic_provider.py │ ├── openai_provider.py │ ├── google_provider.py │ └── factory.py LanguageModel を作る ├── tools/ │ ├── read_file.py │ ├── write_file.py ② readFile types.pyとcli.pyを軽く紹介 ② writeFile │ ├── edit_file.py ② editFile │ ├── exec_command.py ② execCommand │ └── workspace.py ④ サンドボックスのパス検証 ├── core/ │ ├── generate_text.py ① LanguageModel への統一エントリ │ ├── approval.py ④ y/n の対話プロンプト │ └── agent.py ③ 思考ループ └── cli.py Agent を組み立てて動かす 26
  12. LLM API呼び出しのステップ 各種LLM API には tool use (※) という機能がある。 名前から「LLM

    が関数を実行してくれる」と読めてしまうが、実際にはLLMは何も実行しない。 あくまで上図のようなやり取りを行うのみ ※:https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview 30
  13. 外部コマンド実行について execCommandは他のToolよりも危険度が高いので、安全側に倒して実装している 考えられるリスク 対応 ① 危険文字の事前検査 シェルインジェクション 1回の呼び出しで走るコマンドは1個だ けにする ②

    自前の引数分割 文字列をシェルに解釈させること、 予期せぬコマンドを実行しかねない シェルの代わりに自分でparserを組 み、subprocess.runに渡している ③ ホワイトリスト 任意コマンドの実行 許可するコマンドを列挙 ④ パス引数の検証 ディレクトリトラバーサル サンドボックスディレクトリしか操作 できないようにする 38
  14. 最小構成 デモ $ docker compose run --rm cli --verbose \

    "ランダムな迷路を生成し、その最短経路を探索しターミナル上で迷路をスタートからゴール まで動く様子が見れるpythonスクリプトを書いて。 実装する際は以下のルールを守って - ファイル名は「maze_min.py」とすること - 迷路の壁は「█」で作成すること - スタート位置の表記は「S」、ゴール位置の表記は「G」とすること - スタートからゴールまで動かすオブジェクトは「*」にすること - 完成したら、最後に実行コマンドを表示すること " 44
  15. 課題ごとにモジュールを拡張 takapy_code/ext/ ├── agent.py ExtAgent(拡張版の思考ループ) ├── compaction.py コンテキスト圧縮(課題:履歴が溢れる) ├── subagent.py

    Sub Agent(課題:調査でコンテキストを浪費する) ├── mcp/ MCP クライアントと Tool への変換(課題:ツールが固定) ├── skills.py Markdown スキル(課題:ツールが固定) └── cli.py REPL つき CLI(課題:記憶が消える) 最小構成のコードは1行も書き換えずに、各構成要素にadd onする形で実装している 47
  16. Sub Agentのメリットは文脈が分離できること デメリット メリット Main Agentが複数ファイルを読み込む Sub Agentを使う 以降ずっと毎ターン送られ続ける 調査過程のトークンはMainの

    コンテキストを一切消費しない 調査が多いほど、Mainで使える コンテキストが減る Mainのコンテキストに入るのは 調査結果のみ 調査はSub Agentに任せ、Main Agentには結論だけを返す 51
  17. 拡張版 デモ $ docker compose run --rm ext >> ランダムな迷路を生成し、その最短経路を探索しターミナル上で迷路をスタートからゴールまで動く様子

    が見れるpythonスクリプトを書いて。 実装する際は以下のルールを守って - ファイル名は「maze_ext.py」とすること - 迷路の壁は「█」で作成すること - スタート位置の表記は「S」、ゴール位置の表記は「G」とすること - スタートからゴールまで動かすオブジェクトは「*」にすること - 完成したら、最後に実行コマンドを表示すること — ファイル編集を試す: >> 移動対象オブジェクトを「@」に変更してください 53
  18. まとめ 構成要素 実装 この発表で話したこと ① LLM API との対話 providers/, core/generate_text.py

    LanguageModelとMessageのリスト 毎ターン全会話履歴を送る ② Tool の実行 tools/read_file.py, write_file.py, edit_file.py, exec_command.py dataclassがToolの正体 descriptionはLLM 向けのプロンプト ③ 思考ループ core/agent.py 実態はwhile文 / 異常系を会話に戻して LLMの自己回復に利用する ④ ガードレール tools/workspace.py, core/approval.py ワークスペースの逸脱判定、y/n 承認、 ステップ上限 55
  19. 59