智能体不是模型,是状态机
一句话总结
智能体不是模型,而是状态机。真正困难的不是调用模型的位置,而是决定何时停止、携带什么状态,以及失败后走向哪里。
为什么需要——演示与产品的分界线
智能体演示通常运行得很好:输入一个问题,它调用工具并给出答案。但一旦成为产品,三个问题会同时爆发。
- 无法停止。 工具没有返回预期答案,就不断重试,最终由令牌账单提醒你。
- 编造不知道的内容。 查询空手而归,却自行生成答案。
- 无法解释原因。 同一问题产生不同答案,却没有记录经过了哪条路径。
这三个都不是模型问题,而是图的问题。因此,本课程不调用模型,只处理图。接入模型只是替换一个节点,并不困难。
状态是覆盖还是累积
LangGraph 中,节点返回一个只包含需要变更部分的字典;如何合并由状态类型决定。
class State(TypedDict):
question: str
steps: Annotated[list, operator.add] # 쌓인다
tries: int # 덮어쓴다
若没有 Annotated[list, operator.add],只会保留最后一个节点返回的值。这样“走过的路径”会消失,之后无法追问发生了什么。哪些字段累积、哪些覆盖,是必须主动做出的设计决策。
转成图后,什么变得可测试
把智能体写成一个整体循环,就没有位置可以单独询问哪里出了问题。拆成图的真正原因,是每个节点都能独立测试。
让节点尽量接近纯函数。 如果它接收状态并返回其中一部分,就可以单独取出节点,输入状态并观察结果。模型调用可以放在节点内部,但应支持注入,测试时替换成返回固定答案的假模型。
def plan(state: State, llm=None) -> dict:
llm = llm or default_llm
out = llm.invoke(state["messages"])
return {"plan": parse_plan(out), "step": state["step"] + 1}
分支应放在条件函数,而不是节点中。 把决定下一步去向的函数独立出来,就能针对它制作表格,验证“这种状态调用工具,那种状态结束”。无需调用模型,也能检查完整流程。
明确状态中哪些累积、哪些覆盖。 对话记录累积,当前计划覆盖。把规则写进类型,扩展节点时就不容易混淆。该累积的被覆盖,语境会消失;该覆盖的不断累积,提示词会无限膨胀。
保存中间状态,恢复与人工介入就同时具备。 每个节点都保存状态后,可以在中途暂停、询问人类,再用回答继续。需要审批的工作(发送邮件、支付、删除)应在这里中断。
观测应按节点记录。 必须知道哪个节点用了几秒、消耗多少令牌,才能找到慢点和昂贵点。只记录总耗时,就找不到改进位置。
在实际项目中
面试中被问“是否做过智能体”,对方真正想听的通常不是框架名称,而是三件事:用什么作为终止条件、工具失败后转向哪里、保留了什么记录。
而且这并非智能体专属问题。重试上限、失败路径与观测记录,本来就是后端一直在做的判断。LangGraph 只是让这些判断以图的形式表达。