feat: 将插件 stdin 协议定为 v1,并按声明注入凭据
进程外插件改为只读环境变量和自身配置,避免读宿主 appsettings;宿主按 plugin.json 声明注入 LLM / OpenWeather 等凭据。
This commit is contained in:
+17
-4
@@ -2,19 +2,32 @@
|
||||
|
||||
每个子文件夹是一个插件。软件启动后扫描**网页宿主执行目录**下的 `plugins/`(即 `MAF1.Web.exe` 旁边),不是源码目录。
|
||||
|
||||
协议:`plugin.json` 的 `protocolVersion` 为 **1**(缺省也当 1)。stdin 形状见 [docs/plugin-protocol-v1.schema.json](../docs/plugin-protocol-v1.schema.json)。
|
||||
|
||||
## 必备文件
|
||||
|
||||
- `plugin.json`:id、启动命令、凭据声明、inputs、outputs
|
||||
- `plugin.json`:id、protocolVersion、启动命令、凭据声明、inputs、outputs
|
||||
- `README.md`:给使用者看的说明
|
||||
- 可执行文件 / 脚本 / 源码:由 `launch.command` + `launch.args` 原样启动
|
||||
|
||||
非秘密配置(例如天气 URL 模板)放在**插件自己目录**的 `appsettings.json` 或 `plugin.json` 的 `env`。不要去读宿主 exe 旁的配置文件。
|
||||
|
||||
## 凭据(不要写进 plugin.json)
|
||||
|
||||
n8n / Dify 一类产品把 API Key 放在宿主凭据库,节点只声明「我需要哪种凭据」。本项目同样:
|
||||
|
||||
- Key 和 endpoint 配在宿主的环境变量或 `appsettings.json`
|
||||
- 节点可选 `credentialId`(默认 `llm-default`)
|
||||
- 启动子进程时注入环境变量,并在 stdin JSON 的 `credentials` 里再传一份
|
||||
- Key 和 endpoint 配在宿主的环境变量或 `appsettings.json` 的 `Credentials` / `Llm`
|
||||
- 节点可选 `credentialId`(LLM,默认 `llm-default`);其它类型用 `credential:<name>` 或按 type 取默认
|
||||
- 只注入 **plugin.json 里声明过的**凭据。未声明 LLM 的插件拿不到 `OPENAI_API_KEY`
|
||||
- 启动子进程时写入对应环境变量,并在 stdin JSON 的 `credentials` 里再传一份
|
||||
- 浏览器和流程图 JSON **不会**包含 apiKey
|
||||
|
||||
已知 `type` 与环境变量:
|
||||
|
||||
| type | 环境变量 |
|
||||
|------|----------|
|
||||
| `openai-compatible` / `llm` | `OPENAI_ENDPOINT` / `OPENAI_API_KEY` / `OPENAI_CHAT_MODEL` |
|
||||
| `openweather` | `OPENWEATHER_API_KEY` |
|
||||
| 其它 | `extra` 里的键原样写入环境变量 |
|
||||
|
||||
第三方若要用自己的模型,可在插件目录放 `.env`(不要提交)。节点选中的宿主凭据会覆盖其中的同名变量。
|
||||
|
||||
@@ -7,7 +7,7 @@ using MAF1.Utils;
|
||||
PluginRequest request = await PluginStdio.ReadRequestAsync();
|
||||
PluginStdio.ApplyCredentialsToEnvironment(request.Credentials);
|
||||
|
||||
AgentFactory factory = new(AgentFactory.Load(PluginStdio.LoadHostConfiguration()));
|
||||
AgentFactory factory = new(AgentFactory.LoadFromEnvironment());
|
||||
var result = await FileCityAgent.RunAsync(FileCityAgent.Create(factory), request.Inputs);
|
||||
await PluginStdio.WriteOutputsAsync(result.Outputs);
|
||||
return 0;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
独立进程。宿主只读取本目录的 `plugin.json`,按 `launch` 原样启动,不会替你拼命令。
|
||||
|
||||
`protocolVersion` 为 1。LLM 只从环境变量读取(宿主注入),不读宿主 `appsettings.json`。
|
||||
|
||||
## 输入 / 输出
|
||||
|
||||
字段名必须和 `plugin.json` 以及 `Program.cs` 里读写的 JSON 键一致。
|
||||
@@ -24,6 +26,7 @@ stdin:
|
||||
|
||||
```json
|
||||
{
|
||||
"protocolVersion": 1,
|
||||
"inputs": { "filePath": "Data/cities.txt" },
|
||||
"credentials": {
|
||||
"llm": { "id": "llm-default", "type": "openai-compatible", "endpoint": "...", "apiKey": "...", "model": "..." }
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"name": "FileCityAgent",
|
||||
"description": "读取指定文本文件,判断并抽出有效城市名。",
|
||||
"version": "1.0.0",
|
||||
"protocolVersion": 1,
|
||||
"timeoutSeconds": 120,
|
||||
"launch": {
|
||||
"command": "dotnet",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// 进程外 Weather 插件。同样只做 stdio 适配,天气实现仍在 MAF1.Core。
|
||||
// 进程外 Weather 插件。stdio 适配 + 复用 MAF1.Core 的 WeatherAgent。
|
||||
// LLM / OpenWeather Key 只来自宿主注入的环境变量;天气站点配置读本插件目录的 appsettings.json。
|
||||
using MAF1.Agents.Weather;
|
||||
using MAF1.PluginContract;
|
||||
using MAF1.Tools;
|
||||
@@ -8,9 +9,15 @@ using Microsoft.Extensions.Configuration;
|
||||
PluginRequest request = await PluginStdio.ReadRequestAsync();
|
||||
PluginStdio.ApplyCredentialsToEnvironment(request.Credentials);
|
||||
|
||||
IConfiguration config = PluginStdio.LoadHostConfiguration();
|
||||
IConfiguration config = PluginStdio.LoadPluginConfiguration();
|
||||
AgentFactory factory = new(AgentFactory.Load(config));
|
||||
WeatherOptions weatherOptions = config.GetSection("Weather").Get<WeatherOptions>() ?? new WeatherOptions();
|
||||
string? openWeatherKey = Environment.GetEnvironmentVariable("OPENWEATHER_API_KEY");
|
||||
if (!string.IsNullOrWhiteSpace(openWeatherKey))
|
||||
{
|
||||
weatherOptions.OpenWeather.ApiKey = openWeatherKey;
|
||||
}
|
||||
|
||||
using HttpClient http = WeatherTools.CreateHttpClient();
|
||||
var result = await WeatherAgent.RunAsync(
|
||||
WeatherAgent.Create(factory, new WeatherTools(weatherOptions, http)),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# Weather 插件
|
||||
|
||||
独立进程。启动命令只看 `plugin.json` 的 `launch`。
|
||||
独立进程。启动命令只看 `plugin.json` 的 `launch`。`protocolVersion` 为 1。
|
||||
|
||||
## 输入 / 输出
|
||||
|
||||
- 输入 `cities`(字符串数组,与代码、清单同名)
|
||||
- 输出 `summary`
|
||||
|
||||
## 凭据
|
||||
## 凭据与配置
|
||||
|
||||
LLM 的 endpoint / apiKey 由宿主注入,不出现在流程图端口上。天气 HTTP 配置走宿主 `appsettings.json`(通过环境变量 `MAF1_CONTENT_ROOT` 定位)。
|
||||
- LLM:宿主注入 `OPENAI_*`,以及 stdin `credentials.llm`
|
||||
- OpenWeather(可选):宿主凭据类型 `openweather`,注入 `OPENWEATHER_API_KEY`。默认 Provider 是免费的 Wttr,不需要这个 Key
|
||||
- 天气 URL / 语言:读**本插件目录**的 `appsettings.json`,不读宿主配置
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
<None Update="plugin.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="appsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="README.md">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Weather": {
|
||||
"Provider": "Wttr",
|
||||
"Language": "zh",
|
||||
"Wttr": {
|
||||
"UrlTemplate": "https://wttr.in/{location}?lang={lang}&format=3"
|
||||
},
|
||||
"OpenWeather": {
|
||||
"UrlTemplate": "https://api.openweathermap.org/data/2.5/weather?q={location}&appid={apiKey}&units=metric&lang={lang}",
|
||||
"ApiKey": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"name": "WeatherAgent",
|
||||
"description": "按城市列表查询天气并汇总。",
|
||||
"version": "1.0.0",
|
||||
"protocolVersion": 1,
|
||||
"timeoutSeconds": 180,
|
||||
"launch": {
|
||||
"command": "dotnet",
|
||||
@@ -14,6 +15,12 @@
|
||||
"type": "openai-compatible",
|
||||
"required": true,
|
||||
"description": "由宿主注入 OpenAI 兼容的 endpoint / apiKey / model,不要写进本文件。"
|
||||
},
|
||||
{
|
||||
"name": "weather",
|
||||
"type": "openweather",
|
||||
"required": false,
|
||||
"description": "仅当本插件 appsettings.json 的 Weather:Provider 为 OpenWeather 时需要。Wttr 免费接口不用填。"
|
||||
}
|
||||
],
|
||||
"inputs": [
|
||||
|
||||
Reference in New Issue
Block a user