はじめに
AIエージェント開発において、function callingは「当たり前」の選択肢とされてきた。OpenAIが2023年にAPIとして提供して以来、ほぼすべてのエージェントフレームワークがfunction callingを前提に設計されている。
しかし、話題のAIエージェントManusの元バックエンドリードは、Redditの投稿で「2年のエージェント開発を経て、function callingを完全にやめた」と語った。411 upvotes、123コメントという大きな反響を呼んだこの投稿は、エージェント開発の常識に疑問を投げかけている。
本記事では、function callingの限界、代替アプローチとしてのコード生成、そしてClaude Codeの内部設計から得られる知見を整理する。
function callingの3つの限界
1. スキーマ管理の複雑性
function callingでは、各ツールをJSONスキーマで定義する。ツールが5個程度であれば問題ないが、実務のエージェントではツール数が数十個に膨らむ。すべてのツール定義はコンテキストウィンドウに含まれるため、ツールが増えるほどモデルが使える文脈が減る。
Manusの実績では、平均50回のツール呼び出しが1タスクに必要だった。50種類のツール定義をすべてコンテキストに載せると、それだけで数千トークンを消費する。
2. 表現力の制限
function callingの構造は本質的に「1回の呼び出しで1つのアクション」である。以下のような処理を表現しようとすると、すぐに限界にぶつかる。
- ループ処理(10個のファイルに同じ変換を適用)
- 条件分岐(レスポンスコードに応じて異なる処理を実行)
- エラーリカバリ(失敗時に代替手段を試行)
- 複数ステップの組み合わせ(検索→フィルタ→加工→保存)
これらはすべて、プログラミング言語であれば数行で書ける処理である。function callingでは、モデルとの往復を何度も繰り返す必要がある。
3. プロバイダ間の非互換性
OpenAI、Anthropic、Google、Mistralはそれぞれ異なるfunction calling仕様を持つ。ツール定義のフォーマット、レスポンスの構造、エラーハンドリングの方式が微妙に異なる。
マルチモデル戦略を採用するエージェント(Manusはまさにこれに該当する)では、この非互換性が開発・保守コストを押し上げる。
代替アプローチ: コード生成への転換
Manusが採用したのは、CodeActと呼ばれるパラダイムである。ICML 2024で発表されたこのアプローチは、エージェントのアクションをPythonコード(またはBashコマンド)の生成・実行で行う。
CodeActの基本的な考え方
従来のfunction calling方式:
{
"function": "search",
"arguments": {"query": "Python async patterns"}
}
CodeAct方式:
results = search("Python async patterns")
filtered = [r for r in results if r.relevance > 0.8]
for item in filtered:
content = fetch_page(item.url)
save_to_file(f"research/{item.title}.md", content)
後者では、検索→フィルタ→取得→保存を1回のアクションで完了できる。function callingでは4回以上の往復が必要になる処理である。
定量的な優位性
Apple Machine Learning Researchの論文によると、CodeActはfunction callingと比較して最大20%の成功率向上を示した。特に、複数ステップの組み合わせが必要なタスクでの差が顕著である。
CodeActの課題
万能ではない。以下のトレードオフが存在する。
- コードパースエラー: 2.4%の確率で構文エラーが発生し、連鎖的な失敗を引き起こすことがある
- モデル依存性: LLMのコーディング能力に直接依存する。能力の低いモデルでは効果が薄い
- サンドボックスの必要性: 任意のコードを実行するため、セキュリティの隔離が必須
Manusのアーキテクチャ — 実践的な設計パターン
Manusの公式ブログで公開されたアーキテクチャから、実務に応用可能なパターンを抽出する。
レイヤードアクションスペース
Manusはfunction callingを完全に排除したわけではない。正確には、function callingの範囲を極限まで絞り、残りをコード生成に委ねた設計である。
| レイヤー | 内容 | ツール数 |
|---|---|---|
| Function Calling層 | Bash実行、ファイル操作、コード実行などのアトミック操作 | 20未満 |
| サンドボックス層 | 実際のタスク処理。VM内でBashやMCPツール経由で実行 | 無制限 |
ツール定義の肥大化を防ぐために、機能をファイルシステムに格納し、必要に応じてサンドボックス内で呼び出す設計を採用している。
KVキャッシュの最適化
Manusの実データでは、入出力トークン比が100:1に達する。つまり、出力1トークンに対して100トークンの入力がある。この環境では、KVキャッシュのヒット率がコストに直結する。
| トークン種別 | コスト |
|---|---|
| キャッシュトークン | $0.30/MTok |
| 非キャッシュトークン | $3.00/MTok |
10倍のコスト差があるため、システムプロンプトやツール定義の先頭部分を安定させ、キャッシュヒット率を最大化する設計が極めて重要になる。ツールの動的な追加・削除はキャッシュを破壊するため、代わりにデコーディング時のlogitマスキングで特定のツールを無効化する手法を採用している。
ファイルシステムを「究極のコンテキスト」として活用
コンテキストウィンドウの制約を超えるため、Manusはファイルシステムを外部メモリとして活用する。
- todo.md: タスクの進捗管理。更新し続けることで、モデルの注意(アテンション)を目標に集中させる
- 中間結果のファイル保存: 大量データをコンテキストに載せず、ファイルに保存してからコードで処理する
- エラー情報の保持: 失敗したアクションとスタックトレースをコンテキストに残し、同じ失敗を繰り返さない
マルチエージェント構成
| エージェント | 役割 |
|---|---|
| Planner | タスク分解と割り当て |
| Knowledge Manager | 情報の取捨選択とファイルシステムへの保存判断 |
| Executor | 割り当てられたタスクの実行(分離コンテキスト) |
当初はPlannerを分離していなかったが、todo.mdの更新にアクション全体の約1/3を費やしていたことが判明し、専用エージェントとして切り出した。
Claude Codeの内部設計から学ぶ
Claude Codeのリバースエンジニアリング分析からは、Manusとは異なるアプローチの設計判断が見えてくる。
TAORループ
Claude Codeのオーケストレーションは驚くほどシンプルである。
- Think: モデルが次のアクションを決定
- Act: ツールを実行
- Observe: 出力をキャプチャ
- Repeat: 完了シグナルが出るまで繰り返し
このループを実装するコードはわずか約50行。すべての知能はモデル自身とシステムプロンプトに存在する。「オーケストレーターは薄く、モデルに任せる」という設計哲学である。
4つのプリミティブツール
Claude Codeが公開するツールは、100以上の専用統合ではなく、わずか4種類のプリミティブに絞られている。
| ツール | 機能 |
|---|---|
| Read | ファイル読み取り、grepパターン |
| Write | ファイル作成・変更 |
| Execute | Bashコマンド実行 |
| Connect | MCP経由の外部サービス接続 |
この設計の背景にある考え方は、「100の専用ツールを定義するより、4つの汎用ツールを組み合わせたほうが、モデルは柔軟に問題を解決できる」というものである。Manusの「アトミック操作を20未満に絞る」設計と思想的に共通している。
RAGを排除した検索設計
Claude CodeはRAG(Retrieval-Augmented Generation)を採用していない。類似度検索、リランカー、チャンキング戦略——これらを使わず、代わりにripgrep、jq、findコマンドとLLM駆動の検索ロジックを組み合わせている。
理由は「隠れた失敗モード」の排除である。RAGのパイプラインは、チャンキングの粒度、エンベディングの品質、リランキングの閾値など、多くのパラメータが絡み合い、失敗時の原因特定が困難になる。コマンドラインツールであれば、失敗は明示的で再現可能である。
Function Calling vs Code Generation — 6軸の比較
| 評価軸 | Function Calling | Code Generation |
|---|---|---|
| 表現力 | 1アクション=1ツール | ループ・条件分岐・関数定義が可能 |
| コンテキスト効率 | ツール数に比例して消費 | プリミティブのみ定義すれば良い |
| エラーハンドリング | フレームワーク依存 | コード内でtry-exceptが書ける |
| プロバイダ互換性 | 各社仕様が異なる | Python/Bashは共通 |
| 型安全性 | JSONスキーマで保証 | パースエラーのリスクあり(2.4%) |
| 学習コスト | フレームワーク固有の知識が必要 | プログラミングの一般知識で対応可能 |
どちらが「正解」というわけではない。小規模で固定的なツールセットならfunction callingが適切であり、複雑で動的なタスクにはコード生成が適している。
実際、ManusもClaude Codeもハイブリッド構成を採用している。基本的なインターフェースにはfunction calling的な構造を残しつつ、実際の作業はコード生成で行う。違いは「どこに境界線を引くか」である。
まとめ — エージェント設計の方向性
Manusの元リードの決断から読み取れるのは、「フレームワークの抽象化に頼りすぎない」という教訓である。
function callingは、ツールの数が少なく、タスクが単純な場合には優れた選択肢である。しかし、エージェントが扱うタスクの複雑性が増すにつれて、以下の方向への移行が見られる。
- ツール定義は最小限に: Manusは20未満、Claude Codeは4つのプリミティブ
- コード生成でアクションを表現: 条件分岐・ループ・エラーハンドリングはコードのほうが自然
- ファイルシステムを外部メモリとして活用: コンテキストウィンドウの制約を回避
- オーケストレーターは薄く: Claude Codeのループはわずか50行。知能はモデルに任せる
- KVキャッシュを意識した設計: プロダクション環境では10倍のコスト差がある
LLMの能力が向上するにつれて、「ハードコードされたロジック」は減り、「モデルに委ねる範囲」が広がる。Manusの公式ブログでも「ハーネス(制御構造)は時間とともに縮小すべき」と述べられている。
function callingを使うか使わないかという二項対立ではなく、「どこまでをフレームワークで制御し、どこからをモデルに委ねるか」——この境界線の設計こそが、エージェント開発の核心である。
参考
- I was backend lead at Manus(Reddit r/LocalLLaMA)
- Context Engineering for AI Agents(Manus公式ブログ)
- I reverse-engineered Claude Code(Reddit r/ClaudeCode)
- Claude Code Architecture (Reverse Engineered)(Substack)
- CodeAct: Your LLM Agent Acts Better when Generating Code(Apple ML Research / ICML 2024)
- What makes Claude Code so damn good(MinusX)