Apache Burr 实战: 状态机构建可恢复 AI Agent,对比 LangGraph

TL;DR

写过几个 LLM 应用的人多半都经历过同一条曲线:第一版是几行脚本,prompt 拼好、调一次模型、打印结果,跑通了很开心。等到要做多轮对话、要带记忆、要在某一步插入人工审批、要在出错后从中间继续——那几行脚本就开始膨胀成一团互相缠绕的全局变量和 if/else。状态散落在函数参数、闭包和模块级变量里;某一步抛了异常,整个会话从头再来;线上出了诡异回答,你想复盘”它当时到底看到了什么 state、走了哪条分支”,却发现什么都没留下。

这三件事——状态散乱、出错难恢复、执行不可观测——是无状态脚本拼 LLM 调用的通病。问题的根源不在某一行代码,而在心智模型:你其实在构建一个会随输入改变内部状态、并据此做决策的系统,却用”一次性函数调用”的方式去写它。

Apache Burr 给出的解法是把这类应用显式建模成状态机(state machine):把”数据”沉淀进不可变的 State,把”每一步动作”写成声明了读写范围的 Action,把”下一步走哪”写成显式的 Transitions 和 Conditions。这样一来,应用的控制流就从藏在 prompt 链和分支里的隐式逻辑,变成了一张能画出来、能回放、能审计的图。Burr 给自己定的三个目标也正对着上面三个痛点:可靠(reliable)、可恢复(resumable)、可观测(observable)。这篇文章用一个能跑的最小 chatbot 例子,把这套思路讲透,再和 LangGraph、CrewAI 等同类框架 做个老实的横向对比。

Apache Burr 是什么

Apache Burr 是一个用状态机构建有状态、会做决策应用的 Python 库,典型场景包括 chatbot、agent 和各类模拟(simulation)。它目前是 Apache 软件基金会的孵化器(incubator)项目,截至 2026 年 5 月的版本是 v0.42.0-incubating,采用 Apache 2.0 许可证。在 GitHub 上有约 2,100 个 star,PyPI 累计下载量超过 40 万。

它的一个鲜明取向是低抽象、几乎零依赖:核心就是一个纯 Python 库,不绑定任何特定的 LLM SDK,也不要求你先买账一整套生态。这一点和”AI Agent 不应该自己重造一遍基础设施“的思路是一致的——Burr 想做的是编排与状态管理这一层薄薄的骨架,而不是把检索、工具、记忆全都塞进来变成一个大而全的框架。

理解 Burr 只需要抓住五个核心概念:

  • State(状态):一个不可变(immutable)的数据结构,承载应用的全部数据。每一步动作不是”原地修改”它,而是返回一个新的 State。不可变意味着任意时刻的 state 都是一份可以单独保存、比较、回放的快照。
  • Action(动作):用 @action 装饰的函数,是应用里”做一件事”的最小单元。它必须显式声明自己读哪些写哪些 state 变量(reads=[...]writes=[...])。这份声明不只是文档,它让框架(和读代码的人)一眼就能看出每一步动了什么数据。
  • Application(应用):通过 ApplicationBuilder 把一组 action 编排起来的整体。它是你最终拿来运行、暂停、恢复的对象。
  • Transitions(转移):连接 action 的有向边,定义”这一步之后可能走到哪一步”。
  • Conditions(条件):决定在多条出边里实际走哪一条的逻辑——这是状态机能”做决策”的地方。

把这五个词记住,你就握住了 Burr 的全部心智负担。剩下的都是在这套骨架上长出来的能力。

快速上手:跑通最小 chatbot

安装一行就够:

pip install "apache-burr[start]"

这里的 [start] 是额外依赖组,会顺带装上 Burr UI 等可视化组件。如果你只要核心库,去掉 [start] 也行,但第一次上手强烈建议带上,因为可视化是 Burr 最直观的卖点之一。

下面是一个最小的对话循环——人输入、AI 回应、再回到人输入,无限往复:

from burr.core import action, State, ApplicationBuilder

@action(reads=[], writes=["prompt", "chat_history"])
def human_input(state: State, prompt: str) -> State:
    chat_item = {"role": "user", "content": prompt}
    return state.update(prompt=prompt).append(chat_history=chat_item)

@action(reads=["chat_history"], writes=["response", "chat_history"])
def ai_response(state: State) -> State:
    response = _query_llm(state["chat_history"])
    chat_item = {"role": "system", "content": response}
    return state.update(response=response).append(chat_history=chat_item)

app = (
    ApplicationBuilder()
    .with_actions(human_input, ai_response)
    .with_transitions(
        ("human_input", "ai_response"),
        ("ai_response", "human_input"),
    )
    .with_state(chat_history=[])
    .with_entrypoint("human_input")
    .build()
)

逐段拆开看,每一行都在体现前面那五个概念。

@action 的 reads / writes。 第一个 action human_input 声明 reads=[](它不依赖任何已有 state,纯粹接收外部输入)、writes=["prompt", "chat_history"](它会写这两个变量)。第二个 action ai_response 声明 reads=["chat_history"]writes=["response", "chat_history"],意思是它只关心历史对话、产出一条回复并追加进历史。这份读写清单就是 Burr 的”数据契约”:你不必通读函数体,也能从签名上判断每一步触碰了什么。这种把”动了哪些数据”摆到台面上的做法,本质上和 prompt / tool call / token 全链路追踪 想解决的是同一类问题——让黑箱变白箱。

不可变 State 的更新方式。 注意函数体里没有 state["prompt"] = ... 这样的原地赋值,而是 state.update(prompt=prompt)state.append(chat_history=chat_item),并把结果 return 出去。update 用于覆盖标量,append 用于往列表里追加。它们都返回新的 State 对象,旧的那份原封不动。这正是”可恢复”的地基:每一步的 state 都是一份能被独立持久化的快照。

ApplicationBuilder 编排。 ApplicationBuilder 用链式调用把零件拼起来:with_actions 注册所有 action;with_transitions 声明边——这里两条边 ("human_input", "ai_response")("ai_response", "human_input") 构成一个环,对话因此能一轮一轮转下去;with_state(chat_history=[]) 给出初始 state;with_entrypoint("human_input") 指定从哪个 action 起步;最后 .build() 产出可运行的 app

_query_llm 是你自己接的模型调用——可以是任意厂商的 SDK,也可以是 Claude Agent SDK 这类封装。Burr 不替你选模型,这正是它”framework-agnostic(框架无关)”的体现:它只管编排和状态,模型怎么调是你的自由。

跑起来之后,你得到的不再是一段线性脚本,而是一台明确的状态机:当前停在哪个 action、state 长什么样、下一步可以往哪走,全都有据可查。

状态机心智模型:为什么显式比隐式更可控

很多 LLM 应用的”控制流”其实藏在两个地方:一是 prompt 里那些”如果用户问 X 就……否则……”的自然语言指令,二是散在 Python 里的临时 if/else。两者都属于隐式状态——系统当前处于什么阶段、为什么做出这个决策,没有一个单一、可查的来源。

状态机的价值在于把这些都显式化。Burr 强迫你回答三个问题:现在有哪些状态变量(State)?每一步会读写其中哪些(Action 的 reads/writes)?从这一步出发能去哪、按什么条件去(Transitions + Conditions)?一旦这三件事写成了代码而非散落在 prompt 字里行间,好处是连锁的:

  • 可推理。 整个应用就是一张有向图,你能把它画出来、和同事在白板上讨论”这条路径对不对”,而不是反复读 prompt 猜模型会怎么理解。
  • 可定位。 出问题时,你知道它停在哪个 node、当时的 state 是什么、是哪个 condition 把它送上了错误的边。这和专门的 AI Agent 调试器 HALO 想给你的能力是同一个方向:让 agent 的执行过程可断点、可回看。
  • 可演进。 加一个新阶段就是加一个 action 加几条边,不会牵一发动全身。

这套”显式状态 + 显式转移”的思路,和把 agent 看成一个反复运行的 loop engineering / harness 是相通的——agent 的本质就是”观察状态 → 决策 → 改变状态”的循环,Burr 只是把这个循环的每一环都摆到明面上、变成可检查的对象。相比之下,把复杂决策完全压进一个大 prompt(哪怕用上 多智能体辩论这类把推理内化进模型 的技巧),在工程可控性上始终隔着一层。

还有一个常被忽略的好处:显式状态机让”迭代”这件事变得有据可依。当你想优化某一步的 prompt 或某条 condition 的判断逻辑时,因为每次执行都留下了完整的 state 轨迹,你能拿真实历史数据来对照”改之前 / 改之后”走的分支有没有变好,而不是凭感觉调。这正是 prompt learning 反馈圈 强调的——没有可度量、可对照的执行记录,所谓”优化”只是盲改。状态机把每一步的输入输出都钉在了 state 上,等于天然给反馈圈准备好了数据底座。

可靠性三件套:persistence、Burr UI、hooks

显式建模只是地基,Burr 真正让人愿意把它用在生产环境的,是建立在状态机之上的三组能力。

Persistence / checkpointing:回放、调试、审计

因为 State 不可变,Burr 可以在每一步之间对 state 做快照(checkpointing)并持久化下来。这带来几个直接收益:

  • 可恢复。 长流程跑到一半崩了,不必从头再来,从最近一次快照接着跑即可。对那些一步要调好几次 LLM、耗时又花钱的流程,这是实打实的省钱省时间。
  • 可回放调试。 你能把某次执行的 state 序列原样重放,复现当时的每一步,定位”它为什么走了这条分支”。
  • 可审计。 完整的执行历史本身就是一份审计日志:谁在什么 state 下做了什么决策,全部留痕。在需要合规留证的场景里,这不是锦上添花而是硬需求。

Burr UI:把执行轨迹画出来

[start] 装上的 Burr UI 能实时可视化执行轨迹:一边是状态机的结构图(哪些 node、哪些边),一边是每一步对应的 state 内容。你能看着应用一步步往前走,state 怎么变、走了哪条边一目了然。这是它和很多需要外接 SaaS 才能看执行链路的框架最直观的差别——监控 UI 是开源、自带、本地可跑的。

Hooks:可观测与集成的接入点

Burr 提供 hooks 作为扩展点:你可以挂上自定义逻辑,在动作执行前后做记录、把 state 写进自己的存储、对接外部的 telemetry/可观测系统。需要把 Burr 嵌进现有技术栈时,hooks 就是那个不破坏核心、又能让你接管细节的口子。

此外 Burr 还支持 streaming(流式输出)——对话类应用要做”边生成边显示”的打字机效果时直接可用。

这三件套合起来,正好把开头那三个痛点逐一对上:persistence 治”出错难恢复”,Burr UI + hooks 治”执行不可观测/不可审计”,而显式状态机本身治”状态散乱”。

Burr vs LangGraph vs CrewAI:怎么选

Burr 不是这个领域唯一的玩家,最常被拿来比较的是 LangGraph,此外还有 CrewAI 等同类。先看一张核实过的对比:

维度 Apache Burr LangGraph
显式建模状态机
框架无关(framework-agnostic)
自带开源监控 UI ❌(靠 LangSmith)
支持非 LLM 用例

把 LangGraph 也说公道些:它把应用建模成一张有向图,state 用 TypedDict 或 Pydantic 来定义,原生支持环(agentic loop条件边和通过 checkpointer 做持久化。它最大的优势在生态——背靠 LangChain,工具、记忆、检索这些组件信手可得;可观测则交给 LangSmith。如果你的团队已经在 LangChain 体系里(比如已经在用 LangChain 做生产级 RAG),上手 LangGraph 几乎是零成本,复杂的图控制流也表达得很顺。

所以选型建议很朴素,不必踩谁捧谁:

  • 优先选 Apache Burr,当你的首要诉求是可靠性 + 可审计性:要 state 可回放、执行可复盘、要一个开箱即用的开源监控 UI,或者你的应用里有大量非 LLM 用例(纯模拟、超参搜索、规则决策),不想被绑死在某个 LLM 生态上。Burr 低抽象、零依赖的取向在这些场景里很贴合。
  • 优先选 LangGraph,当你已经在 LangChain 生态里、需要复杂的图控制流,并且愿意用 LangSmith 这套(部分付费的)可观测方案。生态红利能让你少写很多胶水代码。
  • CrewAI 则是另一种风味的同类竞品,更偏向以”角色化的多 agent 协作”为中心来组织应用——当你的问题天然就是”几个分工明确的 agent 一起干活”,它的抽象会更顺手。

值得强调的是:这三者在”显式建模 + framework-agnostic”上是趋同的,差别更多在生态绑定程度、可观测方案、以及对非 LLM 场景的友好度。想系统地把这些框架放在一张地图上看,可以读 AI Agent 框架全景

典型落地场景

Burr 官方和社区里反复出现的几类用例,正好覆盖了”有状态决策应用”的主要形态:

  • 多轮对话 chatbot。 就是前面那个最小例子的放大版——chat_history 在 State 里累积,每轮在 human/AI 之间转移。state 天然成了对话记忆。
  • 带记忆的 RAG 应用。 检索、改写、生成、引用核查可以各自是一个 action,中间状态(检索到的片段、置信度)全在 State 里。出错时能从某一步重跑,而不是整条链重来。这类把检索和生成拆成显式步骤的做法,和构建 生产级 RAG 的工程思路高度契合。
  • human-in-the-loop 审批流。 这是状态机最闪光的场景:流程跑到”待审批” action 就停下、持久化当前 state,等人点了同意/驳回,再凭 condition 走向不同的后续分支。靠 persistence,这个”暂停—等待—恢复”可以跨进程、跨小时甚至跨天。
  • 模拟与超参搜索。 因为 Burr 不要求一定用 LLM,纯逻辑的状态演化(多智能体模拟、参数扫描)一样能用它来组织——这正是上面对比表里”支持非 LLM 用例”那一栏的实际价值。
  • 其他。 邮件写作助手、用 LLM 做叙事的文字冒险游戏等,本质都是”状态随交互演化 + 每步做决策”,都落在 Burr 的舒适区。

如果你正在从零搭建自己的 agent 应用,AI Agent 开发全攻略Coze + Python + MCP 集成 这两篇可以帮你把工具调用、外部集成这些环节补齐,再用 Burr 把它们编排成一台可控的状态机。

相关阅读

FAQ

Q:用了 Burr 就不能用 LangGraph 了吗?两者冲突吗?
不冲突,但通常没必要在同一条流程里硬塞两套编排框架——它们解决的是同一层问题(状态 + 控制流编排),叠在一起只会增加心智负担。更务实的做法是按上面的选型建议二选一。值得复用的是经验本身:你为状态机画出的那张图、拆出的那些步骤,换框架时大多能平移,这也是 Agent skill 跨工具复用 想表达的——沉淀下来的是结构,不是某个 API。

Q:还在 incubating,能上生产吗?
“incubating” 是 Apache 软件基金会对项目治理成熟度的标注(社区、流程、商标等还在按基金会规范完善),并不直接等于”代码不能用于生产”。是否上生产,要按你自己的标准评估:是否有充分测试、是否锁定了版本、回滚预案是否齐备。Burr 低抽象、近零依赖的设计反而降低了被框架”锁死”的风险——真要迁移,迁移面也小。建议从非核心链路或可灰度的场景开始试。

Q:一定要用 LLM 吗?
不需要。Burr 的核心是状态机,不假设你一定调 LLM。纯逻辑的模拟、决策流、超参搜索都能用它来组织——这也是它和很多”为 LLM 而生”的框架的一个本质区别(见对比表”支持非 LLM 用例”)。

Q:可视化 UI 怎么开?
安装时带上 [start] 这个额外依赖组(pip install "apache-burr[start]"),它会把 Burr UI 等组件一并装好,之后即可在本地启动、实时看状态机结构图和每一步的 state。它是开源、自带、本地可跑的,不依赖外部 SaaS。

Q:Burr 和 CrewAI 该怎么取舍?
如果你的问题天然是”几个分工明确的 agent 协作”,CrewAI 的角色化抽象更顺手;如果你更看重单条流程的可靠、可恢复、可审计,以及对非 LLM 场景的支持,Burr 更对路。想进一步铺开了解 agent 生态,可参考 AI Agent 学习路径与资源GLM 与 GPT 的架构与 Agent 能力对比

结语

Apache Burr 的价值不在某个炫技特性,而在它逼你把”有状态应用”老老实实建模成状态机:状态显式、转移显式,于是可靠、可恢复、可观测顺理成章。当 LLM 脚本开始失控,它值得一试。

C code80.ai · AI 编码 API 聚合 Claude / GPT 多模型统一接入,稳定不限速,按量计费,几行配置接入 Claude Code。 了解一下 ›

抢沙发

评论前必须登录!

立即登录   注册