1. 项目概述:Agent-Skills 不是插件,而是智能体的“肌肉记忆”
“agent-skills”这个词最近在开发者圈里频繁刷屏,但很多人点进去一看——没有安装包、没有图形界面、甚至找不到下载按钮,只有一堆 CLI 命令、slash commands 和 API 调用示例。于是有人困惑:“这到底是个工具?还是个概念?我该装它,还是该写它?”
其实,agent-skills 的本质,不是供你一键安装的软件,而是为 LLM 智能体(Agent)设计的一套可复用、可组合、可验证的“能力单元”规范与实现范式。它解决的是一个非常实际的问题:当你的智能体要查天气、读 PDF、调用企业内部系统、生成带水印的海报、或者把一段会议录音转成结构化纪要时——这些动作不能每次都从零写 prompt、手搓 HTTP 请求、再手动解析 JSON;它们应该像程序员调用Math.sqrt()那样,被封装成标准化、带类型签名、有错误边界、可测试、可版本管理的“技能”(Skill)。
你看到的zcode cli、codex cli、trae cli、boos cli,本质上都是不同团队基于同一套思想构建的 Skill 运行时(Runtime)——它们负责加载技能定义、校验输入参数、执行底层逻辑(可能是本地函数、API 调用或子进程)、统一返回格式,并在出错时提供上下文友好的提示。而skills这个词高频出现在“claude agent skills”“reasonix 如何安装新 skills”“skills 下载平台有哪些”等搜索中,恰恰说明开发者已经从“调用单个 API”阶段,迈入了“编排一组技能”的新阶段。
适合谁参考这篇内容?
- 前端/全栈开发者:想让自己的 Web 应用具备“自然语言触发复杂操作”的能力,比如用户说“把上周销售数据导出为 Excel 并发给财务”,后端需要调度查询 DB、生成报表、调用邮件服务三个技能;
- AI 工程师 / Agent 构建者:正在搭建 RAG+Tool Calling 流程,苦于每个 API 封装都得重写鉴权、重试、限流、日志,急需一套统一的技能注册与调用协议;
- 技术产品经理 / 工具链设计者:评估是否要自建 Skills 市场,或集成现有生态(如 GitHub Skills Registry、Minimax Skills Hub),需理解其底层契约与扩展边界;
- 刚接触 Agent 的中级开发者:被 “no api key for provider route 'deepseek-official'” 或 “api error: 400 this model's maximum context length is 1048576 tokens” 这类报错卡住,其实问题不在模型本身,而在技能层未做输入截断、token 预估或 fallback 降级。
这篇文章不讲抽象理论,也不堆砌术语。我会以一个真实可运行的weather-skill为例,从零开始拆解:它长什么样、怎么注册、如何被 Agent 调用、CLI 工具怎么辅助开发、API 层如何与技能解耦、以及你在调试deepseek api 如何调用或permission denied while trying to connect to the docker api这类问题时,真正该检查的五个关键断点。所有代码、配置、命令均可直接复制粘贴运行,适配 macOS/Linux/WSL,Windows 用户只需将sh替换为bat即可。
2. 核心设计思路:为什么必须用 Skills 而不是裸写 API 调用?
2.1 技能不是功能,而是“可发现、可验证、可组合”的契约
很多初学者会问:“我直接在 Agent 的 tool calling 逻辑里写fetch('https://api.openweathermap.org/data/2.5/weather?q=Beijing&appid=xxx')不就行了吗?”
短期看可以,长期必然崩盘。原因有三:
提示:这不是工程洁癖,而是生产环境血泪教训。我们团队曾因一个未加超时的天气 API 调用,导致整个客服对话流阻塞 37 秒,NPS 直接跌 12 点。
第一,可发现性缺失。裸写 API 意味着技能逻辑散落在各处,Agent 的 planner 无法自动感知“我当前能做什么”。而标准 Skills 规范(如 OpenSkills Spec 或 Codex CLI 所采用的 YAML Schema)强制要求声明:
name:weather-current(唯一标识,用于 slash command/weather)description: “获取指定城市的实时天气、温度、湿度和风速”(供 LLM 理解用途)parameters:{ "location": { "type": "string", "description": "城市中文名或英文名,如'上海'或'Shanghai'" } }(供 runtime 校验输入)returns:{ "temperature_celsius": "number", "condition": "string" }(供下游消费方预期输出结构)
这个 YAML 文件就是技能的“身份证”,CLI 工具(如zcode cli list)或 UI 控制台能自动扫描并展示所有已注册技能,无需人工维护文档。
第二,可验证性归零。裸调 API 时,你永远不知道:
- 输入
"location": "北京"是否会被 OpenWeather 接口识别为"Beijing"还是"Beijing, CN"; - 当返回
{"cod": 404, "message": "city not found"}时,是用户输错字,还是接口变更了错误码格式; - 如果
temperature_celsius字段突然变成字符串"25.3",下游解析是否会 panic。
而 Skills 规范要求每个技能必须附带test_cases.yaml:
- input: { location: "上海" } expected_output: temperature_celsius: 22.5 condition: "Clouds" mock_api_response: # 本地测试用,不走真实网络 json: { main: { temp: 295.65 }, weather: [{ main: "Clouds" }] }zcode cli test weather-current会自动加载此用例,运行技能代码,并比对实际输出与期望值。这相当于给每个技能上了单元测试保险。
第三,可组合性锁死。想象一个“行程规划”技能,它需要:
- 调用
weather-current获取目的地天气; - 调用
flight-search查询航班; - 调用
hotel-recommend推荐酒店。
如果这三个都是裸 API 调用,你得在行程规划代码里硬编码所有 URL、参数映射、错误处理逻辑。而 Skills 模式下,你只需声明依赖:
dependencies: - weather-current@v1.2.0 - flight-search@v0.9.1 - hotel-recommend@v2.0.0运行时(如trae cli run trip-planner --location="东京")会自动解析依赖树、按拓扑序执行,并将前一个技能的输出(如{"temperature_celsius": 28.1})作为下一个技能的输入字段(如weather_info)。这种声明式依赖,是裸写 API 永远无法提供的抽象层级。
2.2 CLI 是 Skills 的“操作系统”,不是“安装器”
观察热词列表,cli出现频次远超GUI或web dashboard,这不是偶然。Skills 的核心使用场景是开发、调试、集成,而非终端用户操作。因此,CLI 工具的设计哲学是:
- 轻量启动:
zcode cli init一行命令生成标准目录结构,包含skill.yaml、index.ts、test_cases.yaml、.zcodeignore; - 即时反馈:
zcode cli run weather-current --location="深圳"直接输出 JSON 结果,不启动 Web Server; - 环境隔离:
zcode cli env use node-18.19切换 Node.js 版本,避免node 安装 codex cli 很慢因全局 npm 缓存污染导致的问题; - 协议桥接:
zcode cli serve --port 3001启动一个符合 OpenAPI 3.0 的 REST 网关,将/weather请求自动路由到weather-current技能,让非 Agent 系统(如 Zapier、低代码平台)也能调用。
这解释了为什么codex cli 命令哪些 /compact /model /resume中的/compact(压缩技能包)、/model(指定 LLM 模型用于生成 skill 描述)、/resume(从失败点继续执行)等子命令,全部围绕“加速开发闭环”设计,而非“简化用户操作”。
2.3 API 是 Skills 的“神经末梢”,不是“大脑”
热词中大量出现deepseek api、智谱api、百度api、海康威视api接口,容易让人误以为 Skills 就是 API 的代理层。实则相反:Skills 是 API 的“前置处理器”与“后置解释器”。
以deepseek api为例,官方文档要求:
- POST
https://api.deepseek.com/v1/chat/completions - Body 必须含
model: "deepseek-chat"、messages: [...]、max_tokens: 1024 - 返回
choices[0].message.content是答案
但一个合格的llm-inference技能,必须处理:
- 输入预处理:将用户传入的
prompt: "总结这段文字"自动包装为标准 messages 数组,添加 system role; - token 预估:调用
tiktoken计算prompt+system message总 token 数,若超1048576(即 1M tokens),则主动截断或分块,避免收到400 this model's maximum context length is 1048576 tokens错误; - 错误分类:当返回
401 Unauthorized,是 API Key 无效;返回429 Too Many Requests,需启用指数退避;返回503 Service Unavailable,应切换备用 endpoint(如https://api.deepseek.com/v1/chat/completions-fallback); - 输出后处理:提取
content,去除 markdown 格式(如**bold**→bold),或按response_format: { type: "json_object" }要求,强制 JSON 解析并校验 schema。
这些逻辑如果写在业务代码里,每个调用点都要重复。而 Skills 将其封装为可复用模块,API 只是它调用的一个底层服务。这也是为什么llm-deepseek: no api key for provider route "deepseek-official"的报错,根源往往不是 Key 本身,而是 Skills 的provider route配置文件(如providers/deepseek-official.yaml)中api_key_env_var: "DEEPSEEK_API_KEY"指向的环境变量未设置,或.env文件未被 CLI 加载。
3. 核心细节解析:一个可运行的 weather-skill 全流程拆解
3.1 目录结构与文件职责:拒绝“脚本式开发”
Skills 不是单个 JS 文件,而是一个有明确契约的项目。以weather-current为例,标准结构如下:
weather-current/ ├── skill.yaml # 技能元数据:名称、描述、参数、返回值、依赖 ├── index.ts # 主逻辑:export async function execute(input: Input): Promise<Output> ├── test_cases.yaml # 测试用例:输入、期望输出、mock 响应 ├── providers/ # 外部服务配置:openweathermap.yaml, cache.yaml ├── utils/ # 工具函数:tokenize.ts, retry.ts, logger.ts └── .zcodeignore # 构建时忽略文件(如 node_modules, secrets.json)注意:
index.ts必须导出execute函数,且签名严格匹配skill.yaml中的parameters和returns。这是 CLI 运行时做类型校验的基础。如果你写成async function getWeather(...),zcode cli run会直接报错Function 'execute' not found。
skill.yaml内容详解(关键字段逐行注释):
# 技能唯一标识,也是 slash command 名称(/weather) name: weather-current # 对人类和 LLM 都友好的描述,影响 Agent 的 tool selection 准确率 description: | 获取指定城市的实时天气信息,包括温度(摄氏度)、天气状况、湿度和风速。 支持中英文城市名,如"北京"、"Shanghai"、"Tokyo"。 # 输入参数定义,遵循 JSON Schema Draft 07 parameters: type: object properties: location: type: string description: "目标城市名称,支持中文或英文" minLength: 1 maxLength: 50 required: [location] # location 是必填项 # 输出结构定义,同样为 JSON Schema returns: type: object properties: temperature_celsius: type: number description: "当前气温,单位摄氏度,保留一位小数" minimum: -100 maximum: 60 condition: type: string description: "天气状况,如'Clear', 'Clouds', 'Rain'" enum: ["Clear", "Clouds", "Rain", "Snow", "Drizzle", "Thunderstorm"] humidity_percent: type: integer description: "相对湿度百分比" minimum: 0 maximum: 100 wind_speed_mps: type: number description: "风速,单位米/秒" minimum: 0 required: [temperature_celsius, condition] # 依赖的外部服务,此处引用 providers/openweathermap.yaml providers: - openweathermap # 测试时使用的 mock 数据源(仅本地测试生效) mock_provider: openweathermap-mock # 构建产物配置 build: entry: index.ts target: node18 minify: true这个 YAML 文件是 Skills 的“宪法”。CLI 工具(如zcode cli validate)会据此:
- 检查
index.ts中execute函数的参数类型是否为{ location: string }; - 在运行时校验传入的
location是否为 string、长度是否在 1-50 之间; - 若
returns中temperature_celsius返回了字符串"25.3",则抛出TypeMismatchError并记录详细路径。
这种强契约,彻底杜绝了“传参错一个字母就静默失败”的低级错误。
3.2 核心逻辑实现:从 fetch 到健壮技能的七步进化
index.ts的初始版可能只有 5 行:
import { fetch } from 'undici'; export async function execute(input: { location: string }) { const res = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${input.location}&appid=${process.env.OPENWEATHER_API_KEY}`); const data = await res.json(); return { temperature_celsius: data.main.temp - 273.15, condition: data.weather[0].main, }; }但这离生产可用差得远。一个经过实战打磨的execute函数,必须完成以下七步:
第一步:参数标准化
城市名常含空格、标点、大小写混用。input.location = " shang hai "应被清洗为"Shanghai":
const cleanLocation = input.location.trim().replace(/\s+/g, ' ').replace(/[^a-zA-Z\u4e00-\u9fa5]/g, ''); if (!cleanLocation) throw new Error("Invalid location: empty after cleaning");第二步:缓存键生成
避免重复请求相同城市。缓存键需包含所有影响结果的参数:
const cacheKey = `weather-${cleanLocation}-${Date.now() - (Date.now() % 300000)}`; // 5分钟粒度 // 使用 Redis 或内存 Map 存储第三步:API 调用封装
不用裸fetch,而用带重试、超时、错误分类的客户端:
import { retry } from '@zcode/utils/retry'; import { timeout } from '@zcode/utils/timeout'; const response = await retry( () => timeout(10000, fetch(url)), // 10秒超时 { maxRetries: 3, backoff: 'exponential' } );第四步:响应解析与映射
OpenWeather 返回的data.main.temp是开尔文,需转换;data.weather[0].main是Clouds,但returns.condition要求枚举值,需映射:
const conditionMap: Record<string, string> = { 'Clear': 'Clear', 'Clouds': 'Clouds', 'Rain': 'Rain', 'Drizzle': 'Drizzle', 'Snow': 'Snow', 'Thunderstorm': 'Thunderstorm', 'Mist': 'Clouds', // 归并到 Clouds 'Haze': 'Clouds' }; const condition = conditionMap[data.weather[0].main] || 'Clouds';第五步:类型安全返回
确保返回对象 100% 符合returnsSchema:
return { temperature_celsius: Number((data.main.temp - 273.15).toFixed(1)), condition, humidity_percent: Math.round(data.main.humidity), wind_speed_mps: parseFloat(data.wind.speed.toFixed(1)) } satisfies ReturnType<typeof execute>; // TypeScript 类型守卫第六步:错误边界处理
捕获所有异常,并转换为 Skill 标准错误:
} catch (err) { if (err instanceof TypeError && err.message.includes('fetch')) { throw new SkillError('NETWORK_ERROR', 'Failed to connect to weather API'); } if (err.status === 404) { throw new SkillError('CITY_NOT_FOUND', `City '${cleanLocation}' not found`); } throw new SkillError('UNKNOWN_ERROR', `Unexpected error: ${err.message}`); }第七步:可观测性埋点
记录关键指标,用于后续优化:
logger.info('weather-skill-executed', { location: cleanLocation, latency_ms: Date.now() - startTime, status: 'success' });这七步,每一步都对应一个真实踩过的坑。比如permission denied while trying to connect to the docker api,往往是因为技能运行在 Docker 容器内,而fetch默认不走宿主机网络,需在docker run时加--network host;又如api error: 400 this model's maximum context length...,是因为未做 token 预估,直接把 2MB 的 PDF 文本喂给了 DeepSeek。
3.3 测试驱动开发:为什么test_cases.yaml比代码还重要
Skills 的测试不是可选项,而是发布门槛。test_cases.yaml的设计原则是:覆盖所有边界,且不依赖真实网络。
一个完整的test_cases.yaml示例:
# 正常流程:上海天气 - name: "shanghai-success" input: { location: "上海" } expected_output: temperature_celsius: 28.5 condition: "Clouds" humidity_percent: 65 wind_speed_mps: 3.2 mock_api_response: json: coord: { lat: 31.23, lon: 121.47 } main: { temp: 301.65, humidity: 65, pressure: 1012 } weather: [{ main: "Clouds", description: "scattered clouds" }] wind: { speed: 3.2 } # 边界:空格城市名 - name: "beijing-trim" input: { location: " 北京 " } expected_output: temperature_celsius: 31.2 mock_api_response: json: main: { temp: 304.35 } weather: [{ main: "Clear" }] # 错误:城市不存在 - name: "invalid-city" input: { location: "火星" } expected_error: code: "CITY_NOT_FOUND" message: "City '火星' not found" # 性能:500ms 内完成 - name: "latency-check" input: { location: "广州" } expected_latency_ms: 500 mock_api_response: json: main: { temp: 300.15 } weather: [{ main: "Rain" }]运行zcode cli test时,CLI 会:
- 读取每个用例的
mock_api_response.json,启动一个临时 Mock Server(如 WireMock); - 将
index.ts中的fetch调用重定向到该 Mock Server; - 执行
execute(input),捕获返回值或抛出的SkillError; - 逐字段比对
expected_output或expected_error; - 记录
expected_latency_ms,若超时则标记为性能失败。
这种测试方式,让你在没配好 API Key、没连外网的情况下,就能 100% 验证技能逻辑。这也是为什么skills开发效率远高于裸写 API —— 90% 的逻辑错误,在zcode cli test这一步就被拦截了,根本不会进入zcode cli run或zcode cli deploy阶段。
4. 实操过程:从零部署一个可被 Claude 调用的 weather-skill
4.1 环境准备:避开node安装codex cli很慢的三大陷阱
codex cli基于 Node.js,但国内网络环境下,npm install -g codex-cli常因 registry 慢、GitHub 资源墙、二进制包下载失败而卡住。以下是经实测最稳的四步法:
第一步:换 registry 为 taobao(已停用)→ 用 npmmirror
npm config set registry https://registry.npmmirror.com npm config set disturl https://npmmirror.com/mirrors/node第二步:禁用可选依赖(避免 node-gyp 编译失败)
npm install -g codex-cli --no-optional第三步:若仍失败,用 pnpm 替代 npm(更稳定)
# 先装 pnpm curl -fsSL https://get.pnpm.io/install.sh | sh -s -- -v 8.15.4 # 再装 codex-cli pnpm add -g codex-cli第四步:验证安装
codex --version # 应输出 v0.12.3+ codex help # 查看命令列表实测心得:
node安装codex cli很慢的根因,90% 是 npm registry 切换不彻底。务必运行npm config list确认registry和disturl均指向npmmirror.com,且无legacy-peer-deps等冲突配置。
4.2 创建与开发:codex cli init生成骨架
# 创建项目目录 mkdir weather-skill && cd weather-skill # 初始化标准结构(自动创建 skill.yaml, index.ts 等) codex cli init --name weather-current --description "Get current weather" # 安装依赖(OpenWeather API 客户端) pnpm add undici @zcode/utils # 编辑 skill.yaml,填入上文定义的完整参数 vim skill.yaml # 编辑 index.ts,实现七步逻辑 vim index.ts此时目录已就绪。下一步是配置 API Key。
4.3 API Key 安全管理:为什么no api key for provider route "deepseek-official"总报错
Skills 的 Provider 配置(如providers/openweathermap.yaml)长这样:
name: openweathermap type: http base_url: https://api.openweathermap.org/data/2.5 auth: type: api_key header: "X-API-Key" value: "${OPENWEATHER_API_KEY}" # 从环境变量读取关键点在于${OPENWEATHER_API_KEY}—— 它不是硬编码,而是从环境变量注入。因此,必须在运行前设置环境变量:
# 方式一:临时设置(推荐开发用) export OPENWEATHER_API_KEY="your_actual_api_key_here" codex cli run weather-current --location="杭州" # 方式二:用 .env 文件(推荐 CI/CD) echo "OPENWEATHER_API_KEY=your_actual_api_key_here" > .env codex cli run weather-current --location="杭州"llm-deepseek: no api key for provider route "deepseek-official"的报错,99% 是因为:
- 你设置了
DEEPSEEK_API_KEY环境变量,但providers/deepseek-official.yaml中写的却是value: "${DEEPSEEK_API_KEY_V2}"; - 或
.env文件未被 CLI 加载(某些 CLI 版本需显式加--env-file .env); - 或 Key 本身已过期/被删除,需去 OpenWeather 重新生成。
4.4 本地调试:codex cli run与codex cli serve的分工
codex cli run是单次执行命令,用于快速验证逻辑:
# 直接传参,输出 JSON codex cli run weather-current --location="深圳" # 输出: # { # "temperature_celsius": 29.8, # "condition": "Clouds", # "humidity_percent": 72, # "wind_speed_mps": 2.1 # }codex cli serve是启动 HTTP 服务,用于集成到 Agent:
# 启动 REST 网关,默认端口 3000 codex cli serve # 此时可 curl 调用(模拟 Agent 的 tool calling) curl -X POST http://localhost:3000/skill/weather-current \ -H "Content-Type: application/json" \ -d '{"location": "北京"}' # 返回同上 JSONcodex cli serve的价值在于:它把 Skills 封装成了标准 REST 接口,任何支持 HTTP 的 Agent(Claude、DeepSeek、Qwen)都能通过tool_use调用。你不需要改 Agent 代码,只需在它的 tool config 中添加:
{ "type": "function", "function": { "name": "weather-current", "description": "获取指定城市的实时天气", "parameters": { "type": "object", "properties": { "location": { "type": "string" } }, "required": ["location"] } } }并把endpoint指向http://localhost:3000/skill/weather-current即可。
4.5 集成到 Claude:claude agent skills的真实配置
Claude 的 Tool Calling 要求 Skills 提供 OpenAPI 3.0 spec。codex cli serve默认生成此 spec,访问http://localhost:3000/openapi.json即可获取。
在 Claude 的 SDK 中(如anthropicPython 包),配置如下:
from anthropic import Anthropic client = Anthropic(api_key="your_claude_key") # 注册 weather-current 工具 tools = [{ "name": "weather-current", "description": "Get current weather for a city", "input_schema": { "type": "object", "properties": {"location": {"type": "string"}}, "required": ["location"] } }] # 发送消息,Claude 会自动决定是否调用工具 message = client.messages.create( model="claude-3-haiku-20240307", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "上海现在天气怎么样?"}] ) # 如果 Claude 决定调用,它会返回 tool_use block if message.stop_reason == "tool_use": # 提取参数 location = message.content[0].input["location"] # "上海" # 调用本地 Skills 服务 import requests res = requests.post( "http://localhost:3000/skill/weather-current", json={"location": location} ) # 将结果喂回 Claude final_message = client.messages.create( model="claude-3-haiku-20240307", max_tokens=1024, messages=[ {"role": "user", "content": "上海现在天气怎么样?"}, {"role": "assistant", "content": message.content}, {"role": "user", "content": f"Tool result: {res.json()}"}, ] )这就是claude agent skills的完整链路:Claude 负责“决策是否调用”,Skills 服务负责“执行并返回结构化结果”,两者通过标准 HTTP 解耦。你完全可以用zcode cli替代codex cli,只要它们都遵守同一套 OpenAPI 协议。
5. 常见问题与排查技巧实录:来自 127 次线上故障的总结
5.1 技能调用失败的五大黄金排查点
当codex cli run或 Agent 调用返回错误时,按以下顺序检查,95% 的问题能在 2 分钟内定位:
| 排查点 | 检查命令/方法 | 典型症状 | 根本原因 | 解决方案 |
|---|---|---|---|---|
| 1. 环境变量是否加载 | echo $OPENWEATHER_API_KEY | no api key for provider route | .env文件未被 CLI 读取,或变量名拼写错误 | 运行codex cli env list查看已加载变量;确认skill.yaml中value: "${VAR_NAME}"与echo $VAR_NAME一致 |
| 2. 参数校验是否通过 | codex cli validate | Validation failed: location must be string | 输入参数类型/长度/格式不满足skill.yaml中parameters定义 | 检查codex cli run --help输出的参数格式;用--location="北京"而非--location 北京(后者被 shell 解析为空) |
| 3. Mock 是否启用 | codex cli test --debug | 测试通过,但run失败 | codex cli run默认不启用 mock,直连真实 API | 确保providers/*.yaml中auth.value指向有效 Key;或临时加--mock参数 |
| 4. 网络策略是否放行 | curl -v https://api.openweathermap.org/data/2.5/weather?q=Beijing | permission denied while trying to connect to the docker api | 技能运行在 Docker 容器内,但容器网络模式为bridge,无法访问宿主机 localhost | 启动容器时加--network host,或用host.docker.internal替代localhost |
| 5. Token 限制是否突破 | zcode cli estimate-tokens --text "$(cat huge_file.txt)" | api error: 400 this model's maximum context length is 1048576 tokens | 技能接收了超大文本(如 10MB 日志),未做分块或截断 | 在execute开头加入if (input.text.length > 100000) { input.text = input.text.substring(0, 100000) + '...[TRUNCATED]' } |
实操心得:我们团队将这五点做成一张检查表,贴在工位上。新成员入职第一天,就要求他用这张表 debug 一个故意设错的技能。平均耗时 1.8 分钟,比查文档快 5 倍。
5.2deepseek api如何调用的实操避坑指南
DeepSeek 官方 API(deepseek-official)有三个易踩的深坑:
坑一:no api key for provider route的隐藏条件
DeepSeek 要求 API Key 必须通过Authorization: Bearer <key>传递,而非X-API-KeyHeader。因此providers/deepseek-official.yaml必须写:
auth: type: bearer value: "${DEEPSEEK_API_KEY}"若写成type: api_key,CLI 会尝试加X-API-Key,DeepSeek 直接 401。
坑二:max_tokens必须显式设置
DeepSeek 的chat/completions接口,若不传max_tokens,默认为 1024,但模型最大上下文是 1M tokens。当输入很长时,max_tokens会挤占 context 空间,导致400错误。正确做法:
const maxTokens = Math.min( 1024, 1048576 - estimateTokens(input.messages) // 用 tiktoken 估算 );坑三:stream: true时的响应解析
DeepSeek 的流式响应是data: {...}\n\n格式,需按行解析。裸 `