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 协议。不是又一个框架,而是一套思考框架。

本文提纲

  1. Agent 不是你想的那样:大多数"智能体"其实就是带 LLM 步骤的普通程序
  2. 12 条原则全解析:从 Tool Calling 到无状态 Reducer
  3. 为什么这 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人工智能时代,转载请注明出处。

posted @ 2026-05-20 17:11  iTech  阅读(1)  评论(0)    收藏  举报