12-Factor Agents:从 21k Star 的开源项目看 AI Agent 的正确构建姿势
12-Factor Agents:从 21k Star 的开源项目看 AI Agent 的正确构建姿势
你有没有过这样的经历:用 LangChain 或 CrewAI 搭了个 Agent demo,效果惊艳,老板看了直说"上生产"。然后你花了两个月把它从 80% 的完成度推到 95%,却发现框架的抽象层变成了最大的绊脚石。最后只能推倒重来,自己写循环、自己管状态、自己调 prompt。
这个剧情太熟悉了。Humanlayer 的创始人 Dex Horthy 跟 100+ SaaS 创始人聊过之后,发现几乎所有人都在重复同一个循环:选框架 → 快速到 80% → 卡在最后 20% → 反编译框架 → 从头开始。
所以他搞了一个项目叫 12-Factor Agents,借鉴 2011 年 Heroku 提出的经典 12-Factor App 方法论,给 AI Agent 开发总结出 12 条核心原则。21k Star,16 个 contributor,Apache 2.0 协议。不是又一个框架,而是一套思考框架。
本文提纲
- Agent 不是你想的那样:大多数"智能体"其实就是带 LLM 步骤的普通程序
- 12 条原则全解析:从 Tool Calling 到无状态 Reducer
- 为什么这 12 条比任何框架都值得学
Agent 不是你想的那样
Dex 的一个关键洞察是:市面上大部分号称"AI Agent"的产品,其实没那么 agentic。它们主要是确定性代码,只是在恰当的位置嵌入了 LLM 步骤,让体验变得像魔法一样。
这不是坏事。事实上,这恰恰是正确的方式。
Agent 的核心是一个简单循环:
initial_event = {"message": "..."}
context = [initial_event]
while True:
next_step = await llm.determine_next_step(context)
context.append(next_step)
if next_step.intent == "done":
return next_step.final_answer
result = await execute_step(next_step)
context.append(result)
三步:LLM 决定下一步 → 确定性代码执行 → 结果塞回上下文。就这么简单。但围绕这个循环,有 12 个工程决策需要做对。
12 条原则全解析
Factor 1:Natural Language to Tool Calls
自然语言转结构化调用,这是 Agent 最基本的操作模式。
用户说"帮我给 Terri 创建一个 750 美元的付款链接",LLM 把它翻译成结构化的 Stripe API 调用:
{
"function": {
"name": "create_payment_link",
"parameters": {
"amount": 750,
"recipient": "Terri"
}
}
}
这不是新概念,但 12-Factor Agents 把它作为第一原则强调:Agent 的核心能力就是把模糊意图翻译成精确操作。做好这一步,后面的一切才有意义。
Factor 2:Own Your Prompts
别把 prompt 工程外包给框架。
很多框架给你一个黑盒:
agent = Agent(
role="...",
goal="...",
personality="...",
tools=[tool1, tool2, tool3]
)
result = agent.run(task)
上手很快,但当你需要调优到最后 20% 的时候,你发现 prompt 藏在框架深处,改不了、看不到、debug 不着。
Dex 引用了 Hamel Husain 的观点:你的 prompt 是你产品的核心 IP。你必须能看到它、控制它、迭代它。框架可以帮你生成初始 prompt,但最终你需要完全掌控。
Factor 3:Own Your Context Window
Context Engineering 才是真正的工程。
LLM 是无状态函数,输入决定输出。所谓"Context Engineering"就是:在每一步,你到底往 context window 里塞了什么?
这包括:
- 系统指令和 prompt
- 对话历史
- Tool 调用结果
- 检索到的外部知识(RAG)
- 状态信息(当前步骤、已完成的任务)
你不必用标准的 message 格式。你可以自己决定如何组织上下文,只要最终能被 LLM 理解。有些团队用自定义的事件流格式,而不是 OpenAI 风格的 messages 数组,效果反而更好。
Factor 4:Tools are Just Structured Outputs
Tool 就是结构化输出,别搞复杂了。
Agent 需要调用的每个 tool,本质上就是让 LLM 输出一个 JSON 对象,然后你的确定性代码去执行它:
class CreateIssue:
intent: "create_issue"
issue: Issue
class SearchIssues:
intent: "search_issues"
query: str
LLM 的任务是从这些 intent 中选一个,填上参数。你的代码根据 intent 分发到对应的处理函数。就这么简单,不需要复杂的 tool registry 或 plugin 系统。
Factor 5:Unify Execution State and Business State
把执行状态和业务状态统一管理。
很多系统试图把这两者分开:
- 执行状态:当前步骤、下一步、重试次数、等待状态
- 业务状态:Agent 到目前为止做了什么(消息历史、tool 调用结果)
12-Factor Agents 建议:别急着分开。对于大多数 Agent 来说,统一管理反而更简单。用一个事件流(event stream)记录所有发生的事情,既是业务日志,也是执行状态。
thread = {
"events": [
{"type": "user_message", "content": "..."},
{"type": "tool_call", "tool": "search", "result": "..."},
{"type": "tool_call", "tool": "create", "result": "..."},
{"type": "assistant_message", "content": "..."},
]
}
当你的 Agent 确实需要复杂的状态管理时再拆分,而不是一开始就过度设计。
Factor 6:Launch/Pause/Resume with Simple APIs
Agent 应该像普通程序一样可以启动、暂停、恢复。
这意味着你的 Agent 需要暴露简单的 API:
# 启动
run = agent.launch(initial_event)
# 查询状态
status = agent.get_status(run.id)
# 暂停(等待人工审批)
agent.pause(run.id, reason="waiting_for_approval")
# 恢复
agent.resume(run.id, approval_result)
Agent 不应该是一个一旦启动就无法控制的黑盒。它需要能被外部系统(Cron、Webhook、其他 Agent)触发,也需要能在任何点暂停等待。
Factor 7:Contact Humans with Tool Calls
把"联系人类"也作为一种 Tool Call。
这是 12-Factor Agents 最有洞察力的原则之一。LLM 每次输出都面临一个关键选择:是返回纯文本内容,还是返回结构化的 tool call?这个选择压在了第一个 token 上。
更好的方式是:让 LLM 永远输出结构化 JSON,然后用不同的 intent 来表达不同行为:
class ContactHuman:
intent: "request_human_input"
message: str
urgency: str
class DoneForNow:
intent: "done"
summary: str
这样"向人类请求帮助"就跟"调用一个 API"一样,是一个普通的 tool call。你的控制流代码只需要检测到 request_human_input 这个 intent,就暂停执行,通知人类,等回复后继续。
这个模式天然支持 Human-in-the-Loop:不是事后打补丁,而是从一开始就设计好。
Factor 8:Own Your Control Flow
控制流是你的,不是框架的。
如果你掌控了控制流,就能做很多有意思的事:
while True:
next_step = await determine_next_step(context)
# 人类审批?暂停循环
if next_step.intent == "request_human_input":
await notify_human(next_step)
human_response = await wait_for_human()
context.append(human_response)
continue
# 需要 LLM 裁判?加一步判断
if next_step.intent == "risky_action":
judgment = await llm_judge(next_step)
if not judgment.approved:
context.append({"type": "rejection", "reason": judgment.reason})
continue
# 普通执行
result = await execute_step(next_step)
context.append(result)
你可以自由地在循环中加入:
- LLM-as-judge(让另一个 LLM 审核关键决策)
- 上下文压缩(context window 快满时自动总结)
- 自定义重试逻辑
- 审计日志
这些在框架里要么做不到,要么得 hack。
Factor 9:Compact Errors into Context Window
错误信息也要压缩进上下文。
Agent 的一大优势是"自愈"——tool 调用失败时,LLM 能读错误信息,然后调整下一次调用。
result = await handle_next_step(next_step)
if result.error:
thread["events"].append({
"type": "error",
"message": result.error_message,
"stack_trace": result.stack_trace[:500] # 截断,不要塞整个堆栈
})
关键技巧:截断错误信息。一个完整的 Python stack trace 可能有几千个 token,但 LLM 通常只需要最后几行就能理解问题。主动压缩错误信息,给有用的内容留空间。
Factor 10:Small, Focused Agents
小而专注的 Agent,不要搞巨无霸。
这是实践中最重要的原则之一。LLM 的能力随 context 增长而下降——步数越多,context 越长,LLM 越容易迷路。
一个好的 Agent 应该只负责 3-20 步操作。超过这个范围,就拆成多个 Agent 协作。
不要做一个"万能客服 Agent",而是做:
- 订单查询 Agent:只查订单
- 退款 Agent:只处理退款
- 升级 Agent:只负责转人工
然后用确定性代码编排它们。每个 Agent 小到能在 context window 里高效运行,精准可靠。
Factor 11:Trigger from Anywhere
让 Agent 能从任何地方触发,用户在哪,Agent 就在哪。
结合 Factor 6(Launch/Pause/Resume)和 Factor 7(Contact Humans with Tool Calls),你的 Agent 应该能:
- 从 Slack 消息触发
- 从邮件触发
- 从短信触发
- 从 Webhook 触发
- 从定时任务触发
而且它联系人类的时候,也应该能通过用户习惯的渠道回复。在 Slack 里提问就在 Slack 里回复,不要跳转到另一个 App。
Factor 12:Make Your Agent a Stateless Reducer
把 Agent 做成无状态的 Reducer。
这一条来自函数式编程的思想。Agent 的每一步都是:
(context_so_far, next_event) -> new_context
就像 foldl(或 reduce)一样:
final_state = reduce(execute_step, events, initial_state)
Agent 本身不持有状态,所有状态都在 context 里。这意味着:
- 可重放:从事件流重建任何时刻的状态
- 可调试:看事件流就知道发生了什么
- 可扩展:多个实例可以处理同一个 Agent 的不同步骤
为什么这 12 条比任何框架都值得学
Dex 在项目 README 里写了一段很诚实的话:
我试过市面上的每一个 Agent 框架,从 LangChain 到 smolagents 到 LangGraph。我跟很多优秀的 YC 创始人聊过,他们大多数都在自己写。
这不是说框架没用。框架帮你快速到 80%,但最后 20% 的差距——那种让客户愿意付费的质量——需要你理解底层在发生什么。
12-Factor Agents 的核心信息是:Agent 的构建模块其实不复杂。自然语言转 tool call、管理 context window、控制循环流、处理错误、联系人类——这些概念任何一个有经验的工程师都能理解和实现。
你不需要一个 5000 行的框架来做这些。你需要的是理解这些原则,然后把它们用最适合你产品的方式组合起来。
这 12 条原则也回答了一个更深的问题:什么样的 AI 软件才能上生产? 答案不是"更聪明的 LLM"或者"更复杂的框架",而是用工程化的方法管理 LLM 的不确定性——结构化输入输出、可控的状态管理、可审计的决策日志、自然的 Human-in-the-Loop。
项目地址:github.com/humanlayer/12-factor-agents
作者: itech001
来源: 公众号:AI人工智能时代
网站: https://www.theaiera.cn/
每日分享最前沿的AI新闻资讯和技术研究。
本文首发于 AI人工智能时代,转载请注明出处。

浙公网安备 33010602011771号