AI・機械学習
エージェントループ:トレンチコートを着た3つのループ
The Agentic Loop: Three loops in a trench coat (bobbytables.io)
要約
この記事では、自律型エージェントの構築における「エージェントループ」の概念を、単純な単一ループではなく、実際には3つの連携したループ(推論ループ、ツールループ、人間ループ)から成る複雑な構造として解説しています。各ループの役割と、それらがどのように連携して顧客に「エージェント的」な体験を提供するのかを、具体的なコード例を交えながら説明しています。
全文翻訳
エージェントループ:トレンチコートを着た3つのループ
自律型エージェントの構成要素は、見た目ほど単純ではありません。
ロバート・ロス
2026年7月14日
シェア
エージェントループはしばしば単純化されすぎています。それらは単一のループとして提示されますが、実際には顧客にとって「エージェント的」な体験を構成する3つのループがトレンチコートを着ているのです。私は(そう、私がこれを書きました、信じられないですよね?)エージェントループに関するまた別のブログを書くためにここにいます。例のコードブロックも疑似コードであり、これらのアイデアを説明するためのものです。また、投稿を複雑にするストリーミングは省略しましたが、これらの形状は同じままです。
この画像も私が作りました!
推論ループ
大規模言語モデル(LLM)を最も還元的に説明すると、テキストを受け取り、次の文字…いや、トークンを予測することです。あなたの元カノとは違い、彼らは本当にあなたの文章を完成させます。これは推論ループによって達成されます。
推論ループは3つの責任を負います。
チャット完了API呼び出し(次の単語を推論する)
ツールループへのツール使用リクエストを渡す(後述)
チャット履歴の永続化を管理する(ツールの結果、またはユーザーメッセージの)
エージェントを構築する際、あなたが作成する最初の「外部」ループは推論ループです。これは、システムプロンプト、ユーザーメッセージ、アシスタントメッセージ、および利用可能なツールをLLMの「チャット完了」エンドポイントに送信するループです。Anthropicにはそれがあります。OpenAIにもあります。OpenRouterはそれらすべてを簡単に利用できるようにします。
LLMがメッセージを返したら、会話を継続できるように、それらを「チャット履歴」に追加するのはあなたの責任です。これは配列、データベーステーブル、Redisキーなど、何でも構いません。それはあなたの自由です。
```javascript
chatMessages = [
{ role: "system", content: "あなたはカップルセラピストです。人々がうまくいくのを助けるか、決してうまくいかないことを悟らせます。あなたが話しているユーザーはトムという名前です。" },
{ role: "user", content: "レイニーがちょっと恋しいんだ、彼女に何て書けばいい?" }
]
```
```javascript
continueInferenceLoop = true
while (continueInferenceLoop) {
inferred = aiClient.completeChat(messages: chatMessages)
chatMessages.push({ role: "assistant", content: inferred.message.content, toolCalls: inferred.toolCalls })
if (inferred.toolCalls.length == 0) {
continueInferenceLoop = false
} else {
// ツールを処理する(後述)
}
}
finalResult = chatMessages.last.content
puts "Assistant responded with: #{finalResult}"
```
(ほとんどの)大規模言語モデルプロバイダーのAPI設計はステートレス設計です。これは、モデルプロバイダーが、あなたが以前にそれと行った会話について何も知らないことを意味します。毎回、会話全体を提供する必要があります。だから、「長い会話はトークンをより速く消費する」という警告が表示されるのです。はい、私はダッシュを入力しました。なぜなら、あなたは元カノに連絡すべきかどうかについての、積み上げられたメッセージの書物を送っているからです。
やめろ、トム。
ツールループ
LLMは瓶の中の脳です。それらはそれ自体では機能的な価値を提供しません。LLMに与えるツールが、それをエージェントにするものです。
外部推論ループでモデルに「ここにあなたが持っているツールがあります」と伝えると、モデルは推論(応答)でそれらを使用しようとするかもしれません。これは、脳が、送信したいメールのEnterキーを必死に押そうとしている指に電気信号を送るのと同じことです。トム、境界線を設定する必要があります。
APIリクエストに含める個別のツール定義は、通常、モデルが処理するトークンストリームのシステムプロンプトフィールドにシリアライズされます。そして、モデルは1回のターンで複数のツールの使用を推論する可能性があります。(したがって:ツールループ)。
```javascript
chatMessages = [
{ role: "system", content: "あなたはカップルセラピストです。人々がうまくいくのを助けるか、決してうまくいかないことを悟らせます。あなたが話しているユーザーはトムという名前です。" },
{ role: "user", content: "レイニーがちょっと恋しいんだ、彼女に何て書けばいい?" }
]
tools = [
{
type: "function",
function: {
name: "send_ex_girlfriend_an_email",
parameters: {
type: "object",
properties: {
email_message: { type: "string" }
},
required: ["email_message"]
}
}
}
]
```
```javascript
continueInferenceLoop = true
while (continueInferenceLoop) {
inferred = aiClient.completeChat(messages: chatMessages, tools: tools)
chatMessages.push({ role: "assistant", content: inferred.message.content, toolCalls: inferred.toolCalls })
if (inferred.toolCalls.length == 0) {
continueInferenceLoop = false
} else {
// 見よ…ツールループ!
while (tool = inferred.toolCalls.shift) {
// ここでメール送信などのツールを実行する
// 名前によってどのツールを制御するか、スイッチ/ケース文を使用する場合があります。
// ツール呼び出しにはIDも含まれます
toolResult = "...次のセクションを参照..."
// ツール結果の形状は、プロバイダーのAPIによって異なります
chatMessages.push({
role: "user",
content: [
{
type: "tool_result",
toolCallId: tool.toolCallId,
content: toolResult
}
]
})
}
}
}
```
あなたのツールループは、モデルが使用すると推論したツールを検索し、モデルが提供したパラメータであなた自身の関数を呼び出す必要があります。しかし、いくつかの点に注意してください。
ツール呼び出しも推論されたテキストであるため、ツール名や関数パラメータが誤って生成される可能性があります。あなたは「ツールが見つかりません:"call_my_ex_girlfriend"」のようなもので、これらの不正なツール呼び出しに対して防御的であるべきです。
APIは、ツール呼び出しID(Anthropicを使用している場合はtool_use_id)で応答します。このIDは、APIとモデルが呼び出しと応答を相関させるために使用されます。ツール呼び出しIDなしで後続の完了リクエストを送信すると、APIはエラーになります。
一部のプロバイダーでは、ツール結果にエラーコードやエラー状態がありません。結果のコンテンツがエラーです。XMLタグを使用してエラーを示すことができます。例:`<tool_call_error>Phone number blocked</tool_call_error>`
Anthropicにはis_errorフィールドがありますが、それも同様です。
人間ループ
このループの名前については議論しました。このループは存在する必要はありません。人間である必要もありません。しかし、AIは人間の利益になるべきだと私は信じているので、私はそれに固執します。もしあなたが反論したいなら、それを「安全ループ」または「正気ループ」と呼ぶこともできます。どちらも機能するでしょう。
エージェント実装の制御を好むと仮定すると、ツールを承認/拒否するか、モデルを別の方向に誘導するために、3番目のループを実装する必要があります。
また、この「ループ」はプログラム的なループではないことを認めなければなりません。それはよりブロッキング関数呼び出しであり、誰かまたは何かが、ツール呼び出しを進めるかどうかを自分でループしています。ループはあなたのコードの範囲内ではなく、承認者の範囲内にあります。
```javascript
while (tool = inferred.toolCalls.shift) {
result = ""
case tool.name {
when "send_ex_girlfriend_an_email" {
// このループをブロックする。
// これはSMS、Eメールを送信するか、決定ボタンを表示するかもしれません。
decision = wait_for_approval(tool.parameters)
if (decision.approved?) {
send_email(tool.parameters["email_message"])
result = "Email sent"
} elsif (decision.denied_with_instructions?) {
result = "<tool_call_denied_with_instructions> #{decision.instructions} </tool_call_denied_with_instructions>"
} else {
result = "<tool_call_error>Tool call was not approved</tool_call_error>"
}
}
else {
result = "<tool_call_error>The tool '#{tool.name}' was not found</tool_call_error>"
}
}
chatMessages.push({
role: "user",
content: [
{
type: "tool_result",
toolCallId: tool.toolCallId,
content: result
}
]
})
}
```
人間ループは、エージェントシステムで実装するのが最も難しい部分と言えるでしょう。数時間ブロックされるコード片を持つことはできません。サーバーが再起動したらどうしますか?応答する必要のある他のリクエストが数千件来たらどうしますか?最初の2つのループ(推論とツール)は十分に単純です。人間ループは難易度を上げます。だからこそ、Temporalのような耐久性のある実行フレームワークが存在するのです。
しかし、人間ループは必要です。なぜなら、トムがレイニーにあのメッセージを送るのを実際に止める唯一のものだからです。2年も前のことだよトム、前に進もう!
ループバック
すべてをまとめると:
推論ループ - チャット完了APIを呼び出し、ツール呼び出しをあなたの…
ツールループ - モデルが実行しようとしているツール使用リクエストを処理し、承認をあなたの…
人間ループ - 承認または新しい方向性を求めるために、に引き渡します。この結果はツールの結果として伝播します。
これら3つのループは、