一句话:Agent 不是调一次工具就结束,而是在一个循环里不断"想→做→看→再想",直到任务完成。这个循环就是 Runtime——框架帮你封装了一切,但你得知道封装了什么。
划重点
- Agent 的核心不是某一次工具调用,而是循环——ReAct 模式:思考 → 行动 → 观察 → 再思考
- Runtime 的三个关键设计:状态管理(对话历史)、工具分发(函数映射)、终止条件(何时停下)
- 40 行核心代码就能写出 Agent Runtime 的最小闭环(minimal runtime)
- 框架(LangGraph、OpenAI Agents SDK)封装的不只是循环,还有状态持久化、错误恢复、工作流编排
上期回顾
上一期的结尾我们说:Agent 真正的突破,不是模型有了执行能力,而是 Runtime 把 LLM、Tool、Memory、Workflow 和 Feedback Loop 连接成了闭环系统。
今天就来拆这个 Runtime。
什么是 Agent Runtime?
回忆上一期的 Function Calling:用户提问 → 模型输出工具调用意图 → 程序执行 → 结果返回。这只是"动了一次手"。
但真实任务不是一步就能完成的。"帮我分析这份数据并生成报告",Agent 可能需要:读文件 → 跑分析 → 画图表 → 写报告 → 自检修改。多步骤、有状态、能自主决定下一步——这才是 Agent。
驱动这一切的运行引擎,就是 Agent Runtime。
它的核心是一个循环,叫 ReAct(Reason + Act):
┌───────────────────┐│ Reason(思考) │ ← 模型决定下一步做什么│ Act(行动) │ ← 调用工具执行│ Observe(观察) │ ← 结果喂回模型,进入下一轮 Reason└───────┬───────────┘ ↓ 任务完成了吗? ├── 没完成 → 继续循环 └── 完成了 → 输出最终回答
关键洞察:Agent 的下一步通常不是硬编码的,而是模型根据当前状态动态决定的。 这就是 Agent 能处理开放式任务的原因。不过在生产系统中,通常会给 Agent 加大量约束——限制可用工具、限制步骤数、强制审批节点——完全自由的 Agent 并不稳定。
核心代码:一个完整的 Agent 循环
下面用 Python 实现这个循环。工具定义部分上期已经讲过,这里直接用:
import json, openaiclient = openai.OpenAI() # 也支持 DeepSeek、Qwen 等兼容 API# 注:OpenAI 新版已推出 Responses API,但 chat.completions 仍是跨平台通用接口# 工具定义 + 实现(上期内容,这里简化)tools = [{"type": "function", "function": {"name": "get_weather","description": "查询城市天气","parameters": {"type": "object","properties": {"city": {"type": "string"}},"required": ["city"]}}}]tool_map = {"get_weather": lambda city: f"{city}今天 28°C,晴"}# ========== Agent 核心循环 ==========defagent_loop(user_input, max_turns=10): messages = [ {"role": "system", "content": "你是一个有用的助手。"}, {"role": "user", "content": user_input} ]for turn in range(max_turns): resp = client.chat.completions.create( model="gpt-4o", messages=messages, tools=tools, temperature=0 ) msg = resp.choices[0].message messages.append(msg) # 状态管理:累积上下文ifnot msg.tool_calls: # 终止:模型不再调工具return msg.contentfor call in msg.tool_calls: # 工具分发 func = tool_map[call.function.name] args = json.loads(call.function.arguments) result = func(**args) messages.append({"role": "tool","tool_call_id": call.id,"content": str(result) # 观察:结果喂回 })return"达到最大轮次"print(agent_loop("北京和上海今天哪个更热?"))
40 行代码,Agent Runtime 的最小闭环就跑起来了。
三个关键设计
状态管理:messages 列表
用户输入、模型回复、工具结果全在 messages 里累积。模型每次推理都能看到完整历史——这可以理解成 Agent 的临时工作记忆。
工具分发:tool_map
把函数名映射到实际实现。模型说"调 get_weather",程序去映射表找到对应函数执行。真实 Agent 可能有几十个工具,分发逻辑一样。真实系统通常还会加入工具不存在检查、异常捕获和重试机制——模型偶尔会幻觉出一个不存在的工具名。
终止条件:两道保险
模型自己决定不再调工具(自然结束),或达到最大轮次(防止无限循环)。缺一不可——没有 max_turns,一个幻觉工具调用就能让 Agent 死循环。生产系统的终止条件更复杂:超时、预算耗尽、评估器判定完成、工作流到达终止节点——控制权越来越多地从模型转向 Runtime。
手写 vs 框架
上面是手写的循环。实际项目用框架,同样的功能 OpenAI Agents SDK 大约 10 行:
from agents import Agent, Runner, function_tool@function_tooldefget_weather(city: str) -> str:returnf"{city}今天 28°C,晴"agent = Agent(name="助手", tools=[get_weather])result = Runner.run_sync(agent, "北京和上海哪个更热?")print(result.final_output)
框架帮你处理了错误重试、日志记录、流式输出、多 Agent 编排。但核心逻辑——那个 for 循环——和我们手写的一模一样。
但框架最核心的价值,不只是封装循环,而是把状态持久化、错误恢复、工作流编排等工程能力标准化。 LangGraph 最值钱的是 durable execution 和 graph orchestration,而不是循环本身。
知道循环怎么转的,你才能在框架出问题时知道去哪里找原因。
总结
| |
|---|
| |
| Reason + Act,Agent 的基本工作模式 |
| messages 列表累积上下文 = Agent 的工作记忆 |
| |
| 封装了循环 + 错误处理 + 编排,核心逻辑和手写一样 |
Runtime 是 Agent 的骨架,但目前它只有"工作记忆"(messages 列表)——对话结束就全丢了,下次什么都不记得。
下期预告:给 Agent 加记忆。为什么 Agent 会"忘事"?短期记忆和长期记忆分别怎么实现?