Files
MAF1/README.md
T
2026-08-26 18:06:02 +08:00

14 KiB
Raw Blame History

MAF1 — 多智能体工作流编排

MAF1 是一个基于 .NET 10Microsoft Agents AI 的多智能体(Multi-Agent)演示项目。它把「读文件抽城市 → 查天气」这条业务链路做成两种用法:

  1. 可视化编排器:浏览器里拖节点、连线、配条件,然后一键运行。
  2. 命令行工作流:用 Microsoft Agents AI Workflows 对比「条件写在节点里」和「条件写在边上」两种编排方式。

内置 Agent 在进程内执行;同一套能力也可以以 独立进程插件 的形式出现在画布上。LLM 的 endpoint / API Key / 模型由宿主注入,不会画成节点端口,也不会写进流程图 JSON。

默认打开可视化界面:http://127.0.0.1:5288


功能概览

  • 可视化工作流:左侧节点面板(系统节点 + 扫描到的插件)、画布连线、右侧检查器、底部运行日志。
  • 系统节点fileCity-system(读文本抽城市)、weather-system(按城市查天气并汇总)。
  • 进程外插件:扫描输出目录 plugins/*/plugin.json,按 launch 启动子进程,stdin / stdout 走 JSON。
  • 凭据隔离:宿主配置 LLM;节点只声明需要哪种凭据(默认 llm-default)。浏览器拿不到原始 Key。
  • 边条件:连线可设 hasValidCities / !hasValidCities,不满足则跳过下游节点。
  • CLI 对照node 模式把判断放在 Gate 节点内;edge 模式把判断写在 Workflow 边上。
  • 天气数据源:默认 wttr.in,可改为 OpenWeatherMap。

技术栈

部分 说明
运行时 .NET 10net10.0
宿主 ASP.NET Core Web SDKMinimal API + 静态文件
编排库 Microsoft.Agents.AI.Workflows 1.19.0
LLM Azure.AI.OpenAI + Microsoft.Agents.AI.OpenAI(兼容 OpenAI / Azure OpenAI / DeepSeek 等)
前端 wwwroot 下原生 HTML / CSS / JS,无 npm 依赖
插件 独立控制台进程,协议见 MAF1.Core/PluginContract

解决方案文件:MAF1.slnx(包含宿主、MAF1.Core、两个插件工程)。


仓库结构

MAF1/
├── MAF1.csproj                 # 宿主:Web UI + CLI
├── MAF1.slnx
├── Program.cs                  # 有 CLI 参数则走命令行,否则启动 Web
├── CliHost.cs
├── appsettings.json            # LLM / 插件 / 天气配置
├── app.manifest                # Windows 控制台 UTF-8
├── Properties/launchSettings.json
├── Data/                       # 示例文本
│   ├── cities.txt
│   └── not-cities.txt
├── Web/                        # 目录、图执行、运行时组装
├── PluginHost/                 # 扫描插件、起进程、注入凭据
├── Workflows/                  # CLI 用的 Microsoft Agents AI Workflow
├── Orchestration/              # CLI 事件流式输出
├── wwwroot/                    # 可视化编排界面
├── MAF1.Core/                  # 共享:Agent、Tool、LLM、插件协议
│   ├── Agents/FileCity/
│   ├── Agents/Weather/
│   ├── Tools/
│   ├── Utils/AgentFactory.cs
│   └── PluginContract/
└── plugins/
    ├── README.md               # 插件目录约定
    ├── file-city/              # FileCity 独立进程
    └── weather/                # Weather 独立进程

构建宿主时会编译两个插件,并把输出复制到宿主 bin/.../plugins/file-cityplugins/weather运行时扫描的是可执行文件旁边的 plugins/,不是源码树。


环境要求

  • .NET 10 SDK
  • 一台 OpenAI 兼容的 Chat Completions 服务(默认配置指向 DeepSeek)
  • 访问天气接口的网络(wttr.in 或 OpenWeatherMap

快速开始

1. 还原并编译

cd 仓库根目录
dotnet restore
dotnet build

2. 配置模型

不要把 API Key 提交进 Git。任选一种方式:

方式 A:环境变量(推荐)

$env:OPENAI_API_KEY = "你的密钥"
$env:OPENAI_ENDPOINT = "https://api.deepseek.com"
$env:OPENAI_CHAT_MODEL = "deepseek-v4-flash"

也识别:

变量 含义
OPENAI_API_KEY / AZURE_OPENAI_API_KEY 密钥
OPENAI_ENDPOINT / OPENAI_BASE_URL / AZURE_OPENAI_ENDPOINT 服务地址
OPENAI_CHAT_MODEL / AZURE_OPENAI_DEPLOYMENT_NAME 模型名或 Azure 部署名

配置优先级:环境变量会覆盖 appsettings.json 里空的或未填的对应项(见 AgentFactory.Load)。

方式 Bappsettings.jsonLlm 节点

"Llm": {
  "ApiKey": "",
  "Endpoint": "https://api.deepseek.com",
  "Model": "deepseek-v4-flash"
}

Azure OpenAI:把 Endpoint 设成 Azure 资源地址,模型字段填 部署名。程序会按 URL 判断是否走 Azure 客户端。

Visual Studio / dotnet run --launch-profile designer 会读 Properties/launchSettings.json 里的环境变量。该文件容易带上真实密钥,请勿把含 Key 的版本推到远程

3. 启动可视化编排(默认)

dotnet run

浏览器打开 http://127.0.0.1:5288。控制台会打印:可视化编排: http://127.0.0.1:5288

界面操作建议:

  1. 示例图,生成「抽城市 → 查天气」的演示图(含边条件)。
  2. 选中节点,在右侧填 filePath(例如 Data/cities.txt),或检查端口映射。
  3. 运行,底部查看每步输入 / 输出。
  4. 改完 plugins/ 后点 刷新节点 重新加载目录。

4. 命令行工作流

dotnet run -- node Data/cities.txt    # 条件在 Gate 节点内部
dotnet run -- edge Data/cities.txt    # 条件在边上分流
dotnet run -- --help

无有效城市时可用 Data/not-cities.txt 看跳过天气查询的路径。

启动配置(launchSettings.json):

Profile 作用
designer Web 编排器(默认)
node CLI,节点内判断
edge CLI,边上判断

配置说明

appsettings.json 会复制到输出目录。主要段落:

Llm

宿主内置 Agent 与默认凭据 llm-default 使用这一段(可再被环境变量覆盖)。

Plugins

字段 含义
Directory 相对宿主内容根的插件目录,默认 plugins
DefaultTimeoutSeconds 插件进程默认超时(秒),清单里可单独覆盖

插件进程会收到 MAF1_CONTENT_ROOT,用于定位宿主的 appsettings.json(天气插件读天气配置)。

Credentials

节点只声明凭据类型。llm-defaultsource: llm-section 表示从 Llm 段生成,不在 JSON 里再写一份 Key。

Weather

字段 含义
Provider Wttr(默认)或 OpenWeather
Language 语言,默认 zh
Wttr.UrlTemplate {location}{lang} 占位
OpenWeather.UrlTemplate / ApiKey OpenWeatherMap;需自行申请 Key

内置 Agent

两条链路语义相同,只是执行位置不同。

FileCity(抽城市)

  • 输入:filePath(本地文本路径,相对宿主工作目录即可)
  • 输出:hasValidCitiescitiesreasonraw
  • 行为:LLM + 读文件工具,从文本里抽出有效城市名

系统节点类型:fileCity-system
插件 idfileCityplugins/file-city

Weather(查天气)

  • 输入:cities(字符串数组)
  • 输出:summary
  • 行为:LLM + HTTP 天气工具,汇总各城市天气

系统节点类型:weather-system
插件 idweatherplugins/weather

画布上可以混用:例如系统 FileCity 接到插件 Weather,只要端口名对得上。


可视化图模型

运行请求体大致为:

{
  "nodes": [
    {
      "id": "n1",
      "type": "fileCity-system",
      "title": "抽城市",
      "x": 80,
      "y": 120,
      "config": { "filePath": "Data/cities.txt" }
    }
  ],
  "edges": [
    {
      "id": "e1",
      "from": "n1",
      "to": "n2",
      "fromPort": "cities",
      "toPort": "cities",
      "when": "hasValidCities"
    }
  ]
}
  • 拓扑:按依赖排序执行;未满足入边条件的节点会被标记为跳过。
  • 端口fromPorttoPort 把上游输出接到下游输入;未接线的必填项可写在 config
  • 边条件 when(当前实现):
    • 空:始终通过
    • hasValidCities:上游输出该布尔为真
    • !hasValidCities:为假
    • 其它字符串:视为通过

HTTP API

端口固定:http://127.0.0.1:5288Program.cs)。

方法 路径 说明
GET /api/catalog 系统节点 + 插件节点、扫描问题、pluginsRoot
GET /api/credentials 对外凭据列表(不含原始 Key
GET /api/status 当前天气 Provider、插件根路径
POST /api/run Body 为工作流图 JSON,返回逐步 steps

JSON 使用 camelCase。静态站点来自 wwwroot


插件机制

约定详见 plugins/README.md。每个子目录一份插件。

必备文件

  • plugin.json:id、启动命令、凭据声明、inputs、outputs
  • README.md:给人看的说明
  • 可执行文件:由 launch.command + launch.args 原样启动,宿主不替你拼路径

示例(FileCity):

"launch": {
  "command": "dotnet",
  "args": ["FileCityPlugin.dll"]
}

工作目录是该插件文件夹;dotnet FileCityPlugin.dll 能在该目录下找到 dll。

进程协议

stdin(一次请求):

{
  "inputs": { "filePath": "Data/cities.txt" },
  "credentials": {
    "llm": {
      "id": "llm-default",
      "type": "openai-compatible",
      "endpoint": "...",
      "apiKey": "...",
      "model": "..."
    }
  }
}

stdout:单个 JSON 对象,字段名与 outputs 一致。
stderr:日志。不要把日志打到 stdout,否则会破坏解析。

超时:清单 timeoutSeconds,否则用宿主 Plugins:DefaultTimeoutSeconds(默认 180)。

凭据注入

与 n8n / Dify 类似:Key 只在宿主。运行插件时会:

  1. 写入环境变量 OPENAI_ENDPOINT / OPENAI_API_KEY / OPENAI_CHAT_MODEL
  2. 在 stdin JSON 的 credentials 里再传一份

插件目录可选 .env 作为第三方自带模型的默认环境;节点选中的宿主凭据会覆盖同名变量。不要把 .env 提交进仓库。

更细的输入输出说明:

自己加插件

  1. 新建 plugins/你的插件/,写 plugin.json 和启动程序。
  2. 若要随宿主一起编译,可仿照 MAF1.csproj 增加 ProjectReferenceReferenceOutputAssembly=false)和 PublishPluginFolders 复制规则。
  3. id 不要与系统节点类型冲突;若同名,目录会标记覆盖关系。
  4. 重启宿主或点「刷新节点」,确认 /api/catalogissues 为空。

架构

浏览器 wwwroot
    │  REST
    ▼
ASP.NET Minimal API (Program.cs)
    │
    ▼
AgentRuntime
    ├── NodeCatalogService     合并系统节点 + PluginScanner
    ├── CredentialStore        llm-default 等
    ├── ConfigurableWorkflowRunner   拓扑、端口、when、逐步日志
    ├── 系统 Handler           FileCity / Weather(进程内)
    └── PluginProcessRunner    子进程 stdin/stdout

CLI 不走画布,直接:

CliHostCityWeatherWorkflownode / edge)→ WorkflowOrchestration 把 Workflow 事件打到控制台。

MAF1.Core 被宿主与插件共用,避免两套 Agent 逻辑分叉。


示例数据

Data/cities.txt

成都
阿姆斯特丹
北京

Data/not-cities.txt:不含有效城市,用于验证「无城市则不查天气」。

这些文件会随构建复制到输出目录。CLI 传入相对路径时,请在仓库根目录(或已复制 Data 的输出目录)下运行。


常见问题

启动报「还没有配置模型」
Llm:ApiKey 和环境变量都为空。按「快速开始」配置后重启。

画布上看不到插件
插件必须出现在 exe 旁边plugins/。先 dotnet build,确认 bin/Debug/net10.0/plugins/file-city/plugin.json 存在。源码目录里的 plugins/ 不会被运行时直接扫描。

插件超时或卡住
加大 timeoutSeconds;确认 LLM 与天气 HTTP 可访问;日志看 stderr。

天气失败
默认走 wttr.in。若被墙或限流,把 Weather:Provider 改为 OpenWeather 并填写 Key。

Windows 控制台中文乱码
项目带 app.manifest 并在入口启用 UTF-8。若仍乱码,把终端代码页设为 UTF-8。

端口被占用
当前 URL 写死为 127.0.0.1:5288。关掉占用进程,或临时改 Program.cs / launchSettings.json

没有自动化测试 / Docker
仓库目前没有测试项目和容器文件。验证方式:UI 示例图 + CLI node / edge


安全注意

  • LLM Key、OpenWeather Key 只放在本机配置或环境变量,不要写进 plugin.json、流程图或前端。
  • /api/credentials 只返回公开元数据。
  • 若 Key 曾经出现在 launchSettings.json 并被提交或分享,请到服务商控制台轮换密钥。

许可与定位

本仓库是多智能体编排与进程外插件的可运行演示,便于对照系统节点与插件、对照 CLI 的 node / edge 两种条件写法。按你自己的许可证要求补充版权声明即可。