特性

维度实现
语言/平台C# 13 / .NET 10
Agent 模式ReAct (Thought-Action-Observation) 循环;Agent-as-Tool 嵌套调用
Skill 体系抽象 ISkill + SkillManager 渐进式加载;按用户意图打分挑选 topK
Markdown Skill从指定目录递归加载业界常见 Skill.md / SKILL.md,将 frontmatter 映射为 Skill 清单,正文作为激活后的系统提示
模型 ProviderOpenAI 兼容 (GPT/Kimi/MiniMax/Qwen-compat/DeepSeek/OpenRouter/NEWAPI)、Azure OpenAI、Anthropic Claude、Google Gemini、Qwen DashScope 原生
多模态文本 + 图像 (URL / base64) + 工具调用
流式输出SSE (/api/chat/stream) + SignalR Hub (/hubs/chat)
Web 搜索DuckDuckGo (免 Key) / Tavily / Bing / Brave
Vision 技能image_describe 工具,路由到任意支持视觉的 Provider
MCPJSON-RPC 2.0 Client (stdio) + Server (HTTP/SSE + 把本地 Skill 暴露给外部)
Agent 注册IAgentRegistry,Agent 之间可互相调用 (agent.<id> 工具)
WebUIReact 19 + Vite + TS,SSE 流式渲染、Markdown、图片粘贴
钉钉自定义机器人签名校验 + 分段 Markdown 卡片更新

项目结构

QianYuan.AgenticFramework/
├── QianYuan.AgenticFramework.sln
├── nuget.config                # 锁定到 nuget.org
├── Directory.Build.props       # net10.0, nullable, latest C#
├── src/
│   ├── QianYuan.Core/                    # 抽象 + 模型 + 流式 chunk + 异常
│   ├── QianYuan.Kernel/                  # ReAct 引擎、SkillManager、Agent/Provider 注册表
│   ├── QianYuan.Providers.OpenAICompat/  # OpenAI 协议 (GPT/Kimi/MiniMax/Qwen-compat/NEWAPI)
│   ├── QianYuan.Providers.AzureOpenAI/   # Azure OpenAI Service (deployment + api-version)
│   ├── QianYuan.Providers.Anthropic/     # Claude Messages API
│   ├── QianYuan.Providers.Gemini/        # Gemini v1beta
│   ├── QianYuan.Providers.QwenNative/    # DashScope 原生
│   ├── QianYuan.Skills.Builtin/          # WebSearch / Vision / FileSystem / Code
│   ├── QianYuan.Mcp/                     # MCP Client (stdio) + Server core
│   ├── QianYuan.Integrations.DingTalk/   # 钉钉 webhook 收发
│   ├── QianYuan.Api/                     # ASP.NET Core 10 host (SSE + SignalR + Swagger)
│   └── QianYuan.Web/                     # React + Vite WebUI
├── samples/QianYuan.Sample.Console/
└── tests/QianYuan.Core.Tests/            # xUnit + FluentAssertions

快速开始

0. 一键启动脚本(推荐)

仓库内置了三平台的一键脚本,会自动 restore + build + 启动 Api 与 WebUI,
日志写到 .runtime/logs/,进程 PID 写到 .runtime/*.pid

# macOS / Linux
./scripts/start.sh        # 启动
./scripts/start.sh --stop # 停止

# Windows (cmd / PowerShell 任一)
scripts\start.cmd
scripts\stop.cmd
# 或直接
pwsh -File scripts\start.ps1
pwsh -File scripts\start.ps1 -Stop

脚本会检测 .NET 10 SDK 与 Node.js (>=18);缺 Node 时只起 Api。
默认地址:Api http://localhost:5050(Swagger /swagger),WebUI http://localhost:5173
通过 QIANYUAN_API_URL / QIANYUAN_WEB_URL 环境变量可覆盖。

1. 编译

cd QianYuan.AgenticFramework
dotnet build

2. 配置 API Key

编辑 src/QianYuan.Api/appsettings.json 或使用 user-secrets / 环境变量。
任何一家 Provider 配上 ApiKey 即可启动。

NEWAPI / One-Hub / 第三方聚合代理

NEWAPI 完全兼容 OpenAI Chat Completions 协议,直接作为一个 OpenAICompatProviders 条目即可:

{
  "ProviderId": "newapi",
  "BaseUrl": "https://your-newapi-host/v1",
  "ApiKey": "sk-...",
  "DefaultModel": "gpt-4o-mini",
  "SupportsVision": true
}

ProviderId 任取,Kernel 通过它路由;BaseUrl 是你 NEWAPI 部署的对外地址。

Azure OpenAI Service

Azure 的 URL 由 deployment 名决定(不是 model 名),并且需要 api-version 查询参数和
api-key 请求头。在 AzureOpenAIProviders 数组里配置:

{
  "ProviderId": "azure-openai",
  "Endpoint": "https://your-resource.openai.azure.com",
  "ApiKey": "<your-key>",
  "DefaultDeployment": "gpt-4o",
  "ApiVersion": "2024-10-21",
  "SupportsVision": true,
  "ModelToDeployment": {
    "gpt-4o": "gpt-4o-prod",
    "gpt-4o-mini": "gpt-4o-mini-prod"
  }
}
  • Endpoint 不要带 /openai 之类路径,框架会自动拼接。
  • ModelToDeployment 可选,把"逻辑模型名"映射到 Azure 上的实际部署名;
    没配的话,请求里给的 Model 会被直接当作 deployment 用。
  • 同一个数组里可以多份配置不同的 ProviderId,比如分别接 Sweden 与 East-US 两个资源。

3. 启动 WebAPI

dotnet run --project src/QianYuan.Api
# 监听 http://localhost:5050  (Swagger: /swagger)

4. 启动 WebUI

cd src/QianYuan.Web
npm install
npm run dev
# 浏览器打开 http://localhost:5173

Vite dev-server 已配置反向代理:/api 和 /hubs 自动转发到 5050。

5. 跑控制台样例

export QIANYUAN_APIKEY=sk-...
export QIANYUAN_BASEURL=https://api.openai.com/v1
export QIANYUAN_MODEL=gpt-4o-mini
dotnet run --project samples/QianYuan.Sample.Console

6. 跑单元测试

dotnet test

核心抽象(最小集)

public interface ILlmProvider
{
    string ProviderId { get; }
    string DefaultModel { get; }
    LlmCapabilities Capabilities { get; }
    Task<ChatResponse> CompleteAsync(ChatRequest req, CancellationToken ct);
    IAsyncEnumerable<StreamingChunk> StreamAsync(ChatRequest req, CancellationToken ct);
}

public interface ISkill
{
    string Id { get; }
    ValueTask<IReadOnlyList<ToolDefinition>> GetToolsAsync(CancellationToken ct);
    ValueTask<SkillInvocationResult> InvokeAsync(string toolName, string argsJson, SkillInvocationContext ctx, CancellationToken ct);
}

public interface IAgent
{
    string Id { get; }
    IAsyncEnumerable<StreamingChunk> RunAsync(AgentRunRequest req, CancellationToken ct);
}

StreamingChunk 是统一的流式事件 (TextDelta / ThinkingDelta / ToolCallStart / ToolCallArgsDelta /
ToolCallEnd / ToolObservation / Usage / End / Error / Warning),
四家 Provider 都把各自协议规整成它。

ReAct 循环要点

QianYuan.Kernel.ReAct.ReActEngine 每轮:

  1. 用 ISkillManager.SelectRelevantAsync(intent, topK) 渐进式挑选 Skill。
  2. 把已挑选 Skill 的工具 + 注册的其他 Agent (作为 agent.<id> 工具) 合并发给 LLM。
  3. 流式接收 LLM 输出:
    • 文本/思考 → 直接转发给上层。
    • ToolCall (流式 args) → 累积后通过 IToolDispatcher 路由到对应 Skill 或子 Agent。
    • Tool 结果 → 作为 ChatRole.Tool 消息追加到历史,继续下一轮。
  4. 没有新 ToolCall → 终止,发 End

每轮都会重新计算活动 Skill 集合,所以"渐进式扩展"是自动发生的。
默认 Agent 的最大 ReAct 迭代次数由 QianYuan.DefaultAgentMaxIterations 控制,默认值为 100;单次请求仍可通过 MaxIterations 覆盖。

{
  "QianYuan": {
    "DefaultAgentMaxIterations": 100
  }
}

MCP

  • 作为客户端services.AddMcpStdioServer(new McpStdioServerConfig { ServerId="fs", Command="npx", Arguments=["-y","@modelcontextprotocol/server-filesystem","/tmp"] })
    ,启动后 sp.MountMcpSkills() 把外部 MCP Server 的所有工具挂成名为 mcp.fs 的 Skill。
  • 作为服务端: WebAPI 暴露 POST /api/mcp,把本地 SkillManager 的所有工具按 MCP 协议提供给外部 MCP Client (Claude Desktop / Cursor / etc.)。

Web 搜索

"WebSearch": {
  "Provider": "duckduckgo",
  "ApiKey": ""
}
  • duckduckgo / ddg(默认):抓取 html.duckduckgo.com 的免 Key 服务,开箱即用,
    适合本地开发和轻量场景;DDG 会限速,重负载请改用付费服务。
  • tavily / bing / brave:填入相应平台的 ApiKey 即可。
  • 任何 Provider 的 ApiKey 为空时,框架会自动回退到 DuckDuckGo。

Skill 文件体系与扩展注册

QianYuan 支持三类 Skill 来源:代码实现的 ISkill、目录中的 Markdown Skill、以及外部 MCP Server 暴露的工具。
所有来源最终都会进入 ISkillManager,以统一的 manifest 参与渐进式选择;当某个 Skill 被选中时,它的工具会进入 LLM tools,
它的 SystemPromptFragment 也会注入当前轮 system prompt。

Skill 能力分层:

类型能力典型用途是否暴露工具
Markdown Skill根据 SKILL.md 注入领域提示code review、需求分析、API 设计规范
内置 SkillWeb 搜索、视觉、文件系统、脚本执行联网查询、图片理解、沙箱文件读写、运行代码片段
自定义 ISkill任意业务工具或系统集成调内部服务、工作流编排、专有数据查询
MCP Skill调用外部 MCP Server 工具filesystem、browser、database、第三方工具生态

其中脚本执行由内置 qianyuan.code Skill 提供,MCP Server 调用由 McpSkill 适配为一组命名空间化工具。

Markdown Skill 文件体系

可以把 Claude/Copilot/Cursor 等常见的 Skill.md / SKILL.md 目录挂载到 QianYuan。框架会读取 YAML frontmatter 中的
namedescriptiontagsid 等字段,注册成渐进式 Skill;当该 Skill 被选中时,Markdown 正文会注入系统提示。

目录约定:

  • 每个 Skill 一个独立目录,目录内放 SKILL.md 或 Skill.md
  • Recursive = true 时会递归扫描子目录,适合挂载已有的 agent skill 仓库。
  • id 可在 frontmatter 显式声明;未声明时会用 IdPrefix + 相对目录 生成稳定 ID。
  • 同一挂载目录内 ID 重复时,后续重复项会被跳过并记录 warning。
  • Markdown Skill 是提示型 Skill,ApproximateToolCount = 0,不直接暴露工具调用。

支持的 frontmatter 字段:

字段作用备注
idSkill 唯一标识可选;会规范化为小写点分 ID
name / titleSkill 展示名称没有时回退到目录名
description / summary用于渐进式选择的描述没有时回退到正文第一行
tags / keywords / categories检索标签支持 [a, b] 或 YAML list

示例文件结构:

skills/
  code-review/
    SKILL.md
  pdf/
    Skill.md

示例 SKILL.md

---
id: sample.code-review
name: code-review
description: Review code for bugs, regressions, and missing tests
tags: [review, testing]
---

# Code Review

Prioritize correctness issues before style comments.

动态加载目录

在 QianYuan.SkillDirectories 中声明要挂载的 Skill 目录:

{
  "QianYuan": {
    "SkillDirectories": [
      {
        "Path": "./samples/skills",
        "Recursive": true,
        "Enabled": true,
        "IdPrefix": "sample"
      },
      {
        "Path": "/Users/you/.agents/skills",
        "Recursive": true,
        "Enabled": true,
        "IdPrefix": "agent"
      }
    ]
  }
}

仓库内置了一个可直接动态加载的示例目录:

samples/skills/
  api-design/SKILL.md
  code-review/SKILL.md
  debugging/SKILL.md
  docs-writing/SKILL.md
  requirements-analysis/SKILL.md

默认 appsettings.json 已启用 ./samples/skills。启动 API 后可通过 GET /api/skills 查看这些示例 Skill 是否注册成功。

API 启动时会执行:

app.Services.RegisterMarkdownSkillsFromDirectories(qy.SkillDirectories.Select(d => new MarkdownSkillDirectoryOptions
{
  Path = d.Path,
  Recursive = d.Recursive,
  Enabled = d.Enabled,
  IdPrefix = d.IdPrefix,
}));

代码 Skill 注册

需要真实工具能力时,实现 ISkill,提供 manifest 属性、工具定义和调用逻辑:

public sealed class MySkill : ISkill
{
  public string Id => "my.skill";
  public string Name => "My Skill";
  public string Description => "Does one focused job.";
  public IReadOnlyList<string> Tags => ["custom"];
  public string? SystemPromptFragment => "Use this skill only when the task matches its description.";

  public ValueTask<IReadOnlyList<ToolDefinition>> GetToolsAsync(CancellationToken ct = default)
    => ValueTask.FromResult<IReadOnlyList<ToolDefinition>>([
      new ToolDefinition(
        "my_tool",
        "Run my custom operation.",
        "{\"type\":\"object\",\"properties\":{}}")
    ]);

  public ValueTask<SkillInvocationResult> InvokeAsync(
    string toolName,
    string argumentsJson,
    SkillInvocationContext context,
    CancellationToken ct = default)
    => ValueTask.FromResult(SkillInvocationResult.Ok("{\"ok\":true}"));
}

注册方式有两种。

通过 DI 注册,适合普通应用启动:

builder.Services.AddSingleton<ISkill, MySkill>();
// app.Build() 后统一挂载到 SkillManager
app.Services.RegisterSkillsFromServices();

直接注册到 ISkillManager,适合运行期管理、插件发现或测试:

var manager = app.Services.GetRequiredService<ISkillManager>();
manager.Register(new MySkill());

如果 Skill 初始化成本较高,也可以只注册轻量 manifest + factory,首次命中时再物化:

manager.Register(
  new SkillManifest(
    "my.lazy-skill",
    "Lazy Skill",
    "Loads resources only when selected.",
    ["custom", "lazy"],
    ApproximateToolCount: 1,
    RequiresNetwork: false,
    RequiresFilesystem: false),
  sp => new MySkill());

脚本执行 Skill

内置 Code Execution Skill 会暴露 code_run 工具,用于在沙箱目录中执行短脚本。默认关闭,需要显式启用:

{
  "QianYuan": {
    "CodeExecution": {
      "Enabled": true,
      "SandboxDirectory": "./_sandbox/code",
      "AllowedRuntimes": ["python", "node"],
      "TimeoutSeconds": 20
    }
  }
}

启用后启动流程会注册:

builder.Services.AddCodeExecutionSkill(new CodeExecutionOptions
{
    SandboxDirectory = cx.SandboxDirectory,
    AllowedRuntimes = new HashSet<string>(cx.AllowedRuntimes, StringComparer.OrdinalIgnoreCase),
    PerCallTimeout = TimeSpan.FromSeconds(cx.TimeoutSeconds),
});

工具协议:

{
  "runtime": "python",
  "code": "print(1 + 1)"
}

当前支持的运行时由 AllowedRuntimes 控制,内置实现支持 pythonnodebash;建议生产环境只开放必要运行时,并把 SandboxDirectory 指向隔离目录。

MCP Skill 注册

外部 MCP Server 可以作为 Skill 挂载。配置 stdio server 后,启动时调用 MountMcpSkills(),每个 MCP client 会被适配成一个 McpSkill

{
  "QianYuan": {
    "McpServers": [
      {
        "ServerId": "fs",
        "Command": "npx",
        "Arguments": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
        "Environment": {}
      }
    ]
  }
}
builder.Services.AddMcpStdioServer(new McpStdioServerConfig
{
  ServerId = "fs",
  Command = "npx",
  Arguments = ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
});

app.Services.MountMcpSkills();

挂载后 Skill ID 为 mcp.<serverId>,工具名会统一前缀化为 mcp.<serverId>.<toolName>,避免多个 MCP Server 之间的工具名冲突。工具列表会延迟到第一次需要该 Skill 时通过 MCP ListTools 获取,调用时再转发到对应 MCP Server 的 CallTool

注册生命周期

启动流程里,Skill 注册顺序是:

  1. RegisterSkillsFromServices():挂载内置 Skill 和通过 DI 注册的自定义 ISkill
  2. RegisterMarkdownSkillsFromDirectories(...):按配置目录动态加载 SKILL.md / Skill.md
  3. MountMcpSkills():把外部 MCP Server 工具挂载为 Skill。

注册完成后,GET /api/skills 可查看 catalog;ReAct 每轮会调用 SelectRelevantAsync(intent, topK) 选择当前最相关的 Skill。

这类 Markdown Skill 是“提示型技能”,不会执行外部命令或暴露工具调用;需要真实工具能力时仍建议实现 ISkill 或通过 MCP 挂载。

钉钉

  1. 创建自定义机器人,拿 outgoing webhook URL + 加签 secret。
  2. 在 appsettings.json 配 QianYuan.DingTalk.Enabled = true 等字段。
  3. 把回调地址 https://<your-host>/api/dingtalk/webhook 配进钉钉机器人。
  4. 框架会签名校验、丢给默认 Agent、把 streaming 文本周期性 markdown 推回。

更多推荐