powered by TechFeed
表示モード
Deep Dive

AIエージェントの正体は80行のループだった — LangChainもCrewAIも、結局これをやっているだけ

7月23日、Sylwia Łaskが「The Dirty Secret Behind AI Agents (Demo 🚀)」と題した記事を公開した。LangChain、CrewAI、Mastraといったフレームワークが「魔法」に見えるのは錯覚で、AIエージェントの本質はNode.jsで約80行のコアループに過ぎない——そう実証してみせた記事だ。

7月23日、Sylwia Łaskが「The Dirty Secret Behind AI Agents (Demo 🚀)」と題した記事を公開した。LangChain、CrewAI、Mastraといったフレームワークが「魔法」に見えるのは錯覚で、AIエージェントの本質はNode.jsで約80行のコアループに過ぎない——そう実証してみせた記事だ。


AIエージェントの正体は「ループ」だった

LangChain、CrewAI、Mastra(TypeScript製のオープンソースAIエージェントフレームワーク)といったフレームワークが普及したことで、AIエージェントの構築には専用のフレームワークが必須という空気感が生まれている。だが実態はどうか。

Sylwiaは記事の冒頭でこう断言する。フレームワークは魔法ではない。会話メモリの管理、ツール実行、リトライ、フォールバックといった処理を簡略化しているだけで、仕組みそのものはシンプルだ。

そして実際に、Node.jsで約80行のコアループを持つAIエージェントを構築して見せた。ソースコードはGitHubで公開されている:

github.com/sylwia-lask/code-review-agent


デモ:コードレビュアー「Steve」

構築したのはコードレビューエージェント「Steve」だ。設定は「15年の経験を持つシニアソフトウェアエンジニア。皮肉屋だが、結論は的確」というキャラクター。ローカルのGit diffを読み込んでレビューを行う。

Steveが自分自身のコード(= Steveを実装したコード)をレビューする構造になっており、「自己修復エージェントのプロトタイプを作ってしまった」と本人も笑っている。


エージェントの動作原理:4ステップで完結

AIエージェントの仕組みは以下の4ステップに過ぎない。

Step 1:プロンプトと利用可能ツールをモデルに送る

ユーザーのメッセージと、モデルが使えるツールの一覧を同時に送信する。このデモで使えるツールは3つ:getDiffgetFilelistFiles。リクエストのJSON構造は概ね以下のようになる——モデル識別子、会話履歴、システムプロンプト、そしてツール宣言(名前・説明・引数スキーマ)をまとめて一つのオブジェクトとして渡す。

{
  "model": "gemini-2.5-flash",
  "contents": [
    { "role": "user", "parts": [{ "text": "Please review the current git diff." }] }
  ],
  "config": {
    "systemInstruction": "You are Steve, a senior software engineer with 15 years of experience...",
    "tools": [{
      "functionDeclarations": [
        { "name": "getDiff", "description": "Get the git diff of the current repository..." },
        { "name": "getFile", "description": "Read a file from the repository..." },
        { "name": "listFiles", "description": "List files and directories at a given path..." }
      ]
    }]
  }
}

Step 2:モデルのレスポンスを待つ

モデルは3種類のいずれかで返答する:テキストのみツール呼び出し要求その両方。テキストのみならレビュー完了。ツール呼び出しが含まれていればStep 3へ進む。たとえばモデルが「まずdiffを見せてくれ」と判断した場合、レスポンスにはテキストとfunctionCallが混在して返ってくる。

Step 3:ツールをローカルで実行し、結果を会話に追加する

アプリケーション側でgit diffを実行したり、ファイルを読んだりして、その結果を会話履歴の一部としてモデルに送り返す。

ここで重要なのが、会話全履歴をリクエストのたびに毎回送信するという点だ。LLMは本質的にステートレスであり、前回のやり取りを自分では記憶していない。各APIコールは独立しており、モデルが「文脈を理解している」ように見えるのは、アプリケーション側が毎回すべての履歴をコンテキストウィンドウに詰め込んで送り直しているからに過ぎない。フレームワークが「会話メモリ管理」と呼んでいるものの実体は、この履歴配列の管理である。

Step 4:モデルが終了を判断するまで繰り返す

Step 2に戻り、ループを続ける。無限ループによるトークン浪費を防ぐため、最大イテレーション数は10回に制限している。


フレームワークが担っているもの

このデモを実装する過程で、Sylwiaが直面した課題はフレームワークの存在意義を逆照射している。

Gemini APIを選んだ理由はトークン単価の安さだが、一部のモデルが高負荷で503エラーを頻発した。そのためリトライ処理を自前で実装する必要があった。実装したのはシンプルなものだが、本番レベルのフレームワークが提供する**指数バックオフ(exponential backoff)**、ジッター(jitter)、別モデルへの自動フォールバックには及ばない。

つまりフレームワークは「魔法」ではなく、こういった生産環境向けの堅牢化を肩代わりするものだ。仕組みを理解した上でフレームワークを選ぶのと、ブラックボックスとして使うのとでは、トラブルシューティングの速度が全く変わる。


モデルはなぜツール呼び出しを理解できるのか

記事の後半では、LLMがツール呼び出しをどう「理解」しているかについても触れている。

GeminiやGPTはネイティブでFunction Callingをサポートしており、SDKが適切なフォーマットでリクエストを組み立て、モデルがツール宣言を解釈してファンクションコールを生成する。

一方、古いLlamaモデルのようにネイティブサポートがないモデルでも、プロンプトでJSON形式の応答を指示すれば似たことができる。ただし、そのモデルが「valid JSONではなくMarkdownや説明文を返してくる」リスクは常にある。新しいオープンソースモデルはFunction Callingネイティブサポートを持つものが増えており、この状況は変わりつつある。


まとめ

記事の結論は明快だ。AIエージェントに必要なのはLLM、ツール、会話履歴、シンプルなループの4つだけ。残りはすべて「便利さ」と「本番環境向けの堅牢化」に過ぎない。

フレームワークを使う前に一度素の実装を試すことで、何がどこで起きているかを把握できる。その上でフレームワークを採用するかどうかを判断するのが、適切なアプローチといえる。

なおSylwiaは今後、SteveをMCP(Model Context Protocol)に接続する記事と、ホステッドモデルをローカルLLMに置き換える記事を執筆予定としている。

詳細はThe Dirty Secret Behind AI Agents (Demo 🚀)を参照していただきたい。