能应对真实输入的智能体工作流

大多数智能体演示之所以能跑通,是因为演示输入很干净。真实输入则并非如此。它们常常不完整、缺少字段、上下文矛盾、PDF 实际上是图片,用户还会在对话中途改变主意。从一个能演示的原型,到一个可以半年无人值守运行的工作流,差距几乎完全在于你如何拆解问题,以及把防护栏设在哪里。

我在 BizFlowAI 每天都在设计多智能体工作流,下文提到的模式就是我反复使用的。它并不炫酷,却是我让内容流水线 24/7 运行而无需盯梢,以及让无服务器 AWS 集成达成 SLA 而不会半夜被叫醒的原因。

先把工作流写成确定性流水线,再决定让智能体承担哪些环节

在触碰任何 LLM SDK 之前,我都会把工作流写成仿佛智能体不存在的样子:一组枯燥但带类型输入输出的函数序列。这是智能体设计里最有用的练习,却也是大多数团队会跳过的。

下面是我对每个新工作流都会做的拆解:

  1. 触发器是什么? 一个 webhook、一个定时任务、一条队列消息、或用户上传的文件。先写出确切负载结构。
  2. 最终产物是什么? 一个 JSON 对象、一篇已发布的文章、一次 Zendesk 工单更新、或一行数据库记录。先写出确切模式。
  3. 中间产物有哪些? 在触发器与最终产物之间,列出所有必须存在的对象。每个对象都要有名称和模式。
  4. 针对每一次产物间的转换,判断:这需要判断力,还是只需执行规则? 规则交给代码,判断交给 LLM。这就是全部的启发式方法。

在我的 ContentStudio 流水线里,流程大致如下:

trigger (topic gap detected)
  -> ResearchBrief        [LLM: judgment on angle + gap]
  -> OutlineDraft         [LLM: judgment on structure]
  -> DraftPost            [LLM: generation]
  -> SEOAuditReport       [code: deterministic checks]
  -> RevisedPost          [LLM: targeted rewrites only]
  -> PublishPayload       [code: schema validation]
  -> published (WordPress API)

Enter fullscreen mode Exit fullscreen mode

可以看到,四步是代码,三步是 LLM。早期版本里我曾让 LLM 做 SEO 审计和发布载荷组装,结果它胡编 slug、捏造 canonical URL,还试图发布到一个根本不存在的站点。把这些步骤改成确定性代码后,错误率几乎降为零,整个流水线也变得可调试。

我遵循的规则:智能体应该对决策负责,而不是对决策周围的管道负责。

挑选工具就像招聘,而不是逛街

工具选择是大多数智能体项目悄然失败的地方。团队给智能体 15 个工具,只因为“工具越多 = 能力越强”,之后却发现它 30% 的时间都挑错工具。

我根据真实生产智能体得出的数据:

可用工具数 正确工具选择率
3-5 个,范围严格限定 96-99%
6-10 个 88-93%
11-20 个 70-82%
20+ 个 通常低于 70%,严重依赖提示

这些数字来自我自己的流水线,仅供参考。但该模式是真实的,且在不同模型家族中都成立。解决办法不是换更聪明的模型,而是工具路由:用一个小型调度智能体挑选子智能体,每个子智能体只拥有 3-5 个工具。

我写工具规格时,把它当作职位描述来对待:

  • 名称:以动词开头,含义明确。例如 search_customer_by_email,而不是 customer_lookup
  • 描述:一句话说明它做什么,一句话说明何时不该用它。“何时不该用”这一行能阻止“自信的错误调用”。
  • 输入模式:严格的类型,必填字段明确标记。没有把带默认值的可选字段藏在字符串里。
  • 输出模式:每次返回相同的形状,包括错误形状。绝不让智能体去解析自由格式的错误字符串。

如果你写不出“何时不该用它”这一句,说明工具太宽泛,请拆分它。

防护栏应设在边界,而非提示里

提示级防护栏(“不要编造客户姓名”“始终验证邮箱”)充其量只是建议。在生产环境中,我把它们当作备份,而不是主要防线。主要防线位于每一步的边界。

我总会设置防护栏的四类边界:

1. 工具调用的输入验证。 每个工具在执行任何操作前,都用严格的模式验证输入(我在 TypeScript 中用 Zod,在 Python 中用 Pydantic)。如果智能体幻觉出一个字段,工具会返回结构化错误,智能体可以带着反馈重试。绝不例外,也不静默强制。

2. LLM 响应的输出验证。 每个产生结构化输出的 LLM 调用都会经过验证器。如果需要结构化输出,我会先使用提供商的结构化输出模式(Claude tool use、OpenAI response_format),再做一次验证。双保险。我就是这样捕获模型回归的。

3. 成本与循环上限。 每个工作流都有硬预算:最大 token 数、最大工具调用次数、最大墙钟时间。如果超出任意一项,它会大声失败、写入死信队列,并呼叫我。一个智能体若悄无声息地循环 40 分钟并花费 80 美元,那是一个你只能在账单上发现的 bug。

4. 副作用门控。 任何会写入真实世界的工具(发送邮件、发布文章、创建工单、扣款)都有独立的授权检查,且该检查不在 LLM 提示里。它从配置或功能开关读取数据。这就是当你授予智能体写入权限时,你还能睡得着觉的原因。

下面是我使用的极简副作用门控模式:

async function publishPost(input: PublishInput, ctx: AgentContext) {
  // 1. Schema validation
  const parsed = PublishSchema.parse(input);

  // 2. Authorization check (NOT in prompt)
  if (!ctx.permissions.canPublishTo(parsed.siteId)) {
    return { ok: false, error: "unauthorized_site" };
  }

  // 3. Idempotency: has this artifact already been published?
  const existing = await db.publishedPosts.findOne({
    contentHash: parsed.contentHash
  });
  if (existing) {
    return { ok: true, alreadyPublished: existing.url };
  }

  // 4. Actual side effect, wrapped in retry with backoff
  const result = await wpClient.publish(parsed);
  await db.publishedPosts.insert({ ...parsed, url: result.url });
  return { ok: true, url: result.url };
}

Enter fullscreen mode Exit fullscreen mode

在真正调用 WordPress API 之前,已经发生了四件事。每件事都在生产环境中捕获过真实 bug。

可观测性是“它能跑”与“我能证明它能跑”之间的区别

如果你没有在最开始就做好埋点,就无法通过事后读日志来调试智能体工作流。而你确实需要调试,因为真实输入会做出你未曾预料的事情。

我为每个工作流发布时都包含的最低配置:

  • 每个工作流运行都有一个 trace ID,它会贯穿每一次工具调用、LLM 调用和数据库写入。只需用一个 ID 就能 grep
  • 结构化日志,而不是 print 语句。JSON 包含 trace_id、step_name、duration_ms、token_in、token_out、cost_estimate 和 outcome。
  • 持久化每一次 LLM 调用:提示、响应、模型、temperature、token 数、延迟。存储很便宜;无法复现一次坏运行则代价高昂。
  • 持久化每一次工具调用:输入、输出、耗时、错误。
  • 每个工作流都有一个运行摘要记录:触发器、最终状态、总成本、总时长、产生的产物。

我为此使用一个简单的 Postgres 模式。不是因为它花哨,而是因为我可以在出问题时用 SQL 查询它。当一次运行失败时,我可以用一个查询拉出整个历史并重放。

我在每个工作流中真正关注的指标:

信号 为什么重要
每个工作流版本的成功率 提示变更后的回归
每步的 p50 / p95 延迟 哪个步骤是瓶颈
每次运行的成本(token + 工具) 单位经济效益、预算告警
按工具统计的工具调用错误率 哪个工具规格让智能体困惑
每步的重试率 静默的不稳定性
人工介入率 自主性的真实度量

最后这个指标是没人愿意看的。如果一个工作流 15% 的时间都需要人工介入,那它不是自主的,而是辅助的。没关系,但请说清楚,并据此计算 ROI。

针对你已经见过的失败模式进行设计

做过足够多的这类项目后,你会发现失败模式是重复出现的。从第一天起就要针对它们进行设计。

自信的错误输出。 智能体输出看似合理但实际错误。缓解措施:增加一个验证步骤,用真实数据(数据库查询、规则检查、高风险输出的第二模型评审)来检查输出。在我的内容流水线中,每篇草稿在发布前都要经过确定性的 SEO/AEO 审计。如果失败,它会带着列出的具体问题返回给一个定向修订智能体。

静默的工具失败。 工具返回 200 但响应体为空,或返回一个看似没问题的部分成功。缓解措施:工具必须返回明确的 { ok: boolean, ... } 形状,且智能体的工具使用循环必须把 ok: false 当作一等情况,而不是去解释的字符串。

失控循环。 智能体用略微不同的输入反复调用同一个工具。缓解措施:在循环中跟踪最近的工具调用,并在相同 tool+input 哈希重复时注入系统消息。同时使用前面提到的墙钟上限。

上下文腐烂。 长对话中智能体遗忘或推翻先前的决定。缓解措施:不要运行一个长对话。把工作流拆成多个步骤,每个步骤都有全新的上下文窗口,只把该步骤需要的产物传递给下一步。这是多步智能体设计中最大的可靠性提升。

模型漂移。 供应商更新模型后,你的提示悄然失效。缓解措施:在代码中固定模型版本,并在每次部署时运行一组小型评估集。即使只有 20 个代表性案例,也能捕获大多数回归。

如果我明天要开始一个新的智能体工作流,我会怎么做

顺序很重要。下面是我实际会做的事:

  1. 先把工作流写成纯流水线,不包含任何 LLM 调用。把模式写对。
  2. 找出真正需要判断力的 2-4 个步骤。其余步骤都保留为代码。
  3. 像写职位描述一样写工具规格。如果一个智能体超过 5 个工具,就拆成子智能体并用路由器连接。
  4. 在每个工具和每个结构化 LLM 调用上都加上严格的输入/输出验证。
  5. 在连接任何会写入的工具之前,先加上成本上限、循环上限和副作用门控。
  6. 用 trace ID 埋点,并持久化完整历史。
  7. 在发布前,用 20-50 个真实输入(而非合成输入)构建评估集。
  8. 在功能开关后面部署。对生产流量先运行一周影子模式。
  9. 关注人工介入率。优先迭代介入率最高的步骤。
  10. 只有到这时,才考虑扩展范围。

步骤 1-6 占总构建时间的约 60%,却能预防约 90% 的事故。跳过它们,你最终会得到一个在第二周就崩溃的演示。

我反复看到的一个流行错误:团队在把流水线写成枯燥函数之前,就先挑了一个框架(LangGraph、CrewAI,或本季度流行的任何东西)。框架不是让它工作的原因,拆解才是。

如果你正在构建一个必须面对真实生产输入的智能体工作流,并且希望有人帮你审视拆解或防护栏的设计,本季度我可以接受少量咨询。欢迎通过 lazar-milicevic.com/#contact 联系我,或在 博客 阅读更多关于 RAG 评估、智能体上下文以及如何发布能经受用户接触的智能体的文章。