编程 prodagent:把多 Agent 协作拆成 5 个原语,再用 4 维硬预算按住它

2026-09-24 00:04:29

prodagent:把多 Agent 协作拆成 5 个原语,再用 4 维硬预算按住它

prodagent 是极客时间专栏《生产级 Agent 排雷实战》的配套开源框架。它想解决的问题很具体:让 Agent 跑起来是一回事,让它在生产里活下去是另一回事——模型幻觉终止、进程崩了丢状态、新旧记忆打架、越权操作、成本失控,每个项目都会踩到同一批坑,框架要做的就是把这层基建变成一等公民。

  • 仓库:https://github.com/chuaixxy/prodagent
  • PyPI:https://pypi.org/project/prodagent
  • License:AGPL v3

五个协作原语:先定共享状态,再定激活策略

多 Agent 的选型通常被当成「挑框架」,但真正要先定的是协作原语。prodagent 把它拆成两个正交轴:共享状态(store)× 激活策略(activation)。store 决定写语义——谁写、写什么、后写的人看到的是追加还是覆盖;activation 决定唤醒语义——下一步谁被叫起来。这两件事定了,协作拓扑就定了。

原语共享状态(写语义)激活策略(谁被唤醒)
agents= 垂直委派父子结果传递(树)父推送任务给子
peers= 横向接力HandoffPacket(链)上一个转交控制权
Ensemble 共享会话SharedFloor 追加式 transcriptRoundRobin 轮流 / Moderated 主持人选人 / FreeForAll 全员并发发言
Blackboard 共享可变状态Board 版本化字段(乐观并发)Trigger 字段变化触发,并行 fan-out / 抢锁先算
WorkQueue 任务池SharedQueue 租约队列worker 主动领活 · 租约超时回收

agents= 垂直委派:写是树形的父子结果传递,父节点把任务推给子节点,子节点把结果往上交。适合任务可以自上而下拆解的场景;树的写语义只有上下、没有左右,需要多个 Agent 对等来回协商时它不合适。

peers= 横向接力:写单元是 HandoffPacket,链式传递,控制权在任一时刻只落在一个 peer 手上。适合交接型流程——上一个环节做完判断,把控制权连同 packet 一起交给下一个。链路结构意味着全局共享上下文要靠 packet 自己携带,而不是靠一块公共黑板。

Ensemble 共享会话:写是 SharedFloor 上的追加式 transcript,只追加不改写。激活策略三选一:RoundRobin 轮流发言、Moderated 由主持人挑人、FreeForAll 全员并发发言。适合辩论、头脑风暴、角色扮演这类需要多方同时在一段会话里的场景。追加式保证互不覆盖,代价是 transcript 线性增长,得配合上下文压缩。

Blackboard 共享可变状态:写是 Board 上带版本号的字段更新,乐观并发——冲突在提交时暴露。激活靠 Trigger 监听字段变化,支持并行 fan-out,也支持抢锁先算。适合流水线式的知识加工:上一步的输出就是下一步的输入,多个步骤可以并行抢同一块板上的活。乐观并发意味着高冲突路径要准备重试。

WorkQueue 任务池:写是 SharedQueue 上的租约,任务被领走时加租约而不是被删除。worker 主动领活,租约超时自动回收。适合任务数量事先不确定、worker 可能挂掉的场景——超时回收保证任务不会因为某个 worker 崩溃而永久卡住。

生产工程点

四维硬预算

turns / seconds / tokens / cost_usd 四个独立维度,任一触顶即硬停。关键一条是子 Agent 的花销实时汇总回父 Agent——否则一 spawn 子 Agent,父的预算约束就被绕过去了。

断点续跑与重试熔断

checkpoint + 事件日志 + 乐观版本控制,进程崩了重启从断点续跑,不是从头再来。后端默认 file + memory 开箱即用,生产可换 Postgres / Neo4j / Qdrant / Redis。

重试是 fixed / exponential / jittered 三种 backoff,按错误码统一分类决定是否重试、是否降级。熔断做到工具级,CLOSED → OPEN → HALF_OPEN 自动探测恢复。安全侧有五层注入防护管道 + 写时拦截 + HITL 审批门禁。可观测是 Span 追踪 + OTLP 导出 + 轨迹漂移检测;评估是黄金评测集 + LLM Judge + CI 回归。

五级上下文压缩的语义损失边界

NONE / TOOL_COMPRESS / HISTORY_SUMMARY / TOPIC_SUMMARY / EMERGENCY,按 token 占用比例自动触发,每一级有明确的语义损失边界。压缩不是免费的,级别越高丢的细节越多。知道边界在哪,才知道什么时候该把关键状态搬到 Blackboard 或长期记忆里,而不是指望一层 summary 兜住。

统一通信底座

五个原语的每一次 Agent 边界穿越都走同一个卡口:Crossing 信封(方向 × 类型 × 类型化载荷)+ 能力管道(固定卡位:去重 → 准入契约 → 裁剪/投影 → 安全门 → 审计)+ 死信边界。下行 assembly_pipeline 在源头组装、容器即白名单;上行 admission_pipeline 在进门处验收,做契约校验 + 白名单改写 + 注入门。原语各写各的,边界检查不用各实现一遍。

三执行模式

  • PLAN_FIRST:LLM 动态出 PLAN DAG,可审计、可 HITL、可断点续跑。
  • REACTIVE:ReAct 循环,边走边看。
  • Workflow:人写静态 PLAN DAG。

装配入口与端口

Agent 是装配入口,三类架构决策:执行模式可切换、横切能力以 Bundle 形式可插拔、后端是 Protocol 端口可替换。HookRegistry 按协议层分流:Event 纯通知不阻断,CheckPoint 阻塞决策、首个 veto 即停,Injection 聚合注入器结果。后端有 15 个 Protocol 端口,各自独立可替换;默认 file + memory 单机零依赖,生产按数据类型分库——关系数据 Postgres、图 Neo4j、向量 Qdrant、缓存与协调 Redis。

再往上还有四通道长期记忆(规则 / 实体 / 精确 / 语义并行 recall + ACT-R 激活衰减)、@tool 声明式注册(按副作用分层 LOW/MEDIUM/HIGH)与原生 MCP 协议接入,以及把成功 run 蒸馏成 Skill、下次按需加载的自我进化闭环。

跑起来

make playground

首次运行进入交互式向导,二选一:

  • FakeLLM —— 离线、零 key,直接体验 9 个 example
  • OpenAI 兼容端点 —— 填 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL。DeepSeek、Qwen、Moonshot、Zhipu 等任何 OpenAI Chat Completions 协议厂商均适用

.env 示例:

USE_FAKE_LLM=1
LLM_BASE_URL=https://open.bigmodel.cn/api/paas/v4
LLM_API_KEY=xxx
LLM_MODEL=glm-5.2

没有 make:

powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
uv sync && uv run prodagent --port 8766

启动后浏览器自动打开 http://127.0.0.1:8766。切生产后端用 make playground-prod,会自动拉起 Postgres / Neo4j / Qdrant / Redis。

9 个 end-to-end 示例:greeter(最小可跑)、trader(对话式多轮协商 + HIGH 副作用 HITL 审批)、deep_research(REACTIVE 探索树 + 五级压缩 + 注入防御 + 记忆防重复)、compliance_audit(PLAN_FIRST 动态 DAG + 人类审批门 + auto-replan 增量重规划 + 幂等写工具)、code_detective(MCP stdio server 桥接外部工具 + REACTIVE 多轮调试)、trip_planner(Workflow DAG + 3 peer 并行 fan-out)、aiops(多 Agent spawn + peer handoff + 记忆 + 学习 + 可观测 + 审批)、dating_chat(Ensemble 多 Agent 共享 floor)、quiz_arena(WorkQueue 后台审题 + 租约超时 + 死信接 Blackboard 正式抢答)。

安装与 SDK

pip install prodagent
pip install "prodagent[postgres,redis,neo4j,qdrant]"
import asyncio
from prodagent import Agent, ExecutionMode, HardBudget, tool

@tool(name="search", readonly=True)
async def search(query: str) -> str:
    return f"results for: {query}"

agent = Agent(
    "demo",
    system_prompt="Find answers.",
    tools=[search],
    mode=ExecutionMode.REACTIVE,
    budget=HardBudget(max_turns=20, max_cost_usd=1.0, max_seconds=1800.0),
)
asyncio.run(agent.chat("What is the weather in Paris?"))

仓库地址:https://github.com/chuaixxy/prodagent ,PyPI:https://pypi.org/project/prodagent 。

推荐文章

程序员茄子在线接单