ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 Claude Code + Opus4.7 从零搭建 Qianyuan AI Agentic Framework:TaoToken 统一 Key 接入实录

用 Claude Code + Opus4.7 从零搭建 Qianyuan AI Agentic Framework:TaoToken 统一 Key 接入实录 1. 从零搭 Qianyuan AI Agentic Framework 的真实起点Qianyuan AI Agentic Framework 是一个用 C# 13 / .NET 10 写的开源 Agent 编排框架核心是把 ReAct 循环、Skill 渐进式加载、多 Provider 模型接入和 MCP 工具协议揉进一套可扩展的骨架里。它适合谁适合已经会用 Claude Code 写代码、但不想每次从零手搓 Agent 循环的后端开发者也适合想把本地 Markdown Skill 目录直接挂载成 Agent 能力的团队。我这次全程用 Claude Code 配合 Opus4.7 来设计和实现模型调用统一走 TaoToken 的 Key/API 通道省掉了在多个 Provider 之间来回切 Key 的麻烦。先说清楚这个框架能做什么。它的 Agent 模式是标准 ReActThought-Action-Observation 循环每一轮用ISkillManager.SelectRelevantAsync(intent, topK)渐进式挑选 Skill把选中 Skill 的工具和注册的其他 Agent以agent.id形式暴露合并发给 LLM流式接收输出遇到 ToolCall 就路由到对应 Skill 或子 Agent工具结果作为ChatRole.Tool消息追加历史继续下一轮直到没有新 ToolCall 就发 End。默认最大迭代次数由QianYuan.DefaultAgentMaxIterations控制默认 100单次请求还能用MaxIterations覆盖。Skill 体系分三类来源代码实现的ISkill、目录里的 Markdown Skill、外部 MCP Server 暴露的工具。三者最终都进ISkillManager以统一 manifest 参与渐进式选择。Markdown Skill 是提示型技能ApproximateToolCount 0不直接暴露工具调用只在该 Skill 被选中时把正文注入系统提示需要真实工具能力时要么实现ISkill要么通过 MCP 挂载。模型 Provider 这块覆盖 OpenAI 兼容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。MCP 既能当客户端stdio也能当服务端HTTP/SSE把本地 Skill 暴露给外部。项目结构大致是这样QianYuan.AgenticFramework/ ├── QianYuan.AgenticFramework.sln ├── nuget.config ├── Directory.Build.props ├── src/ │ ├── QianYuan.Core/ │ ├── QianYuan.Kernel/ │ ├── QianYuan.Providers.OpenAICompat/ │ ├── QianYuan.Providers.AzureOpenAI/ │ ├── QianYuan.Providers.Anthropic/ │ ├── QianYuan.Providers.Gemini/ │ ├── QianYuan.Providers.QwenNative/ │ ├── QianYuan.Skills.Builtin/ │ ├── QianYuan.Mcp/ │ ├── QianYuan.Integrations.DingTalk/ │ ├── QianYuan.Api/ │ └── QianYuan.Web/ ├── samples/QianYuan.Sample.Console/ └── tests/QianYuan.Core.Tests/我踩过的坑是一开始想用 Claude Code 直接生成整个解决方案结果 Opus4.7 给出的 Provider 抽象和 Kernel 耦合太紧后来改成先让它只产出QianYuan.Core的接口定义确认ILlmProvider、ISkill、IAgent三个抽象稳定后再逐层往上写 Kernel 和 Provider返工量小很多。这个顺序建议你也照做。2. TaoToken 统一 Key 接入前置准备在动手写 Provider 之前先把模型通道打通。TaoToken 在这里的角色是统一 Key/API 通道你只需要一个 Base URL 和一个 Key就能在框架里通过ProviderId路由到不同模型不用为每家 Provider 单独维护一套鉴权逻辑。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。前置准备分三步。第一步拿到 Key。登录后在控制台创建 API Key路径是 console 页面创建完记得复制保存页面刷新后不再完整显示。第二步确认你要用的模型 ID。Qianyuan 的 Provider 配置里DefaultModel填的是逻辑模型名TaoToken 侧支持的模型 ID 以文档为准接入文档在 doc 页面。第三步把 Base URL 和 Key 写进配置。这里有个关键点QianYuan 的OpenAICompatProviders数组里BaseUrl要填 TaoToken 的 API 根地址注意不要带多余的路径后缀框架会自动拼接/chat/completions。如果你填成https://taotoken.net/api/v1/v1这种重复路径会直接 404。配置片段长这样路径是src/QianYuan.Api/appsettings.json{ QianYuan: { DefaultAgentMaxIterations: 100, OpenAICompatProviders: [ { ProviderId: taotoken, BaseUrl: https://taotoken.net/api, ApiKey: sk-你的Key, DefaultModel: claude-opus-4-7, SupportsVision: true } ] } }ProviderId任取Kernel 通过它路由BaseUrl是 TaoToken 的对外地址ApiKey就是刚创建的那串DefaultModel填你要用的模型 IDSupportsVision决定这个 Provider 是否参与 Vision 技能路由。如果你不想把 Key 写进appsettings.json可以用 user-secrets 或环境变量。环境变量方式在启动前设置export QIANYUAN_APIKEYsk-你的Key export QIANYUAN_BASEURLhttps://taotoken.net/api export QIANYUAN_MODELclaude-opus-4-7控制台样例samples/QianYuan.Sample.Console会读这三个环境变量。注意环境变量名和配置文件字段不是一一对应控制台样例走的是独立读取逻辑别混用。还有一点TaoToken 的 Key 在框架里只作为OpenAICompatProviders的一个条目存在Kernel 不关心它背后是哪家模型只按 OpenAI Chat Completions 协议发请求。这意味着你后面想换模型只改DefaultModel就行不用动 Kernel 代码。这个解耦是整套设计里最省事的地方。3. 可复制配置与 Agent 编排骨架代码这一节给你能直接抄的配置和骨架代码。先看完整的appsettings.json把 Provider、Skill 目录、代码执行、MCP 都配齐{ QianYuan: { DefaultAgentMaxIterations: 100, OpenAICompatProviders: [ { ProviderId: taotoken, BaseUrl: https://taotoken.net/api, ApiKey: sk-你的Key, DefaultModel: claude-opus-4-7, SupportsVision: true } ], SkillDirectories: [ { Path: ./samples/skills, Recursive: true, Enabled: true, IdPrefix: sample } ], CodeExecution: { Enabled: true, SandboxDirectory: ./_sandbox/code, AllowedRuntimes: [python, node], TimeoutSeconds: 20 }, McpServers: [ { ServerId: fs, Command: npx, Arguments: [-y, modelcontextprotocol/server-filesystem, /tmp], Environment: {} } ], WebSearch: { Provider: duckduckgo, ApiKey: } } }核心抽象最小集这三个接口是整套框架的地基先让 Claude Code 生成它们确认签名稳定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 合并发给 LLM流式接收输出文本/思考直接转发ToolCall 累积后通过IToolDispatcher路由工具结果作为ChatRole.Tool消息追加历史继续下一轮。没有新 ToolCall 就终止发 End。每轮都重新计算活动 Skill 集合所以渐进式扩展是自动发生的。自定义 Skill 的骨架实现ISkill后通过 DI 注册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.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());Markdown Skill 目录约定每个 Skill 一个独立目录目录内放SKILL.md或Skill.mdRecursive true时递归扫描子目录。id可在 frontmatter 显式声明未声明时用IdPrefix 相对目录生成稳定 ID。同一挂载目录内 ID 重复时后续重复项跳过并记 warning。示例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.启动时注册顺序是RegisterSkillsFromServices()挂内置和 DI SkillRegisterMarkdownSkillsFromDirectories(...)按目录加载 Markdown SkillMountMcpSkills()挂 MCP 工具。注册完GET /api/skills可查 catalog。4. 端到端运行验证与成功结果配置和骨架就位后跑一次端到端验证。先编译cd QianYuan.AgenticFramework dotnet build启动 WebAPIdotnet run --project src/QianYuan.Api # 监听 http://localhost:5050 (Swagger: /swagger)启动 WebUIcd src/QianYuan.Web npm install npm run dev # 浏览器打开 http://localhost:5173Vite dev-server 已配反向代理/api和/hubs自动转发到 5050。仓库还内置三平台一键脚本会自动 restorebuild启动 Api 与 WebUI日志写到.runtime/logs/PID 写到.runtime/*.pid# macOS / Linux ./scripts/start.sh ./scripts/start.sh --stop # Windows scripts\start.cmd scripts\stop.cmd脚本会检测 .NET 10 SDK 与 Node.js18缺 Node 时只起 Api。默认地址 Apihttp://localhost:5050WebUIhttp://localhost:5173可用QIANYUAN_API_URL/QIANYUAN_WEB_URL覆盖。验证 Skill 注册是否成功直接查 catalogcurl http://localhost:5050/api/skills返回里应该能看到sample.api-design、sample.code-review、sample.debugging、sample.docs-writing、sample.requirements-analysis这几个 Markdown Skill以及内置的 WebSearch、Vision、FileSystem、Code Skill。如果samples/skills没被加载检查SkillDirectories的Path是不是相对启动目录建议用绝对路径或确认工作目录。跑控制台样例做一次真实模型调用export QIANYUAN_APIKEYsk-你的Key export QIANYUAN_BASEURLhttps://taotoken.net/api export QIANYUAN_MODELclaude-opus-4-7 dotnet run --project samples/QianYuan.Sample.Console成功的话控制台会流式打印模型输出如果触发了工具调用能看到ToolCallStart、ToolCallArgsDelta、ToolObservation这些事件依次出现最后以End收尾。这一步是判断整条链路通没通的关键模型能回、工具能调、流式事件能正确解析三者缺一不可。跑单元测试确认核心逻辑没被改坏dotnet testtests/QianYuan.Core.Tests用 xUnit FluentAssertions覆盖 ReAct 循环、Skill 选择、流式 chunk 解析。测试全绿再往上叠功能。WebUI 侧验证打开http://localhost:5173在对话框输入一个需要联网的问题比如让它查一下某个库的最新版本。如果 WebSearch Skill 被选中你会看到工具调用事件在界面上流式渲染Markdown 正常显示图片粘贴也能走 Vision 技能路由到支持视觉的 Provider。SSE 流式渲染和 SignalR Hub 两条通道都通说明前后端联调没问题。5. 本篇常见错误排查这一节对照真实报错来。第一个高频错误是 401 Unauthorized。表现是模型请求直接返回 401日志里能看到invalid api key或authentication failed。原因通常是ApiKey没填对或者环境变量QIANYUAN_APIKEY和配置文件里的 Key 冲突。排查顺序先确认appsettings.json里OpenAICompatProviders[].ApiKey是不是完整的sk-开头串再确认没有同时设置环境变量覆盖最后确认 Key 在 TaoToken 控制台没过期或被删。如果用的是 user-secrets检查dotnet user-secrets list输出。第二个错误是local proxy failed或连接被拒。这个多半是BaseUrl填错比如填成了https://taotoken.net/api/v1导致框架拼出/v1/chat/completions而实际路径不对或者填了带尾斜杠的地址导致双斜杠。正确做法是BaseUrl只填https://taotoken.net/api让框架自己拼。另外确认本机网络能正常访问该地址公司内网如果有出站限制需要走允许的通道。第三个错误是reading choices相关解析失败。表现是流式响应解析到一半抛异常日志里出现reading choices或unexpected token。原因是 Provider 返回的流式格式和框架预期的 OpenAI SSE 格式不一致或者DefaultModel填了一个 TaoToken 侧不支持的模型 ID返回了错误结构。排查先用 curl 直接打一次接口确认返回结构curl https://taotoken.net/api/chat/completions \ -H Authorization: Bearer sk-你的Key \ -H Content-Type: application/json \ -d {model:claude-opus-4-7,messages:[{role:user,content:hi}],stream:true}如果 curl 返回正常 SSE说明 Key 和模型 ID 没问题问题在框架的 Provider 解析逻辑如果 curl 也报错先修模型 ID 或 Key。第四个错误是 OAuth 相关。如果你在 Claude Code 侧配置了 OAuth 登录但框架走的是 API Key 通道两者不要混。Claude Code 的 OAuth 是编辑器侧的事QianYuan 框架只认ApiKey。出现 OAuth 报错通常是误把 Claude Code 的凭据配到了框架里或者反过来。分开管理Claude Code 用它的登录态QianYuan 用 TaoToken 的 Key。第五个错误是 MCP 挂载失败。表现是MountMcpSkills()后GET /api/skills里没有mcp.fs。原因通常是npx不在 PATH或者modelcontextprotocol/server-filesystem没装。先手动跑一次npx -y modelcontextprotocol/server-filesystem /tmp能起来再配到框架里。另外ServerId重复会导致后挂的覆盖先挂的检查配置里没有重复ServerId。第六个错误是 Markdown Skill 没被加载。GET /api/skills里看不到sample.*。检查SkillDirectories[].Path是不是相对路径且工作目录不对改成绝对路径试试检查Enabled是不是true检查SKILL.md的 frontmatter 格式YAML 头必须用---包裹字段名拼写要对。ID 重复时后续项会被跳过并记 warning翻日志能看到。6. 后续接入与长期编码建议框架跑通后日常开发建议把 TaoToken 的 Key 统一管理不要散落在多个配置文件里。如果你要长期用 Claude Code 做 Agent 开发可以考虑 Coding Plan把编码场景的模型调用集中管理路径是 https://taotoken.net/api 对应的 coding-plan 页面。需要验证某个模型的实际输出效果时用模型对话页面快速试不用每次都起整个框架。API Key 的创建和管理在 console 页面接入细节查 doc 页面。Claude Code 侧的配置如果你想让编辑器直接走 TaoToken 通道需要配全三件套Base URL 填https://taotoken.net/apiKey 填你的sk-串Model ID 填你要用的模型。这三项在 Claude Code 的 settings 里对应ANTHROPIC_BASE_URL、ANTHROPIC_API_KEY、ANTHROPIC_MODEL具体字段名以你用的版本为准。配好后 Claude Code 的请求就走统一通道和 Qianyuan 框架共用同一个 Key省得两边分别维护。Cline MCP 场景类似如果你用 Cline 挂 MCP ServerBase URL、Key、Model ID 三件套同样要配全。Codex 的auth.json里也是这三项格式是 JSON注意字段名和缩进。这三处配置的共同点是Base URL 只填根地址Key 用同一串Model ID 用同一个逻辑名这样切换模型时只改一处。最后给一个实用技巧QianYuan 的DefaultAgentMaxIterations默认 100实际开发时建议先调到 10 左右方便观察 ReAct 循环的每一轮行为确认 Skill 选择逻辑符合预期后再放开。迭代次数太高时一旦 Skill 选择跑偏会浪费大量 token 在无效循环上。调低后配合日志看每轮的SelectRelevantAsync结果能快速定位是 Skill 描述写得不好还是topK设得不合适。这个参数在appsettings.json的QianYuan.DefaultAgentMaxIterations里改单次请求还能用MaxIterations覆盖调试时很顺手。
返回列表