近期使用Claude Code + Opus4.7设计开发了一个开源项目:Qianyuan AI Agentic Framework
特性维度实现语言/平台C# 13 / .NET 10Agent 模式ReAct (Thought-Action-Observation) 循环Agent-as-Tool 嵌套调用Skill 体系抽象ISkillSkillManager渐进式加载按用户意图打分挑选 topKMarkdown 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 / BraveVision 技能image_describe工具路由到任意支持视觉的 ProviderMCPJSON-RPC 2.0 Client (stdio) Server (HTTP/SSE 把本地 Skill 暴露给外部)Agent 注册IAgentRegistryAgent 之间可互相调用 (agent.id工具)WebUIReact 19 Vite TSSSE 流式渲染、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. 一键启动脚本推荐仓库内置了三平台的一键脚本会自动restorebuild 启动 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。默认地址Apihttp://localhost:5050Swagger/swaggerWebUIhttp://localhost:5173。通过QIANYUAN_API_URL/QIANYUAN_WEB_URL环境变量可覆盖。1. 编译cd QianYuan.AgenticFramework dotnet build2. 配置 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 ServiceAzure 的 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. 启动 WebAPIdotnet run --project src/QianYuan.Api # 监听 http://localhost:5050 (Swagger: /swagger)4. 启动 WebUIcd src/QianYuan.Web npm install npm run dev # 浏览器打开 http://localhost:5173Vite dev-server 已配置反向代理/api和/hubs自动转发到 5050。5. 跑控制台样例export QIANYUAN_APIKEYsk-... export QIANYUAN_BASEURLhttps://api.openai.com/v1 export QIANYUAN_MODELgpt-4o-mini dotnet run --project samples/QianYuan.Sample.Console6. 跑单元测试dotnet test核心抽象最小集public interface ILlmProvider { string ProviderId { get; } string DefaultModel { get; } LlmCapabilities Capabilities { get; } TaskChatResponse CompleteAsync(ChatRequest req, CancellationToken ct); IAsyncEnumerableStreamingChunk StreamAsync(ChatRequest req, CancellationToken ct); } public interface ISkill { string Id { get; } ValueTaskIReadOnlyListToolDefinition GetToolsAsync(CancellationToken ct); ValueTaskSkillInvocationResult InvokeAsync(string toolName, string argsJson, SkillInvocationContext ctx, CancellationToken ct); } public interface IAgent { string Id { get; } IAsyncEnumerableStreamingChunk RunAsync(AgentRunRequest req, CancellationToken ct); }StreamingChunk是统一的流式事件 (TextDelta / ThinkingDelta / ToolCallStart / ToolCallArgsDelta /ToolCallEnd / ToolObservation / Usage / End / Error / Warning)四家 Provider 都把各自协议规整成它。ReAct 循环要点QianYuan.Kernel.ReAct.ReActEngine每轮用ISkillManager.SelectRelevantAsync(intent, topK)渐进式挑选 Skill。把已挑选 Skill 的工具 注册的其他 Agent (作为agent.id工具) 合并发给 LLM。流式接收 LLM 输出文本/思考 → 直接转发给上层。ToolCall (流式 args) → 累积后通过IToolDispatcher路由到对应 Skill 或子 Agent。Tool 结果 → 作为ChatRole.Tool消息追加到历史继续下一轮。没有新 ToolCall → 终止发End。每轮都会重新计算活动 Skill 集合所以渐进式扩展是自动发生的。默认 Agent 的最大 ReAct 迭代次数由QianYuan.DefaultAgentMaxIterations控制默认值为100单次请求仍可通过MaxIterations覆盖。{ QianYuan: { DefaultAgentMaxIterations: 100 } }MCP作为客户端:services.AddMcpStdioServer(new McpStdioServerConfig { ServerIdfs, Commandnpx, 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.codeSkill 提供MCP Server 调用由McpSkill适配为一组命名空间化工具。Markdown Skill 文件体系可以把 Claude/Copilot/Cursor 等常见的Skill.md/SKILL.md目录挂载到 QianYuan。框架会读取 YAML frontmatter 中的name、description、tags、id等字段注册成渐进式 Skill当该 Skill 被选中时Markdown 正文会注入系统提示。目录约定每个 Skill 一个独立目录目录内放SKILL.md或Skill.md。Recursive true时会递归扫描子目录适合挂载已有的 agent skill 仓库。id可在 frontmatter 显式声明未声明时会用IdPrefix 相对目录生成稳定 ID。同一挂载目录内 ID 重复时后续重复项会被跳过并记录 warning。Markdown Skill 是提示型 SkillApproximateToolCount 0不直接暴露工具调用。支持的 frontmatter 字段字段作用备注idSkill 唯一标识可选会规范化为小写点分 IDname/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 IReadOnlyListstring Tags [custom]; public string? SystemPromptFragment Use this skill only when the task matches its description.; public ValueTaskIReadOnlyListToolDefinition GetToolsAsync(CancellationToken ct default) ValueTask.FromResultIReadOnlyListToolDefinition([ new ToolDefinition( my_tool, Run my custom operation., {\type\:\object\,\properties\:{}}) ]); public ValueTaskSkillInvocationResult InvokeAsync( string toolName, string argumentsJson, SkillInvocationContext context, CancellationToken ct default) ValueTask.FromResult(SkillInvocationResult.Ok({\ok\:true})); }注册方式有两种。通过 DI 注册适合普通应用启动builder.Services.AddSingletonISkill, MySkill(); // app.Build() 后统一挂载到 SkillManager app.Services.RegisterSkillsFromServices();直接注册到ISkillManager适合运行期管理、插件发现或测试var manager app.Services.GetRequiredServiceISkillManager(); 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 HashSetstring(cx.AllowedRuntimes, StringComparer.OrdinalIgnoreCase), PerCallTimeout TimeSpan.FromSeconds(cx.TimeoutSeconds), });工具协议{ runtime: python, code: print(1 1) }当前支持的运行时由AllowedRuntimes控制内置实现支持python、node、bash建议生产环境只开放必要运行时并把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 时通过 MCPListTools获取调用时再转发到对应 MCP Server 的CallTool。注册生命周期启动流程里Skill 注册顺序是RegisterSkillsFromServices()挂载内置 Skill 和通过 DI 注册的自定义ISkill。RegisterMarkdownSkillsFromDirectories(...)按配置目录动态加载SKILL.md/Skill.md。MountMcpSkills()把外部 MCP Server 工具挂载为 Skill。注册完成后GET /api/skills可查看 catalogReAct 每轮会调用SelectRelevantAsync(intent, topK)选择当前最相关的 Skill。这类 Markdown Skill 是“提示型技能”不会执行外部命令或暴露工具调用需要真实工具能力时仍建议实现ISkill或通过 MCP 挂载。钉钉创建自定义机器人拿 outgoing webhook URL 加签 secret。在appsettings.json配QianYuan.DingTalk.Enabled true等字段。把回调地址https://your-host/api/dingtalk/webhook配进钉钉机器人。框架会签名校验、丢给默认 Agent、把 streaming 文本周期性 markdown 推回。