在 bisheng 平台搭建「主-子智能体」协同:弱模型适配、执行效率与问题记录
关键词:多智能体编排 / 低能力模型适配 / 确定性引擎化 / 长文本落盘 / ReAct 协议陷阱 / 失败可见性
场景:在bisheng平台搭建多智能体协同(专家智能体 → 逻辑流图智能体 → 部署方案智能体 → 部署流图智能体 ),并跑通"总智能体规划主导 + 子智能体执行"。
核心关注点:
- 把"智能"从模型手里拿回到引擎手里------模型只做判断,确定性工作(模块绑定、XML 生成、校验、上传、重试、状态缓存)全部沉淀到 Java 引擎,通过 MCP 工具暴露。用代码来做确定性工作,既能减小模型负担,加快响应速度,同时也减少token的消耗。
- 把长文本赶出聊天链路 ------所有大 payload(IR、部署方案、图 XML)走"工具参数 + 服务端状态 + 落盘文件",聊天里只传一行路径;
- 适配 ReAct 协议的硬约束 ------平台强制每轮输出单个 JSON blob,且工具调用与 FinalAnswer 的
action_input类型不同;这两条一旦踩错,就是"500 崩溃/解析失败/死循环重试"。最终形态):专家(总)只澄清、取标准模板 IR、原样转发需求全文、驱动确认 → 三个"执行> 员"子智能体各调自己的引擎工具(参数子自己查参数清单)→ 聊天全程只有"单行回显行"与文件 路径 → 部署方案定稿为
deploy.json,上传只读该定稿。这套结构在弱模型上连跑通过,崩溃面被设计性地清零。
Bisheng多智能体协同配置
目标平台 Bisheng 是内网部署的助手/工作流平台(支持 内置/API/MCP工具集成)。实测得到的能力边界:助手/流水线不能直接调助手/流水线 , 必须绕道发布为 API ,在助手/流水线中接入API 工具调用。助手绑定工具 :内置 / API / MCP / 技能 助手可拿 MCP 工具,也可调平台 API。以下为API工具配置的截图。

将以上API转为openAPI格式,并添加在bisheng的API工具中。
yaml
openapi: 3.0.0
info:
title: 智能体对话 API
description: 调用指定的智能体(不含工作流)进行对话补全
version: 1.0.0
servers:
- url: http://127.0.0.1:8888
description: 本地开发服务器
paths:
/api/v2/assistant/chat/completions:
post:
operationId: create_chat_completion
summary: 发起对话补全请求
description: 向指定智能体发送消息并获取回复。支持流式和非流式。
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- model
- messages
properties:
model:
type: string
description: 智能体 ID
example: "94995227d55248f4a44f0670466a49f9"
messages:
type: array
description: 对话消息列表(不支持 system 角色)
items:
type: object
required:
- role
- content
properties:
role:
type: string
enum: [user, assistant]
example: "user"
content:
type: string
example: "你好"
temperature:
type: number
minimum: 0
maximum: 2
description: 采样温度,非0值覆盖智能体配置
example: 0
stream:
type: boolean
description: 是否启用流式响应(SSE)
default: false
example: true
example:
model: "94995227d55248f4a44f0670466a49f9"
messages:
- role: "user"
content: "你好"
temperature: 0
stream: true
responses:
'200':
description: 成功响应
content:
application/json:
schema:
type: object
required:
- id
- object
- created
- choices
- usage
properties:
id:
type: string
object:
type: string
created:
type: integer
choices:
type: array
items:
type: object
properties:
index:
type: integer
message:
type: object
properties:
role:
type: string
content:
type: string
finish_reason:
type: string
usage:
type: object
properties:
prompt_tokens:
type: integer
completion_tokens:
type: integer
total_tokens:
type: integer
example:
id: "chatcmpl-123"
object: "chat.completion"
created: 1677654321
choices:
- index: 0
message:
role: "assistant"
content: "您好!有什么可以帮您?"
finish_reason: "stop"
usage:
prompt_tokens: 10
completion_tokens: 15
total_tokens: 25
text/event-stream:
schema:
type: string
description: "流式响应,数据格式为 `data: {JSON对象}\\n\\n`,最后以 `data: [DONE]` 结束"
通过"构建->工具->API工具->创建API工具"。信息填入后,点击"保存",可以看到可用工具。

设计思路:五条原则
1. 分层:认知编排层 × 确定性执行层
┌─ 认知编排层(Bisheng 助手)─────────────────────────────┐
│ 专家(总): 澄清 → 取模块目录 → 出 IR 方案 → 委派 → 确认 → 总结 │
│ 子智能体 : 逻辑流图执行 / 参数配置执行 / 代码生成执行 │
└──────────────────────────┬────────────────────────────┘
│ MCP(HTTP+SSE)
┌─ 确定性执行层(Coder 引擎)──────────────────────────┐
│ create_project / upload_logic_graph / prepare_deployment / upload_flow_graph │
│ 绑定·布局·XML·校验·上传重试·状态缓存·产物落盘 │
└───────────────────────────────────────────────────────┘
原则:判断给模型,事实给引擎;模型永远不生产"机器要解析的结构",引擎永远不做"需要理解需求的事"。
#mermaid-svg-Jl52ak9O48BwAd6o{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Jl52ak9O48BwAd6o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Jl52ak9O48BwAd6o .error-icon{fill:#552222;}#mermaid-svg-Jl52ak9O48BwAd6o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Jl52ak9O48BwAd6o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Jl52ak9O48BwAd6o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Jl52ak9O48BwAd6o .marker.cross{stroke:#333333;}#mermaid-svg-Jl52ak9O48BwAd6o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Jl52ak9O48BwAd6o p{margin:0;}#mermaid-svg-Jl52ak9O48BwAd6o .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Jl52ak9O48BwAd6o .cluster-label text{fill:#333;}#mermaid-svg-Jl52ak9O48BwAd6o .cluster-label span{color:#333;}#mermaid-svg-Jl52ak9O48BwAd6o .cluster-label span p{background-color:transparent;}#mermaid-svg-Jl52ak9O48BwAd6o .label text,#mermaid-svg-Jl52ak9O48BwAd6o span{fill:#333;color:#333;}#mermaid-svg-Jl52ak9O48BwAd6o .node rect,#mermaid-svg-Jl52ak9O48BwAd6o .node circle,#mermaid-svg-Jl52ak9O48BwAd6o .node ellipse,#mermaid-svg-Jl52ak9O48BwAd6o .node polygon,#mermaid-svg-Jl52ak9O48BwAd6o .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Jl52ak9O48BwAd6o .rough-node .label text,#mermaid-svg-Jl52ak9O48BwAd6o .node .label text,#mermaid-svg-Jl52ak9O48BwAd6o .image-shape .label,#mermaid-svg-Jl52ak9O48BwAd6o .icon-shape .label{text-anchor:middle;}#mermaid-svg-Jl52ak9O48BwAd6o .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Jl52ak9O48BwAd6o .rough-node .label,#mermaid-svg-Jl52ak9O48BwAd6o .node .label,#mermaid-svg-Jl52ak9O48BwAd6o .image-shape .label,#mermaid-svg-Jl52ak9O48BwAd6o .icon-shape .label{text-align:center;}#mermaid-svg-Jl52ak9O48BwAd6o .node.clickable{cursor:pointer;}#mermaid-svg-Jl52ak9O48BwAd6o .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Jl52ak9O48BwAd6o .arrowheadPath{fill:#333333;}#mermaid-svg-Jl52ak9O48BwAd6o .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Jl52ak9O48BwAd6o .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Jl52ak9O48BwAd6o .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Jl52ak9O48BwAd6o .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Jl52ak9O48BwAd6o .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Jl52ak9O48BwAd6o .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Jl52ak9O48BwAd6o .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Jl52ak9O48BwAd6o .cluster text{fill:#333;}#mermaid-svg-Jl52ak9O48BwAd6o .cluster span{color:#333;}#mermaid-svg-Jl52ak9O48BwAd6o div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Jl52ak9O48BwAd6o .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Jl52ak9O48BwAd6o rect.text{fill:none;stroke-width:0;}#mermaid-svg-Jl52ak9O48BwAd6o .icon-shape,#mermaid-svg-Jl52ak9O48BwAd6o .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Jl52ak9O48BwAd6o .icon-shape p,#mermaid-svg-Jl52ak9O48BwAd6o .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Jl52ak9O48BwAd6o .icon-shape .label rect,#mermaid-svg-Jl52ak9O48BwAd6o .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Jl52ak9O48BwAd6o .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Jl52ak9O48BwAd6o .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Jl52ak9O48BwAd6o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 确定性执行层 / MCP (MCP over HTTP+SSE)
认知编排层 / Bisheng 助手
create_chat_completion model=逻辑子
model=参数子(工程名+需求全文)
model=代码子(仅工程名)
用户(Bisheng 界面)
专家 总: 澄清·取模板IR·转发需求全文·委派·确认·总结
逻辑流图执行员
参数配置执行员
代码生成执行员
get_chain_template / get_modules / get_project_params (只读)
create_project
upload_logic_graph
prepare_deployment → 定稿快照
upload_flow_graph (只读快照)
产物目录 .radarlab/ai: ir.json · logic.xml · deploy.json(权威) · deploy.txt · deploy.xml
2. 角色分工与工具纪律
| 角色 | 层级 | 工具 | 职责 |
|---|---|---|---|
| 专家 Agent | 总 | get_chain_template(标准链 IR)+ get_modules(仅自定义需求)+ 一个 create_chat_completion(model 选子智能体) |
澄清、取标准模板 IR、原样转发需求全文、依次委派、展示确认、总结 |
| 逻辑流图子 | 子 | createProject、upload_logic_graph |
建工程 + 把总下发的 IR 原样上传 |
| 参数配置子 | 子 | getProjectParams(只读自查)、prepare_deployment(定稿) |
自查本流程参数清单 → 从需求全文抽取参数与设备 → 定稿落盘 |
| 代码生成子 | 子 | upload_flow_graph |
按已定稿快照上传部署流图 |
纪律要点:执行顺序只能由总按协议驱动,子智能体之间无法越权乱序。
3 委派通道:一个工具 + model 参数
平台侧实际给到总智能体的是一个 create_chat_completion(**kwargs) 工具(参数 model/messages/stream),model 就是子智能体的 ID。因此"三个子智能体"在工具视角是同一个工具的三次调用:
json
{"action": "<create_chat_completion>",
"action_input": {"model": "{{LOGIC_AGENT_ID}}",
"messages": [{"role": "user", "content": "创建工程并上传逻辑流图。IR 如下...\n【IR】{...}"}],
"stream": false}}
4 数据传递:三个安全通道(都不走聊天文本)+ 一行回显行
- 工具参数------IR、paramOverrides、deployment、projectName 只出现在工具调用的参数里(模型传结构化参数远比在文本里嵌套 JSON 可靠);
- 引擎服务端状态 ------步骤2 缓存 IR(
state.ir)、步骤3 固化部署定稿快照 (state.deploy),后续步骤零回传; - 落盘文件 ------部署方案写
deploy.json(权威)/deploy.txt(人读),图 XML 写logic.xml/deploy.xml;聊天里只传一行绝对路径; - 回显行(第二版新增) ------每个工具返回文本的最后一行 固定为
回显: 成功|工程名 proj-x|...,由引擎生成并做 JSON 安全化(单行、无 ASCII 双引号、无反斜杠);子智能体的最终回答就是照抄这一行,不需要概括、不需要转义、不需要正则提工程名。
5 协议:外壳 / 内容两层 + 单一 JSON 外壳
- 外壳 (平台的 ReAct 协议):每轮只能是单个 JSON
{"action": ... , "action_input": ...},JSON 之外不写一个字; - 内容 (
action_input的值 ):Final Answer 时必须是非空单行字符串;禁止换行、禁止 ASCII 双引号、禁止 JSON 对象/数组/布尔/空内容、禁止回显 IR/XML/方案全文; - 类型铁律 :工具调用
action_input= JSON 对象 ,Final Answeraction_input= 字符串; - 只有 4 个时机 允许
Final Answer:①澄清提问 ②请用户确认部署方案 ③报错停止 ④最终总结。
弱模型适配:把"智能"从模型手里拿回来
弱模型的失败模式很固定:长输出漂移、结构不守规矩、多步推理中途忘目标。对策不是"教它变强",而是减少它需要做的事 。且做的事情简单。模型的每一次输出要么是一个工具调用 ,要么是一句短话/一行路径------没有中间态,没有长文本,没有结构嵌套。
| # | 法则 | 落地做法 |
|---|---|---|
| 1 | 确定性下沉 | 布局/绑定/XML/校验/上传/重试全在引擎;模型只做"选哪个模板、给什么参数" |
| 2 | 预置模板,禁止自由发挥 | 标准链直接调 getChainTemplate(引擎按本机模块库生成单行 IR);弱模型只负责复制这一行(第一版的中文骨架已被证明命中不了模块库,见 P9) |
| 3 | 单步原子化指令 | 子智能体 prompt 退化为"执行员":调工具 → 抄一行 → 结束 |
| 4 | 只抄一行,不总结 | 每个工具返回的末行是引擎生成的回显行;子智能体最终回复 = 照抄该行,禁止回显长方案/XML 代码块 |
| 5 | 长文本落盘 | deploy.json/deploy.txt/deploy.xml/logic.xml;聊天只传路径,模型零转义负担 |
| 6 | 输出格式铁律 | 每轮单 JSON;工具调用 action_input = 对象 ,Final Answer action_input = 字符串(这条踩过坑,见 §5-P4) |
| 7 | 枚举状态机边界 | 明确"只有 4 个时机能结束回合",其余一律继续调工具,消除"该不该停下"的摇摆 |
| 8 | 错误即停 + 有界重试 | 工具返回"错误:"→ 原文转述并停止;服务异常最多重试 1 次(防死循环) |
| 9 | 参数名写死 | 引擎工具用驼峰 projectName/ir/paramOverrides,prompt 逐字给出,杜绝 project_name 类漂移 |
加快执行效率:为什么这样设计更快
| 优化点 | 做法 | 收益 |
|---|---|---|
| 引擎化批处理 | 一次 upload_logic_graph 调用完成"绑定→布局→XML→校验→上传→失败重试" |
原本需要多轮 LLM 决策的动作变成 1 次工具往返 |
| 服务端状态缓存 | projectName → IR 缓存在引擎;步骤 3/4 直接读 | 每步少一次大 payload 传输与解析 |
| 零 IR 回传 | 子智能体只回"工程名 + 节点数",不需要把 IR 交还总智能体 | 省 token、省延迟、少一次复制出错机会 |
| 单回合连续工具调用 | ReAct 循环内可连续调多个工具,只在确认/总结时结束回合 | 免去"中间结论 → 用户 → 再说继续"的往返 |
| 落盘替代搬运 | 部署方案/XML 走文件,聊天只传路径 | 上下文长度骤降,长文本解析失败风险归零 |
| 模板化 | 固定 IR 骨架 + 固定委派 JSON 模板 | 弱模型 0 推理即可产出合法结构 |
| 不做无意义的并行 | 该流水线本质是强状态串行(每步依赖上一步 projectName/IR),不硬拆并行 | 避免为"多智能体"而引入不一致 |
| 复用既有能力 | 引擎与工具零重写,MCP 直连 | 迁移成本集中在编排层 |
一个容易忽略的效率点:把"顺序"放进协议而不是放进模型推理。顺序由提示词状态机固定,模型每次只需要知道"现在是第几步、下一步是什么",不需要重新规划整条链路。
在"确定性交给java侧,决策性交给模型(而不是由大模型来生成XML)"+ "大文件落盘(而不是在聊天中转发)"+ " 输出格式强要求"的原则下整个流水线的速度提升了80%,同时流水线执行失败率也近乎为0。
遇到的问题
P1|子智能体最终回答是 JSON 对象 → 平台 500
现象 :子智能体任务全部成功(取模块、出 IR、建工程、上传逻辑图),但收尾即报
pydantic ValidationError: 2 validation errors for AIMessage; content.str input_value=True/False;对外表现为总智能体收到"invoke tool failed",并死循环重试同一调用。
根因 :平台源码 assistant_agent.py::react_run:
python
output = result['agent_outcome'].return_values['output'] # {'ok': True, 'project_name': ...}
if isinstance(output, dict):
output = list(output.values())[0] # ← 取第一个值 = True(ok 键排第一)
inputs.append(AIMessage(content=output)) # content 必须是 str/list
旧契约要求子智能体最终回答一段 {"ok": true, ...} JSON;模型在 ReAct 的 json-blob 协议里把对象嵌进 action_input,平台解析后 output 是 dict,取首值恰好是布尔 ok → AIMessage(content=True) → 崩溃。
对策:
- 纯文本汇报协议:子智能体最终回答只能是字符串(短句或单行路径/错误原文)------崩溃分支不可达;
- 总智能体补规则:服务异常/空返回 → 报告并停止,同一任务最多重试 1 次(防死循环)。
验证 :子智能体独立跑真实任务,最终回复为 1-2 句中文,服务端无 ValidationError。
P2|参数名漂移:project_name vs projectName
现象 :upload_logic_graph 首次调用返回错误:缺少 projectName(来自 create_project 工具返回)。;模型自行纠正后成功。
根因 :引擎 MCP 工具 schema 用驼峰 projectName/ir,而提示词里写的是"契约字段" project_name,模型照抄了契约名当工具参数名。
对策 :prompt 里逐字写死驼峰参数名;明确"project_name 只是你与总智能体之间的文本字段名,不是工具参数名"。
验证:全流程日志中不再出现"缺少 projectName"。
P3|长文本经模型搬运 → Could not parse LLM output
现象 :委派到"代码生成"前,专家在"展示部署方案"环节整轮崩溃:
JSONAgentOutputParser: Could not parse LLM output: 部署方案全文如下(请确认...)...(大段全文)
根因 :平台要求每轮输出都是 JSON blob;提示词要求专家"把部署方案全文一字不改 转给用户",长文本让弱模型放弃了 JSON 外壳,直接吐正文 → 解析失败。
对策(本次的核心工程改动):
- 引擎侧 :
prepareDeployment落盘deploy.txt,返回末尾追加一行部署方案产物: <绝对路径>(此前 ir/logic/deploy.xml 已落盘,唯独部署方案没落盘); - 提示词侧 :参数子只回显最后一行路径 ;专家确认环节改成一句话 + 文件路径(用户打开文件看全文);四个 prompt 统一加"输出格式铁律"。
验证 :prepare_deployment 后服务器上 deploy.txt 存在且为全文;弱模型连跑 2 次无 OutputParserException。
P4|工具调用 action_input 写成字符串 → arun() takes 1 positional argument but 2 were given
现象 :专家委派子智能体时报 OpenApiTools.arun() takes 1 positional argument but 2 were given;模型自查后把 action_input 改成对象,重试成功。
根因 :上一版"输出格式铁律"写了"action_input 是字符串"------这条只适用于 Final Answer;工具调用必须传 JSON 对象(dict),键即工具入参名。
对策:
- 铁律改写为:工具调用
action_input= JSON 对象 (附委派模板),Final Answeraction_input= 字符串; - 三个子智能体 prompt 同步补上"调用工具时 action_input 用对象"。
验证 :日志中不再出现 arun() 位置参数错误。
P5|无参数MCP工具集成到助手,上线提示KeyError: 'properties'
现象 :"无参 MCP 工具"在助手里添加/解析时报错,后台出现错误
File "/venv_bisheng_site/langchain_core/tools/base.py", line 555, in args return json_schema"properties" -> {'type': 'object'} KeyError: 'properties'
根因 :无参MCP工具方法的 schema 里缺 properties 字段有关
对策:
private static JsonObject normalizeSchema(JsonObject raw) {
JsonObject schema = raw == null ? new JsonObject()
: JsonParser.parseString(raw.toString()).getAsJsonObject();
if (!schema.has("type")) schema.addProperty("type", "object");
if (!schema.has("properties") || !schema.get("properties").isJsonObject())
schema.add("properties", new JsonObject());
if (!schema.has("required") || !schema.get("required").isJsonArray())
schema.add("required", new JsonArray());
return schema;
}
验证:重新发布程序,并刷新bishegn mcp server。
反思与展望
- 确定性是第一公民:把"机器能算的"从模型手里拿走,是弱模型时代最划算的投资------它同时买到稳定性、速度与可验收性;
- 长文本不该出现在聊天中:凡是大 payload,一律走文件/状态/工具参数;聊天只承载"意图"与"路径";
- 协议要顺着平台的骨架长 :ReAct 的 JSON blob、
action_input的类型差异、每轮单动作------这些不是"提示词技巧",而是必须写进协议的一等约束; - 多智能体的价值在职责隔离,不在并行:这条流水线本质是强状态串行,拆角色的收益来自"小 prompt、可单独调试、可替换模型",而非并发;
- "谁来写最后一个字"决定稳定性 :凡是模型最终要输出的文本,只要它能在引擎侧被确定性地生成,就不要让模型去"概括"------回显行是这条原则的极致形态:模型只抄,不写;
一句话总结:不让模型变强,而是让工程不再依赖模型变强。
愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!