1. 从 Prompt 到 Function Calling:Skill 落地时最容易断在哪一环
很多人第一次接触 Skill 这个概念,会把它理解成"写得更长、更细的 Prompt"。我一开始也这么想,直到把一个真实任务拆开跑了一遍,才发现问题根本不在提示词写得好不好,而在 Prompt、Tool、Function Calling 这三段之间是断的。
先说清楚 Skill 是什么、能做什么、适合谁。Skill 是把 Prompt 的推理规则、Tool 的执行能力、以及一段固定的业务 SOP 封装在一起的复合体。它适合那些"光靠模型想不出来、光靠工具又不知道下一步干嘛"的任务,比如代码规范审查、SQL 超时排查、接口联调排障这类需要多步取证再下结论的场景。如果你只是问"什么是 DDD",那用普通 Prompt 就够了,不需要拉起一整套 Skill 流程。
断点通常出现在三个地方。第一段是 Prompt 到 Tool:提示词里写了"请检查索引使用情况",但没规定用哪个工具、传什么参数,模型就会凭记忆瞎猜,或者干脆跳过取证直接给建议。第二段是 Tool 到 Function Calling:工具定义写好了,但请求体里的tool_choice、parameters结构对不上,模型返回的tool_calls解析不了,整条链路就卡在"模型想调但调不动"。第三段是 Function Calling 回到 Prompt:工具返回的裸 JSON 没有收敛格式,模型把原始数据直接抛给用户,等于白调。
这篇就按这三段走一遍完整链路。接入点用 TaoToken 的统一 Key 和 API 通道,因为它把模型对话、工具调用、多模型切换收敛到一个 Base URL 下,省得你在每个环节换一套鉴权。下面每一段都给可复制的配置片段和请求体,最后附一轮端到端验证和返回结果核对方法。
我试过把同一个 Skill 分别接在两个通道上跑,差异最明显的地方不是模型能力,而是工具调用的参数结构是否稳定。统一通道的好处是tools字段的 schema 校验一致,不会出现这边能解析、那边报reading 'choices'的情况。
2. TaoToken 前置准备:统一 Key 与 API 通道怎么配
在写 Skill 之前,先把接入层理清楚。TaoToken 的作用是给你一个统一的 API 入口,模型对话、Coding Plan、API Keys 管理都在同一套体系下。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置时别把查询串带进去。
你需要准备三样东西,我把它叫做"三件套":Base URL、API Key、Model ID。这三件套在任何支持 Function Calling 的客户端里都要填全,缺一个就会在请求阶段报错。Base URL 填https://taotoken.net/api,API Key 在控制台的 API Keys 页面生成,Model ID 按你实际要用的模型填,比如做代码类 Skill 就选擅长结构化输出的那个。
生成 Key 的入口在这里:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。进去之后新建一个 Key,复制出来存好,页面上只显示一次。如果你用的是 Claude Code 这类客户端,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各客户端的 Base URL 填法。
这里要提醒一句:不要把 Key 硬编码进 Skill 的 Markdown 文件里。Skill 文件是要进版本库、要被反复加载的,Key 写进去等于泄露。正确做法是 Key 放在环境变量或客户端的配置文件里,Skill 文件里只引用变量名。
配置层面,如果你用 Cline 或类似的 MCP 客户端,settings 里通常长这样:
{ "apiProvider": "openai-compatible", "baseUrl": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "modelId": "your-model-id", "enableFunctionCalling": true }如果你用 Codex 系的客户端,auth.json里对应的是:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "your-model-id" }注意base_url结尾不要多加/v1,具体以接入文档为准,不同客户端对路径拼接的处理不一样,多一层少一层都会 404。填完之后先别急着写 Skill,用一次最简单的模型对话验证通道通不通:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。能正常返回,说明三件套没问题,再往下走。
3. 可复制配置:Skill 文件、Tool 定义与 Function Calling 请求体
这一段是核心,把 Skill 从 Prompt 描述到 Tool 调用再到 Function Calling 的配置全部给全。我按一个"代码规范审查"的 Skill 来写,因为它同时用到读文件、查引用、生成结论三类动作,链路完整。
先写 Skill 的 Markdown 骨架。结构上分四块:角色、目标、约束、流程。角色决定语义空间,目标定义可验收的交付物,约束画边界,流程锁推导路径。
## 角色 你是一名代码质量守门人。你的判断依据来自真实线上事故的反推,而不是通用最佳实践。 拒绝讨好:用户说"先这样吧",你仍须完成出口自检才交付。 ## 目标 1. 交付物:生成的代码附带硬红线合规声明。 2. 零 Critical:存在硬红线违规即视为未完成。 3. 可追溯:每个编码决策的取证结论以表格写入回复。 ## 约束 - 失败模式只能更响,不能更静默。 - 防御代码必须真实生效,不得只写注释。 - 取证阶段必须调用工具,禁止凭记忆假设。 ## 执行步骤(严格按序) [Step 1: 档位判定] 输出轻量/标准/复杂。 [Step 2: 事实取证] 输出「疑点 → 取证方式 → 结论」表格。 [Step 3: 生成代码] 应用硬红线与取证结论。 [Step 4: 出口自检] 逐条声明合规状态。这段里最关键的是 Step 2 的取证表格,它把 Prompt 和 Tool 连起来了。没有这张表,模型不知道什么时候该伸手调工具。
接下来定义 Tool。Function Calling 的工具定义是一个 JSON Schema,描述工具名、用途、参数结构。下面这个read_file和search_references是最常用的两个:
{ "type": "function", "function": { "name": "read_file", "description": "读取指定路径的文件内容,用于取证 DDL、注解源码、配置文件", "parameters": { "type": "object", "properties": { "path": { "type": "string", "description": "文件相对项目根目录的路径" }, "start_line": { "type": "integer", "description": "起始行,可选" } }, "required": ["path"] } } }{ "type": "function", "function": { "name": "search_references", "description": "在项目内搜索符号引用,用于确认调用链和事务边界", "parameters": { "type": "object", "properties": { "symbol": { "type": "string", "description": "要搜索的类名、方法名或字段名" }, "scope": { "type": "string", "enum": ["project", "module", "file"], "description": "搜索范围" } }, "required": ["symbol"] } } }工具定义写好后,组装 Function Calling 请求体。注意tools数组、tool_choice和messages三者的配合:
{ "model": "your-model-id", "messages": [ { "role": "system", "content": "你是一名代码质量守门人,按 Skill 流程执行,取证阶段必须调用工具。" }, { "role": "user", "content": "帮我审查 OrderService 里 createOrder 方法的空值处理,项目根目录 /workspace/demo" } ], "tools": [ { "type": "function", "function": { "name": "read_file", "description": "读取文件内容", "parameters": { "type": "object", "properties": { "path": { "type": "string" } }, "required": ["path"] } } } ], "tool_choice": "auto", "temperature": 0.2 }tool_choice设成auto让模型自己判断要不要调工具;如果你在排障阶段想强制它先取证,可以设成{"type": "function", "function": {"name": "read_file"}},逼它先读文件再说话。temperature调低一点,结构化输出更稳。
把 Skill 文件、工具定义、请求体三样凑齐,链路的前半段就通了。后半段是模型返回tool_calls之后,你把工具执行结果塞回messages再请求一次,这个在下一段验证里演示。
4. 端到端验证:一轮请求、返回结果与核对方法
配置写完必须跑一轮真实请求,否则你不知道链路在哪断。下面这轮验证我按"发请求 → 收 tool_calls → 执行工具 → 回填结果 → 收最终答案"五步走。
第一步,发上面那个请求体。用 curl 的话:
curl https://taotoken.net/api/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -d @request.json第二步,看返回。如果模型决定调工具,返回里会有choices[0].message.tool_calls,结构大致是:
{ "choices": [ { "message": { "role": "assistant", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "read_file", "arguments": "{\"path\": \"src/main/java/OrderService.java\"}" } } ] }, "finish_reason": "tool_calls" } ] }核对点一:finish_reason是不是tool_calls。如果是stop,说明模型没调工具直接答了,那你的 Skill 里"必须取证"的约束没生效,回去检查 system 提示词和tool_choice。核对点二:arguments是不是合法 JSON 字符串。有些模型会返回带换行的字符串,解析前先JSON.parse一次,失败就报错。
第三步,执行工具。把arguments解析出来,按path读文件,拿到真实内容。
第四步,回填。把 assistant 的tool_calls消息和 tool 的执行结果一起追加进messages:
{ "role": "tool", "tool_call_id": "call_abc123", "content": "public Order createOrder(CreateOrderCmd cmd) { ... }" }注意tool_call_id必须和请求里的id对上,对不上会报参数校验错误。然后带上完整的messages再请求一次。
第五步,收最终答案。这次finish_reason应该是stop,message.content里是模型基于取证结论生成的审查结果。核对点三:结果里有没有出现「疑点 → 取证方式 → 结论」表格。有,说明 Skill 的 Step 2 落地了;没有,说明模型跳步了,回去把步骤锁死写得更硬。
一轮跑通之后,你可以把tool_choice改成强制模式再跑一遍,对比两次的tool_calls是否一致。一致说明参数提取稳定,不一致说明工具描述写得不够明确,模型在猜。这个对比方法比单看一次结果靠谱得多。
5. 常见报错排查:401、local proxy failed 与 reading choices
链路跑不通时,报错基本集中在几个固定位置。这一段按真实报错对照排查,每个都给定位方法。
401 Unauthorized。最常见的原因是 Key 没带上或带错了。检查Authorization头是不是Bearer加 Key,中间有空格。如果你把 Key 放在环境变量里,确认 shell 里echo $TAOTOKEN_API_KEY有值。还有一种情况是 Key 复制时带了首尾空格,肉眼看不出来,用cat -A看一眼。三件套里 Base URL 和 Key 必须成对,换了 Key 没换 Base URL 也会 401。
local proxy failed。这个报错通常出现在客户端层,不是 API 层。意思是客户端尝试走本地转发但失败了。排查顺序:先确认客户端里 Base URL 填的是https://taotoken.net/api而不是别的地址;再确认客户端没有额外配置本地端口转发;最后看客户端日志里实际请求的 URL 是什么,很多时候是路径拼接多了一层/v1。这个错和网络环境无关,纯粹是配置问题。
reading 'choices'。这个报错说明代码在解析返回时,response.choices是 undefined。根因是返回体结构和你预期的不一样,可能是请求失败返回了错误对象,也可能是流式返回没处理完就解析。定位方法:把原始返回console.log出来,看顶层有没有choices字段。如果没有,看有没有error字段,错误信息会告诉你真实原因。常见触发场景是tools字段格式不对,服务端直接返回了错误对象。
OAuth 相关报错。如果你用的是 Claude Code 这类客户端,报 OAuth 错误说明鉴权方式选错了。这类客户端要选 API Key 模式而不是 OAuth 模式,Base URL 填 TaoToken 的地址。接入文档里有各客户端的鉴权模式对照表,照着选。
tool_calls 解析失败。返回里有tool_calls但解析报错,八成是arguments不是合法 JSON。有些模型会把参数写成单引号或者带尾逗号,解析前先做一次容错处理。另一个原因是tool_call_id回填时对不上,检查你回填的 id 和请求里的 id 是否完全一致。
排查时有个通用技巧:把temperature设成 0,把tool_choice设成强制模式,先让链路跑通,再逐步放开。这样能把"模型随机性导致的偶发失败"和"配置错误导致的必然失败"分开。
6. 把 Skill 接进长期工作流:Coding Plan 与迭代闭环
单次跑通只是开始,真正省时间的是把 Skill 接进日常编码流程。如果你每天都要做代码审查、接口联调这类重复任务,用 Coding Plan 会比单次调用划算,入口在 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。它适合长期编码和 Agent 类场景,把 Skill 挂上去之后,每次触发都走同一套流程。
迭代闭环这块,我踩过的坑是:改了一句 Prompt,修好了一个 case,结果另外几个原本正常的 case 挂了。原因是 Skill 文件是全局的,改一处影响全部。后来我按"通用规则进主文件、项目专属事实进项目档案"的方式分流,主文件保持稳定,回归风险就降下来了。
具体做法是:每次审查发现新的反模式,追加到references/anti-patterns.md,不写进主 Skill 文件;发现某个项目特有的反直觉事实,比如某个 ORM 的置空策略,写进那个项目根目录的AGENTS.md,也不写进主文件。主文件只放跨项目通用的规则。这样主文件的改动频率低,每次改动的影响面就可控。
工具调用这块还有个小技巧:把"必须调用工具"和"禁止调用工具"成对写。只写"必须取证",模型会在纯推理阶段也乱调工具,浪费轮次;只写"禁止乱调",模型在该取证时又不动手。成对写,边界才清楚。
最后一步是验证闭环。每次改完 Skill,用同一组测试输入跑一遍,对比tool_calls序列和最终输出的结构是否一致。一致就说明改动没引入回归,不一致就回滚。这个习惯比事后 debug 省事得多。