☰
A2A协议与LangChain.js实战:用TaoToken统一Key构建微型软件工厂
2026/9/26 10:54:05 网站建设 项目流程

1. 为什么单 Agent 写不动“贪吃蛇”,而 A2A 能

你可能已经用 LangChain.js 写过单 Agent:一个 Prompt 里塞进需求分析、写代码、写测试,模型上下文一长就开始胡言乱语。我试过让一个 Agent 同时干 PM、Dev、QA 三件事,结果它把测试报告写成了代码注释,还把需求文档里的“贪吃蛇”理解成了“贪吃蛇游戏服务器”。

问题不在模型,而在架构。单个 Agent 的上下文窗口有限,技能树也不可能无限扩展。当你面对“开发一个贪吃蛇游戏”这种模糊且复杂的任务时,真正需要的是一个虚拟团队——多智能体协作系统(MAS, Multi-Agent System)。

A2A(Agent-to-Agent)协议就是解决 Agent 之间“怎么说话、怎么发现彼此、怎么移交任务”的设计模式。它不是某个硬性标准,而是一套通信规范:每个 Agent 既是 Server(监听端口响应请求),也是 Client(主动调用其他 Agent)。消息层推荐 JSON-RPC 2.0,因为 Agent 关注的是“行为”(Action)而非“资源”(Resource),POST /generate_code这种指令式语义比 RESTful 更符合直觉。

本文聚焦 Node.js 环境下用 LangChain.js 实现 A2A 协议 Agent 协作,通过 TaoToken 统一 Key/API 通道打通多 Agent 调用。你会拿到可复制的config.toml与settings.json配置骨架、JSON-RPC 消息示例,以及本地启动微型软件工厂的完整验证步骤。适合已经写过单 Agent、想跑通多 Agent 协作链路的 Node.js 开发者。

2. TaoToken 前置:统一 Key 与 API 通道

多 Agent 协作最烦的事情之一,是每个 Agent 都要配一遍 API Key、Base URL、模型名。PM 用 GPT-4o,Dev 用 Claude,QA 用另一个模型,配置文件散落各处,改一个环境变量要翻五个文件。

TaoToken 在这里的角色是统一入口:一个 Key、一个 API 通道,所有 Agent 通过它调用不同模型。你不需要在每个 Agent 里硬编码不同的供应商地址,只需要在配置里指定模型名,请求统一走 TaoToken 的 API 端点。

2.1 获取 API Key

访问 TaoToken 控制台的 API Keys 页面(https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite),创建一个新 Key。建议按项目命名,比如a2a-software-factory,方便后续排查。

拿到 Key 后,不要直接写进代码。我们用.env文件管理,配合dotenv加载。

2.2 配置骨架:config.toml 与 settings.json

虽然 Node.js 项目常用.env,但多 Agent 场景下,用config.toml管理 Agent 角色、端口、能力标签,用settings.json管理模型参数,结构更清晰。

config.toml负责“谁在哪里、能干什么”:

# config.toml - Agent 注册与端口配置 [registry] port = 3000 host = "localhost" [agents.pm] name = "PM_Agent" port = 3001 capabilities = ["requirements_analysis"] model = "gpt-4o-mini" temperature = 0.7 [agents.dev] name = "Dev_Agent" port = 3002 capabilities = ["coding"] model = "gpt-4o-mini" temperature = 0.3 [agents.qa] name = "QA_Agent" port = 3003 capabilities = ["testing"] model = "gpt-4o-mini" temperature = 0.2

settings.json负责“怎么调用模型”:

{ "llm": { "baseURL": "https://taotoken.net/api", "apiKeyEnv": "TAOTOKEN_API_KEY", "defaultModel": "gpt-4o-mini", "timeout": 30000, "maxRetries": 2 }, "rpc": { "version": "2.0", "contentType": "application/json" } }

注意:baseURL填https://taotoken.net/api,不要加 UTM 参数。API Key 通过环境变量TAOTOKEN_API_KEY注入,不要提交到 Git。

2.3 安装依赖

在项目根目录执行:

npm init -y npm install express body-parser axios dotenv @langchain/openai @langchain/core @iarna/toml

@iarna/toml用来解析config.toml,@langchain/openai负责 LLM 调用,axios处理 Agent 之间的 HTTP 通信。

3. 可复制配置:JSON-RPC 消息与 Agent 运行时

这一章是核心。我们把 A2A 协议的四个层面落到代码:传输层用 HTTP,消息层用 JSON-RPC 2.0,发现层用注册中心,语义层用 LangChain 的 Prompt 链。

3.1 JSON-RPC 消息示例

A2A 协议要求 Agent 之间用标准化的 JSON-RPC 2.0 通信。一个完整的请求-响应长这样:

请求(PM 调用 Dev 的write_code方法):

{ "jsonrpc": "2.0", "method": "write_code", "params": { "requirement": "实现一个贪吃蛇游戏的核心逻辑,包含蛇的移动、食物生成、碰撞检测" }, "id": 1712345678901 }

成功响应:

{ "jsonrpc": "2.0", "result": { "code": "class SnakeGame { ... }", "qa_report": "Pass: 逻辑完整,建议增加边界测试" }, "id": 1712345678901 }

错误响应:

{ "jsonrpc": "2.0", "error": { "code": -32601, "message": "Method not found" }, "id": 1712345678901 }

为什么用 JSON-RPC 而不是 RESTful?因为 Agent 之间传递的是“行为”而非“资源”。write_code是一个动作,POST /generate_code这种指令式语义比PUT /code/1更符合 Agent 的直觉。而且 JSON-RPC 无状态、轻量,天然支持method和params的映射,非常适合把 RPC 方法直接映射为 Agent 类的方法。

3.2 协议封装与注册中心

先写协议封装类,把 JSON-RPC 的请求、成功、错误三种消息格式固定下来:

// protocol.js export class JsonRpcProtocol { static request(method, params, id = Date.now()) { return { jsonrpc: "2.0", method, params, id }; } static success(result, id) { return { jsonrpc: "2.0", result, id }; } static error(code, message, id) { return { jsonrpc: "2.0", error: { code, message }, id }; } }

注册中心是 A2A 的“大脑”。Agent 启动时向注册中心汇报自己的能力和 URL,调用方通过能力标签查找目标 Agent,而不是硬编码 IP。这实现了位置解耦:

// registry.js import express from "express"; import bodyParser from "body-parser"; const REGISTRY_PORT = 3000; const registryApp = express(); registryApp.use(bodyParser.json()); const services = {}; registryApp.post("/register", (req, res) => { const { name, url, capabilities } = req.body; services[name] = { url, capabilities }; console.log(`[Registry] 服务上线: ${name} [${capabilities.join(", ")}]`); res.json({ status: "ok" }); }); registryApp.post("/discover", (req, res) => { const { capability } = req.body; const foundName = Object.keys(services).find((key) => services[key].capabilities.includes(capability) ); if (foundName) { const service = services[foundName]; console.log(`[Registry] 发现请求: "${capability}" -> ${service.url}`); res.json({ url: service.url }); } else { res.status(404).json({ error: "Service not found" }); } }); registryApp.listen(REGISTRY_PORT, () => { console.log(`[Registry] 注册中心启动于 http://localhost:${REGISTRY_PORT}`); });

3.3 Agent 运行时基类

把 Agent 的通用行为(启动 HTTP 服务、处理 JSON-RPC、向注册中心汇报、调用其他 Agent)抽成基类。这样具体的业务 Agent 只需要关注 Prompt 和逻辑:

// agent-runtime.js import express from "express"; import bodyParser from "body-parser"; import axios from "axios"; import { ChatOpenAI } from "@langchain/openai"; import { JsonRpcProtocol } from "./protocol.js"; const REGISTRY_PORT = 3000; export class AgentRuntime { constructor(name, port, capabilities, modelConfig) { this.name = name; this.port = port; this.capabilities = capabilities; this.app = express(); this.app.use(bodyParser.json()); this.model = new ChatOpenAI({ modelName: modelConfig.model, temperature: modelConfig.temperature, configuration: { baseURL: process.env.TAOTOKEN_BASE_URL || "https://taotoken.net/api", apiKey: process.env.TAOTOKEN_API_KEY, }, }); this.app.post("/json-rpc", (req, res) => this.handleRpc(req, res)); } async start() { return new Promise((resolve) => { this.app.listen(this.port, async () => { console.log(`[${this.name}] 启动于 Port ${this.port}`); await this.registerSelf(); resolve(); }); }); } async registerSelf() { try { await axios.post(`http://localhost:${REGISTRY_PORT}/register`, { name: this.name, url: `http://localhost:${this.port}/json-rpc`, capabilities: this.capabilities, }); } catch (e) { console.error(`[${this.name}] 注册失败: registry offline?`); } } async handleRpc(req, res) { const { jsonrpc, method, params, id } = req.body; if (jsonrpc !== "2.0") { return res.status(400).json(JsonRpcProtocol.error(-32600, "Invalid Request", id)); } console.log(`[${this.name}] 收到任务: ${method}`); try { if (this[method] && typeof this[method] === "function") { const result = await this[method](params); res.json(JsonRpcProtocol.success(result, id)); } else { res.status(404).json(JsonRpcProtocol.error(-32601, "Method not found", id)); } } catch (error) { console.error(error); res.status(500).json(JsonRpcProtocol.error(-32000, error.message, id)); } } async callOtherAgent(capability, method, params) { console.log(`[${this.name}] 寻找具备 "${capability}" 能力的队友...`); const discoverRes = await axios.post( `http://localhost:${REGISTRY_PORT}/discover`, { capability } ); const targetUrl = discoverRes.data.url; console.log(`[${this.name}] 发送任务到 ${targetUrl}: ${method}`); const rpcPayload = JsonRpcProtocol.request(method, params); const response = await axios.post(targetUrl, rpcPayload); if (response.data.error) throw new Error(response.data.error.message); return response.data.result; } }

3.4 业务 Agent 实现

有了基类,定义具体 Agent 就像写普通业务类。LangChain 在这里负责处理非结构化的自然语言逻辑:

// agents.js import { ChatPromptTemplate } from "@langchain/core/prompts"; import { StringOutputParser } from "@langchain/core/output_parsers"; import { AgentRuntime } from "./agent-runtime.js"; export class PMAgent extends AgentRuntime { constructor(config) { super("PM_Agent", config.port, ["requirements_analysis"], config); } async start_project({ user_input }) { console.log(`[PM] 正在分析用户需求: ${user_input}`); const chain = ChatPromptTemplate.fromTemplate( "将此需求转化为简短的技术需求文档(PRD),包含功能点和技术栈建议: {input}" ) .pipe(this.model) .pipe(new StringOutputParser()); const prd = await chain.invoke({ input: user_input }); console.log(`[PM] PRD 生成完毕`); const codeResult = await this.callOtherAgent("coding", "write_code", { requirement: prd, }); return { status: "finished", prd_summary: prd.slice(0, 50) + "...", final_output: codeResult, }; } } export class DevAgent extends AgentRuntime { constructor(config) { super("Dev_Agent", config.port, ["coding"], config); } async write_code({ requirement }) { console.log(`[Dev] 收到 PRD,开始写代码...`); const chain = ChatPromptTemplate.fromTemplate( "根据需求写一段 JavaScript 代码。只返回代码块,不要 Markdown: {req}" ) .pipe(this.model) .pipe(new StringOutputParser()); const code = await chain.invoke({ req: requirement }); console.log(`[Dev] 代码编写完毕`); const testReport = await this.callOtherAgent("testing", "review_code", { code, requirement, }); return { code, qa_report: testReport }; } } export class QAAgent extends AgentRuntime { constructor(config) { super("QA_Agent", config.port, ["testing"], config); } async review_code({ code, requirement }) { console.log(`[QA] 收到代码,运行静态分析...`); const chain = ChatPromptTemplate.fromTemplate( "需求: {req}\n代码: {code}\n请给出简短评测结论(Pass/Fail)和改进建议:" ) .pipe(this.model) .pipe(new StringOutputParser()); const report = await chain.invoke({ req: requirement, code }); console.log(`[QA] 测试完毕`); return report; } }

4. 验证请求:本地启动微型软件工厂

配置和代码都齐了,现在跑通整条链路。

4.1 启动入口

// index.js import "dotenv/config"; import fs from "fs"; import TOML from "@iarna/toml"; import axios from "axios"; import { JsonRpcProtocol } from "./protocol.js"; import { PMAgent, DevAgent, QAAgent } from "./agents.js"; const config = TOML.parse(fs.readFileSync("./config.toml", "utf-8")); async function main() { const pm = new PMAgent({ ...config.agents.pm, model: config.agents.pm.model }); const dev = new DevAgent({ ...config.agents.dev, model: config.agents.dev.model }); const qa = new QAAgent({ ...config.agents.qa, model: config.agents.qa.model }); await Promise.all([pm.start(), dev.start(), qa.start()]); await new Promise((r) => setTimeout(r, 1000)); console.log("\n=================================================="); console.log(" A2A 分布式网络已建立 (HTTP/JSON-RPC)"); console.log("==================================================\n"); const userPrompt = "帮我写一个简单的 Node.js HTTP Server,返回 Hello World"; try { console.log(`[User] 提交需求给 PM...`); const response = await axios.post( "http://localhost:3001/json-rpc", JsonRpcProtocol.request("start_project", { user_input: userPrompt }) ); console.log("\n============= 最终交付成果 ============="); console.log(JSON.stringify(response.data.result, null, 2)); } catch (e) { console.error("调用失败:", e.message); } } main();

4.2 环境变量与启动命令

创建.env文件:

TAOTOKEN_API_KEY=你的Key TAOTOKEN_BASE_URL=https://taotoken.net/api

启动注册中心(单独终端):

node registry.js

启动软件工厂:

node index.js

4.3 预期输出

你会看到类似微服务日志的流式输出:

[Registry] 注册中心启动于 http://localhost:3000 [PM_Agent] 启动于 Port 3001 [Dev_Agent] 启动于 Port 3002 [QA_Agent] 启动于 Port 3003 [Registry] 服务上线: PM_Agent [requirements_analysis] [Registry] 服务上线: Dev_Agent [coding] [Registry] 服务上线: QA_Agent [testing] [User] 提交需求给 PM... [PM] 正在分析用户需求: 帮我写一个简单的 Node.js HTTP Server,返回 Hello World [PM] PRD 生成完毕 [PM] 寻找具备 "coding" 能力的队友... [Registry] 发现请求: "coding" -> http://localhost:3002/json-rpc [PM] 发送任务到 http://localhost:3002/json-rpc: write_code [Dev] 收到 PRD,开始写代码... [Dev] 代码编写完毕 [Dev] 寻找具备 "testing" 能力的队友... [Registry] 发现请求: "testing" -> http://localhost:3003/json-rpc [Dev] 发送任务到 http://localhost:3003/json-rpc: review_code [QA] 收到代码,运行静态分析... [QA] 测试完毕

最终交付成果里包含prd_summary、code和qa_report三个字段,说明 PM → Dev → QA 的 A2A 调用链完整跑通。

5. 本篇常见错排查

5.1 注册中心 404:Service not found

现象:[Registry] 未找到具备 "coding" 的服务,Dev Agent 明明启动了。

原因通常是 Agent 启动后注册请求还没完成,PM 就开始发现。解决:在main()里Promise.all启动所有 Agent 后,加一个setTimeout等待 1 秒,确保注册完成。生产环境可以用重试机制,发现失败后隔 500ms 重试三次。

5.2 JSON-RPC 返回 -32601 Method not found

现象:调用write_code时返回方法不存在。

检查两点:一是handleRpc里this[method]的映射,方法名必须和 JSON-RPC 请求里的method完全一致,大小写敏感;二是write_code必须定义在 Agent 类上,不能是私有方法或箭头函数属性(箭头函数不在原型链上,this[method]找不到)。

5.3 TaoToken 调用返回 401

现象:LLM 调用报鉴权失败。

检查.env里的TAOTOKEN_API_KEY是否被dotenv正确加载。index.js第一行必须是import "dotenv/config",且.env文件在项目根目录。另外确认baseURL是https://taotoken.net/api,不要多加路径或参数。

5.4 端口冲突 EADDRINUSE

现象:启动时报EADDRINUSE: address already in use :::3001。

说明上一个进程没退干净。用lsof -i :3001找到进程号,kill -9掉。或者改config.toml里的端口号。建议每个 Agent 端口在配置里集中管理,避免硬编码。

5.5 Agent 之间循环调用

现象:PM 调用 Dev,Dev 又调用 PM,日志无限循环。

A2A 协议里没有强制的调用链终止机制,需要你在业务逻辑里控制。比如 Dev 的write_code只调用testing能力,不调用requirements_analysis。能力标签的设计要避免环形依赖。如果确实需要复杂编排,引入一个 Orchestrator Agent 做流程控制,而不是让 Agent 互相随意调用。

6. 下一步:把 Key 管好,把链路跑长

跑通这个微型软件工厂后,你会发现 A2A 的真正价值在于解耦和可扩展。PM 根本不需要知道 QA 的存在,它只关心“能力”。这意味着你可以随时替换 Dev Agent 的实现——从 GPT-4o-mini 换成更强的模型,或者换成用 Python 写的 Agent,只要它注册了coding能力并遵循 JSON-RPC 协议,整条链路无需修改。

多 Agent 协作的调用量会比单 Agent 高一个数量级,统一 Key 和 API 通道能省掉大量配置维护成本。你可以在 TaoToken 控制台(https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite)里查看各模型的调用情况,按项目分配不同的 Key。如果后续要接入长期编码或 Agent 工作流,可以了解 Coding Plan(https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite),它针对高频编码场景做了通道优化。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有各语言 SDK 的完整示例。

先把本地这三个 Agent 跑稳,再试着加第四个 Security Agent,注册security_audit能力,在 Dev 和 QA 之间插入一道安全审查。链路越长,A2A 的解耦优势越明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询