Slide 1

Slide 1 text

作って理解する Coding Agent 〜 フレームワークに頼らないピュアPythonでの実装 〜 Takanobu Nozawa(@takapy0210) 2026/8/22 PyCon JP 2026 #pyconjpA

Slide 2

Slide 2 text

いきなりですが... Coding Agentを使っている方󰢧 2

Slide 3

Slide 3 text

ほとんどの方が使っていると思います。 一方で、その裏側で何が起きているか自分の言葉で説明できますか? 3

Slide 4

Slide 4 text

本発表の湯上がり感♨ Coding Agentの基礎要素をPythonのコードとともに理解し、 「これなら自分でも実装できそう」というイケそう感が持てる ※今日お話するのは「Claude CodeやCodexに匹敵する最強のエージェントを作った」という話では ありません。あくまでCoding Agentの基本的な構成要素をPythonを通して理解する状態を目指します 4

Slide 5

Slide 5 text

00 自己紹介 & 会社紹介

Slide 6

Slide 6 text

自己紹介 名前: 野澤 哲照(Nozawa Takanobu) 所属/役割: コネヒト株式会社 / CTO : @takapy0210 / HN:たかぱい - ML Engineer / PdMなどをメインにデータ基盤整備などに従事 - 2026年4月からCTOに就任 - 猫とマンションとラーメンが好き - PyConJPは2021/2022/2024に登壇 PR:友人とPodcast配信してます wipfm 6

Slide 7

Slide 7 text

コネヒトのVISION あなたの家族像が実現できる 社会をつくる ありとあらゆる価値観が見つめ直され、 それぞれに思い描く家族の姿はどんどん変わっている。 家族の数だけ形があって、つくりたい未来がある。 私たちコネヒトは、その「家族像」というテーマに 向き合う会社です。 すべての家族が思い描く姿を実現できるように。 家族を学んで、アクションし、時にパートナーと一緒に、 「あなたの家族像」が実現できる社会を みなさんとつくってまいります。 7

Slide 8

Slide 8 text

コネヒトの事業 4つの課題を事業領域に定め、 事業開発・アライアンスなど様々な手法で解決を目指す 01 02 03 04 家計 不妊 育児 社会 の悩み の悩み の悩み の意識 8

Slide 9

Slide 9 text

ママリについて Q&Aコミュニティ ユーザーが悩みを投稿、相談しあうQ&A機能 専門家による回答も期間限定で提供 メディア 妊娠・育児などの記事を毎日配信 専門家監修の記事も多数 SNS インスタのハッシュタグ #ママリ の投稿数が 1000万件超 #ママ(約670万件)よりも投稿されている 9

Slide 10

Slide 10 text

01 作ったものと全体像 02 Agentを司る4つの最小構成要素を紹介 03 最小構成の課題と、課題を解決する拡張構成 04 まとめ Contents 10

Slide 11

Slide 11 text

01 作ったものと全体像 Coding Agent を構成要素に分解する

Slide 12

Slide 12 text

今回作成したCoding Agentの実行イメージ このAgentの実装について本発表では見ていこうと思います 12

Slide 13

Slide 13 text

今回実装したCoding Agentの実装規模 約2,000 行 今回はOpenAI / Anthropic / Googleの3モデルに対応しているので 規模が少し大きくなっていますが、 1つのLLM SDKだけで骨格を組むなら 200〜300行で実装できます。 ボリュームは意外と少ない 13

Slide 14

Slide 14 text

“ピュアPython” is … pyproject.tomlでインストールしているのはLLMの公式SDKのみ dependencies = [ "anthropic", "openai", "google-genai" ] 14

Slide 15

Slide 15 text

本日話すCoding Agentの構成要素 ① LLM APIとの対話 会話履歴とツール一覧を送り、テキストかツール呼び出しを受け取る ② Tool 実行 作業ディレクトリ内のファイルを読む/書く/編集する、コマンド実行する ③ 思考ループ ツール呼び出しがなくなるまで、LLM呼び出しとツール実行を交互に回す ④ ガードレール 逸脱と暴走を止めるサンドボックス、承認、ステップ上限 15

Slide 16

Slide 16 text

本日話すCoding Agentの構成要素 ④ ガードレール サンドボックス / 承認 / ステップ上限 ↔ ユーザー の指示 → ① LLM API との対話 Anthropic / OpenAI / Google ③ 思考ループ agentic loop ↔ ② Tool 実行 / ファイル読み書き・コマンドの実行 readFile / writeFile / editFile / execCommand 16

Slide 17

Slide 17 text

02 Agentを司る4つの最小構成要素を紹介 ①〜④について、実物のコードを見ながら理解を深める

Slide 18

Slide 18 text

まず、ディレクトリ構成と 設計のポイントを説明 18

Slide 19

Slide 19 text

ディレクトリ構成は以下の通り 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

Slide 20

Slide 20 text

ディレクトリ構成は以下の通り 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

Slide 21

Slide 21 text

各種SDKの差分をproviders/で吸収 各SDKでリクエストの形も返ってくる形もバラバラなので、 「GenerateParams を受け取って GenerateTextResult を返す」という形だけ をLanguageModelとして固定し、変換はプロバイダー層に任せている。 Anthropic messages.create OpenAI chat.completions Google generate_content → 呼び出し側が知っているのはこの1メソッドだけ。 LLMプロバイダーの追加は、この形に合わせる ファイルを1本足すだけで済む 21

Slide 22

Slide 22 text

ディレクトリ構成は以下の通り 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

Slide 23

Slide 23 text

ディレクトリ構成は以下の通り 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

Slide 24

Slide 24 text

ディレクトリ構成は以下の通り 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

Slide 25

Slide 25 text

設計のポイント providers/もtools/もcore/も参照するのは「types.py」だけで 互いをimportしていない。 3つを組み合わせて動くものにするのは「cli.py」に集約。 → 新たな LLM プロバイダを追加するコストが低くなったり、 実LLM API を呼ばずにテストできたり、責務の境界が明確にな る、というメリットがある 25

Slide 26

Slide 26 text

ディレクトリ構成は以下の通り 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

Slide 27

Slide 27 text

types.pyのコード例 27

Slide 28

Slide 28 text

cli.pyのコード例 28

Slide 29

Slide 29 text

①:LLM API との対話 29

Slide 30

Slide 30 text

LLM API呼び出しのステップ 各種LLM API には tool use (※) という機能がある。 名前から「LLM が関数を実行してくれる」と読めてしまうが、実際にはLLMは何も実行しない。 あくまで上図のようなやり取りを行うのみ ※:https://platform.claude.com/docs/en/agents-and-tools/tool-use/overview 30

Slide 31

Slide 31 text

LLMとの会話履歴はPythonのリスト LLM APIはステートレスなので前回のやり取り を何一つ覚えていない。そのため毎ターン 履歴のリストを丸ごと送り直している イメージ: 1 ターン目 system / user 2 ターン目 system / user / assistant / tool 3 ターン目 … + assistant / tool 31

Slide 32

Slide 32 text

②:Toolの実行 32

Slide 33

Slide 33 text

Toolの正体は 「dataclass」 1個 ファイルの読み書きなどに利用しているToolは「LLM向けの説明文とJSON Schema」と 「実処理の関数」を、この型(Tool)にまとめただけのもの 33

Slide 34

Slide 34 text

Toolのdescriptionには使い方などを記載する descriptionはLLM がツールを選ぶ唯一の手がかり ツール定義の中で、プロンプトエンジニアリングをしているのに等しいので大事 34

Slide 35

Slide 35 text

readFileやwriteFileはある程度シンプルだが editFileは考えることがある 35

Slide 36

Slide 36 text

editFile(差分編集Tool)のポイント ファイルの編集は「どこを」「どう」編集するのか?を定義する必要がある。 そこで、変更前の文字列と変更後の文字列をLLMに提案させ、変更前の文字列が ファイル中にちょうど1回だけ現れるときのみ編集するようにしている。 0回なら「見つかりません」2回以上なら「複数の候補が見つかりました。より具 体的な範囲を指定してください」とエラー(例外)を返す。 36

Slide 37

Slide 37 text

editFile(差分編集Tool)のポイント エラー(例外)は、文字列にして「role="tool"」のメッセージとして会話履歴に 積み、次のステップでLLMに渡している。 →editFile含め、Toolが投げたエラー文は人間ではなくLLMが読む(詳細は後述) 37

Slide 38

Slide 38 text

外部コマンド実行について execCommandは他のToolよりも危険度が高いので、安全側に倒して実装している 考えられるリスク 対応 ① 危険文字の事前検査 シェルインジェクション 1回の呼び出しで走るコマンドは1個だ けにする ② 自前の引数分割 文字列をシェルに解釈させること、 予期せぬコマンドを実行しかねない シェルの代わりに自分でparserを組 み、subprocess.runに渡している ③ ホワイトリスト 任意コマンドの実行 許可するコマンドを列挙 ④ パス引数の検証 ディレクトリトラバーサル サンドボックスディレクトリしか操作 できないようにする 38

Slide 39

Slide 39 text

③:思考ループ 39

Slide 40

Slide 40 text

Agentの思考ループはwhile文 やっていることは「LLM に聞く → ツールを実行する → 結果をリストに足す → もう一度聞く」のみ 40

Slide 41

Slide 41 text

異常系は例外処理ではなく、会話履歴に入れる ツールが見つからないときも承認を拒否されたときも例外が飛んだときも、Agentは動き続ける すべて role="tool" のメッセージとして会話履歴に積まれ、次のステップでLLM APIに渡している → エラー文言は人間向けであると同時に、Agentが自力で回復するためのプロンプトでもある。 エラーからの自律回復例: ステップ 1 execCommand → IndexErrorが文字列で返る ステップ 2 LLMがstderrを読み、readFileで原因を探す ステップ 3 editFileでエラー原因を直す ステップ 4 もう一度実行 例外を握りつぶしているように見えるが、これは自律回復のための設計 41

Slide 42

Slide 42 text

④:ガードレール 42

Slide 43

Slide 43 text

最小構成の守りは三つ ①サンドボックスの制限 ②承認 ツールを実行する直前にツール名と引数 を表示して y/n を待つ ③ステップ上限 無限ループを止める仕掛けとして、 ループの上限を設定 43

Slide 44

Slide 44 text

最小構成 デモ $ docker compose run --rm cli --verbose \ "ランダムな迷路を生成し、その最短経路を探索しターミナル上で迷路をスタートからゴール まで動く様子が見れるpythonスクリプトを書いて。 実装する際は以下のルールを守って - ファイル名は「maze_min.py」とすること - 迷路の壁は「█」で作成すること - スタート位置の表記は「S」、ゴール位置の表記は「G」とすること - スタートからゴールまで動かすオブジェクトは「*」にすること - 完成したら、最後に実行コマンドを表示すること " 44

Slide 45

Slide 45 text

03 最小構成の課題と、拡張構成

Slide 46

Slide 46 text

最小構成における課題 課題 改善方法 タスクごとに記憶が消える 会話履歴を次のターンに持ち回る(REPL) 長い会話でコンテキストが溢れる 古い履歴を要約に置き換える(圧縮) 調査でコンテキストを浪費する 調査を別のエージェントに分離する ツールが固定されている 外部プロセスのツールを取り込む 46

Slide 47

Slide 47 text

課題ごとにモジュールを拡張 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

Slide 48

Slide 48 text

課題:タスクごとに記憶が消える →REPLで解決 ※REPL=指示を打つと応答が返り、続けて次の指示を打てる対話モード(Python の対話シェルと同じ形) 48

Slide 49

Slide 49 text

課題:長い会話でコンテキストが溢れる →要約することで解決 素朴に「直近N件だけ残して古いほうを要約する」と実装すると API にリクエストごと拒否される→要約対象として切る位置を適 切な位置までずらす必要がある 49

Slide 50

Slide 50 text

課題:調査でコンテキストを浪費する →Sub Agentに調査させるようにすることで解決 Main Agent 履歴に入るのは最終レポートだけ ↓ task(prompt=...) ↑ Sub #1 Sub #2 50

Slide 51

Slide 51 text

Sub Agentのメリットは文脈が分離できること デメリット メリット Main Agentが複数ファイルを読み込む Sub Agentを使う 以降ずっと毎ターン送られ続ける 調査過程のトークンはMainの コンテキストを一切消費しない 調査が多いほど、Mainで使える コンテキストが減る Mainのコンテキストに入るのは 調査結果のみ 調査はSub Agentに任せ、Main Agentには結論だけを返す 51

Slide 52

Slide 52 text

課題:ツールが固定されている→ MCPとSkillで解決 MCP SKill workspace/.takapy/skills/*.md を読んで プロンプトの前に展開する 52

Slide 53

Slide 53 text

拡張版 デモ $ docker compose run --rm ext >> ランダムな迷路を生成し、その最短経路を探索しターミナル上で迷路をスタートからゴールまで動く様子 が見れるpythonスクリプトを書いて。 実装する際は以下のルールを守って - ファイル名は「maze_ext.py」とすること - 迷路の壁は「█」で作成すること - スタート位置の表記は「S」、ゴール位置の表記は「G」とすること - スタートからゴールまで動かすオブジェクトは「*」にすること - 完成したら、最後に実行コマンドを表示すること — ファイル編集を試す: >> 移動対象オブジェクトを「@」に変更してください 53

Slide 54

Slide 54 text

04 まとめ

Slide 55

Slide 55 text

まとめ 構成要素 実装 この発表で話したこと ① 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

Slide 56

Slide 56 text

まとめ 今回作成したコードは公開しているのでcloneして遊んでみてください! https://github.com/takapy0210/takapy-code-pyconjp2026 ※ コードはCoding Agentに9割くらい作ってもらいました 56

Slide 57

Slide 57 text

参考文献 ● 書籍:「作って学ぶAIエージェント」 ○ https://www.amazon.co.jp/dp/B0GTPPNKW4 ● 各種SDKの公式ドキュメント ○ Anthropic:https://platform.claude.com/docs/ja/home ○ OpenAI:https://developers.openai.com/api/docs ○ Google:https://ai.google.dev/api 57

Slide 58

Slide 58 text

ご清聴ありがとうございました! 58

Slide 59

Slide 59 text

59