1. 当 Agent 要跑脚本Python 和 C# 却各管各的 Key如果你正在用 Microsoft Agent Framework 搭 Agent大概率会遇到这样一个场景Skill 里定义了一个 Python 脚本Agent 判断该调用它但真正执行脚本的那段代码写在 C# 宿主里。Python 侧要访问模型做推理C# 侧也要访问模型做编排两边各自维护一份 endpoint 和 API Key改一次配置要动两个地方测试环境切生产环境更是灾难。Microsoft Agent Framework 的 Skills 机制本身设计得很清晰Skill 是一组可移植的指令、脚本和资源用SKILL.md描述能力用scripts/放具体脚本。Agent 在运行时发现 Skill、加载SKILL.md、理解指令然后调用你注册的 Tool 去执行。问题出在「执行」这一环——官方文档明确写了 Script execution is not yet supported in C#也就是说 C# 宿主不会自动帮你跑.py你得自己写一个execute_python工具用ProcessStartInfo拉起 Python 进程。这就带来一个很现实的工程问题Python 脚本里如果要用模型能力比如做会议纪要摘要、代码审查、数据分类它需要一套模型访问配置C# 宿主里的 Agent 编排层也需要模型访问配置。两套配置如果指向不同的 endpoint、不同的 Key调用链就会断。我试过在两边分别写死配置结果调试时改了 Python 侧忘了改 C# 侧Agent 加载 Skill 正常、调用 Tool 正常、Python 脚本执行也正常但脚本内部请求模型时 401排查了半天才发现是 Key 不一致。这篇要解决的就是这件事用 TaoToken 作为统一的模型访问入口让 Python 脚本和 C# 宿主共用同一个 Base URL 和同一个 API Key把 Skills 执行 Scripts 的调用链真正打通。适合谁适合正在用 Microsoft Agent Framework 做双栈开发、需要在 Python 与 C# 之间复用模型访问配置的开发者。读完之后你能拿到一套可复制的 Skill 目录结构、C# 执行器代码、Python 脚本模板以及一次请求验证两侧是否走通的方法。核心检索词先摆出来Microsoft Agent Framework Skills 执行 Scripts本质是让 Agent 通过 Skill 决策、通过 Tool 执行本地 Python 脚本而 TaoToken 统一 Key 解决的是双栈模型访问配置复用问题。2. TaoToken 前置一个 Base URL 同时喂给 Python 和 C#在动手改代码之前先把 TaoToken 这一层说清楚。TaoToken 提供的是 OpenAI 兼容的模型访问接口Base URL 是https://taotoken.net/api你拿到的 API Key 在 Python 和 C# 两侧都能用。这意味着你不需要为 Python 单独申请一套、为 C# 再申请一套也不需要维护两个 endpoint。为什么这件事对 Skills 执行 Scripts 特别重要因为 Skill 的执行链路是跨语言的。Agent 在 C# 里做决策SKILL.md告诉它去调用execute_pythonC# 的PythonRunner拉起一个 Python 进程Python 脚本内部可能还要调模型做二次处理。如果 Python 脚本里的模型配置和 C# 宿主里的模型配置不一致就会出现「Agent 决策正常、脚本执行正常、但脚本内部请求失败」这种最难排查的问题。统一到 TaoToken 之后你只需要在一个地方管理 Key两侧读同一份配置。具体怎么拿 Key访问 TaoToken 官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注册后在控制台创建 API Key。控制台地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI Keys 管理页在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。创建时建议给 Key 起一个能区分用途的名字比如agent-framework-skills方便后续排查。拿到 Key 之后你需要确认两件事。第一模型 ID 是什么。TaoToken 支持多种模型你在控制台或模型对话页能看到可用列表模型对话入口是https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。第二Base URL 统一用https://taotoken.net/api注意这个地址不带 UTM 参数是纯 API 端点。这里有个设计选择要提前想清楚Python 脚本和 C# 宿主怎么共享配置有三种做法。第一种是环境变量两边都读TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL最简单适合本地开发和 CI。第二种是配置文件比如一个appsettings.json给 C# 读、一个.env给 Python 读但内容指向同一套值。第三种是 C# 宿主在拉起 Python 进程时通过ProcessStartInfo.Environment把配置注入进去Python 侧只读环境变量。我推荐第三种因为这样 Python 脚本不需要自己管理配置文件完全由宿主控制调用链更清晰。如果你后续要做长期编码或 Agent 任务可以了解 Coding Plan入口是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content里面有各语言的调用示例Python 和 C# 都能直接参考。把这一层想清楚之后后面的配置和代码才有意义。统一 Key 不是目的目的是让 Skills 执行 Scripts 这条跨语言链路只有一个配置源改一处、两侧生效。3. 可复制配置Skill 目录、C# 执行器与 Python 脚本这一节给可直接复制的片段。先看 Skill 目录结构这是 Microsoft Agent Framework 约定的格式skills/ └── meeting-notes/ ├── SKILL.md └── scripts/ └── GetMeetingNotes.pySKILL.md是核心name必须和目录名一致description用于让 Agent 判断是否匹配这个 Skill正文是给 Agent 看的操作说明--- name: meeting-notes description: Various python scripts for meeting-notes --- # The meeting notes [GetMeetingNotes](meeting-notes/scripts/GetMeetingNotes.py) Give above script-path to a execute_python tool.注意最后那句Give above script-path to a execute_python tool它告诉 Agent找到脚本路径后交给名为execute_python的 Tool 去执行。这个 Tool 名字必须和 C# 里注册的名字完全一致否则 Agent 会找不到执行入口。接下来是 C# 侧的 Python 执行器。这段代码的关键点有三个路径拼接用AppContext.BaseDirectory保证相对路径正确、用ProcessStartInfo拉起 Python 并捕获 stdout/stderr、把 TaoToken 配置通过环境变量注入子进程public static class PythonRunner { public static string RunPythonScript(string pythonFilePath) { if (string.IsNullOrWhiteSpace(pythonFilePath)) throw new ArgumentException(File path cannot be empty., nameof(pythonFilePath)); pythonFilePath Path.Combine(AppContext.BaseDirectory, skills, pythonFilePath); if (!File.Exists(pythonFilePath)) throw new FileNotFoundException(Python file not found., pythonFilePath); if (!string.Equals(Path.GetExtension(pythonFilePath), .py, StringComparison.OrdinalIgnoreCase)) throw new ArgumentException(File must have a .py extension., nameof(pythonFilePath)); ProcessStartInfo startInfo new ProcessStartInfo { FileName python, Arguments \ pythonFilePath \, UseShellExecute false, RedirectStandardOutput true, RedirectStandardError true, CreateNoWindow true, StandardOutputEncoding Encoding.UTF8, StandardErrorEncoding Encoding.UTF8 }; // 把 TaoToken 配置注入 Python 子进程两侧共用同一套 Key startInfo.Environment[TAOTOKEN_API_KEY] Environment.GetEnvironmentVariable(TAOTOKEN_API_KEY); startInfo.Environment[TAOTOKEN_BASE_URL] https://taotoken.net/api; startInfo.Environment[TAOTOKEN_MODEL_ID] Environment.GetEnvironmentVariable(TAOTOKEN_MODEL_ID); using (Process process new Process { StartInfo startInfo }) { process.Start(); string output process.StandardOutput.ReadToEnd(); string error process.StandardError.ReadToEnd(); process.WaitForExit(); if (process.ExitCode ! 0 || !string.IsNullOrWhiteSpace(error)) throw new InvalidOperationException( Python script failed. ExitCode process.ExitCode Environment.NewLine error.Trim()); return output.TrimEnd(); } } }然后在Program.cs里把这个方法注册成 Agent ToolTools [ AIFunctionFactory.Create( PythonRunner.RunPythonScript, name: execute_python ) ]Python 脚本侧读环境变量拿配置用 OpenAI 兼容方式调 TaoTokenimport os from openai import OpenAI def main(): client OpenAI( api_keyos.environ[TAOTOKEN_API_KEY], base_urlos.environ[TAOTOKEN_BASE_URL] ) response client.chat.completions.create( modelos.environ[TAOTOKEN_MODEL_ID], messages[ {role: user, content: 用一句话总结Chester 完成了创建用户 API还需要实现更新用户 API。} ] ) return response.choices[0].message.content if __name__ __main__: print(main())如果你用appsettings.json管理 C# 侧配置可以这样写注意BaseUrl不带 UTM{ TaoToken: { BaseUrl: https://taotoken.net/api, ApiKey: 你的_API_Key, ModelId: 你的_模型_ID } }C# 启动时把这三个值读进环境变量PythonRunner再注入给子进程。这样 Python 脚本完全不需要自己读配置文件配置源只有一个。如果你用 Claude Code 做辅助开发接入配置也是同一套 Base URL 和 Key文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。三件套再强调一次Base URL 是https://taotoken.net/apiKey 从 API Keys 页拿Model ID 从控制台或模型对话页确认。这三个值在 Python 和 C# 两侧必须一致。4. 验证请求一次调用确认 Python 与 C# 两侧走通配置写完怎么确认调用链真的通了不要只看 Agent 有没有返回结果要分层验证。我习惯从内到外分三步先单独跑 Python 脚本、再单独跑 C# 执行器、最后跑完整 Agent 流程。第一步单独验证 Python 脚本能调通 TaoToken。在命令行里设置环境变量后直接执行export TAOTOKEN_API_KEY你的_API_Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_MODEL_ID你的_模型_ID python skills/meeting-notes/scripts/GetMeetingNotes.py如果返回一段模型生成的摘要文本说明 Python 侧配置正确。如果报 401说明 Key 不对如果报连接错误检查 Base URL 是不是写成了带 UTM 的地址。第二步验证 C# 执行器能拉起 Python 并拿到输出。写一个最小控制台入口var result PythonRunner.RunPythonScript(meeting-notes/scripts/GetMeetingNotes.py); Console.WriteLine(result);运行前确保TAOTOKEN_API_KEY和TAOTOKEN_MODEL_ID已经在宿主环境里设置好。如果这一步报Python file not found检查AppContext.BaseDirectory下的skills目录是否被复制到了输出目录通常需要在.csproj里加ItemGroup None Updateskills\**\* CopyToOutputDirectoryPreserveNewest/CopyToOutputDirectory /None /ItemGroup第三步跑完整 Agent 流程。用户输入「帮我获取会议纪要」Agent 内部会依次做这些事发现meeting-notesSkill、加载SKILL.md、读到Give above script-path to a execute_python tool、调用execute_python(meeting-notes/scripts/GetMeetingNotes.py)、Python 脚本执行并调 TaoToken、返回结果。最终输出应该是一段模型生成的会议纪要摘要。如果你想更直观地确认两侧用的是同一个 Key可以在 Python 脚本里临时打印一行os.environ[TAOTOKEN_BASE_URL]在 C# 里打印startInfo.Environment[TAOTOKEN_BASE_URL]对比是否都是https://taotoken.net/api。这个笨办法在排查配置不一致时特别有效。验证模型本身是否可用可以走模型对话页https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content手动发一条消息确认 Key 有效。这一步能快速区分是 Key 问题还是代码问题。实测下来最容易出问题的不是代码逻辑而是路径和配置注入。Python 脚本里的相对路径是相对于进程工作目录的而 C# 拉起子进程时工作目录可能不是你以为的那个。所以PythonRunner里用Path.Combine(AppContext.BaseDirectory, skills, pythonFilePath)把路径拼成绝对路径避免这个问题。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节对照真实报错来排查。这些错误我在双栈调用链里都遇到过按出现频率排序。401 Unauthorized。这是最常见的。原因通常是 Python 侧和 C# 侧用了不同的 Key或者 Key 没注入到子进程。排查方法在PythonRunner里确认startInfo.Environment[TAOTOKEN_API_KEY]确实被赋值了而不是null。如果 C# 宿主读的是appsettings.json检查读出来的值有没有被覆盖。另一个可能是 Key 本身失效去 API Keys 页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content确认 Key 状态。local proxy failed。这个报错通常出现在网络层意思是本地代理连接失败。如果你本地配了 HTTP 代理Python 的openai库和 C# 的HttpClient可能走了不同的代理设置。排查方法检查环境变量HTTP_PROXY、HTTPS_PROXY是否被设置Python 侧和 C# 侧是否一致。如果不需要代理把这两个变量清掉再试。注意 TaoToken 的 Base URL 是https://taotoken.net/api直接访问即可。reading choices 相关报错比如KeyError: choices或AttributeError: NoneType object has no attribute choices。这说明模型返回的响应结构不符合预期通常是请求本身失败了但没抛异常。排查方法在 Python 脚本里把response完整打印出来看是不是返回了错误信息。常见原因是 Model ID 写错了或者请求参数不合法。确认 Model ID 从模型对话页https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content能正常使用。OAuth 相关报错。如果你在 C# 侧用了某些需要 OAuth 的客户端库可能会遇到 token 获取失败。Microsoft Agent Framework 本身不强制 OAuth但如果你接入了其他认证体系要确认 OAuth token 和 TaoToken API Key 是两套东西不要混用。TaoToken 侧只需要 API Key不需要 OAuth 流程。Python 脚本执行超时或挂起。ProcessStartInfo里RedirectStandardOutput true和RedirectStandardError true同时开启时如果输出量很大可能死锁。排查方法先读 stdout 再读 stderr或者用异步读取。另外process.WaitForExit()没有超时参数脚本卡住会一直等建议加一个超时控制。Skill 没被 Agent 发现。检查SKILL.md的name是否和目录名完全一致description是否足够清晰让 Agent 判断匹配。如果 Agent 加载了 Skill 但没调用 Tool检查SKILL.md正文里的 Tool 名字是否和 C# 注册的name: execute_python一致。路径拼接错误。FileNotFoundException: Python file not found多半是skills目录没复制到输出目录。检查.csproj里的CopyToOutputDirectory配置或者直接在代码里打印pythonFilePath看拼出来的绝对路径对不对。排查顺序建议先确认 Key 和 Base URL 两侧一致再确认路径正确最后看响应结构。大部分问题在前两步就能定位。6. 把统一 Key 用在长期 Agent 任务上Skills 执行 Scripts 这条链路打通之后你会发现统一 Key 的价值不只是省事。当 Agent 开始跑长期任务比如每天定时拉取会议记录、生成摘要、写入知识库Python 脚本和 C# 宿主之间的配置一致性直接决定了任务能不能稳定运行。任何一侧的 Key 过期或 endpoint 变更都会让整条链路断掉而统一到 TaoToken 之后你只需要在一个地方轮换 Key。如果你要把这套东西用到更复杂的场景比如多 Agent 协作、Agent 调用多个 Skill、每个 Skill 里有多个 Python 脚本建议把 TaoToken 配置抽成一个共享的配置加载模块。C# 侧读一次通过环境变量注入所有子进程Python 侧只读环境变量不自己管理配置文件。这样配置源只有一个排查问题时不用在多个文件之间跳。长期编码或 Agent 任务可以看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。API Key 管理在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content模型对话验证在https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。最后给一个实用技巧在PythonRunner里加一行日志把注入给子进程的TAOTOKEN_BASE_URL和TAOTOKEN_MODEL_ID打出来不要打 Key这样每次执行脚本时都能确认配置正确。这个日志在排查「为什么昨天能跑今天不行」这类问题时特别有用。配置统一不是终点可观测才是。