真正的 Agent 场景远比单轮问答复杂。当用户问 "What's the current time and weather in Vancouver?" 时,模型无法凭自身知识回答(它不知道"现在"是什么时候),需要调用外部工具。下面完整展示这个过程中 Agent 框架与模型之间的每一步交互。
两轮工具调用的完整交互序列
**第一次 API 调用------Agent 框架发送初始请求:**
```javascript
// ═══ Request constructed by the Agent framework (1st call) ═══
{
"model": "Qwen3-0.6B",
"messages": [
{
"role": "system", // ← Written by developer
"content": "You are a helpful assistant. Use the provided tools to get real-time information when needed."
},
{
"role": "user", // ← User input
"content": "What's the current time and weather in Vancouver?"
}
],
"tools": [ // ← Tools defined by developer
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current date and time in a specific timezone",
"parameters": {
"type": "object",
"properties": {
"timezone": { "type": "string", "description": "Timezone name, e.g. America/Vancouver" }
}
}
}
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a specific city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" },
"unit": { "type": "string", "enum": "celsius", "fahrenheit" }
}
}
}
}
]
}
```
**模型返回工具调用请求(不是最终回复):**
```javascript
// ═══ Response returned by the API (model decides to call tools) ═══
{
"choices": [{
"message": {
"role": "assistant", // ← Generated by model
"content": null, // No text response
"tool_calls": [ // Model requests two tool calls
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_time",
"arguments": "{\"timezone\": \"America/Vancouver\"}"
}
},
{
"id": "call_def456",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\": \"Vancouver\", \"unit\": \"celsius\"}"
}
}
]
}
}]
}
```
注意,模型并没有直接回答用户的问题,而是返回了两个**工具调用请求**------它判断"当前时间"和"天气"需要通过工具获取,而且两者之间没有依赖关系,可以并行调用。**模型只是发出了调用请求,真正执行工具的是 Agent 框架**。这是理解 Agent 架构的关键:模型负责决策(调用什么工具、传什么参数),Agent 框架负责执行(实际调用 API、运行代码)。
**Agent 框架执行工具,然后发起第二次 API 调用:**
Agent 框架拿到模型的工具调用请求后,实际执行这两个工具(比如调用时间 API 和天气 API),然后将**完整的对话历史加上工具执行结果**一起发送给模型:
```javascript
// ═══ Request constructed by the Agent framework (2nd call) ═══
{
"model": "Qwen3-0.6B",
"messages": [
{
"role": "system", // ← Same as 1st call
"content": "You are a helpful assistant. Use the provided tools to get real-time information when needed."
},
{
"role": "user", // ← Same as 1st call
"content": "What's the current time and weather in Vancouver?"
},
{
"role": "assistant", // ← Model output from 1st call, included verbatim
"content": null,
"tool_calls": [
{ "id": "call_abc123", "function": { "name": "get_current_time", "arguments": "{\"timezone\": \"America/Vancouver\"}" } },
{ "id": "call_def456", "function": { "name": "get_weather", "arguments": "{\"city\": \"Vancouver\", \"unit\": \"celsius\"}" } }
]
},
{
"role": "tool", // ← Generated by Agent framework (tool execution result)
"tool_call_id": "call_abc123",
"content": "{\"timezone\": \"America/Vancouver\", \"datetime\": \"2025-09-13T05:18:47\", \"day_of_week\": \"Saturday\"}"
},
{
"role": "tool", // ← Generated by Agent framework (tool execution result)
"tool_call_id": "call_def456",
"content": "{\"city\": \"Vancouver\", \"temperature\": 13.2, \"unit\": \"celsius\", \"conditions\": \"clear\", \"humidity\": 93}"
}
],
"tools": ... // ← Same tool definitions as above, omitted
}
```
这里有三个关键细节:
-
**第二次请求包含了第一次的全部对话历史**------system 消息、user 消息、第一次的 assistant 回复(包含工具调用),以及新增的 tool 结果。这就是前面所说的"每次调用都是无状态的":模型不会"记住"上一次的对话,Agent 框架必须每次都把完整历史送回去。
-
**第一次的 assistant 消息被原样放回消息列表**------这让模型能"看到"自己之前做了什么决策。
-
**tool 消息通过 `tool_call_id` 与对应的工具调用关联**------模型据此知道哪个结果对应哪个调用。
**模型根据工具结果生成最终回复:**
```javascript
// ═══ Response returned by the API (final reply) ═══
{
"choices": [{
"message": {
"role": "assistant", // ← Generated by model
"content": "It's currently 5:18 AM on Saturday, September 13, 2025 in Vancouver.\n\nWeather: 13.2°C with clear skies and 93% humidity. It's quite cool this morning - you might want to grab a jacket."
}
}]
}
```
这一次模型没有返回 tool_calls,而是直接给出了文本回复------它判断已经有了足够的信息来回答用户的问题。如果模型认为还需要更多信息(比如用户追问"那东京呢?"),它会再次返回 tool_calls,Agent 框架再执行、再送回结果,如此循环。**这个"请求→工具调用→执行→送回结果→再请求"的循环,就是第一章介绍的 ReAct 循环在 API 层面的具体实现。**
用代码实现 Agent 的核心循环
理解了 JSON 结构之后,让我们用 Python 代码把上面的交互过程串起来。以下是一个最简的 Agent 实现------核心就是一个 while 循环:
```python
from openai import OpenAI
client = OpenAI()
── Tool definitions ──
tools = [
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current date and time in a specific timezone",
"parameters": {
"type": "object",
"properties": {
"timezone": {"type": "string", "description": "Timezone name, e.g. America/Vancouver"}
},
},
},
},
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a specific city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"},
"unit": {"type": "string", "enum": "celsius", "fahrenheit"},
},
},
},
},
]
── Tool execution function (stub with canned results; a real implementation
must parse the JSON `arguments` and call actual APIs) ──
def execute_tool(name, arguments):
if name == "get_current_time":
return '{"datetime": "2025-09-13T05:18:47", "day_of_week": "Saturday"}'
elif name == "get_weather":
return '{"temperature": 13.2, "unit": "celsius", "conditions": "clear", "humidity": 93}'
── Initial message list ──
messages = [
{"role": "system", "content": "You are a helpful assistant. Use tools to get real-time information when needed."},
{"role": "user", "content": "What's the current time and weather in Vancouver?"},
]
── Agent core loop ──
Production code needs a max_iterations cap here: as discussed later in
this chapter, Agents can get stuck repeating the same tool calls forever
while True:
response = client.chat.completions.create(
model="Qwen3-0.6B", messages=messages, tools=tools
)
assistant_message = response.choices0.message
Append model's response to message list (whether text or tool calls)
messages.append(assistant_message)
If no tool calls requested, the model has produced its final response
if not assistant_message.tool_calls:
print(assistant_message.content)
break
Execute each tool requested by the model, append results to message list
for tool_call in assistant_message.tool_calls:
result = execute_tool(tool_call.function.name, tool_call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
Return to top of loop, call model again with updated message list
```
这段代码的核心逻辑只有一个 while 循环和一个判断:**模型返回了 tool_calls 就执行工具并继续循环,没有就输出结果并退出**。整个过程中,`messages` 列表不断增长------每一轮都会追加模型的回复和工具的执行结果。
让我们跟踪 `messages` 列表在每一轮的变化:
**初始状态(第 1 次调用前):**
```
messages = [
{ role: "system", content: "You are a helpful assistant..." }, # 开发者写的
{ role: "user", content: "What's the current time and weather in Vancouver?" }, # 用户输入
]
```
**第 1 次调用后(模型返回工具调用):**
```
messages = [
{ role: "system", content: "..." },
{ role: "user", content: "What's the current time..." },
{ role: "assistant", tool_calls: get_current_time, get_weather }, # + Generated by model
{ role: "tool", tool_call_id: "call_abc", content: "{time...}" }, # + Executed by framework
{ role: "tool", tool_call_id: "call_def", content: "{weather...}" }, # + Executed by framework
]
```
**第 2 次调用后(模型返回最终回复,循环结束):**
```
messages = [
{ role: "system", content: "..." },
{ role: "user", content: "What's the current time..." },
{ role: "assistant", tool_calls: get_current_time, get_weather },
{ role: "tool", tool_call_id: "call_abc", content: "{time...}" },
{ role: "tool", tool_call_id: "call_def", content: "{weather...}" },
{ role: "assistant", content: "It's currently Saturday, Sep 13, 2025 in Vancouver..." }, # + Final reply
]
```
从这个过程可以清楚地看到:**Agent 框架的核心工作就是管理这个 messages 列表**------在合适的时机往里追加消息,然后把整个列表送给模型。本章后续所有的上下文工程技术,本质上都是在优化这个列表的内容和结构。