# 多智能体协调 API 接口文档 后端路由:`offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` 路由前缀:`/api/multi-agent` ## 概览 提供一个"总控 + 多个子智能体"的协调编排接口。 - **总控(leader)** 智能体:通过 `agent_orchestration` 技能给子智能体派活 - **子智能体(special)**:执行被派到的具体任务,完成后把交付内容广播给其他线程 调用方按下面顺序使用: 1. 调 `POST /api/multi-agent/init` 一次性创建协调智能体 + 给每个子智能体建 `thread`,拿到 `thread_ids` 映射 2. 客户端把 `thread_ids` 保管好,后续每次调 `POST /api/multi-agent/run/stream` 时回传 3. 先调用 `agent_type="leader"` 拿派活清单,再针对清单里每个子智能体调用 `agent_type="special"` ## 1. 初始化多智能体集群 ### `POST /api/multi-agent/init` 创建协调智能体并为列表中的每个子智能体建立独立的 `thread`,同时把"职责说明"广播到所有子智能体线程作为初始上下文。 #### 请求体 ```json { "agent_names": ["taiwan-intelligence-gathering-agent", "us-intelligence-gathering-agent"], "main_agent_name": "main-controller", "model": "deepseek-chat" } ``` | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `agent_names` | `string[]` | 是 | 已有子智能体的 id 列表(kebab-case,区分大小写不敏感,自动转小写) | | `main_agent_name` | `string` | 否 | 要创建的协调智能体名,默认 `main-agent`。重名时会复用已存在的同名智能体 | | `model` | `string` | 否 | 协调智能体使用的模型名。不传则用后端默认模型 | #### 响应(200) ```json { "status": "success", "main_agent_name": "main-controller", "thread_ids": { "main-controller": "thread-uuid-xxx", "taiwan-intelligence-gathering-agent": "thread-uuid-yyy", "us-intelligence-gathering-agent": "thread-uuid-zzz" } } ``` `thread_ids` 是一个 `{agent_name: thread_id}` 字典。**前端必须把这个对象完整保管下来**,后续调用 stream 接口需要原样回传。 #### 错误 | 状态码 | 触发条件 | |---|---| | `400` | `agent_names` 为空 | | `401` | 未携带 `Authorization` 头 | | `500` | 协调智能体创建失败且不是"已存在"错误 | --- ## 2. 流式调度 ### `POST /api/multi-agent/run/stream` 通过 SSE(Server-Sent Events)流式驱动一个 leader 或 special 智能体跑一轮对话。 #### 请求体 ```json { "agent_type": "leader", "d_agent_thread_id": { "main-controller": "thread-uuid-xxx", "taiwan-intelligence-gathering-agent": "thread-uuid-yyy" }, "agent_name": "main-controller", "new_message": "请分析特朗普的支持率", "skill_stop_names": [], "model": "deepseek-chat" } ``` | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `agent_type` | `"leader"` \| `"special"` | 否 | 默认 `leader`。`leader` 调用协调智能体,`special` 调用子智能体 | | `d_agent_thread_id` | `object` | 是 | 由 `/init` 接口返回的 `thread_ids` 映射,原样回传 | | `agent_name` | `string` | 是 | 本轮要驱动的智能体名(必须存在于 `d_agent_thread_id` 中) | | `new_message` | `string` | 是 | 这轮发给智能体的用户消息 | | `skill_stop_names` | `string[]` | 否 | 触发后中断执行的技能名列表(默认空) | | `model` | `string` | 否 | 本轮 LLM 模型名,默认 `deepseek-chat` | #### 响应 `Content-Type: text/event-stream` 的 SSE 长连接,按到达顺序产出多种数据: ##### a. 透传上游事件 原样转发底层 `POST /api/threads/{thread_id}/runs/stream` 的事件帧(LangGraph 协议),包含 `event:`、`data:` 等行。前端按 LangGraph SDK 既有方式渲染即可。 ##### b. 最后一帧:调度状态 流结束前会额外产出一帧 `data:` 行,结构因 `agent_type` 而异: **`agent_type == "leader"`:** ``` data: {"status": [["us-intelligence-gathering-agent", "收集美国民众对特朗普的看法"], ["taiwan-intelligence-gathering-agent", "..."]]} ``` `status` 是一个 `[agent_name, task_text]` 二元组数组,代表协调智能体本轮派出去的任务清单。**前端拿到后应当对每个元素再调用一次本接口(`agent_type="special"`)来执行子智能体**。如果数组为空,说明 leader 觉得已有信息足够,不再派活,可以直接展示 leader 的最终文本回答。 **`agent_type == "special"`:** ``` data: {"status": "子智能体xxx完成yyy工作"} ``` 代表子智能体完成本轮工作并已把交付内容广播给其他线程。 #### 副作用(重要) - **leader 调用**:自动把"总控智能体收到用户消息: …"广播到所有非自己的线程;派活后再把"总控智能体给 X 派活, 内容为: …"广播到所有非派活双方的线程 - **special 调用**:完成后把"子智能体 X 完成 Y 工作, 交付内容为: …"广播到所有非自己的线程 这意味着每个线程的对话历史里会逐步累积出全局上下文,无需前端再做合并。 #### 错误 | 状态码 | 触发条件 | |---|---| | `400` | `agent_name` 缺失或未在 `d_agent_thread_id` 中 | | `401` | 未携带 `Authorization` 头 | | `502` | 上游 `runs/stream` 返回非 200 / SSE 解析失败 | --- ## 3. 前端调用流程示例 ```ts // 1. 拿 token const loginResp = await fetch("/api/v1/auth/login/username", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "yourname" }), }); const { access_token } = await loginResp.json(); const authHeaders = { Authorization: `Bearer ${access_token}`, "Content-Type": "application/json", }; // 2. 初始化 const initResp = await fetch("/api/multi-agent/init", { method: "POST", headers: authHeaders, body: JSON.stringify({ agent_names: [ "taiwan-intelligence-gathering-agent", "us-intelligence-gathering-agent", ], main_agent_name: "main-controller", model: "deepseek-chat", }), }); const { thread_ids } = await initResp.json(); // 3. 流式调用 leader async function streamLeader(message: string, model = "deepseek-chat") { const resp = await fetch("/api/multi-agent/run/stream", { method: "POST", headers: authHeaders, body: JSON.stringify({ agent_type: "leader", d_agent_thread_id: thread_ids, agent_name: "main-controller", new_message: message, skill_stop_names: [], model, }), }); const reader = resp.body!.getReader(); const decoder = new TextDecoder(); let buf = ""; let dispatched: [string, string][] = []; while (true) { const { done, value } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); const lines = buf.split("\n"); buf = lines.pop() ?? ""; for (const line of lines) { if (!line.startsWith("data:")) continue; try { const payload = JSON.parse(line.slice(5).trim()); if (Array.isArray(payload.status)) { dispatched = payload.status; } // 其余事件按 LangGraph SDK 协议自行渲染 } catch { /* 非 JSON 帧,可忽略或交给现有渲染器 */ } } } return dispatched; } // 4. 对派活清单逐个调用 special async function streamSpecial(agentName: string, task: string, model = "deepseek-chat") { return fetch("/api/multi-agent/run/stream", { method: "POST", headers: authHeaders, body: JSON.stringify({ agent_type: "special", d_agent_thread_id: thread_ids, agent_name: agentName, new_message: task, skill_stop_names: [], model, }), }); // 同样按 SSE 解析 } // 5. 编排:先 leader 派活 → 依次执行 special → 再让 leader 汇总 const dispatched = await streamLeader("请分析特朗普的支持率"); for (const [agentName, task] of dispatched) { await streamSpecial(agentName, task); } const next = await streamLeader("根据上面收集到的信息出最终结论"); // 当 leader 返回 status: [] 时,意味着汇报完毕,可结束循环 ``` --- ## 4. 注意事项 - **`model` 字段的取值**:必须是后端 `config.yaml` 中 `models[]` 已配置的模型名(如 `deepseek-chat`、`glm-4`、`gpt-4o` 等)。传错会导致上游 502 - **`thread_ids` 必须每次回传**:后端不存储这个映射,是无状态接口 - **协调智能体必须装有 `agent_orchestration` 技能**:`/init` 创建时会自动指定 `skills=["agent_orchestration"]`。后端 `skills/` 目录里必须存在该技能定义,否则 leader 无法派活 - **同名协调智能体冲突**:`main_agent_name` 重名时后端不会报错("already exists" 走兼容分支),会复用既有智能体——所以同一个用户多次 init 用同一个名字是安全的,但 SOUL 不会被覆盖 - **SSE 断连**:本接口底层 `on_disconnect: "continue"`,断连后上游 run 会继续跑到结束(不会取消) - **并发广播**:广播消息使用 `asyncio.gather` 并发更新所有线程 state,性能上不会随子智能体数线性变慢 --- ## 5. 相关接口(底层依赖) 本接口内部转发以下 Gateway 原生接口,了解细节可查: | 接口 | 路由 | 用途 | |---|---|---| | 创建 thread | `POST /api/threads` | 为每个智能体建立独立会话 | | 读 thread state | `GET /api/threads/{id}/state` | 广播前取当前消息列表 | | 写 thread state | `POST /api/threads/{id}/state` | 把广播消息追加到 messages | | 取 agent 详情 | `GET /api/agents/{id}` | init 时读取子智能体的 name/description | | 建 agent | `POST /api/agents` | init 时创建协调智能体 | | 流式 run | `POST /api/threads/{id}/runs/stream` | leader/special 的实际执行 |