feat: 拆分 CLI 与网页宿主,并在无有效城市时暂停等人确认
将原单体 MAF1 拆成 MAF1(控制台工作流)和 MAF1.Web(可视化编排)。 抽城市失败时暂停:网页弹确认、CLI 控制台询问,超时采用默认结束查询; 也可改填城市后继续。共享决策模型放在 MAF1.Core。
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
# MAF1 — 多智能体工作流编排
|
||||
|
||||
MAF1 是一个基于 **.NET 10** 和 **Microsoft Agents AI** 的多智能体(Multi-Agent)演示项目。它把「读文件抽城市 → 查天气」这条业务链路做成两种用法:
|
||||
MAF1 是一个基于 **.NET 10** 和 **Microsoft Agents AI** 的多智能体演示仓库。同一条「读文件抽城市 → 查天气」链路拆成两个可执行项目:
|
||||
|
||||
1. **可视化编排器**:浏览器里拖节点、连线、配条件,然后一键运行。
|
||||
2. **命令行工作流**:用 Microsoft Agents AI Workflows 对比「条件写在节点里」和「条件写在边上」两种编排方式。
|
||||
1. **MAF1**:命令行工作流。用 Microsoft Agents AI Workflows 对比「条件写在节点里」和「条件写在边上」。
|
||||
2. **MAF1.Web**:可视化编排器。浏览器里拖节点、连线、配条件,然后一键运行。
|
||||
|
||||
内置 Agent 在进程内执行;同一套能力也可以以 **独立进程插件** 的形式出现在画布上。LLM 的 endpoint / API Key / 模型由宿主注入,**不会**画成节点端口,也不会写进流程图 JSON。
|
||||
内置 Agent 在 `MAF1.Core` 里;网页还可以把同一套能力以 **独立进程插件** 的形式画到画布上。LLM 的 endpoint / API Key / 模型由各宿主注入,**不会**画成节点端口,也不会写进流程图 JSON。
|
||||
|
||||
默认打开可视化界面:<http://127.0.0.1:5288>
|
||||
网页打开:<http://127.0.0.1:5288>(先启动 `MAF1.Web`)
|
||||
|
||||
---
|
||||
|
||||
@@ -18,7 +18,8 @@ MAF1 是一个基于 **.NET 10** 和 **Microsoft Agents AI** 的多智能体(M
|
||||
- **进程外插件**:扫描输出目录 `plugins/*/plugin.json`,按 `launch` 启动子进程,stdin / stdout 走 JSON。
|
||||
- **凭据隔离**:宿主配置 LLM;节点只声明需要哪种凭据(默认 `llm-default`)。浏览器拿不到原始 Key。
|
||||
- **边条件**:连线可设 `hasValidCities` / `!hasValidCities`,不满足则跳过下游节点。
|
||||
- **CLI 对照**:`node` 模式把判断放在 Gate 节点内;`edge` 模式把判断写在 Workflow 边上。
|
||||
- **人机决策**:抽城市失败时暂停等待确认;必须有默认方案(结束查询)。网页弹出确认框,CLI 在控制台询问。超时未确认则自动采用默认方案,也可以改填其它城市继续查天气。
|
||||
- **CLI 对照**:`node` 模式把「有没有城市」写在 Gate 节点里;`edge` 模式把这条判断写在边上。两种模式缺城市时都会进入同一套确认。
|
||||
- **天气数据源**:默认 [wttr.in](https://wttr.in),可改为 OpenWeatherMap。
|
||||
|
||||
---
|
||||
@@ -28,48 +29,43 @@ MAF1 是一个基于 **.NET 10** 和 **Microsoft Agents AI** 的多智能体(M
|
||||
| 部分 | 说明 |
|
||||
|------|------|
|
||||
| 运行时 | .NET 10(`net10.0`) |
|
||||
| 宿主 | ASP.NET Core Web SDK,Minimal API + 静态文件 |
|
||||
| 编排库 | `Microsoft.Agents.AI.Workflows` 1.19.0 |
|
||||
| 控制台 | `MAF1`:普通控制台,Microsoft Agents AI Workflows |
|
||||
| 网页 | `MAF1.Web`:ASP.NET Core Minimal API + `wwwroot` |
|
||||
| LLM | `Azure.AI.OpenAI` + `Microsoft.Agents.AI.OpenAI`(兼容 OpenAI / Azure OpenAI / DeepSeek 等) |
|
||||
| 前端 | `wwwroot` 下原生 HTML / CSS / JS,无 npm 依赖 |
|
||||
| 插件 | 独立控制台进程,协议见 `MAF1.Core/PluginContract` |
|
||||
| 前端 | `MAF1.Web/wwwroot` 下原生 HTML / CSS / JS,无 npm 依赖 |
|
||||
| 插件 | 独立控制台进程,协议见 `MAF1.Core/PluginContract`,由网页宿主扫描 |
|
||||
|
||||
解决方案文件:`MAF1.slnx`(包含宿主、`MAF1.Core`、两个插件工程)。
|
||||
解决方案文件:`MAF1.slnx`(`MAF1`、`MAF1.Web`、`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/ # 控制台项目
|
||||
│ ├── MAF1.csproj
|
||||
│ ├── Program.cs / CliHost.cs
|
||||
│ ├── Workflows/ # Microsoft Agents AI Workflow
|
||||
│ ├── Orchestration/ # 控制台事件流 + 确认提示
|
||||
│ ├── Data/
|
||||
│ └── appsettings.json
|
||||
├── MAF1.Web/ # 网页编排项目
|
||||
│ ├── MAF1.Web.csproj
|
||||
│ ├── Program.cs # Minimal API
|
||||
│ ├── Web/ # 图执行、运行时组装
|
||||
│ ├── PluginHost/ # 扫描插件、起进程、注入凭据
|
||||
│ ├── wwwroot/ # 可视化界面
|
||||
│ ├── Data/
|
||||
│ └── appsettings.json
|
||||
├── MAF1.Core/ # 共享:Agent、Tool、LLM、插件协议
|
||||
│ ├── Agents/FileCity/
|
||||
│ ├── Agents/Weather/
|
||||
│ ├── Tools/
|
||||
│ ├── Utils/AgentFactory.cs
|
||||
│ └── PluginContract/
|
||||
└── plugins/
|
||||
├── README.md # 插件目录约定
|
||||
├── file-city/ # FileCity 独立进程
|
||||
└── weather/ # Weather 独立进程
|
||||
├── file-city/
|
||||
└── weather/
|
||||
```
|
||||
|
||||
构建宿主时会编译两个插件,并把输出复制到宿主 `bin/.../plugins/file-city` 与 `plugins/weather`。**运行时扫描的是可执行文件旁边的 `plugins/`,不是源码树。**
|
||||
构建 **MAF1.Web** 时会编译两个插件,并把输出复制到网页宿主 `bin/.../plugins/`。**运行时扫描的是网页 exe 旁边的 `plugins/`,不是源码树。** 控制台项目不扫描插件。
|
||||
|
||||
---
|
||||
|
||||
@@ -125,12 +121,14 @@ $env:OPENAI_CHAT_MODEL = "deepseek-v4-flash"
|
||||
|
||||
Azure OpenAI:把 Endpoint 设成 Azure 资源地址,模型字段填 **部署名**。程序会按 URL 判断是否走 Azure 客户端。
|
||||
|
||||
Visual Studio / `dotnet run --launch-profile designer` 会读 `Properties/launchSettings.json` 里的环境变量。该文件容易带上真实密钥,**请勿把含 Key 的版本推到远程**。
|
||||
Visual Studio / `dotnet run --project MAF1.Web --launch-profile designer` 会读 `MAF1.Web/Properties/launchSettings.json` 里的环境变量。该文件容易带上真实密钥,**请勿把含 Key 的版本推到远程**。
|
||||
|
||||
### 3. 启动可视化编排(默认)
|
||||
控制台 profile 在 `MAF1/Properties/launchSettings.json`(`node` / `edge`)。
|
||||
|
||||
### 3. 启动可视化编排
|
||||
|
||||
```bash
|
||||
dotnet run
|
||||
dotnet run --project MAF1.Web
|
||||
```
|
||||
|
||||
浏览器打开 <http://127.0.0.1:5288>。控制台会打印:`可视化编排: http://127.0.0.1:5288`。
|
||||
@@ -145,26 +143,43 @@ dotnet run
|
||||
### 4. 命令行工作流
|
||||
|
||||
```bash
|
||||
dotnet run -- node Data/cities.txt # 条件在 Gate 节点内部
|
||||
dotnet run -- edge Data/cities.txt # 条件在边上分流
|
||||
dotnet run -- --help
|
||||
dotnet run --project MAF1 -- node Data/cities.txt # 条件在 Gate 节点内部
|
||||
dotnet run --project MAF1 -- edge Data/cities.txt # 条件在边上分流
|
||||
dotnet run --project MAF1 -- --help
|
||||
```
|
||||
|
||||
无有效城市时可用 `Data/not-cities.txt` 看跳过天气查询的路径。
|
||||
无参数时默认 `node Data/cities.txt`。无有效城市时可用 `Data/not-cities.txt`。工作流会在控制台询问如何继续(默认结束查询,也可输入其它城市);超时走默认方案。
|
||||
|
||||
启动配置(`launchSettings.json`):
|
||||
```bash
|
||||
dotnet run --project MAF1 -- node Data/not-cities.txt
|
||||
dotnet run --project MAF1 -- edge Data/not-cities.txt
|
||||
```
|
||||
|
||||
| Profile | 作用 |
|
||||
|---------|------|
|
||||
| `designer` | Web 编排器(默认) |
|
||||
| `node` | CLI,节点内判断 |
|
||||
| `edge` | CLI,边上判断 |
|
||||
控制台会出现:
|
||||
|
||||
```
|
||||
[需要确认]
|
||||
没有读到有效城市。…
|
||||
1) 结束查询(默认) ← 默认
|
||||
2) 改查其它城市
|
||||
输入序号,或直接回车采用默认:
|
||||
```
|
||||
|
||||
输入 `1` 或回车结束;输入 `2` 后再填城市名继续查天气;也可以直接输入 `成都`。等待秒数与网页相同,来自 `Workflow:DecisionTimeoutSeconds`。
|
||||
|
||||
启动配置:
|
||||
|
||||
| 项目 | Profile | 作用 |
|
||||
|------|---------|------|
|
||||
| `MAF1.Web` | `designer` | 网页编排器 |
|
||||
| `MAF1` | `node` | CLI,节点内判断 |
|
||||
| `MAF1` | `edge` | CLI,边上判断 |
|
||||
|
||||
---
|
||||
|
||||
## 配置说明
|
||||
|
||||
`appsettings.json` 会复制到输出目录。主要段落:
|
||||
`MAF1/appsettings.json` 与 `MAF1.Web/appsettings.json` 会分别复制到各自输出目录。网页项目额外包含 `Plugins` / `Credentials`。主要段落:
|
||||
|
||||
### Llm
|
||||
|
||||
@@ -192,6 +207,14 @@ dotnet run -- --help
|
||||
| `Wttr.UrlTemplate` | `{location}`、`{lang}` 占位 |
|
||||
| `OpenWeather.UrlTemplate` / `ApiKey` | OpenWeatherMap;需自行申请 Key |
|
||||
|
||||
### Workflow
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `DecisionTimeoutSeconds` | 需要用户确认时的等待秒数,默认 `30`。超时后采用默认方案。 |
|
||||
|
||||
节点 `config` 可覆盖:`decisionTimeoutSeconds`(秒)、`askOnEmptyCities`(`false` 时抽不到城市不询问,直接按边条件跳过)。
|
||||
|
||||
---
|
||||
|
||||
## 内置 Agent
|
||||
@@ -203,6 +226,7 @@ dotnet run -- --help
|
||||
- 输入:`filePath`(本地文本路径,相对宿主工作目录即可)
|
||||
- 输出:`hasValidCities`、`cities`、`reason`、`raw`
|
||||
- 行为:LLM + 读文件工具,从文本里抽出有效城市名
|
||||
- 若没有有效城市且后面还有节点:工作流会 **暂停等待确认**。默认方案是结束后续查询;可选方案是手动输入城市继续。超时走默认。
|
||||
|
||||
系统节点类型:`fileCity-system`
|
||||
插件 id:`fileCity`(`plugins/file-city`)
|
||||
@@ -250,6 +274,7 @@ dotnet run -- --help
|
||||
```
|
||||
|
||||
- **拓扑**:按依赖排序执行;未满足入边条件的节点会被标记为跳过。
|
||||
- **人机决策**:节点输出 `hasValidCities = false` 时,若后面还有节点,则返回 `status: needsDecision`,不立刻跑完。
|
||||
- **端口**:`fromPort` → `toPort` 把上游输出接到下游输入;未接线的必填项可写在 `config`。
|
||||
- **边条件 `when`**(当前实现):
|
||||
- 空:始终通过
|
||||
@@ -261,14 +286,39 @@ dotnet run -- --help
|
||||
|
||||
## HTTP API
|
||||
|
||||
端口固定:`http://127.0.0.1:5288`(`Program.cs`)。
|
||||
端口固定:`http://127.0.0.1:5288`(`MAF1.Web/Program.cs`)。
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| `GET` | `/api/catalog` | 系统节点 + 插件节点、扫描问题、`pluginsRoot` |
|
||||
| `GET` | `/api/credentials` | 对外凭据列表(不含原始 Key) |
|
||||
| `GET` | `/api/status` | 当前天气 Provider、插件根路径 |
|
||||
| `POST` | `/api/run` | Body 为工作流图 JSON,返回逐步 `steps` |
|
||||
| `POST` | `/api/run` | Body 为工作流图 JSON。可能直接跑完,也可能返回 `needsDecision` |
|
||||
| `GET` | `/api/run/{runId}` | 查询暂停中或已结束的一次运行 |
|
||||
| `POST` | `/api/run/{runId}/decide` | Body:`{ "optionId": "stop" }` 或 `{ "optionId": "query-cities", "text": "成都" }` |
|
||||
|
||||
`POST /api/run` 在需要确认时返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"status": "needsDecision",
|
||||
"runId": "…",
|
||||
"decision": {
|
||||
"prompt": "没有读到有效城市…",
|
||||
"defaultOptionId": "stop",
|
||||
"timeoutSeconds": 30,
|
||||
"deadline": "2026-08-27T09:22:00+00:00",
|
||||
"options": [
|
||||
{ "id": "stop", "label": "结束查询(默认)", "isDefault": true },
|
||||
{ "id": "query-cities", "label": "改查其它城市", "requiresText": true }
|
||||
]
|
||||
},
|
||||
"steps": []
|
||||
}
|
||||
```
|
||||
|
||||
默认方案必须存在。服务端到期会自动按 `defaultOptionId` 继续;客户端倒计时结束后可再 `GET` 一次拿最终结果。
|
||||
|
||||
JSON 使用 camelCase。静态站点来自 `wwwroot`。
|
||||
|
||||
@@ -336,7 +386,7 @@ JSON 使用 camelCase。静态站点来自 `wwwroot`。
|
||||
### 自己加插件
|
||||
|
||||
1. 新建 `plugins/你的插件/`,写 `plugin.json` 和启动程序。
|
||||
2. 若要随宿主一起编译,可仿照 `MAF1.csproj` 增加 `ProjectReference`(`ReferenceOutputAssembly=false`)和 `PublishPluginFolders` 复制规则。
|
||||
2. 若要随网页宿主一起编译,可仿照 `MAF1.Web.csproj` 增加 `ProjectReference`(`ReferenceOutputAssembly=false`)和 `PublishPluginFolders` 复制规则。
|
||||
3. `id` 不要与系统节点类型冲突;若同名,目录会标记覆盖关系。
|
||||
4. 重启宿主或点「刷新节点」,确认 `/api/catalog` 的 `issues` 为空。
|
||||
|
||||
@@ -345,10 +395,10 @@ JSON 使用 camelCase。静态站点来自 `wwwroot`。
|
||||
## 架构
|
||||
|
||||
```
|
||||
浏览器 wwwroot
|
||||
浏览器 MAF1.Web/wwwroot
|
||||
│ REST
|
||||
▼
|
||||
ASP.NET Minimal API (Program.cs)
|
||||
MAF1.Web Program.cs(Minimal API)
|
||||
│
|
||||
▼
|
||||
AgentRuntime
|
||||
@@ -359,11 +409,11 @@ AgentRuntime
|
||||
└── PluginProcessRunner 子进程 stdin/stdout
|
||||
```
|
||||
|
||||
CLI 不走画布,直接:
|
||||
控制台项目不走画布:
|
||||
|
||||
`CliHost` → `CityWeatherWorkflow`(node / edge)→ `WorkflowOrchestration` 把 Workflow 事件打到控制台。
|
||||
`MAF1` → `CliHost` → `CityWeatherWorkflow`(node / edge)→ `WorkflowOrchestration`。缺城市时走 `RequestPort`,与网页同一套默认方案 + 超时。
|
||||
|
||||
`MAF1.Core` 被宿主与插件共用,避免两套 Agent 逻辑分叉。
|
||||
`MAF1.Core` 被控制台、网页与插件共用,避免两套 Agent 逻辑分叉。
|
||||
|
||||
---
|
||||
|
||||
@@ -377,9 +427,9 @@ CLI 不走画布,直接:
|
||||
北京
|
||||
```
|
||||
|
||||
`Data/not-cities.txt`:不含有效城市,用于验证「无城市则不查天气」。
|
||||
`Data/not-cities.txt`:不含有效城市,用于验证「无城市则询问后默认不查天气」。
|
||||
|
||||
这些文件会随构建复制到输出目录。CLI 传入相对路径时,请在仓库根目录(或已复制 Data 的输出目录)下运行。
|
||||
这些文件会随构建复制到各项目输出目录。CLI 传入相对路径时,请在仓库根目录用 `--project MAF1` 运行,或在已复制 Data 的输出目录下运行。
|
||||
|
||||
---
|
||||
|
||||
@@ -389,7 +439,7 @@ CLI 不走画布,直接:
|
||||
`Llm:ApiKey` 和环境变量都为空。按「快速开始」配置后重启。
|
||||
|
||||
**画布上看不到插件**
|
||||
插件必须出现在 **exe 旁边** 的 `plugins/`。先 `dotnet build`,确认 `bin/Debug/net10.0/plugins/file-city/plugin.json` 存在。源码目录里的 `plugins/` 不会被运行时直接扫描。
|
||||
插件必须出现在 **MAF1.Web.exe 旁边** 的 `plugins/`。先 `dotnet build MAF1.Web/MAF1.Web.csproj`,确认 `MAF1.Web/bin/Debug/net10.0/plugins/file-city/plugin.json` 存在。源码目录里的 `plugins/` 不会被运行时直接扫描。
|
||||
|
||||
**插件超时或卡住**
|
||||
加大 `timeoutSeconds`;确认 LLM 与天气 HTTP 可访问;日志看 stderr。
|
||||
@@ -401,7 +451,7 @@ CLI 不走画布,直接:
|
||||
项目带 `app.manifest` 并在入口启用 UTF-8。若仍乱码,把终端代码页设为 UTF-8。
|
||||
|
||||
**端口被占用**
|
||||
当前 URL 写死为 `127.0.0.1:5288`。关掉占用进程,或临时改 `Program.cs` / `launchSettings.json`。
|
||||
当前 URL 写死为 `127.0.0.1:5288`。关掉占用进程,或临时改 `MAF1.Web/Program.cs` / `MAF1.Web/Properties/launchSettings.json`。
|
||||
|
||||
**没有自动化测试 / Docker**
|
||||
仓库目前没有测试项目和容器文件。验证方式:UI 示例图 + CLI `node` / `edge`。
|
||||
|
||||
Reference in New Issue
Block a user