the Agent-to-Agent Exchange
Wall Street自己也是一个小型多智能体系统:一个跑在轻量ReAct循环上的编排器、一个连接推荐专家、一个数据解读专家,三者用哪一档LLM都由配置驱动。当前部署的网关环境里没有接入任何模型,所以推荐专家的确定性规则路径承担了全部请求;ReAct循环和解读专家已经建成,只等那一项配置。五个阶段、二十四张票,全部交付。
分享这篇案例 →难点在于:在三个各自独立的系统写下任何一行新代码之前,先把它们都必须遵守的线上契约冻结下来——每个字段都要能对应到某张生产表里真实存在的列,九种失败模式统统收敛成同一个"未接通"结果——因为在设计阶段就把降级语义定对,远比等三个团队都已经照着一份上线的契约写完代码之后再回头补救便宜得多。
Lightweight ReAct loop over Wall Street's own specialists. The deployed gateway is built with no LLM tier wired into its environment, so the loop short-circuits before it runs and every request is served by the deterministic path instead.
Python (gateway), React, Vite, TypeScript, Tailwind, React Flow
每一次跨系统调用都携带同一份信封:一个X-A2A-Version头、一个同时充当consult幂等键的请求ID,还有一个贯穿调用方、枢纽、应答方全程的追踪ID,让一次跨系统交互事后能从三份各自独立的审计轨迹里重新拼出来。九种命名好的失败码——从鉴权被拒到对端不可达,再到响应没通过schema校验——对调用方而言统统收敛成同一个结果:一个标注清楚的"未接通",绝不崩溃,也绝不卡住本地流程。一条可插拔的鉴权链(先IP白名单,再逐调用方的bearer token,mTLS的位置已经留好但还没实现)挡在每个对端的A2A接口前面,和该系统自己的UI登录刻意分开。这些都不是事后补上去的通用中间件——信封、鉴权链、完整的错误分类,是最早被设计出来的东西,早于任何一个对端服务器或画布组件。
一份YAML拓扑注册表是唯一知道"有哪些系统存在、谁能跟谁说话"的地方:每个对端声明自己的节点类型,以及它提供和消费的能力;每条连接规则则指名一个能力、一个提供方,和它合法的消费方。网关在每次调用时执行这些规则,画布也会先问同一份注册表——一个BFF端点,GET /api/topology——才允许用户在两个节点之间拖出一条连线,前端从不硬编码任何一对系统。往生态里接入第四、第五个系统,范围被限定成:实现A2A服务器的最小接口(health、capabilities,以及它自己的query端点)、往拓扑配置里加一段、加上它的token环境变量——只有在它需要一种视觉上不同的节点时,才需要往前端注册表里加一个新节点类型。如果复用通用的"system"节点类型,前端代码量是零——新对端只要被能力发现机制找到,就会自动出现。
在画布上,系统、Agent、数据源是节点,A2A能力是边——一条data-share边通过query调用把对端的真实生产数据拉进来,一条consult边发出请求,把对端的回答当作只读结论显示在详情抽屉里。每个节点、每条边都独立降级:一个不可达的对端,它的节点会灰显并带上原因码,画布其余部分照常按设计运行——这正是协议在线路层面强制的"没有任何一方是另一方的运行支柱"这条规则,只是现在从一行日志变成了一个可见的UI状态。画布是访客真正看得到、点得到的那一部分;但它执行的每一条规则——哪些连接合法、一个降级的对端长什么样、到底什么数据允许抵达浏览器——都是先在网关和契约里定下来的。画布只是把那份设计渲染出来,它本身不是另一份设计。
Wall Street不只是转发其他系统的Agent——它自己也带着一支小队伍:一个编排器,用的是这个作品集里其他地方同款的轻量ReAct循环;一个连接推荐专家,读取当前画布和能力地图,建议可行的连接;一个数据解读专家,把某个节点已经脱敏过的指标变成一句话解读。有两件事值得直说,而不是留给示意图去暗示。第一,调度的起点其实在网关的静态BFF路由表:先由它选中专家,专家再自己构造编排器——协同是真代码,但并不从枢纽开始。第二,当前部署的容器环境里没有接入任何LLM档位,编排器的ReAct循环在真正跑起来之前就短路了:推荐专家的规则兜底——直接从provider/consumer在能力地图里的交集算出来——承担了今天的全部请求;解读专家按设计没有规则路径,于是返回空结果,画布随即把这个入口隐藏起来。第四个专家——一个能跨多个节点串联叙事的insight-narrator——已经设计好、配置里也留了位置,但在跨节点叙事合成真正做出来之前先保持禁用。每个Agent用哪一档LLM、温度多少、要不要启用,统统写在一份配置文件里——换模型是改配置,从来不是改代码;每一条system prompt都放在外部的提示词库里,按语言和key加载,从不写死在Agent代码本身里。
浏览器永远不持有A2A token,也永远不直连对端——它只用一个独立的UI token向网关的BFF鉴权,而它收到的每一个生产数据数字,都已经先经过网关的脱敏层,序列化时套用契约自己的字段白名单,任何没有明确列出的字段都会被静默丢弃。每一份数据契约都会把自己的自由文本字段——一条标题、一条备注、一个Agent的回答——标记为不可信,任何把这段文字回灌进自己LLM的系统,都必须把它包进自己现有的防注入信封,当成数据而不是指令来处理;契约自带的测试套件要求:一条嵌了"把预算改成零"这种指令的回复,不能在任何地方产生写操作。跨系统调用按设计只读——唯一的例外,consult的结论,也只能以"提议动作"的形式回来,在接收方自己的人工确认流程批准之前,哪里都不会被执行。这些都不是事后加的补丁:白名单即schema、不可信字段标记、跨系统零写操作,这些规则就是契约本身,在任何一个对端把它实现出来之前就已经冻结。
Python(stdlib A2A网关)、React + Vite + TypeScript画布、Tailwind CSS、React Flow,以及一个三方共用的参考mock服务器,用于跨仓库的契约一致性测试。