基于 Harness 工程规范的多智能体交互过程实现

0 阅读14分钟

1. 背景与目标

1.1 问题出发点

传统 BPM(业务流程管理)系统的核心是"人"驱动流程:人工节点负责决策,系统节点负责执行预定义规则。这一模型在面对复杂、动态、需要多步推理的企业场景时暴露出明显局限:

  • 规则节点无法处理非结构化输入(自然语言需求、图片、文档)
  • 复杂决策依赖大量 if-else 硬编码,维护成本高
  • 多系统协作需要人工中转,效率低

1.2 多智能体的价值

将 LLM Agent 节点引入 BPM 流程,每个 Agent 节点具备:

  • 推理能力:理解自然语言输入,分析上下文
  • 工具调用能力(Function Calling):调用企业系统 API、数据库、外部服务
  • 协作能力:通过共享上下文,多个 Agent 接力完成复杂任务
传统流程:用户 → 人工审核 → 系统执行 → 人工复核 → 结束

多智能体流程:用户 → Agent₁(需求分析) → Agent₂(方案搜索) →
              Agent₃(合规检查) → Agent₄(价格评估) → Agent₅(执行下单) → 结束

1.3 设计目标

目标说明
流程驱动Agent 行为由 BPMN 工作流定义驱动,不硬编码在程序里
工具解耦工具实现与流程定义松耦合,通过 toolSetName 桥接
开发友好业务开发者写普通 C# Service 类,不接触框架细节
可扩展支持从代码工具逐步演进到声明式工具、MCP 协议工具

2. Harness 工程规范

Harness 是 Slickflow.AI 多智能体模块采用的工程架构规范,核心思想来源于 Claude 工程体系:以"注册→发现→执行"三段式管理智能体工具,使框架代码与业务代码完全分离。

2.1 核心思想

┌───────────────────────────────────────────────────────────────────┐
│                    Harness 三原则                                  │
│                                                                   │
│  1. 声明在设计时(BPMN + 属性面板)                                   │
│     toolSetName = "SupplierSearch"                                │
│                                                                   │
│  2. 注册在启动时(应用/测试初始化)                                    │
│     AgentToolSetRegistry.Global.Register<SupplierSearchService>() │
│                                                                   │
│  3. 执行在运行时(引擎自动驱动)                                       │
│     toolSetName → activityId → IReadOnlyList<IAgentTool>          │
└───────────────────────────────────────────────────────────────────┘

2.2 职责边界

角色职责代码位置
流程设计师定义节点类型(Agent)、绑定 toolSetName、设置 System PromptBPMN 设计器(数据库)
业务开发者实现 [AgentToolSet] 服务类,写业务方法ToolSets/*.cs
框架层ReAct 循环、工具注册/发现、参数绑定、上下文管理Slickflow.AI/Agent/
引擎层读取 BPMN 定义,驱动 Agent 节点执行Slickflow.Engine/

2.3 命名与注册规范

工具集命名(toolSetName)规则:

✅ 推荐:PascalCase,业务语义清晰
   NeedsAnalysis / SupplierSearch / ComplianceCheck

❌ 避免:技术实现名称
   AgentTool001 / Step2Handler / MyFunc

工具方法命名规则:

// ✅ 动词+名词,描述行为
public Task<SupplierQuote> RequestQuote(string supplierId, int quantity)

// ❌ 不描述行为的名称  
public Task<object> Execute(JsonObject args)

注册时机:在应用程序/测试的入口点一次性注册,不在请求处理中动态注册:

// Program.cs 或 测试入口
AgentToolSetRegistry.Global
    .Register<NeedsAnalysisService>()
    .Register<SupplierSearchService>()
    .Register<ComplianceCheckService>();

2.4 上下文传递规范

多 Agent 之间通过共享变量键(Variable Key) 传递数据,键名在代码中集中定义为常量:

// ✅ 在测试/应用入口集中定义
private const string VarPurchaseRequest        = "PurchaseRequest";
private const string VarStructuredRequirements = "StructuredRequirements";
private const string VarSupplierQuotes         = "SupplierQuotes";

// ✅ 数据流配置与工具注册分离
["SupplierSearch"] = new(InputKey: VarStructuredRequirements,
                         OutputKey: VarSupplierQuotes)

3. 核心架构设计

3.1 整体架构图

┌──────────────────────────────────────────────────────────────────┐
│                    Slickflow.AI 多智能体架构                       │
│                                                                  │
│  ┌─────────────┐    ┌─────────────────────────────────────────┐ │
│  │  BPMN 设计器 │    │              Slickflow.Engine            │ │
│  │             │    │                                         │ │
│  │ toolSetName ├───►│  WorkflowService → ProcessModelFactory  │ │
│  │ SystemPrompt│    │  AIServiceExecutor → AgentMultiTurnService│ │
│  └─────────────┘    └──────────────┬──────────────────────────┘ │
│                                    │                             │
│                    ┌───────────────▼──────────────────────────┐ │
│                    │           Slickflow.AI / Agent            │ │
│                    │                                          │ │
│                    │  AgentToolSetRegistry                    │ │
│                    │    toolSetName → IReadOnlyList<IAgentTool>│ │
│                    │                  │                       │ │
│                    │  AgentToolRegistry                       │ │
│                    │    activityId → IReadOnlyList<IAgentTool> │ │
│                    │                  │                       │ │
│                    │  AgentNodeBase (ReAct Loop)              │ │
│                    │    Reason → Act → Observe → ...          │ │
│                    │                  │                       │ │
│                    │  OpenAIToolCallLlmClient                 │ │
│                    │    HTTP POST (tools + messages)          │ │
│                    └──────────────────┼───────────────────────┘ │
│                                       │                          │
│                    ┌──────────────────▼───────────────────────┐ │
│                    │         业务工具层 (ToolSets)              │ │
│                    │                                          │ │
│                    │  [AgentToolSet("SupplierSearch")]        │ │
│                    │  class SupplierSearchService             │ │
│                    │  {                                       │ │
│                    │    [AgentTool("搜索供应商")]              │ │
│                    │    Task<List<Supplier>> SearchSuppliers()│ │
│                    │  }                                       │ │
│                    └──────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘

3.2 ReAct 循环

每个 Agent 节点执行 ReAct(Reason → Act → Observe)  循环,直到 LLM 给出最终答案:

用户输入 / 上一个 Agent 输出
        │
        ▼
┌───────────────────────────────────────────────┐
│  Iteration 1                                  │
│  Reason: LLM 收到输入 + 工具定义 → 决策       │
│  Act:    LLM 返回 finish_reason="tool_calls"  │
│          → 调用 search_suppliers(category=IT) │
│  Observe: 工具返回 [SUP001, SUP002, SUP003]   │
├───────────────────────────────────────────────┤
│  Iteration 2                                  │
│  Reason: LLM 收到工具结果 → 继续决策           │
│  Act:    调用 request_quote(supplierId=SUP002) │
│  Observe: 返回报价 ¥2950×1029500            │
├───────────────────────────────────────────────┤
│  Iteration 3                                  │
│  Reason: LLM 综合所有结果                      │
│  → finish_reason="stop"(最终答案)            │
└───────────────────────────────────────────────┘
        │
        ▼
最终文本输出 → 写入 OutputVariableKey → 下一个 Agent 的输入

循环保护MaxIterations(默认 10)防止死循环,超出后返回降级答案。

3.3 工具注册体系

两层注册表实现"逻辑名 → 活动 ID"的解耦:

AgentToolSetRegistry(名称注册)
  "SupplierSearch"  IReadOnlyList<IAgentTool>
        
         TryResolveToActivityRegistry(toolSetName, activityId)
        
AgentToolRegistry(活动注册)
  "Activity_ABC123"  IReadOnlyList<IAgentTool>
        
         引擎执行节点时调用
        
  AgentNodeBase.ExecuteReActAsync()

这样做的好处:BPMN 定义里的 activityId 是运行时才知道的(每个流程实例可能不同),toolSetName 才是设计时确定的。两层注册表在运行时完成映射。

3.4 共享记忆(AgentConversationMemory)

多个 Agent 节点通过 AgentConversationMemory 共享输出,实现跨节点的上下文传递:

// Agent₁ 写入
AgentConversationMemory.RecordOutput(
    processInstanceId, activityId, "StructuredRequirements", result);

// Agent₂ 可以读取 Agent₁ 的输出(通过 sharedVars 传递)
var input = sharedVars["StructuredRequirements"];

记忆以 processInstanceId 为隔离键,流程实例结束后调用 Evict() 释放。


4. 属性化工具注册(当前实现)

4.1 问题与方案

问题:早期实现使用 lambda + 手写 JSON Schema,框架代码和业务代码混在一起:

// ❌ 旧方式:开发者需要理解 JsonObject、AgentToolResult 等框架类型
.Register("quote_request", "向供应商请求报价",
    Schema("""{"type":"object","properties":{"supplierId":{"type":"string"},...}}"""),
    async (args, ct) => {
        var suppId = args["supplierId"]?.GetValue<string>() ?? "";
        return new AgentToolResult(true, JsonSerializer.Serialize(result));
    })

方案:通过 [AgentToolSet] / [AgentTool] 属性 + 反射引擎,业务开发者只写普通 Service 类:

// ✅ 新方式:普通 C# 类,无框架代码
[AgentToolSet("SupplierSearch")]
public class SupplierSearchService
{
    [AgentTool("向指定供应商发起报价请求,返回单价、货期和总金额")]
    public async Task<SupplierQuote> RequestQuote(string supplierId, int quantity)
    {
        // 纯业务逻辑
        return new SupplierQuote { SupplierId = supplierId, UnitPrice = 2950m };
    }
}

4.2 反射引擎(AgentToolReflector)工作原理

AgentToolReflector.BuildToolsFromType<T>() 扫描类的方法,自动完成:

框架任务自动化方式
JSON Schema 生成方法参数类型 → {"type":"string"/"integer"/"number"...}
工具名称方法名 PascalCase → snake_case(RequestQuote → request_quote
参数绑定JsonObject args["supplierId"] → string supplierId
可选参数default 值的参数不进入 required 数组
异步支持Task<T> → 自动 await,提取 Result
返回值序列化强类型返回值 → JsonSerializer.Serialize() → LLM 可读字符串
CancellationToken自动注入,不出现在 Schema 中

4.3 注册扩展:Register<T>() 泛型重载

AgentToolSetRegistry 增加泛型重载,从属性读取 toolSetName,无需重复书写字符串:

// AgentToolSetRegistry.Register<T>() 内部实现
public AgentToolSetRegistry Register<T>() where T : class, new()
{
    var attr      = typeof(T).GetCustomAttribute<AgentToolSetAttribute>();
    var toolSetName = attr.Name;                          // 从属性读
    var tools       = AgentToolReflector.BuildToolsFromType(typeof(T)); // 反射构建
    _store[toolSetName] = () => tools;
    return this;
}

5. 采购流程多智能体演示

5.1 业务场景

企业员工提交采购需求后,由 5 个 AI Agent 节点依次处理,全程无需人工干预,最终自动生成采购订单。

5.2 流程节点设计

用户提交采购需求
        │
        ▼
┌──────────────────┐
│  NeedsAnalysis   │  分析需求,提取品类和规格
│  [Agent 节点]    │  工具:ClassifyCategory / ExtractSpecs
└────────┬─────────┘
         │ StructuredRequirements(结构化需求)
         ▼
┌──────────────────┐
│  SupplierSearch  │  搜索供应商,获取报价
│  [Agent 节点]    │  工具:SearchSuppliers / RequestQuote
└────────┬─────────┘
         │ SupplierQuotes(供应商报价列表)
         ▼
┌──────────────────┐
│ ComplianceCheck  │  合规审查(并行支持)
│  [Agent 节点]    │  工具:CheckRegulations / CheckBlacklist
└────────┬─────────┘
         │ ComplianceReport(合规报告)
         ▼
┌──────────────────┐
│ PriceEvaluation  │  综合评估,推荐最优方案
│  [Agent 节点]    │  工具:CalculateTco(总拥有成本)
└────────┬─────────┘
         │ PurchaseRecommendation(采购建议)
         ▼
┌──────────────────┐
│  OrderExecution  │  在 ERP 中创建采购订单
│  [Agent 节点]    │  工具:CreatePurchaseOrder
└────────┬─────────┘
         │ PurchaseOrderId(订单号)
         ▼
     采购完成

流程图示例:
QQ_1780275964234

5.3 Agent 节点定义(Service 类)

需求分析服务

[AgentToolSet("NeedsAnalysis")]
public class NeedsAnalysisService
{
    [AgentTool("根据采购物品描述,识别采购类别代码")]
    public string ClassifyCategory(string description)
    {
        return description.Contains("router", StringComparison.OrdinalIgnoreCase)
            ? "IT_HARDWARE" : "GENERAL";
    }

    [AgentTool("从采购描述中提取规格参数,返回数量、单位和技术规格")]
    public async Task<ItemSpec> ExtractSpecs(string text, string category)
    {
        await Task.Delay(5); // 实际场景:调用 NLP 服务或规格库
        return new ItemSpec { Quantity = 10, Unit = "units",
                              Specification = "5G/LTE, DIN rail, -40~70°C, IPX5" };
    }
}

供应商搜索服务

[AgentToolSet("SupplierSearch")]
public class SupplierSearchService
{
    [AgentTool("搜索供应商目录,返回符合条件的供应商列表")]
    public async Task<List<Supplier>> SearchSuppliers(string category, string specification = "")
    {
        // 实际场景:查询供应商数据库
        return new List<Supplier>
        {
            new() { SupplierId = "SUP001", Name = "TechNet Industrial Ltd", Rating = 4.2 },
            new() { SupplierId = "SUP002", Name = "GlobalRouter Solutions",  Rating = 4.5 },
        };
    }

    [AgentTool("向指定供应商发起报价请求,返回单价、货期和总金额")]
    public async Task<SupplierQuote> RequestQuote(string supplierId, int quantity)
    {
        // 实际场景:调用供应商门户 API 或 EDI 接口
        return new SupplierQuote { SupplierId = supplierId, UnitPrice = 2950m,
                                   LeadTimeDays = 10, QualityScore = 4.5,
                                   TotalAmount = 2950m * quantity };
    }
}

5.4 数据流配置

private static readonly IReadOnlyDictionary<string, AgentRunConfig> RunConfigs =
    new Dictionary<string, AgentRunConfig>(StringComparer.OrdinalIgnoreCase)
    {
        ["NeedsAnalysis"]  = new(InputKey: "PurchaseRequest",        OutputKey: "StructuredRequirements"),
        ["SupplierSearch"] = new(InputKey: "StructuredRequirements", OutputKey: "SupplierQuotes"),
        ["ComplianceCheck"]= new(InputKey: "StructuredRequirements", OutputKey: "ComplianceReport"),
        ["PriceEvaluation"]= new(InputKey: "SupplierQuotes",         OutputKey: "PurchaseRecommendation"),
        ["OrderExecution"] = new(InputKey: "PurchaseRecommendation", OutputKey: "PurchaseOrderId")
    };

5.5 启动注册(一次性)

// 应用启动时(Program.cs 或测试入口)
AgentToolSetRegistry.Global
    .Register<NeedsAnalysisService>()
    .Register<SupplierSearchService>()
    .Register<ComplianceCheckService>()
    .Register<PriceEvaluationService>()
    .Register<OrderExecutionService>();

5.6 BPMN 侧配置(设计器)

流程设计师在 BPMN 属性面板为每个 Agent 节点设置:

属性说明
typeAgent节点类型
toolSetNameSupplierSearch与注册名称对应
system_prompt存入 ai_activity_config角色定义,可数据库配置

5.7 运行日志示例

=== WorkflowDrivenProcurementTest ===
    ProcessCode : SmartProcurement  Version: 1
    Model       : qwen-plus

[OK] Loaded process: 智能采购流程  (id=1001)
[OK] Found 5 Agent node(s):
       [NeedsAnalysis]   toolSetName=NeedsAnalysis   id=Activity_001
       [SupplierSearch]  toolSetName=SupplierSearch   id=Activity_002
       [ComplianceCheck] toolSetName=ComplianceCheck  id=Activity_003
       [PriceEvaluation] toolSetName=PriceEvaluation  id=Activity_004
       [OrderExecution]  toolSetName=OrderExecution   id=Activity_005

─── Agent 1/5: NeedsAnalysis [NeedsAnalysis] ───
[Agent] ReAct iteration 1/8
[Agent][LLM] POST ... model=qwen-plus tools=2 msgs=2
[Agent][LLM] tool_calls: classify_category id=call_001
[Agent] Calling tool 'classify_category' | args={"description":"...routers..."}
    [NeedsAnalysisService.ClassifyCategory] → IT_HARDWARE
[Agent] ReAct iteration 2/8
[Agent][LLM] tool_calls: extract_specs id=call_002
    [NeedsAnalysisService.ExtractSpecs] 提取规格中...
[Agent] ReAct iteration 3/8
[Agent][LLM] text response (256 chars)
[Agent] Final answer produced

─── Agent 2/5: SupplierSearch [SupplierSearch] ───
...
    [SupplierSearchService.SearchSuppliers] 搜索中...
    [SupplierSearchService.RequestQuote] SUP002 → ¥2950×10
...

─── Agent 5/5: OrderExecution [OrderExecution] ───
    [OrderExecutionService.CreatePurchaseOrder] 已创建 PO-20260531-7823

═══════════════════════════════════════
  采购完成  订单号:PO-20260531-7823
  供应商:GlobalRouter Solutions
  金额:¥29,500
═══════════════════════════════════════

6. 落地路线图

当前实现(属性化 Service 类)解决了"开发者写代码"的体验问题,但面向企业客户还有更深一层的问题:谁来为每家客户写那些 Service 类?

三个阶段的答案是:从"写代码"演进到"填配置",最终达到"零配置自发现"。


6.1 近期:预置企业工具库

核心思想:平台方提前实现 80% 的高频企业操作,客户只需选择并配置,不需要写代码。

工具库分类设计

Slickflow.AI.Enterprise.Tools/
├── ErpTools/
│   ├── InventoryQueryTool      查库存
│   ├── PurchaseOrderTool       创建/查询采购订单
│   ├── GoodsReceiptTool        收货确认
│   └── VendorMasterTool        供应商主数据查询
│
├── ApprovalTools/
│   ├── StartApprovalTool       发起审批(钉钉/企微/飞书/OA)
│   ├── ApprovalStatusTool      查询审批状态
│   ├── ApprovalUrgerTool       审批催办
│   └── ConditionalBranchTool   条件判断(金额/部门/级别)
│
├── HrTools/
│   ├── EmployeeQueryTool       查询人员信息
│   ├── LeaveBalanceTool        假期余额查询
│   ├── OrgChartTool            组织架构查询
│   └── AttendanceTool          考勤记录查询
│
├── NotifyTools/
│   ├── EmailSendTool           发送邮件
│   ├── DingTalkMsgTool         钉钉消息
│   ├── WeChatWorkMsgTool       企业微信消息
│   └── TodoCreateTool          创建待办事项
│
└── DataTools/
    ├── SqlQueryTool            参数化 SQL 查询(只读)
    ├── FormDataWriteTool       写入表单数据
    └── ReportQueryTool         报表数据查询

配置化设计(以 ERP 为例)

工具类通过配置文件或数据库读取连接参数,不硬编码:

[AgentToolSet("ErpInventory")]
public class InventoryQueryTools
{
    private readonly ErpConfig _config;

    public InventoryQueryTools()
    {
        // 从 appsettings.json 或数据库读取
        _config = ErpConfigLoader.Load("ERP_MAIN");
    }

    [AgentTool("查询指定物料的当前库存数量和仓库分布")]
    public async Task<InventoryResult> QueryInventory(string materialCode, string warehouse = "")
    {
        // 调用配置的 ERP 接口
        return await _config.ErpClient.GetInventoryAsync(materialCode, warehouse);
    }
}
// appsettings.json
{
  "ErpConfigs": {
    "ERP_MAIN": {
      "BaseUrl": "http://erp.company.com/api",
      "ApiKey": "sk-xxx",
      "Timeout": 30
    }
  }
}

注册方式(集成时)

// 客户集成时,只需选择需要的工具集并配置连接参数
AgentToolSetRegistry.Global
    .Register<InventoryQueryTools>()    // 平台预置
    .Register<PurchaseOrderTools>()     // 平台预置
    .Register<DingTalkApprovalTools>()  // 平台预置
    // 以下才是客户自己写的业务特定工具
    .Register<CustomerSpecificPricingTools>();

覆盖范围预估

场景预置工具覆盖率
采购申请审批~90%
费用报销~85%
员工入职~80%
合同审批~75%
客户拜访管理~70%

6.2 中期:声明式 HTTP 工具适配器

核心思想:为 REST API 类工具提供 JSON/数据库配置方式,业务人员在设计器里填写 API 地址和参数映射,不需要写任何 C# 代码

工具描述格式(存储在数据库)

{
  "toolSetName": "SupplierPortalTools",
  "tools": [
    {
      "name": "search_suppliers",
      "description": "搜索供应商目录,返回符合条件的供应商列表",
      "type": "http",
      "method": "GET",
      "url": "http://supplier-portal.company.com/api/suppliers",
      "headers": {
        "Authorization": "Bearer {{config.ApiToken}}",
        "Content-Type": "application/json"
      },
      "parameters": [
        { "name": "category",      "type": "string",  "required": true,  "in": "query" },
        { "name": "specification", "type": "string",  "required": false, "in": "query" }
      ],
      "responseMapping": "$.suppliers"
    },
    {
      "name": "request_quote",
      "description": "向指定供应商请求报价",
      "type": "http",
      "method": "POST",
      "url": "http://supplier-portal.company.com/api/quotes",
      "body": {
        "supplierId": "{{args.supplierId}}",
        "quantity":   "{{args.quantity}}"
      },
      "parameters": [
        { "name": "supplierId", "type": "string",  "required": true },
        { "name": "quantity",   "type": "integer", "required": true }
      ]
    }
  ]
}

框架层:HttpToolAdapter

// 框架层新增,业务开发者不接触
public sealed class HttpToolAdapter : IAgentTool
{
    private readonly HttpToolDescriptor _descriptor;
    private readonly IHttpClientFactory _httpFactory;

    public string     Name        => _descriptor.Name;
    public string     Description => _descriptor.Description;
    public JsonObject InputSchema  => BuildSchemaFromDescriptor(_descriptor);

    public async Task<AgentToolResult> ExecuteAsync(JsonObject args, CancellationToken ct)
    {
        var url     = ResolveTemplate(_descriptor.Url, args);
        var headers = ResolveHeaders(_descriptor.Headers);
        var body    = _descriptor.Method == "POST" ? ResolveBody(_descriptor.Body, args) : null;

        var response = await _httpFactory
            .CreateClient()
            .SendAsync(BuildRequest(url, headers, body, _descriptor.Method), ct);

        var json = await response.Content.ReadAsStringAsync(ct);
        return new AgentToolResult(response.IsSuccessStatusCode, json);
    }
}

注册方式(数据库驱动)

// 框架启动时,从数据库加载所有声明式工具集
var toolSetDescriptors = await db.LoadHttpToolSetDescriptors();
foreach (var descriptor in toolSetDescriptors)
{
    var tools = descriptor.Tools
        .Select(t => new HttpToolAdapter(t, httpFactory))
        .ToList();
    AgentToolSetRegistry.Global.RegisterTools(descriptor.ToolSetName, tools);
}

设计器集成

BPMN 设计器新增"HTTP 工具配置"面板,业务人员直接在界面上:

  1. 填写 API Endpoint
  2. 拖拽参数映射
  3. 测试连接并保存

这一步实现后,新增一个企业系统集成的成本从"写代码+部署"降为"填表单+保存"。


6.3 长期:MCP 协议集成

核心思想:企业各系统各自部署 MCP Server(Model Context Protocol) ,Agent 节点通过标准协议动态发现和调用工具,平台侧零代码、零配置完成集成。

MCP 是由 Anthropic 推动的 AI 工具标准协议,OpenAI、Microsoft 等主流厂商均已跟进支持。

架构图

┌─────────────────────────────────────────────────────────┐
│                  Slickflow Agent 节点                     │
│                                                         │
│  AgentNodeBase (ReAct Loop)                             │
│       │                                                 │
│       │ Tools = McpClientTool × N                       │
│       ▼                                                 │
│  McpServerClient (已实现)                               │
│       │                                                 │
│       │ JSON-RPC 2.0                                    │
└───────┼─────────────────────────────────────────────────┘
        │
        ├──► MCP Server (ERP 系统)
        │        tools/list → [query_inventory, create_order, ...]
        │        tools/call → 执行并返回结果
        │
        ├──► MCP Server (CRM 系统)
        │        tools/list → [get_customer, update_opportunity, ...]
        │
        ├──► MCP Server (OA 审批系统)
        │        tools/list → [start_approval, get_status, urge_approval]
        │
        └──► MCP Server (第三方供应商门户)
                 tools/list → [search_suppliers, request_quote, ...]

项目现有基础:McpClientTool

// 已实现:source/core/Slickflow.AI/Agent/Tools/McpClientTool.cs
public sealed class McpClientTool : IAgentTool
{
    // JSON-RPC 2.0 代理,自动发现并调用 MCP Server 暴露的工具
    public Task<AgentToolResult> ExecuteAsync(JsonObject args, CancellationToken ct)
        => _mcpClient.CallAsync(_toolName, args, ct);
}

使用方式(设计时)

// BPMN 节点配置 MCP Server 地址
// sf:AiService mcpServerUrl="http://erp.company.com/mcp"
//              toolSetName="ErpTools"(自动发现,不需要预注册)

// 引擎运行时自动执行:
var mcpClient = new McpServerClient(mcpServerUrl);
var tools     = await mcpClient.DiscoverToolsAsync();  // 自动获取工具列表
AgentToolRegistry.Global.Register(activityId, tools);

MCP Server 部署参考(企业侧)

以 ERP 系统为例,企业只需在 ERP 旁边部署一个轻量 MCP Server(提供商已有 SDK):

ERP 主系统                MCP Server(新增)
   │                           │
   │  内部调用                  │ JSON-RPC 2.0(供 Agent 使用)
   ◄──────────────────────────►│
                               │
                    ┌──────────┘
                    │  暴露的工具:
                    │  - query_inventory
                    │  - create_purchase_order
                    │  - check_vendor_status

三阶段能力对比

维度近期(预置工具库)中期(声明式 HTTP)长期(MCP 协议)
接入新系统的工作量写 C# Service 类填 JSON 配置表单部署 MCP Server
需要开发人员参与否(配置即可)否(标准协议)
工具更新方式代码发布修改数据库配置MCP Server 动态更新
适用范围高频标准场景任意 REST API任意支持 MCP 的系统
技术门槛低(写普通类)极低(填表单)中(部署 MCP Server)

7. 自然语言驱动多智能体全流程生成

7.1 现有基础与补充目标

Slickflow 产品已具备自然语言 → BPMN 流程图的生成能力:用户用中文描述业务场景,LLM 自动生成对应的 BPMN XML,并在设计器中渲染出可编辑的流程图。

将这一能力延伸到多智能体场景,面临一个新问题:

现有能力(已有):
  "我需要一个采购审批流程" → LLM → BPMN 结构(节点 + 连线)

需要补充的能力(新增):
  BPMN 结构中的 Agent 节点 → 需要进一步配置:
    ① toolSetName(绑定哪个工具集)
    ② System Prompt(角色定义)
    ③ 输入/输出变量键(数据流)
    ④ MaxIterations、模型选择等参数

这些 Agent 专属配置无法完全从自然语言描述中一次性推断,需要一套分层生成 + 人工确认的补充方案。


7.2 三层生成架构

整体方案分三层,依次递进:

┌────────────────────────────────────────────────────────────────┐
│  第一层:自然语言 → BPMN 结构(已有功能延伸)                    │
│                                                                │
│  用户输入:"我需要一个采购审批流程,包括供应商查询、合规审查        │
│           和价格评估,金额超过 50 万需要人工审批"               │
│                                                                │
│  LLM 输出:                                                    │
│    ① BPMN XML(含 Agent 节点类型标记)                          │
│    ② 每个 Agent 节点的语义描述("供应商查询节点")               │
│    ③ 初步推断的数据流(输入/输出变量名建议)                     │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│  第二层:Agent 节点配置(AI 预填 + 表单确认)                    │
│                                                                │
│  对于 BPMN 中识别到的每个 Agent 节点,AI 自动:                  │
│    ① 从现有 toolSetName 注册表中模糊匹配,推荐最接近的工具集      │
│    ② 根据节点语义生成 System Prompt 草稿                        │
│    ③ 推断 InputKey / OutputKey(基于上下游节点关系)             │
│                                                                │
│  用户在属性面板:确认 or 修改(低成本,不需从零填写)             │
└────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌────────────────────────────────────────────────────────────────┐
│  第三层:缺失工具集的代码生成(Claude Code CLI)                  │
│                                                                │
│  当第二层推荐的 toolSetName 不存在(注册表中没有匹配项时):       │
│    ① 用户在设计器中点击"生成工具类"按钮                          │
│    ② 填写 API 描述或上传 OpenAPI Spec                           │
│    ③ 调用 Claude Code 生成 [AgentToolSet] C# 代码               │
│    ④ 开发者 Review → 注册到工具库 → 回填 toolSetName            │
└────────────────────────────────────────────────────────────────┘

7.3 表单辅助配置层

第二层的核心是一个智能预填的属性面板,在 BPMN 设计器中每个 Agent 节点的侧边栏展示。

AI 预填逻辑(服务端)

// 当 BPMN 中检测到 Agent 节点时,后端自动执行预填推断
public sealed class AgentNodeConfigSuggester
{
    private readonly AgentToolSetRegistry _registry;
    private readonly ILlmClient           _llm;

    /// <summary>
    /// 为一个 Agent 节点生成配置建议
    /// 输入:节点的语义描述 + 上下游节点信息 + 已有注册表
    /// 输出:预填的 AgentNodeConfig(用户可在面板中修改)
    /// </summary>
    public async Task<AgentNodeConfigSuggestion> SuggestAsync(
        string nodeDescription,    // "查询供应商并获取报价"
        string upstreamOutputKey,  // 上游节点的 OutputKey(如 "StructuredRequirements"string downstreamHint)     // 下游节点的语义(如 "合规审查")
    {
        // 1. 从注册表中获取所有可用 toolSetName
        var availableToolSets = _registry.GetAllToolSetNames();

        // 2. 让 LLM 从中选择最匹配的,并生成其他配置
        var prompt = $"""
            已注册的工具集列表:{string.Join(", ", availableToolSets)}
            
            当前 Agent 节点描述:{nodeDescription}
            上游节点的输出变量:{upstreamOutputKey}
            下游节点的用途:{downstreamHint}
            
            请输出 JSON,包含:
            - toolSetName: 从已注册列表中选择最匹配的(没有匹配时输出 null)
            - systemPrompt: 根据节点描述生成的角色定义(100字以内)
            - inputKey: 建议的输入变量名
            - outputKey: 建议的输出变量名
            - confidence: 推荐置信度(high/medium/low)
            只输出 JSON。
            """;

        var json = await _llm.CompleteAsync(prompt);
        return JsonSerializer.Deserialize<AgentNodeConfigSuggestion>(json)!;
    }
}

public sealed record AgentNodeConfigSuggestion(
    string?  ToolSetName,     // null 表示未找到匹配,需要生成
    string   SystemPrompt,
    string   InputKey,
    string   OutputKey,
    string   Confidence);     // "high" / "medium" / "low"

属性面板 UI 行为

Agent 节点属性面板(设计器侧边栏)
┌─────────────────────────────────────────┐
│  节点名称:供应商查询                     │
│                                         │
│  工具集 (toolSetName)                   │
│  ┌─────────────────────────┐ [生成代码] │
│  │ SupplierSearch ✓ AI推荐  │           │
│  └─────────────────────────┘           │
│  置信度:● 高  ( 可从下拉列表选其他 )    │
│                                         │
│  System Prompt                          │
│  ┌─────────────────────────────────┐   │
│  │ 你是供应商查询专家,负责根据采购  │   │
│  │ 规格搜索合适供应商并获取报价...   │   │
│  └─────────────────────────────────┘   │
│  [AI 重新生成]                          │
│                                         │
│  输入变量:StructuredRequirements       │
│  输出变量:SupplierQuotes               │
│                                         │
│  最大迭代:10   模型:claude-sonnet     │
│                                         │
│         [确认配置]  [重置]              │
└─────────────────────────────────────────┘

⚠ 当 toolSetName = null 时,面板显示:
┌─────────────────────────────────────────┐
│  ⚠ 未找到匹配的工具集                   │
│  请选择:                               │
│  ○ 从预置库选择(近似功能)              │
│  ● 声明式 HTTP 配置(填写 API 地址)     │
│  ○ 生成 C# 工具类(Claude Code)        │
└─────────────────────────────────────────┘

7.4 Claude Code 工具类生成

当没有现成工具集时,第三层方案是通过 Claude Code CLI 直接生成符合 Slickflow Harness 规范的 [AgentToolSet] C# 代码。

两种触发方式

方式一:设计器内嵌调用

在属性面板点击"生成工具类"后,设计器将用户填写的接口描述组装成 Prompt,通过后端调用 Anthropic API,在界面中实时展示生成的代码供 Review:

// 后端:AgentToolCodeGenerator.cs
public sealed class AgentToolCodeGenerator
{
    private readonly ILlmClient _claude;

    public async Task<string> GenerateToolSetAsync(ToolGenerationRequest request)
    {
        var prompt = $"""
            请生成一个符合 Slickflow Harness 规范的 C# AgentToolSet 类。
            
            工具集名称:{request.ToolSetName}
            业务描述:{request.Description}
            API 信息:{request.ApiDescription}
            
            规范要求:
            1. 类上标注 [AgentToolSet("{request.ToolSetName}")]
            2. 每个方法标注 [AgentTool("工具功能描述")]
            3. 使用强类型参数和返回值,不使用 JsonObject
            4. 异步方法返回 Task<T>
            5. 通过构造函数注入 HttpClient 或配置类
            
            参考现有规范:
            [AgentToolSet("SupplierSearch")]
            public class SupplierSearchService
            {{
                [AgentTool("搜索供应商目录,返回符合条件的供应商列表")]
                public async Task<List<Supplier>> SearchSuppliers(string category) {{ ... }}
            }}
            
            只输出 C# 代码,不要额外解释。
            """;

        return await _claude.CompleteAsync(prompt);
    }
}

public sealed record ToolGenerationRequest(
    string ToolSetName,
    string Description,
    string ApiDescription);    // 用户填写的 API 说明,或上传的 OpenAPI Spec JSON

方式二:Claude Code CLI 命令行

开发者也可以在终端直接调用 Claude Code,适合在本地开发环境中快速生成:

# 基于描述生成工具类
claude "根据以下 Slickflow Harness 规范,生成一个 C# AgentToolSet 类。
工具集名称:InventoryQuery
业务场景:查询 SAP 库存系统,支持按物料编码查询库存数量和仓库分布。
API endpoint:GET http://sap.company.com/api/inventory/{materialCode}
参考规范:[AgentToolSet] + [AgentTool] 属性化注册,强类型返回值,构造函数注入 HttpClient。"

# 基于 OpenAPI Spec 文件生成工具类
claude "读取 ./supplier-portal-openapi.json,为 Slickflow Harness 框架生成
对应的 [AgentToolSet] C# 类,仅包含 GET /suppliers 和 POST /quotes 两个接口,
类名为 SupplierPortalTools。"

生成结果示例

// Claude Code 生成的代码(开发者 Review 后直接使用)
[AgentToolSet("InventoryQuery")]
public class InventoryQueryService
{
    private readonly HttpClient _http;

    public InventoryQueryService(IHttpClientFactory httpFactory)
    {
        _http = httpFactory.CreateClient("SapInventory");
    }

    [AgentTool("查询指定物料编码的当前库存数量,支持按仓库筛选")]
    public async Task<InventoryResult> QueryStock(
        string materialCode,
        string warehouse = "")
    {
        var url = string.IsNullOrEmpty(warehouse)
            ? $"/api/inventory/{materialCode}"
            : $"/api/inventory/{materialCode}?warehouse={warehouse}";

        var response = await _http.GetFromJsonAsync<SapInventoryResponse>(url);
        return new InventoryResult
        {
            MaterialCode = materialCode,
            Quantity     = response?.Quantity ?? 0,
            Warehouse    = response?.Warehouse ?? warehouse,
            Unit         = response?.Unit ?? "EA"
        };
    }

    [AgentTool("批量查询多个物料的库存汇总,返回可用/缺货状态")]
    public async Task<List<InventoryResult>> QueryBatchStock(List<string> materialCodes)
    {
        var tasks = materialCodes.Select(code => QueryStock(code));
        return (await Task.WhenAll(tasks)).ToList();
    }
}

生成后的接入流程

① 设计器界面点击"生成工具类",填写接口描述
        
        
② 后端调用 LLM(或 Claude Code CLI),生成 C# 代码
        
        
③ 设计器弹出代码预览窗口,开发者 Review + 小改
        
        
④ 开发者将代码保存到 ToolSets/ 目录,提交并部署
        
        
⑤ 在 Program.cs 注册:AgentToolSetRegistry.Global.Register<InventoryQueryService>()
        
        
⑥ 设计器属性面板的 toolSetName 下拉列表中出现新工具集,回填到 Agent 节点
        
        
⑦ 全流程配置完成,可以执行

7.5 三种交互模式对比

根据用户角色和场景复杂度,自然语言生成多智能体流程有三种落地模式:

模式适用场景操作路径工具集来源
A. 全自动模式需求完全匹配预置工具库NL → BPMN → AI 预填配置 → 一键确认预置工具库
B. 表单辅助模式工具集存在但需自定义 HTTP APINL → BPMN → 表单填写 API 地址 → 保存声明式 HTTP 适配器
C. 代码生成模式需要全新工具集NL → BPMN → Claude Code 生成类 → Review 注册AI 生成 C# 代码

预期用户体验

普通业务人员(无代码):
  用自然语言描述 → 流程图自动生成 → 从预置工具库选择 → 确认 → 运行
  全程 5-10 分钟,零代码

集成工程师(低代码):
  自然语言生成框架 → 填写 HTTP API 表单 → 测试连通 → 保存
  全程 30 分钟,零 C# 代码

开发者(有代码能力):
  自然语言生成框架 → Claude Code 生成工具类 → Review + 小改 → 注册
  全程 1-2 小时,大量代码由 AI 生成

当前可实现程度

子能力现状所需工作
NL → BPMN 结构✅ 已有增加 Agent 节点类型识别
AI 预填 toolSetName🔧 可实现接入注册表的模糊匹配 API
AI 生成 System Prompt🔧 可实现复用现有 LLM 调用能力
表单式 HTTP 工具配置🔧 规划中(6.2 章节)中期工作
Claude Code 工具类生成🔧 可实现接入 Anthropic API,增加代码预览组件
生成代码自动注册⚠ 需谨慎安全评估后再考虑自动化

8. 总结

8.1 核心设计决策

决策原因
ReAct 循环代替单次 LLM 调用复杂任务需要多步推理和工具协作,单次调用无法完成
两层注册表(SetRegistry + Registry)toolSetName 是设计时名称,activityId 是运行时名称,两者必须解耦
属性化工具注册代替 lambda业务开发者不应接触框架细节,Service 类可读性更高、可测试性更强
BPMN 驱动代替硬编码节点顺序流程结构由设计师在 BPMN 里调整,不需要改代码
共享变量传递上下文Agent 之间通过键值对共享状态,避免强耦合