AI Agent 控制流实战指南:别再堆 Prompt 了,写代码吧
发布日期: 2026-05-08 • 阅读: 8 分钟 • 标签: AI Agent, 控制流, LLM 编排, 状态机, prompt 工程2026 年 5 月,一篇名为 "Agents Need Control Flow, Not More Prompts" 的文章在 Hacker News 上获得 400+ 讨论。
核心论点直击当下 AI Agent 开发的痛点:你用 MANDATORY、DO NOT SKIP、CRITICAL 这些大写单词堆出来的 prompt,本质上只是在碰运气。
本文把这篇文章的核心思想翻译成可实操的工程方案——怎么从"写更好的 prompt"转型到"写更好的控制流代码"。
Prompt 的极限在哪里
想象一种编程语言:它的语句是"建议",函数可能返回"成功"但实际上啥都没做。你能在这种语言上构建可靠的复杂系统吗?不能。
但这就是我们用 prompt 写 AI Agent 的现状:
- 你写
MANDATORY: 必须输出 JSON→ LLM 偶尔还是输出 markdown - 你写
DO NOT SKIP 步骤 3→ Agent 自作聪明跳过了 - 你写
CRITICAL: 验证结果→ 它"验证"了然后返回一个幻觉
这不是 LLM 的错。这是工具选型的错。软件工程之所以可靠,靠的是递归可组合性:函数调用函数,模块调用模块,每一步的行为都是确定性的。Prompt chain 缺少这个核心性质。
当你发现自己开始在 prompt 里写 IMPORTANT!!! 甚至 ⚠️⚠️⚠️ 的时候,就该停下来想想了——你已经碰到了 prompt 工程的天花板。
核心思路:把 LLM 当组件,而不是当系统
可靠 Agent 的架构应该遵循一个原则:逻辑走代码,创造力走 LLM。
具体来说,你需要把 Agent 的流程拆成三层:
┌─────────────────────────────────┐
│ Layer 3: Orchestration Layer │ ← 状态机 / DAG / while 循环(代码控制)
│ ┌───────────────────────────┐ │
│ │ Layer 2: Validation Layer │ ← 断言检查 / schema 校验 / 边界检测
│ └───────────────────────────┘ │
│ ┌───────────────────────────┐ │
│ │ Layer 1: LLM Layer │ ← 纯 prompt,只做生成/推理
│ └───────────────────────────┘ │
└─────────────────────────────────┘
每一层各司其职。LLM 只负责"产生内容"和"做推理",控制流、校验、重试全部交给代码。
模式一:状态机驱动 Agent
这是最基础也最实用的模式。把 Agent 的工作流建模为有限状态机:
States: [PLAN, EXECUTE, VERIFY, REVISE, DONE]
Transitions:
PLAN → EXECUTE (规划完成)
EXECUTE → VERIFY (执行完成)
VERIFY → DONE (验证通过)
VERIFY → REVISE (验证失败)
REVISE → EXECUTE (修正完成)
VERIFY → PLAN (需要重新规划)
REVISE → FAILED (修正次数超限)
用 TypeScript 实现大概长这样:
type AgentState = 'plan' | 'execute' | 'verify' | 'revise' | 'done' | 'failed';
class AgentWorkflow {
private state: AgentState = 'plan';
private retryCount = 0;
private readonly maxRetries = 3;
async run(task: string): Promise<Result> {
while (this.state !== 'done' && this.state !== 'failed') {
switch (this.state) {
case 'plan':
this.plan = await llm.plan(task);
this.state = 'execute';
break;
case 'execute':
this.output = await llm.execute(this.plan);
this.state = 'verify';
break;
case 'verify':
const valid = await this.validate(this.output);
if (valid) {
this.state = 'done';
} else if (this.retryCount < this.maxRetries) {
this.retryCount++;
this.state = this.decideReviseOrReplan();
} else {
this.state = 'failed';
}
break;
case 'revise':
this.output = await llm.revise(this.output, this.feedback);
this.state = 'verify';
break;
}
}
return this.state === 'done' ? success(this.output) : failure();
}
}
关键点:状态转换是确定性的。LLM 不决定下一步做什么,它只负责在给定状态下生成内容。流程的决定权在代码手里。
模式二:校验检查点(Validation Checkpoints)
这是最容易立刻上手的改进。在每次 LLM 调用之后插入一个代码级的校验:
async function safeLLMCall<T>(
prompt: string,
schema: z.ZodSchema<T>,
options?: { maxRetries?: number }
): Promise<T> {
const maxRetries = options?.maxRetries ?? 2;
for (let i = 0; i <= maxRetries; i++) {
const raw = await llm.generate(prompt);
try {
// 尝试解析 JSON
const parsed = JSON.parse(raw);
// Zod schema 校验
return schema.parse(parsed);
} catch (e) {
if (i === maxRetries) throw new ValidationError(e);
// 把校验失败信息传给 LLM 让它修正
prompt += `\n\nPrevious attempt failed: ${e.message}\nPlease fix and retry.`;
}
}
}
这个模式的精髓在于:校验是代码干的,不是 LLM 干的。Zod / Pydantic / JSON Schema 是确定性的——通过就是通过,不通过就是不通过。你不应该让 LLM 自己判断自己的输出对不对。
常见的校验类型:
- Schema 校验 — 输出格式是否符合预期(JSON schema / Zod)
- 语义校验 — 输出内容是否包含关键信息(正则 / 关键词匹配)
- 边界校验 — 数值是否在合理范围(金额不能为负)
- 一致性校验 — 多次调用结果是否自洽(交叉验证)
模式三:结构化错误恢复
Agent 一定会出错。区别在于你怎么设计恢复策略。
三种常见的恢复策略:
1. 重试 + 反馈注入(Return with Feedback)
校验失败时,把具体的失败信息作为 feedback 重新发给 LLM。因为 LLM 能看到"我之前做错了什么",修正成功率远高于从头再生成一次。
# 效果对比
# 无反馈重试: 正确率 65% → 68% (几乎没用)
# 带反馈重试: 正确率 65% → 91% (显著提升)
2. 退化回退(Graceful Degradation)
复杂任务失败后,降级到更简单但更可靠的方案:
try:
result = await agent.complex_reasoning(task)
except MaxRetriesExceeded:
# 降级到规则匹配
result = await rule_based_fallback(task)
# 甚至:
# Agent 失败 → 模板匹配 → 人工兜底
3. 人类介入(Human-in-the-Loop)
对于高风险场景,在 Agent 报错或置信度过低时,把控制权交给人。这是最老土但最稳妥的方案。
工具生态:现成的控制流框架
2026 年已经有不少工具帮你实现上述模式,不需要从头造轮子:
- LangGraph — 基于图的 Agent 编排框架,原生支持状态机和条件路由
- Vercel AI SDK — 内置 tool calling 和 step 管理,适合轻量级 Agent
- Temporal / Inngest — 生产级工作流引擎,自带重试、超时、可观测性
- Pydantic AI — Python 生态,以类型安全为核心的 Agent 框架
- Durable Functions / Cloudflare Workflows — 有状态 serverless 工作流
选择建议:
- 原型阶段 → Vercel AI SDK / Pydantic AI
- 生产级单 Agent → LangGraph
- 多 Agent 编排 + 可靠性要求高 → Temporal / Inngest
- 和现有云基础设施集成 → Durable Functions / Cloudflare Workflows
常见反模式
以下做法看起来对,实际上会让你的 Agent 越来越不可靠:
- 一条 prompt 包所有 — 把全部指令塞进 system prompt,指望 LLM 自己排序执行。正确的做法是拆成多个独立步骤,每个步骤一个明确的 prompt + 校验。
- 用 prompt 做错误处理 — "如果遇到错误,重试最多三次"写在 system prompt 里。LLM 经常忘记数到几了。应该交给代码的 for 循环。
- 相信 LLM 的自我评估 — "请验证你的回答是否正确"。LLM 几乎不会说自己错了。需要独立的校验层。
- 无限制重试 — 不加最大重试次数,可能导致无限循环烧钱。代码级必须设置上限。
可观测性:没有日志就没有可靠性
如果生产环境的 Agent 出了问题你却不知道是哪一步失败的,那它算不上可靠。
至少需要记录这些信息:
- 每一步的输入输出
- 状态转换路径和耗时
- 校验通过/失败记录
- 重试次数和原因
- 每次 LLM 调用的 token 用量
推荐使用 OpenTelemetry 标准追踪,或者至少在每个状态转换时打结构化日志。
# 最少日志格式
{"timestamp":"...","event":"state_transition","from":"plan","to":"execute",
"duration_ms":2300,"tokens":1450,"task_id":"..."}
{"timestamp":"...","event":"validation_failed","step":"verify",
"reason":"schema_mismatch","retry_count":1,"task_id":"..."}
总结
如果你从这篇文章只记住一件事,那就是:prompt 是给 LLM 看的,控制流是给代码写的。
每次你想往 system prompt 里加一个 IMPORTANT 时,停下来想一下——这个逻辑能不能用 if 语句、for 循环、状态机或者 try/catch 来实现?如果可以,那就写在代码里。
LLM 擅长的是创造性推理和内容生成。让 LLM 做 LLM 擅长的事,让代码做代码擅长的事。这样你的 Agent 既聪明又可靠。
参考阅读:Agents need control flow, not more prompts (bsuh.bearblog.dev)