ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

跨平台AI编程智能体生态:多Agent协作架构设计与工程实践

跨平台AI编程智能体生态:多Agent协作架构设计与工程实践

1. 从“单兵作战”到“团队协作”:为什么我们需要跨平台 AI 编程智能体生态

过去一年里,我几乎把市面上能叫得上名字的 AI 编程助手都试了一遍。最开始是那种单文件、单会话的代码补全工具,写个函数、补个测试用例确实快,但一旦项目规模上去,问题就全暴露出来了:上下文窗口不够用、跨文件重构容易改崩、多个任务之间互相污染状态。说白了,单个 AI 助手就像一个能力很强但记性很差的实习生,你让他改 A 文件,他改着改着就把 B 文件的逻辑给忘了。

后来我开始接触“智能体”这个概念,也就是 Agent。它和普通 AI 助手的本质区别在于:Agent 有目标、有记忆、有工具调用能力,还能自主规划执行路径。而“Agency Agents”这个项目标题里的“Agency”,我理解它强调的不是单个 Agent 有多强,而是多个 Agent 之间如何分工、协作、互相校验,形成一个有“能动性”的工程团队。这就像从“一个全能程序员”变成“一个配置齐全的项目组”——有人负责需求拆解,有人负责编码,有人负责审查,有人负责测试。

那“跨平台”又意味着什么?我在实际项目里踩过的最大的坑就是:本地开发用一套工具链,CI/CD 跑在另一套环境,云端部署又是第三套。如果 AI 智能体只能绑定某一个平台,那它的价值就被锁死了。跨平台的核心诉求是:同一套 Agent 定义和协作逻辑,能在本地 IDE、命令行、云端沙箱、甚至不同的操作系统之间无缝迁移。这背后涉及抽象层设计、工具接口标准化、状态序列化等一系列工程问题。

这篇文章适合谁看?如果你只是想让 AI 帮你补全几行代码,那没必要往下读。但如果你正在负责一个中大型项目,想让 AI 真正参与到日常开发流程里,或者你本身就在做 AI 工程化相关的工作,那这篇内容应该能给你一些可以直接抄作业的思路。我会从架构设计、核心模块拆解、实操落地、问题排查四个维度展开,尽量把“为什么这么设计”讲透,而不是只丢一堆配置代码。

2. 架构拆解:Agency Agents 的核心模块与协作逻辑

2.1 智能体角色划分与职责边界

一个能跑起来的 Agency Agents 系统,第一步不是写代码,而是定义角色。我见过太多项目一上来就搞一个“万能 Agent”,结果就是它什么都做,什么都做不精。合理的做法是参考真实软件团队的配置,把职责拆开。

我目前用得比较顺的一套角色划分是这样的:

  • 规划 Agent(Planner):负责接收高层需求,拆解成可执行的任务列表,并标注依赖关系。它不写代码,只输出结构化的任务描述。
  • 编码 Agent(Coder):根据任务描述生成代码,它需要访问代码库的上下文,但不需要知道全局规划。
  • 审查 Agent(Reviewer):对编码 Agent 的输出做静态检查、逻辑校验,甚至跑一遍单元测试。
  • 测试 Agent(Tester):专门负责生成边界用例和集成测试,它和审查 Agent 的区别在于,审查关注“代码写得对不对”,测试关注“功能跑得通不通”。
  • 协调 Agent(Orchestrator):这是整个系统的中枢,负责调度上述 Agent,处理任务失败重试、上下文传递、状态持久化。

为什么要拆这么细?因为每个角色的提示词(Prompt)和工具集(Toolset)差异很大。规划 Agent 需要的是强推理能力,编码 Agent 需要的是大上下文和代码生成质量,审查 Agent 需要的是严谨性和对规则的理解。如果混在一起,提示词会变得极其臃肿,效果反而下降。

注意:角色数量不是越多越好。我试过拆成七个角色,结果协调开销大到离谱,任务在 Agent 之间传递时信息损耗严重。四个到五个角色是比较平衡的区间。

2.2 跨平台抽象层的设计要点

跨平台这件事,说起来简单,做起来全是细节。核心思路是:把“平台相关”和“平台无关”的部分彻底分离。平台无关的部分包括 Agent 定义、任务图、状态机、提示词模板;平台相关的部分包括文件系统访问、进程执行、网络请求、环境变量读取。

我采用的做法是定义一个Runtime Interface,所有平台相关的操作都通过这个接口暴露。比如:

class RuntimeInterface: def read_file(self, path: str) -> str: ... def write_file(self, path: str, content: str) -> None: ... def execute_command(self, cmd: str, timeout: int) -> CommandResult: ... def get_env(self, key: str) -> str: ... def list_files(self, directory: str) -> list[str]: ...

然后在不同平台上实现这个接口:本地开发环境用LocalRuntime,云端沙箱用SandboxRuntime,CI 环境用CIRuntime。Agent 的逻辑代码只依赖RuntimeInterface,不直接调用os或subprocess。这样同一套 Agent 定义,换个 Runtime 实现就能跑在不同平台上。

这个设计的好处我在一次迁移中体会特别深:当时需要把本地跑通的 Agent 流程搬到云端定时任务里,因为抽象层做得好,只改了一个 Runtime 实现类的注入配置,Agent 逻辑一行没动。如果没有这层抽象,光是路径分隔符和权限问题就能折腾一整天。

2.3 状态管理与上下文传递机制

多 Agent 协作最大的难点之一是状态管理。每个 Agent 执行完任务后,会产生新的上下文信息,这些信息需要传递给下游 Agent。如果传递不当,要么信息丢失导致下游 Agent 胡编,要么信息冗余导致上下文爆炸。

我的方案是采用分层状态存储:

  • 全局状态(Global State):存储项目级信息,比如代码库结构、依赖清单、环境配置。所有 Agent 都能读取,但只有协调 Agent 能写入。
  • 任务状态(Task State):每个任务独立的状态,包括输入、输出、执行日志、错误信息。任务完成后归档,不污染后续任务。
  • 会话状态(Session State):单个 Agent 在一次执行中的临时记忆,执行结束后丢弃。

状态序列化我用的是 JSON + 文件系统,没有上数据库。原因很简单:Agent 执行频率不高,文件系统足够用,而且调试的时候直接看文件比查数据库方便得多。每个任务的状态存成一个独立 JSON 文件,命名规则是{task_id}_{agent_role}.json,出问题的时候直接翻文件就能定位。

实操心得:状态文件一定要加版本号字段。我有一次改了状态结构,旧文件反序列化直接报错,后来加了schema_version字段,做兼容处理就轻松多了。

3. 核心细节解析:提示词工程与工具调用的实战要点

3.1 提示词模板的分层设计

提示词写得好不好,直接决定 Agent 的输出质量。我早期犯的错误是把所有指令塞进一个巨大的 System Prompt 里,结果模型经常“选择性失忆”,前面的规则后面就忘了。后来改成分层提示词结构,效果稳定很多。

分层结构大概是这样的:

  1. 角色层(Role Layer):定义 Agent 的身份、职责边界、输出格式要求。这部分固定不变。
  2. 任务层(Task Layer):描述当前要完成的具体任务,由协调 Agent 动态生成。
  3. 上下文层(Context Layer):提供完成任务所需的代码片段、文件列表、历史记录。
  4. 约束层(Constraint Layer):列出本次执行的硬性限制,比如“不要修改测试文件”“必须使用 TypeScript 严格模式”。

每一层之间用明确的分隔符隔开,比如---或 XML 标签。我实测下来,用 XML 标签包裹各层,模型对边界的识别准确率更高。比如:

<role> 你是一名资深代码审查员,只关注逻辑错误和安全问题,不评论代码风格。 </role> <task> 审查以下 diff,找出可能导致运行时异常的改动。 </task> <context> {{diff_content}} </context> <constraints> - 只输出问题列表,每条包含文件路径、行号、问题描述。 - 如果没有发现问题,输出 "NO_ISSUES_FOUND"。 </constraints>

这种结构的好处是,当需要调整某一层时,不会影响其他层。比如我想让审查 Agent 更严格一点,只需要改约束层,角色层和任务层不用动。

3.2 工具调用的参数校验与容错

Agent 调用工具(比如执行命令、读写文件)时,最容易出问题的地方是参数格式。模型有时候会生成看起来合理但实际跑不通的参数,比如路径里多了空格、命令里用了不支持的 flag。

我的做法是在工具层加一道参数校验与规范化逻辑。具体来说:

  • 所有路径参数先做strip()和normalize(),把..和.解析掉。
  • 命令执行前检查是否在白名单里,不在白名单的直接拒绝。
  • 文件写入前检查目标目录是否存在,不存在则自动创建。
  • 对超时参数做范围限制,最小 5 秒,最大 300 秒。

这些校验逻辑不放在 Agent 的提示词里,而是放在工具实现里。原因很简单:提示词是“建议”,工具层是“强制”。模型可能忽略提示词,但没法绕过工具层的校验。

踩过的坑:有一次编码 Agent 生成了一个rm -rf命令,虽然本意是清理临时目录,但路径拼接出错,差点把源码目录删了。从那以后,所有破坏性命令都必须经过协调 Agent 二次确认,而且执行前会先做一次 dry-run。

3.3 多 Agent 之间的通信协议

Agent 之间怎么“说话”,这个协议设计得好不好,直接影响协作效率。我试过两种方案:一种是自由文本通信,Agent 直接输出自然语言,下游 Agent 自己解析;另一种是结构化通信,所有消息都遵循固定的 JSON Schema。

自由文本的优点是灵活,缺点是解析成本高,而且容易产生歧义。结构化通信的优点是可靠,缺点是前期定义 Schema 比较费劲。我最终选择了混合方案:Agent 的输出分为两部分,一部分是给人看的自然语言说明,另一部分是给下游 Agent 用的结构化数据。

比如编码 Agent 的输出格式:

{ "summary": "实现了用户登录接口的参数校验逻辑", "files_changed": [ {"path": "src/auth/login.ts", "action": "modify", "lines": "45-78"} ], "next_action": "review", "structured_payload": { "function_name": "validateLoginParams", "test_cases_needed": ["empty_email", "invalid_password_length"] } }

协调 Agent 只读next_action和structured_payload来做调度决策,审查 Agent 读files_changed来定位审查范围。自然语言部分主要用于日志和人工排查。

4. 实操落地:从零搭建一个可运行的跨平台 Agent 流程

4.1 环境准备与依赖安装

先说明一下,我这里演示的是一个最小可运行版本,不依赖任何特定的云平台或商业服务。你可以在本地机器上跑通整个流程,然后再按需替换 Runtime 实现。

基础环境要求:

  • Python 3.10 以上(我用的是 3.11,类型提示支持更好)
  • 一个支持函数调用的模型接口(本地模型或 API 都行,这里不绑定具体厂商)
  • Git(用于代码库操作)
  • 建议用虚拟环境,避免依赖冲突

依赖安装清单:

pip install pydantic>=2.0 pip install pyyaml pip install rich pip install pytest

pydantic用来做数据模型和校验,pyyaml读配置文件,rich做终端输出美化,pytest跑测试。没有引入重量级框架,因为 Agent 系统的核心逻辑不复杂,自己写反而更可控。

目录结构建议这样组织:

agency_agents/ ├── config/ │ ├── agents.yaml │ └── runtime.yaml ├── core/ │ ├── runtime_interface.py │ ├── state_manager.py │ └── orchestrator.py ├── agents/ │ ├── planner.py │ ├── coder.py │ ├── reviewer.py │ └── tester.py ├── runtimes/ │ ├── local_runtime.py │ └── sandbox_runtime.py └── main.py

这个结构的好处是职责清晰,core放平台无关逻辑,runtimes放平台相关实现,agents放各角色的提示词和工具定义。

4.2 定义第一个 Agent:规划 Agent 的实现

规划 Agent 是整个流程的入口,它的输入是一个自然语言需求,输出是结构化的任务列表。我用的提示词模板如下:

PLANNER_PROMPT = """ <role> 你是一名技术项目规划师。你的职责是将高层需求拆解为可独立执行的任务。 </role> <task> 分析以下需求,输出任务列表。每个任务包含:任务ID、描述、依赖任务ID、预期输出。 </task> <context> 需求:{requirement} 代码库结构:{repo_structure} </context> <constraints> - 任务粒度控制在单个文件或单个函数的修改范围内。 - 依赖关系必须是有向无环图。 - 输出格式为 JSON 数组,不要输出其他内容。 </constraints> """

解析输出的代码:

import json from pydantic import BaseModel, Field class Task(BaseModel): task_id: str description: str depends_on: list[str] = Field(default_factory=list) expected_output: str def parse_planner_output(raw: str) -> list[Task]: # 去掉可能的 markdown 代码块标记 cleaned = raw.strip().removeprefix("```json").removesuffix("```").strip() data = json.loads(cleaned) return [Task(**item) for item in data]

这里有个细节:模型有时候会把 JSON 包在 markdown 代码块里,所以解析前要先清理。另外用 Pydantic 做校验,如果模型输出的字段缺失或类型不对,会直接报错,而不是带着脏数据往下跑。

4.3 协调 Agent 的调度循环实现

协调 Agent 的核心是一个循环:取任务 -> 找可用 Agent -> 执行 -> 更新状态 -> 重复。伪代码大概是这样:

class Orchestrator: def __init__(self, agents: dict, state_manager, max_retries=3): self.agents = agents self.state_manager = state_manager self.max_retries = max_retries def run(self, tasks: list[Task]): pending = {t.task_id: t for t in tasks} completed = set() while pending: ready = [ t for t in pending.values() if all(dep in completed for dep in t.depends_on) ] if not ready: raise RuntimeError("检测到循环依赖或死锁") for task in ready: agent = self.select_agent(task) result = self.execute_with_retry(agent, task) self.state_manager.save(task.task_id, result) completed.add(task.task_id) pending.pop(task.task_id)

select_agent根据任务描述里的关键词来路由,比如包含“审查”就交给 Reviewer,包含“测试”就交给 Tester。这个路由逻辑可以做得更复杂,比如用一个小模型做分类,但初期用关键词匹配就够了。

execute_with_retry负责重试逻辑:如果 Agent 执行失败,先检查是不是上下文缺失,补上上下文再试一次;如果连续失败超过max_retries,就把任务标记为阻塞,交给人工处理。

实操心得:重试的时候不要原样重试,一定要在提示词里加上上次失败的原因。我试过原样重试三次,结果三次都犯同样的错误。后来改成把错误信息注入到下一次的上下文里,成功率明显提升。

4.4 跨平台 Runtime 的切换与验证

本地 Runtime 的实现比较简单,直接调os和subprocess。沙箱 Runtime 则需要把文件操作和命令执行代理到远程环境。我这里不展开远程实现细节,重点说切换和验证的方法。

切换 Runtime 只需要改配置文件:

# config/runtime.yaml runtime: local # 可选 local / sandbox / ci local: workspace: ./workspace command_timeout: 60 sandbox: endpoint: ${SANDBOX_ENDPOINT} api_key: ${SANDBOX_API_KEY}

然后在main.py里根据配置注入对应的 Runtime 实例:

def create_runtime(config: dict) -> RuntimeInterface: runtime_type = config["runtime"] if runtime_type == "local": return LocalRuntime(config["local"]) elif runtime_type == "sandbox": return SandboxRuntime(config["sandbox"]) else: raise ValueError(f"未知的 runtime 类型: {runtime_type}")

验证跨平台一致性的方法是:同一组任务,分别在本地和沙箱上跑一遍,对比最终产出的文件内容和测试结果。我通常会准备一个smoke_test任务集,包含文件读写、命令执行、错误处理三个场景,每次切换 Runtime 后先跑这个测试集。

5. 常见问题与排查技巧实录

5.1 Agent 输出格式错乱的排查思路

这是最高频的问题。模型有时候会忘记输出 JSON,或者 JSON 格式不合法。排查步骤我总结成了下面这个流程:

现象可能原因排查方法解决方案
输出纯文本,没有 JSON提示词约束不够强检查约束层是否明确要求 JSON在约束层加“只输出 JSON,不要任何解释”
JSON 解析失败模型生成了非法 JSON打印原始输出,看具体哪里出错加解析容错,尝试提取第一个{到最后一个}
字段缺失模型遗漏了必填字段用 Pydantic 校验,看报错信息在提示词里给出完整的字段示例
字段类型错误模型把数字写成了字符串检查类型定义在 Pydantic 里加类型转换器

我的经验是,给出完整的输出示例比单纯描述字段有效得多。模型看到示例后,格式正确率能提升一大截。

5.2 上下文超限的压缩策略

多 Agent 协作时,上下文很容易膨胀。我的压缩策略分三步:

  1. 去重:相同文件内容只保留一份,其他位置用引用代替。
  2. 摘要:历史执行日志只保留关键决策点和错误信息,详细日志归档到文件。
  3. 截断:对超长文件,只保留与当前任务相关的函数或类,其他部分用...省略。

具体实现上,我写了一个ContextCompressor类,输入是原始上下文,输出是压缩后的上下文。压缩比例可以通过配置调整,默认目标是控制在模型上下文窗口的 70% 以内。

注意:压缩上下文时一定要保留文件路径和行号信息,否则下游 Agent 定位不到具体位置,会开始胡编。

5.3 任务死锁与循环依赖的处理

任务依赖图如果有环,协调 Agent 会陷入死锁。我的处理方式是:

  • 规划阶段就用拓扑排序检测环,有环直接报错,让规划 Agent 重新拆解。
  • 运行阶段如果发现没有就绪任务但还有未完成任务,说明出现了意外死锁,此时把剩余任务全部标记为阻塞,输出依赖图供人工排查。

依赖图输出用简单的文本格式就行:

task_1 -> task_2 -> task_4 task_1 -> task_3 -> task_4 task_5 (阻塞,依赖 task_4)

这样一眼就能看出问题在哪。

5.4 模型输出不稳定时的降级方案

模型输出不稳定是常态,尤其是任务复杂的时候。我的降级方案分三级:

  • 一级降级:换更保守的提示词,减少自由发挥空间。
  • 二级降级:把复杂任务拆成更小的子任务,逐个执行。
  • 三级降级:切换到规则引擎,用预定义的模板生成输出,完全不用模型。

三级降级虽然灵活性差,但至少能保证流程跑通。我一般只在关键路径上启用降级,非关键路径直接跳过。

6. 工程化扩展:从能跑到好用还差哪些东西

6.1 日志与可观测性建设

Agent 系统跑起来之后,最头疼的是“它到底在干什么”。我早期只打 print,后来发现根本不够用。现在我的日志分三个层次:

  • 执行日志:每个 Agent 的输入输出、耗时、token 消耗,写到结构化日志文件。
  • 决策日志:协调 Agent 的调度决策,为什么选这个 Agent,为什么重试,写到单独文件。
  • 审计日志:所有文件修改和命令执行记录,用于回溯和审计。

用rich做终端实时输出,用 JSON Lines 格式写文件日志。排查问题的时候,先看决策日志定位到具体任务,再看执行日志看 Agent 的输入输出,最后看审计日志确认实际改动。

6.2 性能优化:减少不必要的 Agent 调用

Agent 调用是有成本的,不管是时间还是费用。我做了几个优化:

  • 缓存:相同输入的任务直接返回缓存结果,缓存 key 用输入内容的哈希。
  • 批处理:多个小任务合并成一个批次,减少协调开销。
  • 跳过:如果代码库没有变化,审查和测试任务直接跳过。

这些优化加起来,我的测试项目里 Agent 调用次数减少了大约 40%,整体流程时间缩短了一半以上。

6.3 安全边界:权限控制与操作审计

Agent 能执行命令、修改文件,这本身就是风险。我的安全措施包括:

  • 命令白名单:只允许执行预定义的安全命令。
  • 文件写入限制:只能写入工作目录,不能碰系统目录。
  • 敏感操作二次确认:删除、覆盖、批量修改需要协调 Agent 确认。
  • 操作审计:所有操作记录到不可篡改的日志文件。

这些措施不能保证 100% 安全,但能挡住大部分意外情况。我的原则是:宁可让 Agent 少做一点,也不要让它做错事。

6.4 后续扩展方向

这个框架搭好之后,扩展方向其实很多。我目前正在尝试的几个方向:

  • 接入更多 Runtime:比如容器环境、远程开发机,让 Agent 能在更多场景下工作。
  • 引入反馈循环:让审查 Agent 的反馈自动回流到编码 Agent,形成迭代优化。
  • 多模型路由:不同任务用不同模型,简单任务用轻量模型,复杂任务用强模型,平衡成本和质量。
  • 人工介入接口:在关键决策点插入人工确认,让整个流程更可控。

这些扩展不需要改动核心架构,只需要在现有接口上做加法。这也是当初做抽象层设计时预留的灵活性。

我个人在实际操作中的体会是,Agency Agents 这套东西,架构设计比模型选型重要得多。模型能力再强,如果协作逻辑混乱、状态管理一塌糊涂,最终产出照样不可靠。反过来,即使模型能力一般,只要角色划分清晰、通信协议严谨、容错机制到位,整个系统也能稳定跑出可用的结果。另外一个小技巧:每次调整 Agent 配置后,先跑一遍固定的回归测试集,确认没有破坏已有功能,再上真实任务。这个习惯帮我省了很多返工时间。

RELATED READING

延伸阅读

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