秉承 12 要素应用 (12 Factor App) 的精神。 本项目的源代码在 https://github.com/humanlayer/12-factor-agents 公开,欢迎您提出反馈和贡献。让我们一起探索吧!
嗨,我是 Dex。我捣鼓 AI Agent 已经有一段时间了。
我尝试过市面上所有的 Agent 框架,从即插即用的 crew/langchain 类,到“极简主义”的 smolagent 类,再到“生产级”的 langraph、griptape 等等。
我和很多非常优秀的创始人聊过,无论是在 YC 内外,他们都在用 AI 构建令人印象深刻的东西。他们中的大多数人都在自己构建技术栈。我很少看到在面向客户的生产环境 Agent 中使用框架。
我惊讶地发现,市面上大多数自称为“AI Agent”的产品,其 Agent 特性(agentic)并没有那么强。很多产品主要是确定性代码(deterministic code),只是在恰当的关键点上点缀了 LLM 步骤,从而创造出真正神奇的体验。
Agent,至少是那些优秀的 Agent,并不遵循 “给你 Prompt,给你一堆工具,循环直到达到目标” 的模式。相反,它们主要由软件组成。
于是,我开始着手回答:
欢迎来到 12 要素 Agent。正如自戴利以来的每一位芝加哥市长始终在城市主要机场张贴的那样,我们很高兴您能来到这里。
特别感谢 @iantbutler01、@tnm、@hellovai、@stantonk、@balanceiskey、@AdjectiveAllison、@pfbyjy、@a-churchill 以及旧金山 MLOps 社区对本指南提供的早期反馈。
即使 LLM 继续以指数级速度变得更加强大,仍然会有一些核心工程技术能够让 LLM 驱动的软件更可靠、更具可扩展性且更易于维护。
- 我们是如何走到这一步的:软件简史
- 要素 1:自然语言到工具调用
- 要素 2:掌控你的 Prompt
- 要素 3:掌控你的上下文窗口
- 要素 4:工具即结构化输出
- 要素 5:统一执行状态与业务状态
- 要素 6:通过简单的 API 启动/暂停/恢复
- 要素 7:通过工具调用联系人类
- 要素 8:掌控你的控制流
- 要素 9:将错误压缩到上下文窗口中
- 要素 10:小型、专注的 Agent
- 要素 11:随处触发,随时随地满足用户
- 要素 12:让你的 Agent 成为无状态 Reducer
要深入了解我的 Agent 之旅以及我们为何走到这一步,请查看 软件简史 - 这里是一个快速总结:
我们将大量讨论有向图(Directed Graphs, DGs)及其无环(Acyclic)的朋友 DAGs。我首先要指出的是……嗯……软件就是一个有向图。我们过去用流程图来表示程序是有原因的。
大约 20 年前,我们开始看到 DAG 编排器变得流行。我们谈论的是像 Airflow、Prefect 这样的经典工具,以及一些前辈和一些更新的工具(如 dagster、inngest、windmill)。这些工具遵循相同的图模式,并增加了可观测性、模块化、重试、管理等好处。
我不是第一个这么说的人,但当我开始学习 Agent 时,我最大的体会是,你可以扔掉 DAG。软件工程师不再需要为每个步骤和边缘情况编写代码,而是可以给 Agent 一个目标和一组转换(transitions):
然后让 LLM 实时做出决策来找出路径
这里的承诺是,你编写更少的软件,你只需给 LLM 图的“边”(edges),让它找出“节点”(nodes)。你可以从错误中恢复,可以编写更少的代码,并且你可能会发现 LLM 找到了解决问题的新方法。
正如我们稍后将看到的,事实证明这种方法并不像预期的那样有效。
让我们再深入一步——对于 Agent,你有一个包含 3 个步骤的循环:
- LLM 确定工作流中的下一步,输出结构化的 JSON(“工具调用”)
- 确定性代码执行工具调用
- 结果被附加到上下文窗口
- 重复此过程,直到确定下一步是“完成”
initial_event = {"message": "..."} # 初始事件,例如用户消息
context = [initial_event] # 上下文,从初始事件开始
while True:
# LLM 决定下一步
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) # 将结果添加到上下文中
我们最初的上下文只是起始事件(可能是一个用户消息,可能是一个 cron 任务触发,可能是一个 webhook 等),然后我们让 LLM 选择下一步(工具)或者确定我们已经完成。
这是一个多步骤示例:
027-agent-loop-animation.mp4
归根结底,这种方法并不像我们希望的那样有效。
在构建 HumanLayer 的过程中,我与至少 100 位 SaaS 构建者(主要是技术创始人)进行了交流,他们都希望使现有产品更具 Agent 特性。这个过程通常是这样的:
- 决定要构建一个 Agent
- 产品设计、用户体验映射、确定要解决的问题
- 想要快速行动,于是选择了某个框架($FRAMEWORK)并 开始构建
- 达到 70-80% 的质量标准
- 意识到 80% 对于大多数面向客户的功能来说还不够好
- 意识到要突破 80% 需要对框架、Prompt、流程等进行逆向工程
- 从头开始
一些免责声明 (Random Disclaimers)
免责声明 (DISCLAIMER):我不确定在哪里说这个最合适,但这里似乎不错:这绝不是对市面上众多框架或致力于这些框架的相当聪明的人们的贬低。它们实现了令人难以置信的事情,并加速了 AI 生态系统的发展。
我希望这篇文章的一个成果是,Agent 框架的构建者能够从我及其他人的经验中学习,并使框架变得更好。
特别是对于那些希望快速行动但又需要深度控制的构建者。
免责声明 2 (DISCLAIMER 2):我不会讨论 MCP。我相信你能明白它在其中的位置。
免责声明 3 (DISCLAIMER 3):我主要使用 TypeScript,原因在此,但所有这些东西在 Python 或你喜欢的任何其他语言中都适用。
不管怎样,回到正题…
在深入研究了数百个 AI 库并与数十位创始人合作后,我的直觉是:
- 有一些核心要素能让 Agent 变得出色。
- 完全投入一个框架并进行本质上的全新重写(greenfield rewrite)可能适得其反。
- 有一些核心原则能让 Agent 变得出色,如果你引入一个框架,你可能会获得其中大部分或全部原则。
- 但是,我所见过的让构建者能够将高质量 AI 软件交付给客户的最快方法是:从 Agent 构建中提取小型、模块化的概念,并将它们融入到现有的产品中。
- 这些来自 Agent 的模块化概念可以被大多数熟练的软件工程师定义和应用,即使他们没有 AI 背景。
- 我们是如何走到这一步的:软件简史
- 要素 1:自然语言到工具调用
- 要素 2:掌控你的 Prompt
- 要素 3:掌控你的上下文窗口
- 要素 4:工具即结构化输出
- 要素 5:统一执行状态与业务状态
- 要素 6:通过简单的 API 启动/暂停/恢复
- 要素 7:通过工具调用联系人类
- 要素 8:掌控你的控制流
- 要素 9:将错误压缩到上下文窗口中
- 要素 10:小型、专注的 Agent
- 要素 11:随处触发,随时随地满足用户
- 要素 12:让你的 Agent 成为无状态 Reducer
- 在 这里 为本指南做出贡献
- 我在 Tool Use 播客的一期节目中讨论了很多这方面的内容(2025 年 3 月)
- 我在 The Outer Loop 上写了一些相关的东西
- 我与 @hellovai 一起举办 关于最大化 LLM 性能的网络研讨会
- 我们使用这种方法论在 got-agents/agents 下构建 OSS Agent
- 我们无视了自己所有的建议,构建了一个 在 Kubernetes 中运行分布式 Agent 的框架
- 本指南中的其他链接:
- 构建有效的 Agent (Anthropic)
- Prompt 即函数 (Prompts are Functions)
- Mailcrew Agent
- Mailcrew 演示视频
- Chainlit 演示
- 用于 LLM 的 TypeScript
- Schema 对齐解析 (Schema Aligned Parsing)
- 函数调用 vs 结构化输出 vs JSON 模式 (Function Calling vs Structured Outputs vs JSON Mode)
- GitHub 上的 BAML
- OpenAI JSON vs 函数调用
- Outer Loop Agents
- Airflow
- Prefect
- Dagster
- Inngest
- Windmill
- AI Agent 索引 (MIT)
- NotebookLM 关于寻找模型能力边界