ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

agency-agents:可验证协作的智能体工作流设计

agency-agents:可验证协作的智能体工作流设计

1. 项目概述:这不是一个“代理”,而是一套可协作的智能体工作流

“agency-agents”这个词组最近在技术社区里频繁出现,但很多人第一反应是——这不就是“代理”(agent)的复数形式吗?配个“agency”前缀,是不是又在玩概念包装?我最初也这么想,直到在某高校实验室参与一个跨平台自动化任务调度项目时,被要求用这套范式重构整个流程。实操两周后我才真正明白:agency-agents 不是指“多个代理”,而是指一组具备角色分工、目标对齐、通信契约与自主决策边界的智能体协作单元。它解决的核心问题,不是“让AI干活”,而是“让多个AI像人类团队一样分工、协商、回溯、兜底”。关键词里的“agency”不是机构,而是“能动性”——每个 agent 必须拥有明确的授权范围、可观测的执行状态、可验证的输出承诺,以及在边界内拒绝越权请求的能力。

这个内容适合三类人直接上手参考:一是正在设计 RAG+Agent 混合架构的后端开发者,你需要它来解耦检索、推理、执行模块;二是做低代码自动化平台的产品经理,它能帮你把“拖拽节点”背后的行为逻辑标准化;三是高校做多智能体仿真研究的学生,它提供了一套轻量但可验证的协作协议模板,比传统 MAS(Multi-Agent Systems)框架更贴近工程落地。它不依赖大模型厂商私有 SDK,不绑定特定推理引擎,核心逻辑甚至可以用纯 Python + JSON Schema 实现。我试过用它调度本地运行的 OCR 服务、异步调用 Excel 公式计算 API、协调两个 LLM 分别处理法律条款摘要和风险点标注——全程没有写一行胶水代码,所有交互靠结构化消息契约驱动。下面我会从设计底层逻辑开始,一层层拆给你看,为什么这种组织方式正在成为智能体落地的“事实标准”。

2. 内容整体设计与思路拆解:为什么放弃“单一大脑”,选择“分布式能动体”

2.1 传统 Agent 架构的三个硬伤,逼我们重新定义协作

过去一年我帮五个不同团队做过 Agent 系统集成,发现 80% 的失败案例都卡在同一个地方:把所有逻辑塞进一个“全能型”LLM 调度器里。比如让一个大模型同时负责理解用户意图、拆解子任务、调用工具、校验结果、重试失败项、生成最终回复——表面看很“智能”,实际部署后立刻暴露三大问题:

  • 状态不可观测:当用户问“刚才第三步为什么跳过了?”时,调度器无法给出确定性回答。因为它的思考过程是黑盒 token 流,没有结构化中间态记录。我们曾为追踪一个 PDF 解析失败原因,翻了 3 小时日志才定位到是 OCR 服务返回了空数组,但调度器把它当成了“无相关内容”继续往下走。

  • 责任不可切割:工具调用失败时,是模型提示词写得不好?API 响应超时?还是输入数据格式异常?单点调度器把所有错误归因于“推理失败”,导致运维时永远在猜。某次生产事故中,数据库连接池耗尽,但调度器日志只显示“LLM 生成结果超时”,团队花了两天排查模型服务,最后发现是下游 MySQL 连接数配置错了。

  • 扩展不可预测:新增一个“邮件发送”功能,需要改提示词、加工具描述、更新系统指令、测试所有已有流程是否被干扰。某公司上线新审批流后,原有合同生成流程突然开始自动发测试邮件,原因是新工具描述里“send”这个词触发了旧提示词中的模糊匹配。

agency-agents 的设计,本质上是对这三个问题的针对性回应。它不追求“更聪明的单点”,而是构建“更可靠的协作网络”。核心思路就一句话:把智能体(agent)当作具有明确职责边界的微服务,把 agency 当作定义这些边界、通信规则与协作协议的治理层。这和微服务架构演进路径惊人相似——当年我们也是从单体应用里切出用户中心、订单中心、支付中心,不是因为它们更“酷”,而是因为只有切开,才能独立部署、独立监控、独立扩缩容。

2.2 “Agency”不是机构,是四层可验证契约

很多人误以为 agency 是个容器或管理平台,其实它是一组轻量级协议。我在某跨平台系统中实现的最小可行版 agency,仅包含四个 JSON Schema 文件,总代码量不到 200 行,但它定义了整个协作网络的骨架:

  1. Role Schema(角色契约):规定每个 agent 必须声明自己能做什么、不能做什么、输入必须长什么样、输出必须满足什么约束。例如一个“PDF 解析 agent”的 role 定义里,明确写着"input_schema": {"type": "object", "properties": {"file_url": {"type": "string", "format": "uri"}}},任何不符合该结构的调用都会被 agency 层直接拦截,根本不会转发给 agent。

  2. Message Schema(通信契约):定义所有 agent 之间传递的消息格式。关键字段包括intent(本次通信目的,如 "request_execution" 或 "report_failure")、context_id(跨 agent 的事务追踪 ID)、deadline(该消息的绝对过期时间戳)。我们曾用context_id快速定位到某个合同审批流中,OCR agent 和法务分析 agent 之间的上下文丢失问题——原来是因为前者返回了空结果,后者没按契约要求发送report_failure消息,导致流程卡死。

  3. State Schema(状态契约):要求每个 agent 暴露/state接口,返回结构化运行状态。不是简单的 "running/stopped",而是{ "health": "healthy", "queue_length": 2, "last_success_time": "2024-06-15T08:22:15Z", "error_rate_5m": 0.02 }。运维时我们用 Prometheus 抓取这些指标,当error_rate_5m超过 0.1 就自动告警并隔离该 agent。

  4. Coordination Schema(协调契约):定义当多个 agent 需要协同完成一个目标时,如何发起协商、如何达成共识、如何处理分歧。最简实现是“多数表决”,但我们在某图像标注项目中用了更严格的“双签机制”:一个 agent 标注 ROI,另一个 agent 必须独立验证坐标有效性,两者输出一致才进入下一步,否则触发人工审核队列。

这四层契约共同构成了 agency 的“治理能力”。它不干涉 agent 内部怎么实现(你可以用 Llama 3 做推理,也可以用正则表达式做规则匹配),只确保它们“说同一种语言、遵守同一套交通规则”。这种设计让系统具备了真正的可演进性——去年我们替换了全部 OCR agent 为新模型,只要新 agent 的 role schema 和 message schema 不变,上层业务流程零修改。

2.3 为什么不用现有框架?KubeFlow、LangChain 的隐性成本

看到这里你可能会问:LangChain 的 AgentExecutor、KubeFlow 的 Pipeline 不也能编排多个组件吗?我实测对比过三套方案,结论很明确:通用框架在智能体协作场景下,存在不可忽视的抽象泄漏。

LangChain 的 AgentExecutor 本质是个“带记忆的循环调用器”。它把 tool call 当作函数调用,但真实世界中,tool 很少是幂等的——调用一次邮件 API 发送成功,再调用一次可能重复发信。我们曾用它做客户回访任务,因网络抖动重试了三次,结果客户收到三封相同内容的邮件。而 agency-agents 要求每个 agent 显式声明自己的幂等性(在 role schema 中加"idempotent": true/false字段),非幂等操作必须携带唯一request_id,agency 层会自动去重。

KubeFlow Pipeline 更侧重数据流编排,它的 DAG(有向无环图)是静态的。但智能体协作需要动态决策:比如“先调 OCR,如果识别置信度低于 0.8,则启动人工校验 agent”。LangChain 的 ReAct 模式能做,但它的决策逻辑混在 LLM 提示词里,无法被监控、无法被审计、无法被 A/B 测试。agency-agents 把决策逻辑外置为可配置的 coordination policy,我们用 YAML 定义:“当ocr_agent.output.confidence < 0.8时,路由至human_review_agent”,所有策略变更无需重启服务。

最致命的是可观测性鸿沟。KubeFlow 的 metrics 只告诉你 pipeline 执行耗时,LangChain 的 trace 只展示 token 流。而 agency-agents 的 message schema 强制要求每个消息携带context_id和span_id,我们用 OpenTelemetry 自动注入,能在 Grafana 里看到一条完整链路:user_request → intent_parser_agent → ocr_agent (200ms) → confidence_check_policy → human_review_agent (queued)。某次性能优化中,我们发现 70% 的延迟来自 OCR agent 的文件下载环节,于是单独给它加了 CDN 缓存,整体流程耗时下降 40%。

所以 agency-agents 不是另造轮子,而是把工程实践中沉淀下来的“微服务治理思想”,精准移植到智能体协作领域。它不解决“怎么让 AI 更聪明”,而是解决“怎么让 AI 协作更可靠”。

3. 核心细节解析与实操要点:从契约定义到运行时保障

3.1 Role Schema:用 JSON Schema 划清能力边界,比提示词更刚性

Role Schema 是 agency-agents 的基石,它用机器可验证的方式,替代了模糊的提示词约束。很多人觉得“写清楚提示词就行”,但实测发现,LLM 对提示词的理解存在显著漂移——同一个 prompt,在 GPT-4 和 Claude 3 上对“能否修改原始文档”的解读可能完全相反。而 Role Schema 是冷冰冰的校验规则,不讲情面。

以我们实际使用的“Excel 公式计算 agent”为例,它的 role schema 不仅定义输入输出,还嵌入了业务规则:

{ "name": "excel_calculator", "description": "执行 Excel 公式计算,支持 SUM、AVERAGE、IF 函数,不支持宏和外部链接", "input_schema": { "type": "object", "properties": { "file_url": { "type": "string", "format": "uri", "description": "必须是内部存储服务的 HTTPS URL,禁止公网地址" }, "sheet_name": {"type": "string"}, "cell_range": {"type": "string", "pattern": "^\\$?[A-Z]+\\$?\\d+:\\$?[A-Z]+\\$?\\d+$"}, "formula": { "type": "string", "enum": ["SUM", "AVERAGE", "IF"], "description": "仅允许指定函数,禁止任意表达式" } }, "required": ["file_url", "sheet_name", "cell_range", "formula"] }, "output_schema": { "type": "object", "properties": { "result": {"type": ["number", "string"]}, "execution_time_ms": {"type": "integer", "minimum": 0} } }, "constraints": { "max_file_size_mb": 5, "timeout_ms": 10000, "allowed_domains": ["internal-calc-service.example.com"] } }

这个 schema 的威力体现在三个层面:

  • 输入强校验:cell_range字段的正则^\$?[A-Z]+\$\d+:\$?[A-Z]+\$\d+$确保传入的是合法 Excel 区域(如A1:B10或$C$5:$D$20),任何SELECT * FROM users这样的 SQL 注入式输入,在到达 agent 之前就被 agency 层拦截,返回400 Bad Request。我们曾用这个规则挡住了测试环境中 12 次恶意 fuzzing 尝试。

  • 业务规则编码:constraints.allowed_domains强制所有file_url必须来自可信域名,杜绝了 agent 被诱导访问恶意网站的风险。max_file_size_mb和timeout_ms则是资源熔断开关,防止一个超大文件拖垮整个服务。

  • 可文档化:这个 JSON 文件本身就是 API 文档。前端调用方用它自动生成表单校验逻辑,Postman 可直接导入生成测试用例,Swagger UI 能渲染成交互式文档。我们团队新人第一天就能根据这个 schema,写出调用该 agent 的 Python 脚本,无需阅读任何提示词文档。

提示:不要试图在 schema 中定义“语义约束”。比如"description": "不支持宏"是有效的业务说明,但"description": "只能计算财务相关数据"就是无效的——因为“财务相关”无法用 JSON Schema 校验。把语义规则留给 agent 内部实现,schema 只管结构和硬性限制。

3.2 Message Schema:用结构化消息取代自由文本,让协作可追溯

传统 Agent 间通信常依赖自由文本消息,比如 LLM 生成一段话:“请 OCR agent 处理这份合同,URL 是 https://xxx.pdf”。这种方式的问题在于:无法被程序解析、无法被审计、无法被重放。agency-agents 的 message schema 强制所有通信走结构化路径。

我们定义的核心字段如下(精简版):

字段名类型必填说明实际案例
message_idstring是全局唯一 UUID,用于去重和追踪msg_7f3a9b2c-1e4d-4a8f-9c12-5e6d7f8a9b2c
context_idstring是关联同一用户请求的所有消息,贯穿全流程ctx_20240615-082215-abc123
senderstring是发送方 agent 名称intent_parser_agent
receiverstring是接收方 agent 名称ocr_agent
intentstring是通信意图,预定义枚举值request_execution,report_success,report_failure,request_coordination
payloadobject否具体数据,结构由 sender/receiver 的 role schema 约定{"file_url": "https://...", "page_range": [1,3]}
deadlinestring (ISO8601)是消息绝对过期时间,超时自动丢弃2024-06-15T08:25:00Z
retry_countinteger否当前重试次数,用于幂等控制0

这个设计带来的实操价值远超想象。举个真实案例:某次合同审批流中,法务 agent 总是收不到 OCR agent 的结果。我们查日志发现,OCR agent 发送了report_success消息,但法务 agent 的日志里完全没有这条记录。顺着message_id追踪,发现是消息队列(我们用 RabbitMQ)的消费者确认机制出了问题——OCR agent 发送后立即认为成功,但消息在传输中被 RabbitMQ 丢弃了。于是我们加了retry_count字段和指数退避重发逻辑,当retry_count > 3时,自动触发request_coordination消息,让 coordinator agent 启动人工干预流程。

注意:deadline字段是防雪崩的关键。我们设置所有消息的默认 deadline 为 30 秒,但对 OCR 这类 IO 密集型 agent,会在发送时动态计算:now() + base_timeout_ms + file_size_kb * 2。这样 1MB 文件 deadline 是 32 秒,10MB 就是 50 秒,避免小文件等大文件超时。

3.3 State Schema:让每个 agent 成为“可体检”的健康个体

很多团队忽略了一个事实:智能体不是永动机,它会生病、会疲惫、会老化。State Schema 就是给每个 agent 安装的“体检报告生成器”。

我们的 state endpoint (GET /state) 返回:

{ "agent_name": "ocr_agent", "version": "v2.3.1", "health": "healthy", "metrics": { "requests_total": 12478, "success_rate_1h": 0.982, "avg_latency_ms": 142.3, "queue_length": 3, "memory_usage_percent": 65.2, "model_load_time_ms": 890 }, "dependencies": [ { "name": "tesseract_service", "status": "healthy", "latency_ms": 45.2 }, { "name": "storage_gateway", "status": "degraded", "latency_ms": 1200 } ], "last_updated": "2024-06-15T08:22:15Z" }

这个设计解决了三个运维痛点:

  • 故障快速定位:当health变为degraded,我们不查 agent 日志,而是先看dependencies。上次故障中,storage_gateway状态为degraded,latency_ms飙升到 1200ms,立刻锁定是对象存储网关的 TLS 握手慢,而非 OCR 模型本身问题。

  • 容量规划有据可依:queue_length和avg_latency_ms是扩容黄金指标。我们设定规则:当queue_length > 5且avg_latency_ms > 200持续 5 分钟,自动触发水平扩容。实测下来,扩容后queue_length在 2 分钟内回落到 1 以下。

  • 版本灰度可控:version字段配合success_rate_1h,让我们能安全灰度。新版本 v2.4.0 上线后,我们监控其success_rate_1h是否稳定在 0.98 以上。某次发现新版本success_rate_1h为 0.92,回滚后发现是训练数据里混入了扫描质量极差的样本,模型泛化能力下降。

实操心得:不要把所有指标都塞进 state。我们只暴露影响协作决策的指标。比如model_load_time_ms只在 agent 启动时记录一次,因为它是冷启动指标,不影响实时协作;而queue_length是每秒更新的热指标,直接影响 routing decision。

3.4 Coordination Schema:用策略即代码(Policy-as-Code)替代提示词决策

Coordination Schema 是 agency-agents 的“大脑皮层”,它把原本藏在 LLM 提示词里的决策逻辑,变成可版本控制、可单元测试、可 A/B 测试的代码。

我们用 YAML 定义 coordination policy,例如“合同风险扫描流程”的协调规则:

policy_name: contract_risk_scan triggers: - intent: request_execution sender: intent_parser_agent receiver: ocr_agent conditions: - expression: "ocr_agent.output.confidence < 0.8" action: - type: route_to target: human_review_agent priority: high - expression: "ocr_agent.output.confidence >= 0.8 and ocr_agent.output.text contains 'liability'" action: - type: route_to target: legal_analyzer_agent priority: medium - type: log_event event: "high_risk_keyword_detected" default_action: - type: route_to target: legal_analyzer_agent priority: low timeout: 30000 # ms fallback: - type: notify channel: slack-alerts message: "Contract scan timeout, context_id: {{context_id}}"

这个 policy 的实操优势极其明显:

  • 可测试性:我们为每个 policy 写单元测试,用 mock 数据验证条件分支。比如构造一个confidence=0.75的 OCR 输出,断言它确实路由到human_review_agent。上线前 100% 覆盖率,杜绝了“提示词改了但没测全”的事故。

  • 可审计性:每次路由决策都记录policy_name和matched_condition。当用户投诉“为什么我的合同被送去人工审核?”,我们查日志就能看到matched_condition: "ocr_agent.output.confidence < 0.8",证据确凿。

  • 可灰度:新 policy 上线时,我们用traffic_split: 0.1控制 10% 流量走新规则,其余走旧规则。对比两组的success_rate和avg_latency,数据达标后再全量。

注意:policy 中的expression不是 JavaScript,而是我们自研的轻量表达式引擎(基于 ANTLR4),只支持安全操作符(<,>,contains,==),禁用eval()、function等危险特性。这避免了“把业务逻辑写进 eval 字符串”这种高危操作。

4. 实操过程与核心环节实现:从零搭建一个可运行的 agency-agents 系统

4.1 环境准备与最小依赖:5 分钟启动一个可协作的双 agent 系统

agency-agents 的最大优势是轻量。我们用 Python 实现的 reference implementation,核心依赖只有三个:

  • fastapi:提供 HTTP 接口,暴露/state、/execute等端点
  • pydantic:用于校验 Role Schema 和 Message Schema
  • redis:作为消息队列和状态缓存(可选,单机模式可用内存)

安装命令(假设已安装 Python 3.9+):

pip install fastapi uvicorn pydantic redis # 创建项目目录 mkdir agency-demo && cd agency-demo # 初始化虚拟环境(推荐) python -m venv venv source venv/bin/activate # Linux/Mac # venv\Scripts\activate # Windows

项目结构如下(极简版):

agency-demo/ ├── agents/ │ ├── __init__.py │ ├── ocr_agent.py # 实现 OCR 功能的 agent │ └── calculator_agent.py # 实现 Excel 计算的 agent ├── agency/ │ ├── __init__.py │ ├── core.py # agency 核心逻辑:校验、路由、重试 │ └── schemas.py # 所有 Schema 定义 ├── config/ │ └── roles.yaml # 所有 agent 的 role schema 配置 ├── main.py # FastAPI 应用入口 └── requirements.txt

requirements.txt内容:

fastapi==0.110.0 uvicorn==0.29.0 pydantic==2.7.1 redis==4.6.0

提示:Redis 不是必需的。如果你只是本地测试,可以把core.py里的消息队列逻辑替换为内存队列(queue.Queue),state 存储改为dict。我们就是这么做的——开发阶段零外部依赖,uvicorn main:app --reload启动即用。

4.2 定义第一个 agent:OCR Agent 的 Role Schema 与实现

我们先实现ocr_agent,这是最典型的 IO 密集型 agent。它的 role schema(config/roles.yaml)如下:

ocr_agent: name: "ocr_agent" description: "使用 Tesseract OCR 引擎识别 PDF 图片中的文字" input_schema: type: object properties: file_url: type: string format: uri page_range: type: array items: type: integer minItems: 1 maxItems: 10 required: [file_url] output_schema: type: object properties: text: type: string confidence: type: number minimum: 0 maximum: 1 page_count: type: integer minimum: 1 constraints: max_file_size_mb: 10 timeout_ms: 30000 allowed_domains: ["internal-storage.example.com"]

agents/ocr_agent.py的核心实现(简化版):

import requests import tempfile import subprocess import os from pathlib import Path from typing import Dict, Any from pydantic import BaseModel class OCRInput(BaseModel): file_url: str page_range: list[int] = None class OCROutput(BaseModel): text: str confidence: float page_count: int def execute_ocr(input_data: Dict[str, Any]) -> OCROutput: # 1. 校验 file_url 域名 from urllib.parse import urlparse domain = urlparse(input_data["file_url"]).netloc if domain not in ["internal-storage.example.com"]: raise ValueError(f"Invalid domain: {domain}") # 2. 下载文件到临时目录 response = requests.get(input_data["file_url"]) response.raise_for_status() with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as f: f.write(response.content) pdf_path = f.name try: # 3. 调用 tesseract(此处简化,实际需处理图片转换) # 我们用 pdftoppm 将 PDF 转为 PNG,再用 tesseract 识别 png_path = pdf_path.replace(".pdf", ".png") subprocess.run( ["pdftoppm", "-png", "-singlefile", pdf_path, png_path.replace(".png", "")], check=True, capture_output=True ) # 4. OCR 识别(模拟,实际调用 tesseract) # 这里我们返回模拟结果,confidence 随机生成 import random confidence = round(random.uniform(0.6, 0.95), 2) text = f"模拟 OCR 识别结果,置信度 {confidence}。合同编号:CT2024-001,甲方:XXX 公司..." return OCROutput( text=text, confidence=confidence, page_count=1 ) finally: # 清理临时文件 Path(pdf_path).unlink(missing_ok=True) Path(png_path).unlink(missing_ok=True)

这个实现体现了 agency-agents 的关键原则:agent 只关心“怎么做”,不关心“谁让我做”、“做完给谁”。所有路由、重试、超时逻辑,都交给 agency 层处理。

4.3 构建 agency 核心:消息校验、路由与重试的完整实现

agency/core.py是整个系统的中枢。我们实现三个核心方法:

  1. validate_message(message: dict) -> bool: 根据 message schema 校验结构
  2. route_message(message: dict) -> List[str]: 根据 coordination policy 决定下一跳
  3. handle_message_delivery(message: dict) -> dict: 执行发送、重试、超时处理

以下是route_message的核心逻辑(简化版):

from agency.schemas import CoordinationPolicy from typing import List, Dict, Any import yaml def load_policies() -> Dict[str, CoordinationPolicy]: """从 config/policies.yaml 加载所有 coordination policy""" with open("config/policies.yaml") as f: policies_dict = yaml.safe_load(f) return {name: CoordinationPolicy(**data) for name, data in policies_dict.items()} def route_message(message: Dict[str, Any]) -> List[str]: """ 根据 message.intent 和 content,查找匹配的 coordination policy 返回目标 agent 名称列表 """ policies = load_policies() matched_policies = [] # 查找触发条件匹配的 policy for policy_name, policy in policies.items(): for trigger in policy.triggers: if (trigger.intent == message.get("intent") and trigger.sender == message.get("sender") and trigger.receiver == message.get("receiver")): # 检查 conditions for condition in policy.conditions: if evaluate_expression(condition.expression, message): matched_policies.append(condition.action) break # 找到第一个匹配就跳出 if not matched_policies: # 无匹配时走 default_action return [policy.default_action.target for policy in policies.values()] # 解析 action,提取 target targets = [] for action_list in matched_policies: for action in action_list: if action.type == "route_to": targets.append(action.target) return targets def evaluate_expression(expr: str, context: Dict[str, Any]) -> bool: """ 安全表达式求值,只支持白名单操作符 示例:expr = "ocr_agent.output.confidence < 0.8" """ # 实际实现会用 AST 解析,这里用简单字符串替换模拟 # 真实项目中我们用自研的 SafeExpressionEngine try: # 将点号路径转为字典访问 # "ocr_agent.output.confidence" -> context["ocr_agent"]["output"]["confidence"] parts = expr.split(".") val = context for part in parts[:-1]: val = val.get(part, {}) # 获取最终值 final_val = val.get(parts[-1]) # 构造安全比较表达式 safe_expr = f"{final_val} {expr.split()[-2]} {expr.split()[-1]}" return eval(safe_expr, {"__builtins__": {}}, {}) except: return False

handle_message_delivery则实现了重试逻辑:

import time import redis from agency.schemas import Message def send_message_with_retry(message: Message, max_retries: int = 3) -> bool: """ 发送消息,失败时按指数退避重试 """ r = redis.Redis() for attempt in range(max_retries + 1): try: # 1. 校验消息 deadline from datetime import datetime, timezone deadline = datetime.fromisoformat(message.deadline.replace("Z", "+00:00")) if datetime.now(timezone.utc) > deadline: print(f"Message {message.message_id} expired, skipping") return False # 2. 发送到目标 agent 的队列 queue_name = f"agent:{message.receiver}:queue" r.lpush(queue_name, message.model_dump_json()) return True except Exception as e: if attempt == max_retries: # 最终失败,触发 fallback trigger_fallback(message) return False # 指数退避:1s, 2s, 4s... wait_time = 2 ** attempt time.sleep(wait_time) return False

这个实现让 agency 层真正成为“交通警察”:它不参与具体工作(OCR 或计算),只确保消息准时、准确、可靠地送达,并在异常时启动预案。

4.4 FastAPI 集成:暴露标准化接口,让任何 client 都能接入

main.py是整个系统的门面。我们暴露三个核心端点:

  • POST /execute: 接收用户请求,启动 agency 工作流
  • GET /state: 返回当前 agency 状态(聚合所有 agent 的 health)
  • GET /agent/{agent_name}/state: 返回指定 agent 的详细状态
from fastapi import FastAPI, HTTPException, BackgroundTasks from agency.core import send_message_with_retry, route_message from agents.ocr_agent import execute_ocr from pydantic import BaseModel from typing import Dict, Any import json app = FastAPI(title="Agency Agents Demo") class ExecuteRequest(BaseModel): intent: str payload: Dict[str, Any] @app.post("/execute") async def execute(request: ExecuteRequest, background_tasks: BackgroundTasks): """ 用户入口:接收原始请求,生成初始 message,启动 workflow """ # 1. 生成初始 message from uuid import uuid4 from datetime import datetime, timezone message = { "message_id": str(uuid4()), "context_id": f"ctx_{datetime.now(timezone.utc).strftime('%Y%m%d-%H%M%S')}-{str(uuid4())[:6]}", "sender": "user_client", "receiver": "intent_parser_agent", # 首个处理 agent "intent": request.intent, "payload": request.payload, "deadline": (datetime.now(timezone.utc) + timedelta(seconds=60)).isoformat().replace("+00:00", "Z"), "retry_count": 0 } # 2. 路由到首个 agent targets = route_message(message) if not targets: raise HTTPException(status_code=400, detail="No target agent found") # 3. 异步发送(避免阻塞 HTTP 请求) background_tasks.add_task(send_message_with_retry, message) return {"status": "accepted", "context_id": message["context_id"]} @app.get("/state") def get_agency_state(): """ 返回 agency 整体状态:聚合所有 agent 的 health """ # 实际中会从 Redis 或各 agent 的 /state 端点聚合 return { "agency_name": "demo-agency", "overall_health": "healthy", "active_agents": 2, "total_requests": 12478, "uptime_hours": 42.5 } @app.get("/agent/{agent_name}/state") def get_agent_state(agent_name: str): """ 代理到具体 agent 的 /state 端点 """ # 实际中会调用 agent 的 HTTP 接口 # 这里返回模拟数据 if agent_name == "ocr_agent": return { "agent_name": "ocr_agent", "health": "healthy", "metrics": {"success_rate_1h": 0.982, "queue_length": 2} } raise HTTPException(status_code=404, detail=f"Agent {agent_name} not found")

启动命令:

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

现在,你可以用 curl 测试:

curl -X POST
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进