1. 背景与目标
1.1 问题出发点
传统 BPM(业务流程管理)系统的核心是"人"驱动流程:人工节点负责决策,系统节点负责执行预定义规则。这一模型在面对复杂、动态、需要多步推理的企业场景时暴露出明显局限:
- 规则节点无法处理非结构化输入(自然语言需求、图片、文档)
- 复杂决策依赖大量 if-else 硬编码,维护成本高
- 多系统协作需要人工中转,效率低
1.2 多智能体的价值
将 LLM Agent 节点引入 BPM 流程,每个 Agent 节点具备:
- 推理能力:理解自然语言输入,分析上下文
- 工具调用能力(Function Calling):调用企业系统 API、数据库、外部服务
- 协作能力:通过共享上下文,多个 Agent 接力完成复杂任务
scss
传统流程:用户 → 人工审核 → 系统执行 → 人工复核 → 结束
多智能体流程:用户 → Agent₁(需求分析) → Agent₂(方案搜索) →
Agent₃(合规检查) → Agent₄(价格评估) → Agent₅(执行下单) → 结束
1.3 设计目标
| 目标 | 说明 |
|---|---|
| 流程驱动 | Agent 行为由 BPMN 工作流定义驱动,不硬编码在程序里 |
| 工具解耦 | 工具实现与流程定义松耦合,通过 toolSetName 桥接 |
| 开发友好 | 业务开发者写普通 C# Service 类,不接触框架细节 |
| 可扩展 | 支持从代码工具逐步演进到声明式工具、MCP 协议工具 |
2. Harness 工程规范
Harness 是 Slickflow.AI 多智能体模块采用的工程架构规范,核心思想来源于 Claude 工程体系:以"注册→发现→执行"三段式管理智能体工具,使框架代码与业务代码完全分离。
2.1 核心思想
ini
┌───────────────────────────────────────────────────────────────────┐
│ Harness 三原则 │
│ │
│ 1. 声明在设计时(BPMN + 属性面板) │
│ toolSetName = "SupplierSearch" │
│ │
│ 2. 注册在启动时(应用/测试初始化) │
│ AgentToolSetRegistry.Global.Register<SupplierSearchService>() │
│ │
│ 3. 执行在运行时(引擎自动驱动) │
│ toolSetName → activityId → IReadOnlyList<IAgentTool> │
└───────────────────────────────────────────────────────────────────┘
2.2 职责边界
| 角色 | 职责 | 代码位置 |
|---|---|---|
| 流程设计师 | 定义节点类型(Agent)、绑定 toolSetName、设置 System Prompt | BPMN 设计器(数据库) |
| 业务开发者 | 实现 [AgentToolSet] 服务类,写业务方法 |
ToolSets/*.cs |
| 框架层 | ReAct 循环、工具注册/发现、参数绑定、上下文管理 | Slickflow.AI/Agent/ |
| 引擎层 | 读取 BPMN 定义,驱动 Agent 节点执行 | Slickflow.Engine/ |
2.3 命名与注册规范
工具集命名(toolSetName)规则:
✅ 推荐:PascalCase,业务语义清晰
NeedsAnalysis / SupplierSearch / ComplianceCheck
❌ 避免:技术实现名称
AgentTool001 / Step2Handler / MyFunc
工具方法命名规则:
arduino
// ✅ 动词+名词,描述行为
public Task<SupplierQuote> RequestQuote(string supplierId, int quantity)
// ❌ 不描述行为的名称
public Task<object> Execute(JsonObject args)
注册时机:在应用程序/测试的入口点一次性注册,不在请求处理中动态注册:
scss
// Program.cs 或 测试入口
AgentToolSetRegistry.Global
.Register<NeedsAnalysisService>()
.Register<SupplierSearchService>()
.Register<ComplianceCheckService>();
2.4 上下文传递规范
多 Agent 之间通过共享变量键(Variable Key) 传递数据,键名在代码中集中定义为常量:
csharp
// ✅ 在测试/应用入口集中定义
private const string VarPurchaseRequest = "PurchaseRequest";
private const string VarStructuredRequirements = "StructuredRequirements";
private const string VarSupplierQuotes = "SupplierQuotes";
// ✅ 数据流配置与工具注册分离
["SupplierSearch"] = new(InputKey: VarStructuredRequirements,
OutputKey: VarSupplierQuotes)
3. 核心架构设计
3.1 整体架构图
scss
┌──────────────────────────────────────────────────────────────────┐
│ 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 给出最终答案:
ini
用户输入 / 上一个 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×10=¥29500 │
├───────────────────────────────────────────────┤
│ Iteration 3 │
│ Reason: LLM 综合所有结果 │
│ → finish_reason="stop"(最终答案) │
└───────────────────────────────────────────────┘
│
▼
最终文本输出 → 写入 OutputVariableKey → 下一个 Agent 的输入
循环保护 :MaxIterations(默认 10)防止死循环,超出后返回降级答案。
3.3 工具注册体系
两层注册表实现"逻辑名 → 活动 ID"的解耦:
swift
AgentToolSetRegistry(名称注册)
"SupplierSearch" → IReadOnlyList<IAgentTool>
│
│ TryResolveToActivityRegistry(toolSetName, activityId)
▼
AgentToolRegistry(活动注册)
"Activity_ABC123" → IReadOnlyList<IAgentTool>
│
│ 引擎执行节点时调用
▼
AgentNodeBase.ExecuteReActAsync()
这样做的好处:BPMN 定义里的 activityId 是运行时才知道的(每个流程实例可能不同),toolSetName 才是设计时确定的。两层注册表在运行时完成映射。
3.4 共享记忆(AgentConversationMemory)
多个 Agent 节点通过 AgentConversationMemory 共享输出,实现跨节点的上下文传递:
less
// Agent₁ 写入
AgentConversationMemory.RecordOutput(
processInstanceId, activityId, "StructuredRequirements", result);
// Agent₂ 可以读取 Agent₁ 的输出(通过 sharedVars 传递)
var input = sharedVars["StructuredRequirements"];
记忆以 processInstanceId 为隔离键,流程实例结束后调用 Evict() 释放。
4. 属性化工具注册(当前实现)
4.1 问题与方案
问题:早期实现使用 lambda + 手写 JSON Schema,框架代码和业务代码混在一起:
typescript
// ❌ 旧方式:开发者需要理解 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 类:
csharp
// ✅ 新方式:普通 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,无需重复书写字符串:
csharp
// 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 流程节点设计
css
用户提交采购需求
│
▼
┌──────────────────┐
│ NeedsAnalysis │ 分析需求,提取品类和规格
│ [Agent 节点] │ 工具:ClassifyCategory / ExtractSpecs
└────────┬─────────┘
│ StructuredRequirements(结构化需求)
▼
┌──────────────────┐
│ SupplierSearch │ 搜索供应商,获取报价
│ [Agent 节点] │ 工具:SearchSuppliers / RequestQuote
└────────┬─────────┘
│ SupplierQuotes(供应商报价列表)
▼
┌──────────────────┐
│ ComplianceCheck │ 合规审查(并行支持)
│ [Agent 节点] │ 工具:CheckRegulations / CheckBlacklist
└────────┬─────────┘
│ ComplianceReport(合规报告)
▼
┌──────────────────┐
│ PriceEvaluation │ 综合评估,推荐最优方案
│ [Agent 节点] │ 工具:CalculateTco(总拥有成本)
└────────┬─────────┘
│ PurchaseRecommendation(采购建议)
▼
┌──────────────────┐
│ OrderExecution │ 在 ERP 中创建采购订单
│ [Agent 节点] │ 工具:CreatePurchaseOrder
└────────┬─────────┘
│ PurchaseOrderId(订单号)
▼
采购完成
流程图示例:

5.3 Agent 节点定义(Service 类)
需求分析服务:
csharp
[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" };
}
}
供应商搜索服务:
csharp
[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 数据流配置
less
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 启动注册(一次性)
scss
// 应用启动时(Program.cs 或测试入口)
AgentToolSetRegistry.Global
.Register<NeedsAnalysisService>()
.Register<SupplierSearchService>()
.Register<ComplianceCheckService>()
.Register<PriceEvaluationService>()
.Register<OrderExecutionService>();
5.6 BPMN 侧配置(设计器)
流程设计师在 BPMN 属性面板为每个 Agent 节点设置:
| 属性 | 值 | 说明 |
|---|---|---|
type |
Agent |
节点类型 |
toolSetName |
SupplierSearch |
与注册名称对应 |
system_prompt |
存入 ai_activity_config |
角色定义,可数据库配置 |
5.7 运行日志示例
ini
=== 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% 的高频企业操作,客户只需选择并配置,不需要写代码。
工具库分类设计
markdown
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 为例)
工具类通过配置文件或数据库读取连接参数,不硬编码:
csharp
[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);
}
}
json
// appsettings.json
{
"ErpConfigs": {
"ERP_MAIN": {
"BaseUrl": "http://erp.company.com/api",
"ApiKey": "sk-xxx",
"Timeout": 30
}
}
}
注册方式(集成时)
scss
// 客户集成时,只需选择需要的工具集并配置连接参数
AgentToolSetRegistry.Global
.Register<InventoryQueryTools>() // 平台预置
.Register<PurchaseOrderTools>() // 平台预置
.Register<DingTalkApprovalTools>() // 平台预置
// 以下才是客户自己写的业务特定工具
.Register<CustomerSpecificPricingTools>();
覆盖范围预估
| 场景 | 预置工具覆盖率 |
|---|---|
| 采购申请审批 | ~90% |
| 费用报销 | ~85% |
| 员工入职 | ~80% |
| 合同审批 | ~75% |
| 客户拜访管理 | ~70% |
6.2 中期:声明式 HTTP 工具适配器
核心思想 :为 REST API 类工具提供 JSON/数据库配置方式 ,业务人员在设计器里填写 API 地址和参数映射,不需要写任何 C# 代码。
工具描述格式(存储在数据库)
json
{
"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
ini
// 框架层新增,业务开发者不接触
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);
}
}
注册方式(数据库驱动)
ini
// 框架启动时,从数据库加载所有声明式工具集
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 工具配置"面板,业务人员直接在界面上:
- 填写 API Endpoint
- 拖拽参数映射
- 测试连接并保存
这一步实现后,新增一个企业系统集成的成本从"写代码+部署"降为"填表单+保存"。
6.3 长期:MCP 协议集成
核心思想 :企业各系统各自部署 MCP Server(Model Context Protocol) ,Agent 节点通过标准协议动态发现和调用工具,平台侧零代码、零配置完成集成。
MCP 是由 Anthropic 推动的 AI 工具标准协议,OpenAI、Microsoft 等主流厂商均已跟进支持。
架构图
scss
┌─────────────────────────────────────────────────────────┐
│ 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
ruby
// 已实现: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);
}
使用方式(设计时)
ini
// 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):
arduino
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,并在设计器中渲染出可编辑的流程图。
将这一能力延伸到多智能体场景,面临一个新问题:
makefile
现有能力(已有):
"我需要一个采购审批流程" → LLM → BPMN 结构(节点 + 连线)
需要补充的能力(新增):
BPMN 结构中的 Agent 节点 → 需要进一步配置:
① toolSetName(绑定哪个工具集)
② System Prompt(角色定义)
③ 输入/输出变量键(数据流)
④ MaxIterations、模型选择等参数
这些 Agent 专属配置无法完全从自然语言描述中一次性推断,需要一套分层生成 + 人工确认的补充方案。
7.2 三层生成架构
整体方案分三层,依次递进:
bash
┌────────────────────────────────────────────────────────────────┐
│ 第一层:自然语言 → 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 预填逻辑(服务端)
csharp
// 当 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 行为
ini
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:
csharp
// 后端: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,适合在本地开发环境中快速生成:
ini
# 基于描述生成工具类
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。"
生成结果示例
csharp
// 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();
}
}
生成后的接入流程
swift
① 设计器界面点击"生成工具类",填写接口描述
│
▼
② 后端调用 LLM(或 Claude Code CLI),生成 C# 代码
│
▼
③ 设计器弹出代码预览窗口,开发者 Review + 小改
│
▼
④ 开发者将代码保存到 ToolSets/ 目录,提交并部署
│
▼
⑤ 在 Program.cs 注册:AgentToolSetRegistry.Global.Register<InventoryQueryService>()
│
▼
⑥ 设计器属性面板的 toolSetName 下拉列表中出现新工具集,回填到 Agent 节点
│
▼
⑦ 全流程配置完成,可以执行
7.5 三种交互模式对比
根据用户角色和场景复杂度,自然语言生成多智能体流程有三种落地模式:
| 模式 | 适用场景 | 操作路径 | 工具集来源 |
|---|---|---|---|
| A. 全自动模式 | 需求完全匹配预置工具库 | NL → BPMN → AI 预填配置 → 一键确认 | 预置工具库 |
| B. 表单辅助模式 | 工具集存在但需自定义 HTTP API | NL → BPMN → 表单填写 API 地址 → 保存 | 声明式 HTTP 适配器 |
| C. 代码生成模式 | 需要全新工具集 | NL → BPMN → Claude Code 生成类 → Review 注册 | AI 生成 C# 代码 |
预期用户体验:
bash
普通业务人员(无代码):
用自然语言描述 → 流程图自动生成 → 从预置工具库选择 → 确认 → 运行
全程 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 之间通过键值对共享状态,避免强耦合 |