文章建议先用普通函数串联模型、检索、工具和校验,让执行顺序、分支及数据传递保持清晰。通过结构化输出和边界日志提升可测试性,等编排与状态管理出现实际需求后再引入框架。
不少 Agent 教程一上来就导入一个框架。我会从普通函数开始:调用模型、返回结构化数据,再按从上到下就能读懂的顺序调用工具。单次模型调用很少能构成完整的产品。复合 AI 系统会把模型调用、检索、工具、校验和控制逻辑串成一条流水线。系统质量至少有一半取决于这些部分如何配合,而不只是你选了哪个模型。
当编排和状态管理能解决你的 Agent 已经遇到的问题时,框架才值得引入。在遇到这些问题之前,一个依赖反而可能把你仍需测试的部分藏起来,尤其是你提供给模型的数据。
函数能让你清楚地看见整个工作流:执行什么、按什么顺序执行,以及向下一步传递什么。分支逻辑一目了然。你可以在自己掌控的边界记录输入和输出。出问题时,错误会暴露在你自己写的、也读得懂的代码里。
结构化输出是关键。模型默认输出自由文本,人读起来没问题,程序处理起来却很脆弱。如果下一步要交给代码处理,比如提取字段、给工单分类,或者选择工具名称和参数,就要求模型返回符合 schema 的 JSON。当你需要每次都拿到相同字段时,强制遵循 schema 比宽松的 JSON mode 更可靠。不过,你仍然需要校验字段值,并处理模型拒绝回答或响应被截断的情况。
下面只是一个示意,并非生产代码。它展示了如何先通过 schema 约束规划步骤,再以确定性的逻辑执行工具。
# Sketch: agent as functions + structured outputs (not production code)
from typing import Literal, Optional
from pydantic import BaseModel
class Plan(BaseModel):
action: Literal["search_kb", "reply", "escalate"]
query: Optional[str] = None
reply_text: Optional[str] = None
def plan_next_step(user_message: str, kb_snippet: str) -> Plan:
# Call your model API here with schema=Plan
...
def search_kb(query: str) -> str:
return "..."
def run_turn(user_message: str) -> str:
snippet = search_kb(user_message[:200])
plan = plan_next_step(user_message, snippet)
if plan.action == "search_kb" and plan.query:
return search_kb(plan.query)
if plan.action == "reply" and plan.reply_text:
return plan.reply_text
return "escalated"
这个循环不需要框架。要增加检索、额外的模型调用和检查,就继续添加函数。你是在直接组装这条复合流水线。
把框架实现与同一工作流的小型直接实现放在一起比较。“Agent 框架在哪里能创造价值”这场讨论,把决策的重点放在了你的 Agent 实际遇到的问题上。
可以把下面这些情况视为升级信号:
出错时是否容易调试。 Agent 出错时,你能否直接检查工具调用和应用状态,而不必四处翻找?如果不能,而且故障经常发生,那么运行追踪和持久化的步骤记录,或许就足以成为引入依赖的理由。
出错时是否容易调试。 Agent 出错时,你能否直接检查工具调用和应用状态,而不必四处翻找?如果不能,而且故障经常发生,那么运行追踪和持久化的步骤记录,或许就足以成为引入依赖的理由。
工作被中断。 你能否恢复一项长时间运行的任务,同时避免重复执行带有副作用的操作,比如发送邮件、信用卡扣款或写入一行数据?如果用户或工作流需要持久化检查点,那么你需要的就是状态管理和重放语义,而框架可能提供这些能力。
工作被中断。 你能否恢复一项长时间运行的任务,同时避免重复执行带有副作用的操作,比如发送邮件、信用卡扣款或写入一行数据?如果用户或工作流需要持久化检查点,那么你需要的就是状态管理和重放语义,而框架可能提供这些能力。
分支与协调。 如果控制流不再是一条直线,而是开始出现并行分支、人工审批或子 Agent 之间的交接,那么编排原语就能替代那些你已经不想再维护的临时状态机。
分支与协调。 如果控制流不再是一条直线,而是开始出现并行分支、人工审批或子 Agent 之间的交接,那么编排原语就能替代那些你已经不想再维护的临时状态机。
检索迭代。 你能否调整获取上下文和评估答案的方式,而不必与框架的抽象较劲?如果实验推进缓慢,要么简化技术栈,要么选择把检索视为可插拔组件的工具。
检索迭代。 你能否调整获取上下文和评估答案的方式,而不必与框架的抽象较劲?如果实验推进缓慢,要么简化技术栈,要么选择把检索视为可插拔组件的工具。
LlamaIndex 联合创始人 Jerry Liu 曾提出,随着 Agent 模式逐渐成熟,稳定的抽象会让开发者有更多空间,通过生产环境中的上下文做出差异化。这也解释了他的公司选择在哪个方向投入。不过,当应用最终会是什么形态仍不确定时,他依然认为框架层有价值:
当一个领域还有很多东西尚在探索,而最终应用又有很大的灵活空间时,框架层就有价值,因为框架提供了核心抽象,让某些东西更容易构建。
Jerry Liu,LlamaIndex,Chain of Thought 第 61 期
在同一期节目中,他也把这种转变与上下文工作联系了起来:
因为那些抽象正在逐渐稳定,但生产环境对高质量上下文的需求仍在演变。
Jerry Liu,LlamaIndex,Chain of Thought 第 61 期
把功能对应到你已经感受到的痛点上。我会这样比较手写实现与框架可能提供的能力。中断与恢复:一边是自定义任务 ID、幂等工具,以及自己维护的步骤记录表;另一边是集中管理的运行状态、检查点和重放。复杂分支:一边是嵌套条件判断和手写状态机,另一边是图或工作流原语。工具接入:一边是针对各家提供商编写的封装和参数解析,另一边是注册表和调用约定。可观测性缺口:一边是零散的日志,另一边是与工具调用关联的追踪记录。
如果手写实现对你的团队来说仍然成本很低,框架可能就不划算。简单的工作流可能几乎不需要编排,复杂的工作流则可能受益于框架。无论选择哪一种,你都仍然需要测试提供给模型的上下文。
实际可行的做法是,在同一个工作流上比较两种实现,并用那场讨论中的几个问题来检查:出错时能否查看工具调用和状态,恢复时能否避免重复操作,分支是否变得更简单,以及检索调整是否容易评估。
如果你想进一步了解 Agent 架构、记忆,以及如何把上下文变成护城河,可以看看 Chain of Thought 关于 AI Agent 的精选节目合集。当你希望听到框架创始人之外的声音时,这个合集能提供一些不同视角。
把工作流的最小版本实现两遍:一遍使用函数和结构化输出,另一遍使用你正在考虑的框架。使用相同的工具、相同的检索方式,以及相同的评估集。对两条实现路径都检查前面的四个问题。只有当你同时获得了更好的可检查性、恢复能力,或是难以用普通代码清楚表达的协调能力时,节省下来的实现时间才足以成为引入依赖的理由。
不要因为 Agent 循环在抽象层面有所进步,就采用框架。等到你的代码库已经在尝试长成那个框架时,再采用它。
先从函数、显式控制流和受 schema 约束的模型输出开始,再添加编排依赖。
把 Agent 看作一条复合流水线:模型步骤、检索、工具、校验和胶水代码,共同决定系统质量。
当你需要便于检查的故障信息、可恢复的运行、大量分支,或是当前结构无法支持的更快检索实验时,再考虑框架。
让框架的每项功能,都对应到你今天正在维护的代码上,而不是产品介绍页上的功能对照表。
在决定采用框架之前,用同一个工作流和同一份评估数据,比较直接实现与框架实现。
更详细的讲解以及节目片段,可以在 Chain of Thought 上找到。那篇讲解基于这期节目整理而成。
订阅 Chain of Thought newsletter,即可收到新节目和类似本文的文章。
本文根据节目文字稿,在 AI 辅助下起草。
如需进一步操作,你可以考虑屏蔽此人和/或举报滥用行为。