在 bisheng 平台搭建「主-子智能体」协同:弱模型适配、执行效率与问题记录

关键词:多智能体编排 / 低能力模型适配 / 确定性引擎化 / 长文本落盘 / ReAct 协议陷阱 / 失败可见性
场景:在bisheng平台搭建多智能体协同(专家智能体 → 逻辑流图智能体 → 部署方案智能体 → 部署流图智能体 ),并跑通"总智能体规划主导 + 子智能体执行"。

核心关注点:

  1. 把"智能"从模型手里拿回到引擎手里——模型只做判断,确定性工作(模块绑定、XML 生成、校验、上传、重试、状态缓存)全部沉淀到 Java 引擎,通过 MCP 工具暴露。用代码来做确定性工作,既能减小模型负担,加快响应速度,同时也减少token的消耗。
  2. 把长文本赶出聊天链路——所有大 payload(IR、部署方案、图 XML)走"工具参数 + 服务端状态 + 落盘文件",聊天里只传一行路径
  3. 适配 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工具中。

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·校验·上传重试·状态缓存·产物落盘                    │
└───────────────────────────────────────────────────────┘

原则:判断给模型,事实给引擎;模型永远不生产"机器要解析的结构",引擎永远不做"需要理解需求的事"。

确定性执行层 / 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. 角色分工与工具纪律

角色层级工具职责
专家 Agentget_chain_template(标准链 IR)+ get_modules(仅自定义需求)+ 一个 create_chat_completion(model 选子智能体)澄清、取标准模板 IR、原样转发需求全文、依次委派、展示确认、总结
逻辑流图子createProjectupload_logic_graph建工程 + 把总下发的 IR 原样上传
参数配置子getProjectParams(只读自查)、prepare_deployment(定稿)自查本流程参数清单 → 从需求全文抽取参数与设备 → 定稿落盘
代码生成子upload_flow_graph按已定稿快照上传部署流图

纪律要点:执行顺序只能由总按协议驱动,子智能体之间无法越权乱序。

3 委派通道:一个工具 + model 参数

平台侧实际给到总智能体的是一个 create_chat_completion(**kwargs) 工具(参数 model/messages/stream),model 就是子智能体的 ID。因此"三个子智能体"在工具视角是同一个工具的三次调用

{"action": "<create_chat_completion>",
 "action_input": {"model": "{{LOGIC_AGENT_ID}}",
   "messages": [{"role": "user", "content": "创建工程并上传逻辑流图。IR 如下…\n【IR】{…}"}],
   "stream": false}}

4 数据传递:三个安全通道(都不走聊天文本)+ 一行回显行

  1. 工具参数——IR、paramOverrides、deployment、projectName 只出现在工具调用的参数里(模型传结构化参数远比在文本里嵌套 JSON 可靠);
  2. 引擎服务端状态——步骤2 缓存 IR(state.ir)、步骤3 固化部署定稿快照state.deploy),后续步骤零回传;
  3. 落盘文件——部署方案写 deploy.json(权威)/deploy.txt(人读),图 XML 写 logic.xml/deploy.xml;聊天里只传一行绝对路径
  4. 回显行(第二版新增)——每个工具返回文本的最后一行固定为 回显: 成功|工程名 proj-x|…,由引擎生成并做 JSON 安全化(单行、无 ASCII 双引号、无反斜杠);子智能体的最终回答就是照抄这一行,不需要概括、不需要转义、不需要正则提工程名。

5 协议:外壳 / 内容两层 + 单一 JSON 外壳

  • 外壳(平台的 ReAct 协议):每轮只能是单个 JSON {"action": … , "action_input": …},JSON 之外不写一个字;
  • 内容action_input):Final Answer 时必须是非空单行字符串;禁止换行、禁止 ASCII 双引号、禁止 JSON 对象/数组/布尔/空内容、禁止回显 IR/XML/方案全文;
  • 类型铁律工具调用 action_input = JSON 对象Final Answer action_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

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,取首值恰好是布尔 okAIMessage(content=True) → 崩溃。

对策

  1. 纯文本汇报协议:子智能体最终回答只能是字符串(短句或单行路径/错误原文)——崩溃分支不可达;
  2. 总智能体补规则:服务异常/空返回 → 报告并停止,同一任务最多重试 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 外壳,直接吐正文 → 解析失败。

对策(本次的核心工程改动):

  1. 引擎侧prepareDeployment 落盘 deploy.txt,返回末尾追加一行 部署方案产物: <绝对路径>(此前 ir/logic/deploy.xml 已落盘,唯独部署方案没落盘);
  2. 提示词侧:参数子只回显最后一行路径;专家确认环节改成一句话 + 文件路径(用户打开文件看全文);四个 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 Answer action_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。

反思与展望

  1. 确定性是第一公民:把"机器能算的"从模型手里拿走,是弱模型时代最划算的投资——它同时买到稳定性、速度与可验收性;
  2. 长文本不该出现在聊天中:凡是大 payload,一律走文件/状态/工具参数;聊天只承载"意图"与"路径";
  3. 协议要顺着平台的骨架长:ReAct 的 JSON blob、action_input 的类型差异、每轮单动作——这些不是"提示词技巧",而是必须写进协议的一等约束;
  4. 多智能体的价值在职责隔离,不在并行:这条流水线本质是强状态串行,拆角色的收益来自"小 prompt、可单独调试、可替换模型",而非并发;
  5. "谁来写最后一个字"决定稳定性:凡是模型最终要输出的文本,只要它能在引擎侧被确定性地生成,就不要让模型去"概括"——回显行是这条原则的极致形态:模型只抄,不写;

一句话总结:不让模型变强,而是让工程不再依赖模型变强。


愿你我都能在各自的领域里不断成长,勇敢追求梦想,同时也保持对世界的好奇与善意!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐