[控场AI]
· 7 分钟阅读· 3,586 字

先写函数,再用框架:AI Agent 开发的最佳实践

先写函数,再用框架:AI Agent 开发的最佳实践

先用函数和结构化输出组装 Agent,遇到具体痛点再引入框架。

文章基于 LlamaIndex 联合创始人 Jerry Liu 在 Chain of Thought 播客中的观点,主张先用手写函数和显式控制流构建 AI Agent,而非一上来就引入框架。核心论点是:函数让工作流、分支逻辑和故障点都清晰可见,结构化输出则是连接模型与代码的关键枢纽。当出现可调试性下降、中断后难以恢复、分支协调复杂或检索实验变慢这四个信号时,才应考虑引入框架。判断框架价值的方法是把每个功能映射到团队正在维护的代码痛点,并在同一工作流和评估集上对比函数实现与框架实现,只有当依赖带来可检查性、恢复行为或协调能力的实质提升时才值得采纳。

大多数 AI Agent 教程一上来就让你引入框架,但如果从一开始就用普通函数来组装 Agent,你会对系统有更清晰的控制力。本文基于 Chain of Thought 播客中 LlamaIndex 联合创始人 Jerry Liu 的观点,探讨何时该手写函数、何时该引入框架,并给出可操作的判断标准。

Agent 开发函数优先示意图

为什么先写普通函数?

函数给你一张清晰的工作流地图:哪些步骤按什么顺序执行,数据如何传递给下一步。分支逻辑保持显式,你可以在自己掌控的边界上记录输入输出日志。当出现故障时,错误会直接暴露在你编写、可阅读的代码里,而不是隐藏在框架的抽象层后面。

结构化输出是这里的关键枢纽。模型默认输出自由文本,对人类友好但对程序脆弱。当下一个步骤是代码时,应该要求模型输出符合 schema 的 JSON——比如提取字段、对工单分类、或者选择工具名和参数。当需要每次获得相同字段时,强制 schema 比松散的 JSON 模式更可靠。当然,你仍然需要验证字段值、处理模型拒答或截断的响应。

下面是一个示意性代码,展示一次带 schema 的规划步骤加上确定性的工具执行:

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:
    # 在此调用模型 API,传入 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"

这个循环不需要框架。随着需求增长,你可以把检索、额外的模型调用和校验逻辑作为更多函数加入,直接组装出一个复合 AI 流水线(compound AI system)。

结构化输出(Structured Output) 指模型直接生成符合预定义 schema(如 JSON Schema 或 Pydantic 模型)的响应,而非自由文本。常见实现方式包括提供函数调用(function calling)接口、约束解码(constrained decoding)或后处理解析加校验。约束解码在采样阶段就屏蔽不符合语法的 token,可靠性最高;函数调用则依赖提供商实现,不同平台的行为可能有差异。无论哪种方式,都需要处理模型拒答、输出截断、字段缺失或类型不符的情况,生产环境通常还要加一层 schema 校验和重试逻辑。

什么信号表明你该上框架了?

框架的价值在于用它解决你已经遇到的问题。在遇到这些问题之前,依赖项只会隐藏你仍需要测试的部分——尤其是你喂给模型的数据。以下四个信号值得关注:

1. 故障下的可调试性

当 Agent 出错时,你能否在不深入框架内部的情况下检查工具调用和应用状态?如果故障频繁发生,而你的日志和状态追踪越来越难维护,那么运行追踪和持久化的步骤记录可能值得引入依赖。

2. 中断后的恢复能力

你能在不重做副作用(比如发送邮件、扣款、写入数据库记录)的前提下恢复一个长任务吗?如果用户或工作流需要持久的检查点,你实际上是在寻找具备状态管理和重放语义的框架。

3. 分支与协调复杂度

当控制流不再是直线,而是并行分支、人工审批或子 Agent 之间的交接时,编排原语可以替代你维护得疲惫不堪的临时状态机。

4. 检索迭代效率

你能否在不与框架抽象层搏斗的情况下改变上下文获取方式并评估答案?如果实验速度很慢,要么简化技术栈,要么选择把检索当作可插拔组件的工具。

LlamaIndex 联合创始人 Jerry Liu 在 Chain of Thought 播客第 61 期中指出,当应用形态仍然开放式时,框架层仍有价值:

"当某个领域还在探索中,最终应用有很大的灵活性时,框架层的价值就体现出来了,因为框架提供了核心抽象,让构建某些东西变得更容易。"

他同时将这一转变与上下文工作联系起来:"因为抽象在固化,但对高质量生产上下文的需求仍在演进。"

检查点(Checkpoint)与重放(Replay)语义 是持久化工作流引擎的核心能力:在关键步骤后保存完整运行状态(包括已完成步骤、中间结果和待执行队列),故障后从最近的检查点恢复,而不是从头重跑。重放时需保证已执行的副作用不被重复触发,这通常要求工具调用具备幂等性,或借助事务性发件箱(transactional outbox)等模式将副作用与状态变更绑定。手写实现时,任务 ID、幂等键和步骤表的维护成本会随分支增多迅速上升,这正是框架能替代的部分。

框架的每个功能在替代什么?

把框架功能和你的实际痛点对应起来,才能判断是否值得引入。

  • 中断与恢复:手写方案需要自定义任务 ID、幂等工具和自己维护的步骤表;框架则提供集中式运行状态、检查点和重放。
  • 复杂分支:手写方案是嵌套条件和手写状态机;框架提供图或工作流原语。
  • 工具管线:手写方案需要逐个提供商的包装器和参数解析;框架提供注册表和调用约定。
  • 可观测性缺口:手写方案是零散的日志;框架提供与工具调用绑定的追踪。

如果手写方案的那一栏对你团队来说仍然成本低廉,框架那一栏可能并不划算。简单工作流几乎不需要编排,复杂工作流则可能从框架中受益。无论选哪种,都无法免除测试模型输入上下文的责任。

复合 AI 系统(Compound AI System) 由 Databricks 等团队提出,指不依赖单一模型端到端完成,而是由多个模型调用、检索器、工具、校验器和胶水代码组合而成的系统。其质量由各组件及其衔接方式共同决定,而非仅取决于底层模型能力。这一视角意味着:评估时应针对整条流水线设计测试集,定位瓶颈时需区分是检索召回不足、模型推理错误还是校验逻辑遗漏。把 Agent 视为复合系统,也解释了为什么手写函数组装在早期更具优势——每个组件的输入输出都显式可见,便于单独测试和替换。

如何公平地对比两种方案?

务实的做法是在同一个工作流上对比两种构建方式。用四个检查问题来评判:故障时工具调用和状态的可检查性、恢复时是否重复副作用、分支是否变得更简单、检索变更是否易于评估。

具体步骤是:用函数加结构化输出构建最小版本,再用你考虑的框架构建同样的版本。相同的工具、相同的检索、相同的评估集,然后对两条路径运行上述四个问题。只有当依赖不仅节省了实现时间,还带来了可检查性、恢复行为或协调能力的提升时,才值得引入。

不要因为 Agent 循环在抽象层面有所改进就采纳框架。当你现有的代码库已经在试图变成那个框架时,才是引入它的时机。

行动清单

  • 在添编排依赖之前,先用函数、显式控制流和绑定 schema 的模型输出起步。
  • 把 Agent 视为复合流水线:模型步骤、检索、工具、校验和胶水代码共同决定质量。
  • 当你需要可检查的故障、可恢复的运行、重度分支或更快的检索实验时,再考虑框架。
  • 把每个框架功能对应到你当前正在维护的代码,而不是落地页上的功能矩阵。
  • 在承诺之前,用同一工作流和同一评估数据对比直接实现与框架实现。
#AI Agent 开发#结构化输出#Agent 框架#复合 AI 系统#LlamaIndex#函数式编程#Agent 编排
分享:

相关推荐