前回の投稿では、.ツール呼び出しは、出力としてテキストを生成するのではなく、AI モデルがどの関数をどの引数とともに使用するかを決定できるようにするメカニズムです。その投稿の終わりまでに、意思決定を行える体制が整いました。 get_current_weather または convert_currencyまたは、両方を同時に同時に呼び出して実行するか、どちらも呼び出さずにテキストのみを生成します。言い換えれば、モデルが次に何をするかを決定し、私たち (コードの残りの部分) がその決定を実行し、結果をモデルに送り返し、最終的にモデルは情報に基づいた回答をテキスト形式でユーザーに提供します。
このループのより高度なバージョンは、モデルの意思決定、コードの実行、結果の返送、モデルの応答を 1 ラウンド行っただけでは停止しません。最後に反応を起こすのではなく、 モデルは、1 つのツール呼び出しの結果を使用して、次のツール呼び出しを行うかどうかを決定できます。。ツール呼び出し投稿の最後ですでに述べたように、これは 反応ループ (理由 + 行動)、これにより、エージェントは 1 回の通話では解決できないタスクを処理できるようになります。
しかし、そのような作業には何が必要なのでしょうか?前回の投稿の並列呼び出しの例では、次のようにしました。 What's the weather in Athens and how much is 100 USD in EUR?これらは 2 つの異なるものであり、応答を得るために 2 つの異なるツールを使用する必要がありますが、互いに独立しています。言い換えれば、2 番目の質問に答えるために最初の質問からの情報を必要とせずに、これら 2 つの質問に独立して同時に答えることができます。
しかし、次のように尋ねたらどうなるでしょうか I bet my friend 100 EUR that it would rain in Athens today. If I won, how many USD is that? ここでは、モデルは呼び出しが必要かどうかを判断できません。 convert_currency 初めて電話がかかるまで get_current_weather そして、実際に雨が降ったかどうかを検出します。簡単に言えば、 2 番目の質問に対する答えは、最初の質問の結果に完全に依存します。 これはまさに、並列ツール呼び出しでは 1 ラウンドでは解決できない種類の依存関係であり、まさに React ループがそのように設計されているものです。
それでは、見てみましょう!
🍨 データクリーム AI、データ、テクノロジーに関するニュースレターです。これらのトピックに興味がある場合は、 ここから購読してください!
しかし、React ループとは一体何なのでしょうか?
あ 反応ループ シーケンス内で繰り返されるステップは 3 つだけです。
- 理由
- 仕事
- 検査

ループの開始時に、モデルを作成します。 理由 どの情報がユーザーにすでに知られており、ユーザーのクエリに正しい答えを提供するためにどのような追加情報が不足しているか。これはそうです 行為 この不足している情報を取得するために、適切なツールを呼び出すことによって。最後に、対応するツール呼び出しが実行され、その結果がモデルに返されると、モデルは 信じます 結果 (ツールの結果をコンテキストに追加します)。その後、再び議論に戻りますが、今回はその文脈にこの新しい観察が含まれています。このループは、利用可能な情報がユーザーのクエリに答えるのに十分であるとモデルが評価するまで繰り返され、この時点でツールの呼び出しを停止し、単純にテキストで応答します。
しかし、これは私たちがすでに知っているツール呼び出しに似ていませんか?ある種ではありますが、完全ではありません。ツール呼び出しの投稿で説明した内容と異なる部分は、ループ自体です。単一のツール呼び出しで、モデルは何かを要求してそれを取得し、その呼び出しに関する限りトランザクションは終了します。 React ループでは、新しい観察が次の推論ステップの新しいコンテキストになるため、会話は開いたままになり、モデルは学習したばかりの内容に基づいて計画を変更できます。
同じツール、新しいトリック
これを具体的にするために、冒頭の最良の例に戻り、信頼できる答えを提供するためにモデルが実際に何をする必要があるかを考えてみましょう。質問は次のとおりです。 I bet my friend 100 EUR that it would rain in Athens today. If I won, how many USD is that? 真ん中の条件文に注目してください。 if I won。モデルが通貨を変換する必要があるかどうかは、天気予報の呼び出しによって決まります。雨が降ったら、モデルは電話しなければなりません convert_currency 入力パラメーターとして 100 ユーロを使用し、変換された賞金を返します。雨が降らなければ、賭けは負けます。 convert_currency は無関係であり、モデルは 2 回目の呼び出しを行わずに、関連するテキストを直接返す必要があります。
言い換えれば、モデルは実際には、ツール呼び出しの完全なシーケンスを事前に計画することはできません。まず天気をチェックし、結果を観察し、その結果が賭けの条件に対して何を意味するかを推論し、それから初めて 2 回目のツール呼び出しが必要かどうかを判断する必要があります。応答に適した並列ツール呼び出しとは異なります。 What's the weather in Athens and how much is 100 USD in EUR?この質問にはループが必要です。
ReAct Loop の優れた点は、新しいツールを必要としないことです。方法が異なるだけで、同じ関数を引き続き使用できます。だから私たちは使用するつもりです get_current_weather そして convert_currency 前回作成したのと同じように、天気には open-meteo を、通貨換算には Frankfurter を使用します (どちらも API キーはまだ必要ありません)。
import requests
import json
from openai import OpenAI
client = OpenAI(api_key="your_api_key")
def get_current_weather(city: str, unit: str = "celsius") -> dict:
# Step 1: geocode the city name to coordinates
geo = requests.get(
"https://geocoding-api.open-meteo.com/v1/search",
params={"name": city, "count": 1}
).json()
lat = geo["results"][0]["latitude"]
lon = geo["results"][0]["longitude"]
# Step 2: fetch current weather
weather = requests.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": lat,
"longitude": lon,
"current": "temperature_2m,precipitation",
"temperature_unit": unit
}
).json()
return {
"city": city,
"temperature": weather["current"]["temperature_2m"],
"precipitation_mm": weather["current"]["precipitation"],
"unit": unit
}
def convert_currency(amount: float, from_currency: str, to_currency: str) -> dict:
response = requests.get(
f"https://api.frankfurter.dev/v2/rate/{from_currency}/{to_currency}"
).json()
rate = response["rate"]
converted = round(amount * rate, 2)
return {
"amount": amount,
"from_currency": from_currency,
"to_currency": to_currency,
"converted_amount": converted,
"rate": rate
}
前回と比べてわずかに増加していることに注目してください。 get_current_weather まだ戻ってくる precipitation_mmモデルではベットの状態を評価するためにこのフィールドが必要であるためです。他はすべて同じです。 tools スキーマも前回の投稿から変更されていません。
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather for a given city, including temperature and precipitation",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "The name of the city"},
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "convert_currency",
"description": "Convert an amount from one currency to another",
"parameters": {
"type": "object",
"properties": {
"amount": {"type": "number", "description": "The amount to convert"},
"from_currency": {"type": "string", "description": "The source currency code, e.g. EUR"},
"to_currency": {"type": "string", "description": "The target currency code, e.g. USD"}
},
"required": ["amount", "from_currency", "to_currency"]
}
}
}
]
また、コードがモデルのツール オプションを実際の Python 関数に送信するために使用するルックアップ ディクショナリを定義する必要もあります。
available_functions = {
"get_current_weather": get_current_weather,
"convert_currency": convert_currency
}
これにより、モデルによって文字列として返されたツール名から、実行する実際の Python 関数に進むことができます。このマッピングはすぐに必要になります。今回は、解決する必要があるツール呼び出しの数や、複数のツール呼び出しがあるかどうかが事前にわからないためです。
ループで考える
ここが本当に新しい部分です。リクエストを作成してツール呼び出しを読み取る代わりに、 交換全体をループでラップします。 各パスで、これまでの会話全体をモデルに送信し、ツールを要求したかどうかを確認し、要求した場合はそのツールを実行し、結果を追加して再度繰り返します。モデルがプレーンテキストで応答し、呼び出すツールが残っていない場合にのみ停止します。
messages = [
{
"role": "user",
"content": "I bet my friend 100 EUR that it would rain in Athens today. If I won, how many USD is that?"
}
]
max_iterations = 5
for i in range(max_iterations):
print(f"--- Step {i + 1}: Reason ---")
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
tools=tools
)
message = response.choices[0].message
messages.append(message)
# If there's no tool call, the model is ready to answer
if not message.tool_calls:
print("Final answer:")
print(message.content)
break
# Otherwise, act on every tool call the model requested
for tool_call in message.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"--- Step {i + 1}: Act ({function_name}) ---")
print(f"Calling {function_name} with {function_args}")
function_response = available_functions[function_name](**function_args)
print(f"--- Step {i + 1}: Observe ---")
print(function_response)
# Feed the observation back in so the next Reason step can use it
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(function_response)
})
また、注意してください max_iterations 必要だと判断したモデルを拘束するキャップ 「あと一つだけ詳しく」 無限にループすることによって。特別な重要性を持っています 通話ごとにモデル料金を支払うため それぞれのループ内で。
最終的に、ループの結果として得られる観察結果は、次のように結合されます。 role: "tool" メッセージ固有の tool_call_id。これにより、モデルは各結果をそれを生成した呼び出しと照合できるようになります。
すべての設定が完了したので、いよいよ React Loop が動作しているのを見ることができます。
したがって、私たちの賭けの質問は、実際の天気に応じて 2 つの方法のいずれかになります。両方を見てみましょう。
1. アテネで雨が降った場合、コードはターミナルに次のような内容を出力します。
--- Step 1: Reason ---
--- Step 1: Act (get_current_weather) ---
Calling get_current_weather with {'city': 'Athens'}
--- Step 1: Observe ---
{'city': 'Athens', 'temperature': 17.4, 'precipitation_mm': 3.2, 'unit': 'celsius'}
--- Step 2: Reason ---
--- Step 2: Act (convert_currency) ---
Calling convert_currency with {'amount': 100, 'from_currency': 'EUR', 'to_currency': 'USD'}
--- Step 2: Observe ---
{'amount': 100, 'from_currency': 'EUR', 'to_currency': 'USD', 'converted_amount': 108.5, 'rate': 1.085}
--- Step 3: Reason ---
Final answer:
It did rain in Athens today (3.2mm of precipitation), so you won the bet!
Your 100 EUR comes out to 108.50 USD at today's exchange rate.
2. アテネに雨が降らなかったら、次のような印刷結果が得られます。
--- Step 1: Reason ---
--- Step 1: Act (get_current_weather) ---
Calling get_current_weather with {'city': 'Athens'}
--- Step 1: Observe ---
{'city': 'Athens', 'temperature': 34.1, 'precipitation_mm': 0.0, 'unit': 'celsius'}
--- Step 2: Reason ---
Final answer:
Unfortunately, it did not rain in Athens today, so it looks like you lost the bet.
No currency conversion needed!
2 番目のシナリオで何が起こったかを確認してください。ループは 1 回だけ実行されました。モデルはこれを観察しました precipitation_mm だった 0.0賭けの条件が満たされていないと主張し、電話せずに中止した convert_currency。誰も彼に 2 番目のツール呼び出しをスキップするように指示しませんでしたが、ループの最初の実行で見たことに基づいて、彼は自分でこの決定を下しました。
これが、並列ツール呼び出しと React ループの主な違いです (少なくともこの単純なシナリオでは)。並行してツールを呼び出すと、プロセス全体をすぐに終了できず、 convert_currency。代わりに、並列セットアップでは、両方のツールが事前に呼び出され、モデルは後で最終応答を生成します。これは特に重要なので、覚えておいてください。 通話ごとに料金を支払います モデルさんへ。したがって、不必要な追加の呼び出しを行わずに、AI モデルの呼び出しを必要なものにアーキテクチャ的に制限できることが非常に重要です。
私の心の中で
では、React ループが実際に並列ツール呼び出しに勝るのはいつでしょうか?
答えは次のとおりです。ツール呼び出しの数、またはそれらの呼び出しの引数は、最初の呼び出しの結果を確認した後にのみ決定できます。
私たちのベットの例では、モデルはコールするかどうかを決定できません。 convert_currency そうでない限りはまったく get_current_weather 雨が降ったかどうかを示します。モデル世界の情報はまだ存在しないため、これを解決する高度なロジックはありません。モデルの世界から抜け出し、天気 API から外部情報を取得して、それをモデルのコンテキストに追加する必要があります。対照的に、並列ツール呼び出しでは、ツール呼び出しを開始する前に、モデルが必要なものをすでに認識していると想定されます。 React ループではそのような仮定は必要ありません。それにより、モデルは何が必要かを理解できるようになります。
具体的には、次の場合、ReAct ループが並列ツール呼び出しよりも優先されます。
- 条件の例のように、結果が別の呼び出しが必要かどうかの条件である場合。
- 後続の呼び出しの引数が、前の呼び出しによって返された値に依存する場合。たとえば、モデルが電話をかける前に都市が使用している通貨を確認する必要がある場合、
convert_currency正しいコード付き。 - たとえば、以前の結果が予期せず返された場合、ユーザーがジオコーディングを行わない都市名を指定した場合や、API がエラーを返した場合、モデルは見つかったものをすべて報告するのではなく、計画を適応させる必要があります。
それにもかかわらず、必要なすべてのツールとそのロジックがユーザーのメッセージだけから明らかである単純なケースでは、並列ツール呼び出しの方が実際にはより良い選択です。これにより、ラウンドトリップが減り、待ち時間が短くなり、同じ結果が得られるからです。
私にとって、並列ツール呼び出しから React Loop への移行で最も興味深いのは、実際に必要なコードがどれだけ少なくなるかということです 😅 :a for ループ、 if ステートメントと辞書検索。それでも、この少量のコードは驚異的に機能します。このフィードバック ループは、何らかの形で、人々が「エージェント」という言葉で意味するもののほとんどの背後にある本当のメカニズムです。
読んでいただきありがとうございます! ▪️
ここまで進んだなら、 ピアゴリズムが役立つかもしれません – 私たちは、チームが組織の知識を 1 か所で安全に管理できるようにするプラットフォームを構築しています。
この投稿は気に入りましたか?参加してください 💌サブスタック そして💼リンクトイン
特に明記されている場合を除き、すべての画像は著者によるものです さもないと









Leave a Reply