真正的 Agent 不是 while true,而是控制流、状态和失败恢复
上一篇结尾那个 run_agent 循环——发请求、调工具、回填、再发请求——很多人会觉得,Agent 不就是这个吗,套个 while 就完事了。
跑几个简单任务确实够用。但任务一复杂,这个朴素循环的问题就全暴露了:它没有规划,经常绕远路甚至原地打转;它没有记忆,多聊几轮上下文就爆;它什么都自己扛,工具一多就选不准。说白了,从"能调工具的循环"到"靠谱的 Agent",中间隔着控制流的选型、记忆的设计,和该不该拆成多个 Agent 的判断。
ReAct 与 Plan-and-Execute:边走边看,还是先谋后动
Agent 的控制流,主流是两种思路,先搞清楚它们各自适合什么。
ReAct(Reasoning + Acting)就是上一篇那个循环再加上显式推理:思考一下→调个工具→观察结果→再思考→再调……每一步都是即兴的,模型看着上一步的结果临时决定下一步。它的好处是灵活,能根据观察动态调整方向,特别适合步数不确定、需要探索的任务。但它的短板也明显:没有全局规划,容易走偏、绕路,甚至在两三个工具间反复横跳出不来;而且每一步都要一次 LLM 调用,步数一多,又慢又贵,错误还会一步步累积。
Plan-and-Execute 是另一种思路:先让 LLM 把整件事想清楚,产出一个完整的步骤计划,然后再逐步执行。它有全局视野,不容易绕路;计划定下来后,执行阶段甚至可以用更便宜的模型、或者无依赖的步骤并行跑,省钱省时间。代价是计划是预先定死的,执行中遇到意外(某步失败了、结果和预期不符)就僵了——所以它必须配一个 replan 机制,发现偏离就回去重新规划。它适合步骤相对可预期的任务。
实践里这两者往往是混着用的:用一个 planner 给出粗粒度计划,每个步骤内部用 ReAct 灵活处理,一旦执行结果明显偏离预期就触发 replan。不存在哪个绝对更好,关键是看你的任务是"探索型"还是"流程型"。
记忆:短期靠压缩,长期靠检索
朴素循环的第二个硬伤是没有记忆。对话多几轮,你把全部历史塞进 context,迟早撑爆窗口,而且越塞越贵越慢。Agent 的记忆要分两层来设计。
短期记忆,本质是当前会话的上下文管理。最简单是滑动窗口——只保留最近 N 轮,但这样早期的关键信息会被直接丢掉。更好的是摘要压缩:把较早的对话交给 LLM 压成一段摘要,只对近几轮保留原文。两者结合是常见做法——近的留原文保细节,远的压成摘要保大意。
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:]
summary = llm(f"把以下对话压缩成一段要点摘要,保留关键事实和结论:\n{old}")
return [{"role": "system", "content": f"[早期对话摘要] {summary}"}, *recent]
长期记忆,是跨会话的持久知识——用户上个月说过的偏好、之前处理过的相似问题。它的实现思路其实你已经熟悉了:把记忆条目存进向量库,每轮根据当前输入检索出相关的几条注入进去。长期记忆,本质就是对"记忆"这件事做 RAG。
长期记忆真正难的不是检索,是两件事。一是写什么——不能把每句话都记下来,那等于没记,得从对话里抽取出值得长期保留的关键事实(用户画像、明确的偏好、重要结论)再存。二是冲突怎么办——用户三个月前说"我喜欢简洁的回答",今天又说"多给我点细节",新记忆和旧记忆矛盾时,需要有更新和取舍的策略,而不是两条都留着让模型精神分裂。此外,当前任务进行中的中间状态(常叫 scratchpad 或工作记忆)是第三种,它既不进短期对话也不进长期库,是任务级的临时草稿。
多 Agent 不是银弹
当单个 Agent 的工具堆到几十个、system prompt 长到几屏、职责糅在一起时,很自然会想到拆——把它拆成多个各管一摊的 Agent。
拆分通常按角色或工具域来:一个研究员 Agent、一个写作 Agent、一个审查 Agent;或者按工具领域,数据库相关的归一个、外部 API 归另一个。协作模式也有几种成熟的:Supervisor 模式——一个主管 Agent 负责调度,把任务路由给合适的 worker,再汇总结果;流水线模式——多个 Agent 串成管道,前一个的输出是后一个的输入;评审/辩论模式——一个负责生成,一个负责挑刺,来回迭代提升质量。
但这里必须说一句实在话:多 Agent 不是银弹,它有实打实的成本,别为了"架构高级"而上。每多一个 Agent,就多一串 LLM 调用,token 和延迟是成倍往上涨的。信息在 Agent 之间传递时会失真——A 理解的"重点",传给 B 可能就跑偏了。调试也更难,一个错误结果你得在好几个 Agent 之间排查到底是谁的锅。经验法则很简单:能用单 Agent 配好工具和记忆解决的,就别上多 Agent。只有当职责确实复杂到一个 Agent 扛不动、或者不同子任务需要明显不同的工具集和人设时,拆分才划算。
LangGraph:把控制流画成一张图
讲到这你会发现一个共同点:ReAct 的循环、Plan-and-Execute 的步骤流转、多 Agent 的任务路由——它们本质都是"一份状态在不同处理环节之间流动"。用裸 while 加一堆 if 去写这种控制流,写到后面没人看得懂。
LangGraph 的思路就是把它显式建模成一张图。三个核心概念:State 是贯穿全图的共享状态,通常用 TypedDict 定义;Node 是节点,就是一个读 state、做点事、写回 state 的函数;Edge 是边,连接节点决定流转——普通边固定指向下一个节点,而条件边(conditional edge)根据当前 state 动态决定下一步去哪。控制流,全在条件边里。
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from operator import add
class AgentState(TypedDict):
messages: Annotated[list, add] # Annotated + add:新消息追加而非覆盖
def call_model(state: AgentState) -> dict:
resp = client.chat.completions.create(
model="gpt-4.1", messages=state["messages"], tools=TOOLS)
return {"messages": [resp.choices[0].message]}
def call_tools(state: AgentState) -> dict:
last = state["messages"][-1]
results = [run_one_tool(c) for c in last.tool_calls]
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=checkpointer)
这张图跑起来其实就是个 ReAct 循环,但比裸 while 多了几样要命的东西。它能可视化,控制流一眼看清。它支持 checkpointer——把每一步的 state 持久化下来,于是 Agent 可以中途暂停、之后从断点恢复,甚至回到某个历史状态重来(时间旅行式调试)。它天然支持 human-in-the-loop——在某个节点停下来等人确认再继续,这对有副作用的操作太重要了。这些能力,你用裸循环一个个手搓会非常痛苦。
把控制流、记忆、协作模式想清楚,再用图把它们组织起来,你才算有了一个真正的 Agent,而不是一个会调工具的 while。
到这里,进阶系列关于"怎么让模型会做事"的部分就齐了。但你可能注意到一个问题:每个 Agent 项目都在重复造工具——同样的搜索、同样的数据库访问,换个项目又写一遍。最后一篇我们讲 MCP,看它怎么把工具接入标准化成一个"通用插座",并且从零动手写一个自己的 MCP Server。
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:Agent 不只是套个循环
本文链接:https://www.sshipanoo.com/blog/ai/llm-advanced/06-Agent不只是套个循环/
本文最后一次更新为 天前,文章中的某些内容可能已过时!