真正的 Agent 不是 while true,而是控制流、状态和失败恢复
Agent 不只是套个 while 循环
上一篇结尾那个工具调用循环——发请求、调工具、回填、再发请求——容易让人得出一个结论:Agent 不就是这个吗,套个 while 就完事了。
跑几个简单任务确实够用。但任务一复杂,这个朴素循环的问题就暴露出来:它没有规划,经常绕远路甚至原地打转;它没有记忆,多聊几轮上下文就爆;它什么都自己扛,工具一多就选不准。从"能调工具的循环"到"靠谱的 Agent",中间隔着控制流的选型、记忆的设计,以及该不该拆成多个 Agent 的判断。这一篇把这三件事讲清楚。下文代码统一用 DeepSeek 作示例供应商。
ReAct 与 Plan-and-Execute
Agent 的控制流,主流是两种思路,先搞清楚它们各自适合什么。
ReAct(Reasoning and Acting)就是上一篇那个循环再加上显式推理:思考一下、调个工具、观察结果、再思考、再调。每一步都是即兴的,模型看着上一步的结果临时决定下一步。它的好处是灵活,能根据观察动态调整方向,特别适合步数不确定、需要探索的任务。短板也明显:没有全局规划,容易走偏、绕路,甚至在两三个工具间反复横跳;每一步都要一次模型请求,步数一多又慢又贵,错误还会逐步累积。
Plan-and-Execute 是另一种思路:先让模型把整件事想清楚,产出一个完整的步骤计划,然后再逐步执行。它有全局视野,不容易绕路;计划定下来后,执行阶段甚至可以用更便宜的模型、或让无依赖的步骤并行跑,省钱省时间。代价是计划预先定死,执行中遇到意外(某步失败、结果和预期不符)就僵住——所以它必须配一个 replan 机制,发现偏离就回去重新规划。它适合步骤相对可预期的任务。
实践里这两者常混着用:用一个 planner 给出粗粒度计划,每个步骤内部用 ReAct 灵活处理,一旦执行结果明显偏离预期就触发 replan。不存在哪个绝对更好,关键看任务是"探索型"还是"流程型"。
记忆:短期靠压缩,长期靠检索
朴素循环的第二个硬伤是没有记忆。对话多几轮,把全部历史塞进上下文,迟早撑爆窗口,而且越塞越贵越慢。Agent 的记忆要分两层来设计。
短期记忆,本质是当前会话的上下文管理。最简单是滑动窗口,只保留最近 N 轮,但这样早期的关键信息会被直接丢掉。更好的是摘要压缩:把较早的对话交给模型压成一段摘要,只对近几轮保留原文。两者结合是常见做法——近的留原文保细节,远的压成摘要保大意。
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1")
def compact_memory(history: list[dict], keep_recent: int = 6) -> list[dict]:
"""近 keep_recent 轮保留原文,更早的压成一条摘要"""
if len(history) <= keep_recent:
return history
old, recent = history[:-keep_recent], history[-keep_recent:]
text = "\n".join(f"{m['role']}: {m['content']}" for m in old)
summary = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user",
"content": f"把以下对话压缩成一段要点摘要,保留关键事实和结论:\n{text}"}],
).choices[0].message.content
return [{"role": "system", "content": f"[早期对话摘要] {summary}"}, *recent]
长期记忆,是跨会话的持久知识——用户上个月说过的偏好、之前处理过的相似问题。它的实现思路其实在前面讲过:把记忆条目存进向量库,每轮根据当前输入检索出相关的几条注入进去。长期记忆,本质就是对"记忆"这件事做 RAG。
长期记忆真正难的不是检索,而是两件事。一是写什么——不能把每句话都记下来,那等于没记,得从对话里抽取出值得长期保留的关键事实(用户画像、明确偏好、重要结论)再存。二是冲突怎么办——用户三个月前说"我喜欢简洁的回答",今天又说"多给我点细节",新旧记忆矛盾时需要更新和取舍的策略,而不是两条都留着让模型无所适从。此外,当前任务进行中的中间状态(常叫 scratchpad 或工作记忆)是第三种,它既不进短期对话也不进长期库,是任务级的临时草稿。
多 Agent:拆分、协作与代价
当单个 Agent 的工具堆到几十个、system prompt 长到几屏、职责糅在一起时,很自然会想到拆——把它拆成多个各管一摊的 Agent。
拆分通常按角色或工具域来:一个研究员 Agent、一个写作 Agent、一个审查 Agent;或者按工具领域,数据库相关的归一个、外部 API 归另一个。协作模式也有几种成熟的:Supervisor 模式,一个主管 Agent 负责调度,把任务路由给合适的 worker 再汇总结果;流水线模式,多个 Agent 串成管道,前一个的输出是后一个的输入;评审模式,一个负责生成、一个负责挑刺,来回迭代。
但这里要说一句实在话:多 Agent 不是银弹,它有实打实的成本,不要为了"架构高级"而上。每多一个 Agent,就多一串模型请求,Token 和延迟成倍上涨。信息在 Agent 之间传递时会失真,A 理解的"重点"传给 B 可能就跑偏了。调试也更难,一个错误结果要在好几个 Agent 之间排查到底是谁的锅。经验法则很简单:能用单 Agent 配好工具和记忆解决的,就别上多 Agent。只有当职责确实复杂到一个 Agent 扛不动、或不同子任务需要明显不同的工具集和人设时,拆分才划算。
LangGraph:把控制流画成一张图
讲到这会发现一个共同点:ReAct 的循环、Plan-and-Execute 的步骤流转、多 Agent 的任务路由,本质都是"一份状态在不同处理环节之间流动"。用裸 while 加一堆 if 去写这种控制流,写到后面没人看得懂。
LangGraph 的思路是把它显式建模成一张图。三个核心概念:State 是贯穿全图的共享状态,通常用 TypedDict 定义;Node 是节点,一个读 state、做点事、写回 state 的函数;Edge 是边,连接节点决定流转——普通边固定指向下一个节点,条件边根据当前 state 动态决定下一步去哪。控制流,全在条件边里。
相比裸 while 循环,用图来组织多了几样要紧的东西。它能可视化,控制流一眼看清。它支持 checkpointer,把每一步的 state 持久化下来,于是 Agent 可以中途暂停、之后从断点恢复,甚至回到某个历史状态重来。它天然支持 human-in-the-loop——在某个节点停下来等人确认再继续,这对有副作用的操作很重要。这些能力,用裸循环一个个手搓会非常痛苦。
完整示例:用 LangGraph 实现一个 ReAct Agent
把 State、Node、条件边组装起来,是一个完整的 ReAct Agent。它的运行轨迹就是"模型节点和工具节点之间循环",直到模型不再要工具:
# react_agent.py
import json
import os
from typing import TypedDict, Annotated
from operator import add
from openai import OpenAI
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
client = OpenAI(api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com/v1")
TOOLS = [{"type": "function", "function": {
"name": "get_weather",
"description": "查询指定城市的实时天气,city 为城市中文名",
"parameters": {"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]}}}]
def get_weather(city: str) -> dict:
return {"city": city, "weather": {"北京": "晴 8℃"}.get(city, "暂无数据")}
class AgentState(TypedDict):
# Annotated + add:新消息追加而非覆盖
messages: Annotated[list, add]
def call_model(state: AgentState) -> dict:
"""模型节点:让模型决定是调工具还是给出回答"""
resp = client.chat.completions.create(
model="deepseek-chat", messages=state["messages"], tools=TOOLS)
return {"messages": [resp.choices[0].message]}
def call_tools(state: AgentState) -> dict:
"""工具节点:执行模型请求的工具,把结果回填"""
last = state["messages"][-1]
results = []
for call in last.tool_calls:
args = json.loads(call.function.arguments)
data = get_weather(**args)
results.append({"role": "tool", "tool_call_id": call.id,
"content": json.dumps(data, ensure_ascii=False)})
return {"messages": results}
def should_continue(state: AgentState) -> str:
"""条件边:模型还要调工具就去 tools,否则结束"""
return "tools" if state["messages"][-1].tool_calls else END
graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_node("tools", call_tools)
graph.set_entry_point("agent")
graph.add_conditional_edges("agent", should_continue,
{"tools": "tools", END: END})
graph.add_edge("tools", "agent") # 工具跑完回到 agent,形成 ReAct 循环
app = graph.compile(checkpointer=MemorySaver()) # 带状态持久化
if __name__ == "__main__":
config = {"configurable": {"thread_id": "demo"}}
result = app.invoke(
{"messages": [{"role": "user", "content": "北京天气怎么样"}]}, config)
print(result["messages"][-1].content)
这张图跑起来就是一个 ReAct 循环,但比裸 while 多了三样东西:控制流在 should_continue 这条条件边里一目了然;MemorySaver 这个 checkpointer 让每一步状态都被持久化,凭 thread_id 可以随时恢复;要加 human-in-the-loop,只需在工具节点前插一个中断点。把朴素循环换成图,Agent 才真正具备了可观测和可恢复的能力。
几个常见踩坑
用 ReAct 处理流程固定的任务。步骤可预期的任务用 Plan-and-Execute 更省、更不绕路;ReAct 适合探索型任务。
短期记忆只做滑动窗口。直接丢弃早期对话会丢关键信息,应结合摘要压缩。
长期记忆把所有对话都存进去。要抽取关键事实再存,并处理新旧记忆的冲突。
为了架构好看就上多 Agent。多 Agent 的 Token、延迟、调试成本都成倍上涨,能单 Agent 解决就别拆。
用裸 while 写复杂控制流。多分支、多 Agent 的流转用图来表达更清晰,还能获得持久化和恢复能力。
Agent 循环不设步数上限。和工具调用一样,Agent 也可能陷入循环,要有最大步数兜底。
本篇要点
- ReAct 灵活、适合探索型任务,Plan-and-Execute 有全局视野、适合流程型任务,常混合使用
- 短期记忆靠滑动窗口加摘要压缩,长期记忆本质是对记忆做 RAG,难点在写什么和冲突处理
- 多 Agent 有实打实的成本,能用单 Agent 解决就不要拆
- LangGraph 用 State、Node、条件边把控制流建模成图,控制流集中在条件边
- 相比裸 while,图能可视化、能持久化、能中断恢复、能插入人工确认
- 用 LangGraph 实现的 ReAct Agent,把工具循环升级成了可观测、可恢复的结构
下一篇
到这里,关于"怎么让模型会做事"的部分就齐了。但每个 Agent 项目都在重复造工具——同样的搜索、同样的数据库访问,换个项目又写一遍。最后一篇讲 MCP,看它怎么把工具接入标准化成一个通用插座,并从零动手写一个 MCP Server。
参考资料
- ReAct 论文:Synergizing Reasoning and Acting
- Plan-and-Solve Prompting 论文
- LangGraph 官方文档
- Anthropic: Building effective agents
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:Agent 不只是套个循环
本文链接:https://www.sshipanoo.com/blog/ai/llm-advanced/06-Agent不只是套个循环/
本文最后一次更新为 天前,文章中的某些内容可能已过时!