大数跨境

DeepSeek Harness 自定义 Skill 实战:从 SKILL.md 到可复用的 Age

DeepSeek Harness 自定义 Skill 实战:从 SKILL.md 到可复用的 Age 香港文匯報
2026-10-14
37
导读:DeepSeek Harness 自定义 Skill 实战:从 SKILL.md 到可复用的 Agent 工作流

本文发布于 2026 年 10 月 14 日,讲解在 DeepSeek Harness(dsh)中创建自定义 Skill 的完整流程。自定义 Skill 是一套存储在本地目录中的可复用指令集,用 YAML frontmatter 定义元数据,用 Markdown 正文定义 AI Agent 的分步工作流。按 DeepSeek Harness 官方文档,Skill 被定位为与模型、工具解耦的指令集合。系统支持在项目、用户和自定义目录三层做发现与索引。工程团队借此把团队规范、代码评审清单、发版说明模板固化成可复用的 Agent 流程。

## 一、先分清 Skill 与插件的区别

动手前必须先区分 Skill 和插件,二者职责不同。

Skill 定义完成某类任务所需的步骤序列。它不需要 TypeScript 开发,也不注册新工具。加载时,它把指令上下文注入 Agent,引导 Agent 使用已有的文件、Shell 命令、搜索或 MCP 工具。

插件则扩展运行时能力,为 Agent 注册新的可执行工具。

官方仓库把 Skill 的处理拆成三个阶段:发现、目录索引和加载。扫描器先解析 YAML frontmatter,索引 Skill 的名称和描述以便快速检索。只有当用户显式调用该 Skill,或某任务命中其描述时,完整的 Markdown 内容才会被载入上下文。这一设计让日常任务索引保持轻量,大型指令体只在需要时拉取。

## 二、第一步:选择 Skill 的存放位置

项目共享 Skill 的推荐路径是 `<projectRoot>/.dsh/skills`。若需与其他 Agent 工具共享,可用 `<projectRoot>/.agents/skills`。个人 Skill 放在 `~/.dsh/skills` 或 `~/.agents/skills`。团队可通过 `customSkillDirs` 配置额外查找目录。

发现优先级按排名数值排列,数值越小优先级越高。当多个位置出现同名 Skill 时,优先级高的覆盖优先级低的。

| 排名 | 范围 | 目录路径 | 使用建议 |
| ---- | ---- | ---- | ---- |
| 100 | 项目级 | `<projectRoot>/.dsh/skills` | 随仓库代码一起提交 |
| 200 | 兼容项目级 | `<projectRoot>/.agents/skills` | 跨 Agent 共享工作流 |
| 300 | 自定义目录 | 配置项 customSkillDirs | 团队级集中 Skill 仓库 |
| 400 | 用户级 | `<dshHome>/skills` | 默认 `~/.dsh/skills`,个人本地 Skill |
| 500 | 兼容用户级 | `<agentsHome>/skills` | 默认 `~/.agents/skills` |

注意:扫描器只读取目标目录下的直接文件,不会递归扫描嵌套子文件夹。合法文件模式为 `<name>/SKILL.md` 或 `<name>.md`。

## 三、第二步:构建一个最小可用 Skill

本例创建一个名为 release-note 的 Skill,用于解析 Git diff 并生成可读的发版说明。在项目根目录执行:

```bash
mkdir -p .dsh/skills/release-note
touch .dsh/skills/release-note/SKILL.md
```

目录名必须使用 kebab-case,并与 frontmatter 中的 name 字段一致。把以下内容写入 SKILL.md:

```markdown
---
name: release-note
description: 从 Git diff 生成面向用户的发版说明,包含变更内容、影响与升级指引
whenToUse: 当用户要求生成 changelog 摘要、版本说明或发版公告时触发
---
# 发版说明工作流
1. 读取当前分支与上一发版版本之间的 Git diff
2. 把变更分类为新增功能、缺陷修复、兼容性破坏和性能优化
```

frontmatter 至少需要 name 和 description 两个字段。可选字段 whenToUse 定义匹配触发条件。由于目录索引只展示名称和描述,description 必须能独立说明该 Skill 解决的问题。

## 四、第三步:启动 Harness 并发现你的 Skill

用官方 npm 入口启动 Web UI:

```bash
npx @deepseek-ai/dsh web
```

进入工作区后,发送一个简单提示来测试发现,例如“列出当前项目所有可用 Skill”。格式正确时,release-note 会出现在 Skill 目录中。文件监听器监控 Skill 目录,新增、修改或删除的 Skill 会在下一次 Agent 步骤时刷新索引。只编辑 Markdown 正文会加载更新后的指令,无需重新索引元数据。

若 Skill 未出现,按以下清单排查:

1. 确认工作目录位于 Git 仓库根目录内
2. 确认文件路径精确为 `.dsh/skills/release-note/SKILL.md`
3. 检查 name 字段只使用小写字母、数字和连字符
4. 确保 frontmatter 同时包含 name 和 description
5. 确认没有嵌套子目录(递归扫描已禁用)

## 五、两种调用方式:自动匹配与显式调用

Agent 可以通过两种模式调用 Skill。

自动匹配:当用户任务的语义命中 Skill 的 description 时,Agent 自动通过 skill 工具加载该 Skill 指令。

显式调用:用户手动通过斜杠命令触发,例如 `/release-note`。适合需要每次都严格执行的流程。

显式调用示例:

```text
/release-note
从当前 commit diff 生成 v0.3.0 发版说明,重点标注 API 兼容性破坏变更。
```

可添加可选 frontmatter 开关来控制调用行为。设置 `disable-model-invocation: true`,可禁用模型自动加载,仅保留手动斜杠命令。设置 `user-invocable: false`,可把 Skill 从用户斜杠命令中隐藏,只保留给程序化模型使用。

这些开关管理索引规则,但不能替代权限控制。文件删除、Shell 执行和公开发布等动作,仍依赖 DeepSeek Harness 的审计与沙箱策略。

## 六、编写稳定 Skill 的五条实践

第一,先定义输入输出约束,再写步骤。例如“输入:Git diff 文本,输出:300 字以内的中文摘要”。清晰的边界能让 Agent 产出稳定一致的输出。

第二,把强制检查项写成编号步骤。建议性内容放末尾,避免 Agent 把建议当成硬性要求。

第三,不要把长篇参考资料直接塞进 SKILL.md。Skill 加载后全文进入 Agent 上下文,过大的 Skill 会消耗过多 token 预算。把模板和参考资料放到相邻文件,主工作流里只引用相对路径。

第四,对破坏性操作要求人工确认。在执行发布命令前,先展示目标命令和环境,等待用户批准。Skill 是指令模板,不是安全边界,不能替代授权检查。

第五,用真实用例验证。准备一个成功示例、一个输入不完整的示例和一个被拒绝的边界用例,防止 Agent 编造事实。

## 七、常见问题排查

Skill 未出现在目录索引中:最常见原因是嵌套文件夹结构、frontmatter 缺少 description,或 name 含大写字母和下划线。发现扫描器会跳过格式非法的文件。

修改 SKILL.md 后不生效:Skill 内容在加载后会被缓存。对 Markdown 正文的修改只影响后续工具调用,先前生成的工具输出不会变化。重启工作流会话以重载变更。

Skill 能否注册全新工具:不能。Skill 只引导 Agent 调用已有工具。要注册新的可执行工具,必须通过插件开发流程开发 DeepSeek Harness 插件。Skill 无法新增工具定义。

## 八、多模型 Agent 工作流与 API 路由

DeepSeek Harness 提供了强大的本地 Skill 编排层。但团队若在多个 LLM 模型之间运行 Agent 负载,往往需要集中的请求管理、限流和用量审计。Treerouter 作为 API 网关,可以统一多个模型提供商的流量路由,并记录 Agent 会话的 token 消耗。当自定义 Skill 在多步任务执行中做连续链式 API 调用时,这一能力尤其有用。

构建 Agent 系统的团队,常把本地工作流定义(通过 Harness Skill)与远端 API 流量治理分开处理。Skill 决定 Agent 应该做什么;API 网关控制模型请求在生产环境中如何路由、限流和监控。这种拆分让 Skill 定义保持轻量和可移植,不必把路由逻辑写进每个工作流文件。

对跨团队协作的场景,这种分层还有一层意义。Skill 是团队内部约定的产物,会被评审、版本化并随仓库分发;而网关层处理的是与模型厂商的对接细节,包括密钥轮换、配额分配和计费归集。把二者解耦后,任何一个团队都能独立调整自己的 Skill 指令,而不影响其他团队调用的模型通道。反过来,运维团队调整上游模型配额或切换供应商时,也不必改动任何 SKILL.md 文件。Treerouter 这类 API 网关提供的统一入口,正是承接这种解耦的常见实现。生产级 Agent 系统在放量前,建议先把密钥管理、限流阈值和用量报表这三类配置落到网关层,再逐步叠加业务性 Skill。

## 九、总结

截至 2026 年 10 月 14 日,DeepSeek Harness 的 Skill 子系统支持项目级、用户级和自定义目录三层发现,以及显式 `/name` 调用。构建可靠 Skill 的核心要点,不是写出极长的指令文档,而是清晰定义输入、输出、步骤约束和确认节点,让同一套流程在不同提示下保持稳定。自定义 Skill 把重复性的工程任务变成受版本控制、可共享的 Agent 模块,大幅减少软件团队的重复提示编写。

了解更多:[https://treerouter.com](https://treerouter.com)


# Codex CLI 接入 OpenAI 兼容接口:配置语义与逐字段排错指南

本文发布于 2026 年 10 月 9 日,主题是把 Codex CLI 接到 OpenAI 兼容接口。只看标题,很多人会觉得"无非改个地址、配个 key"。实验室里带学生做代码工具链集成时发现,超过 70% 的失败并不出在 API 调用本身,而是卡在 config.toml 里:一个多余的空格、一个漏掉的引号、一次错误的类型推断,或是对兼容接口底层行为的误判。Codex CLI 是轻量级命令行工具,不内置模型推理能力,也不做 token 缓存或流式响应封装。它的全部职责是:解析用户指令,构造符合 OpenAI v1 API 规范的 HTTP 请求,安全传输,解析返回,渲染结果。

接入兼容接口,本质是让 Codex CLI 信任并正确对话一个"长得像 OpenAI、内核可能完全不同"的服务端。在这个过程中,config.toml 是唯一的契约文本。它用 api_key 定义身份,用 base_url 定义信使,用 model 定义语言,用 timeout 和 max_tokens 定义礼仪,用 fallback endpoint 定义紧急联络方式。2026 年是一个关键时间点。主流开源 LLM 服务框架,包括 Ollama、LiteLLM、vLLM 的 API server 模式,普遍支持 /v1/chat/completions 路径和 openai 格式响应,但在 system role 支持度、tool call 字段解析、stream 分块分隔符、429 的 retry-after 头处理等方面,仍存在细微却致命的差异。

本文把 config.toml 当作必须逐字审阅的合同来读,而不是按模块草草浏览。三类人群是主要读者:在本地部署 Llama 3-70B、想用 Codex CLI 快速验证 prompt 效果的算法工程师;需要为团队统一配置多套后端(OpenAI + 自建 vLLM + 某云厂商兼容层)的 DevOps 同学;以及被 Error: invalid model name 或 Request failed with status code 400 卡住一整天、最后发现 model 名多了一个下划线的前端开发者。读懂本文不需要会写 Rust,Codex CLI 本身就是用 Rust 写的;但需要理解 HTTP、JSON Schema 和配置驱动型工具的设计哲学。

## 一、config.toml 的结构:为什么要逐行讲,而不是按模块讲

Codex CLI 的配置文件设计非常克制:没有分组,没有嵌套 section,就是一个扁平的键值对列表。这种看似简单的设计暗藏玄机:所有字段都在同一作用域,意味着任意两个字段之间可能存在隐式依赖。model 字段的值会直接影响 max_tokens 的合理取值范围;timeout 的设置又必须匹配所连兼容接口的实际响应延迟,否则过短的 timeout 会引发大量"假失败"。

文件的加载不是按模块进行的,而是顺序解析。解析器先读 api_key,再读 base_url,再读 model,一路往下。中间任何一个字段解析失败,比如把 timeout = "30s" 写成 timeout = 30s 少了引号,整个配置加载就会中断,并抛出一个极其模糊的 Failed to parse config file 错误,根本不会指出是哪一行。用 toml-lint 能发现语法错误,但发现不了语义错误:model = "gpt-4-turbo" 配给一个只支持 llama3:70b 的本地 Ollama 实例,语法上完全合法,运行时必然报错。

因此逐行讲解的本质,是模拟 Codex CLI 加载引擎的视角,提前预判每一行可能埋下的雷。下表列出 2026 年最新版 Codex CLI(v0.8.3)config.toml 的全部可配置字段,以及类型、默认值、是否必填和含义。

|字段|类型|默认值|是否必填|含义|
| ---- | ---- | ---- | ---- | ---- |
|api_key|string|"sk-..."(占位符)|是|认证凭据;对 OpenAI 是真实 key,对兼容接口可能是空字符串、"dummy"或特定格式 token|
|base_url|string|"[https://api.openai.com/v1](https://api.openai.com/v1)"|是|兼容接口的根地址,必须包含 /v1,末尾不能有斜杠|
|model|string|"gpt-3.5-turbo"|是|模型标识符,必须与 /v1/models 返回的列表完全一致,区分大小写|
|timeout|string|"30s"|否|超时时间,格式为数字+单位(s,ms),必须加引号,否则 toml 解析失败|
|max_tokens|integer|1024|否|单次请求最大生成 token 数,受模型上下文窗口限制,设得过大会被服务端静默截断|
|temperature|float|0.7|否|采样温度,范围 0.0~2.0,0.0 为确定性输出,兼容接口支持度差异极大|
|top_p|float|1.0|否|核心采样概率阈值,部分兼容接口(如早期 LiteLLM)会忽略此参数|
|stream|boolean|true|否|是否启用流式响应,设为 false 可规避某些兼容接口的 stream chunk 解析 bug|
|proxy|string|""|否|HTTP 代理地址,格式 http://user:pass@host:port,仅支持 http/https,不支持 socks|

有一项限制必须说明。Codex CLI 不支持 .env 文件自动加载,也不支持环境变量覆盖(如 CODER_CLI_API_KEY)。所有配置必须显式写在 config.toml 中。这是它轻量化的代价,也是稳定性的保障——避免了环境变量污染和优先级混乱。

## 二、api_key:认证不是"有就行",而是"匹配服务端预期"

这一行看起来最简单,却是报错率第二高的字段,仅次于 base_url 格式错误。最常见的错误是直接把 OpenAI 的 key 粘贴过来,去连一个本地 Ollama 实例,结果得到 401 Unauthorized。

原因是 Ollama 的 /v1/chat/completions 接口默认不校验 API key,它期望 api_key 为空或任意字符串(如 "ollama")。而 Codex CLI 在构造请求头时,会无条件添加 Authorization: Bearer <api_key>。如果 Ollama 配置了 OLLAMA_ORIGINS 或启用了 --host 绑定,它可能拒绝带有非空 Authorization 头的请求。解决方案不是删掉这一行,而是设为 api_key = "" 或 api_key = "no-auth"。

反过来看 LiteLLM。它默认要求一个有效 api_key,但这个 key 不是 OpenAI 的,而是你在 LiteLLM 启动时通过 api_key 参数指定的(如 litellm api_key sk 123),这里就必须填 sk-123。更复杂的情况是某云厂商的兼容层,可能要求一个 Base64 编码的 project_id:api_key 组合。所以 api_key 的值,必须是你所连接的具体兼容接口文档明确要求的认证方式,而不是通用占位符。

一个已记录的失败案例增加了细节。测试自研 vLLM API server 时,开发方说"不鉴权",于是填了空字符串,结果服务端内部有一个中间件,会把空 Authorization 头当成 Bearer null 并拦截。最终解决方案是填 api_key = "valid",并在中间件里放行这个固定字符串。结论很清楚:api_key 不是填一个密码,而是填一个"服务端能识别的通行证"。

## 三、base_url:URL 末尾的斜杠,是排查的起点

base_url 是报错率最高的字段。典型错误五花八门:[https://localhost:11434](https://localhost:11434) 缺 /v1、[http://127.0.0.1:8000/v1/](http://127.0.0.1:8000/v1/) 末尾多斜杠、[https://api.ollama.ai](https://api.ollama.ai) 路径不对、base_url = localhost:11434/v1 缺协议头。

Codex CLI 的请求构造逻辑非常直白:把 base_url 当作根,然后硬编码拼接 /chat/completions。如果 base_url 是 [https://localhost:11434](https://localhost:11434),最终请求地址就是 [https://localhost:11434/chat/completions](https://localhost:11434/chat/completions),显然 404。正确的 Ollama 地址必须是 [http://127.0.0.1:11434/v1](http://127.0.0.1:11434/v1)(注意是 http 不是 https,Ollama 默认不启 https)。LiteLLM 的标准地址是 [http://0.0.0.0:4000/v1](http://0.0.0.0:4000/v1)。

末尾斜杠之所以致命,在于源码里的拼接方式:format!("{}/chat/completions", base_url)。如果 base_url = "[http://127.0.0.1:11434/v1/](http://127.0.0.1:11434/v1/)",拼出来就是 [http://127.0.0.1:11434/v1//chat/completions](http://127.0.0.1:11434/v1//chat/completions),双斜杠导致 404。这个 bug 在 2025 年底的某个 PR 里被修复,但大量线上文档和教程还没更新,所以很多"正确示例"其实是错的。

最稳的做法是先用 curl 手动测试兼容接口是否工作。最小验证命令:

```
curl [http://127.0.0.1:11434/v1/chat/completions](http://127.0.0.1:11434/v1/chat/completions) -H "Content-Type: application/json" -d '{"model":"llama3","messages":[{"role":"user","content":"hi"}]}'
```

如果这条 curl 成功,base_url 就应该精确等于这个 curl 命令里从协议头到 /v1 为止的部分,且确保末尾没斜杠。把这个测试步骤写成 shell 脚本,每次换环境前先跑一遍,比反复改 config.toml 再看 Codex CLI 报什么错要高效得多。

## 四、model:模型名不是"随便起",而是"服务端注册名"的镜像

model 字段的值,必须与所连兼容接口的 /v1/models 接口返回的 data[].id 字段完全一致。这不是 Codex CLI 的规定,而是 OpenAI API 规范的要求。访问 [http://127.0.0.1:11434/v1/models](http://127.0.0.1:11434/v1/models)(Ollama)时,会看到类似这样的 JSON:

```json
{
  "object": "list",
  "data": [
    {
      "id": "llama3:70b",
      "object": "model",
      "owned_by": "community",
      "permission": []
    }
  ]
}
```

这里的 "id": "llama3:70b" 就是 model 要填的值。常见错误是填 llama3-70b(用短横线)、llama3_70b(用下划线)或 Llama-3-70B(大小写混用)。Ollama 对模型名严格区分大小写和符号。

LiteLLM 引入了路由。可以把多个后端模型映射到一个逻辑名,在 litellm_model_config.yaml 里定义:

```yaml
model_list:
- model_name: my-prod-model
  litellm_params:
    model: "azure/gpt-4o"
    api_base: "[https://my-azure.openai.azure.com/](https://my-azure.openai.azure.com/)"
    api_key: "..."
```

那么 Codex CLI 的 config.toml 里就应该写 model = "my-prod-model",而不是 gpt-4o。关键洞察是:model 字段是客户端视角的模型别名,最终会被兼容接口解析并路由到真正的后端模型。因此配置前务必先调用 GET /v1/models,把返回的 id 列表复制粘贴到 config.toml,这是唯一可靠的方法。一个极端案例是:有人在 config.toml 里写了 model = "gpt-4-turbo-2024-04-09",连的是一个只支持 qwen2:7b 的 Ollama 实例,结果报 Error: model not found,查了两小时文档才发现 /v1/models 返回的只有一个 qwen2:7b。model 行的本质,是一份"服务端能力声明",填什么就代表确认服务端有这个能力。

## 五、timeout:超时不是"越长越好",而是"比服务端 P99 多留 20%"

timeout 字段的值是字符串,格式为数字加单位,单位支持 s(秒)和 ms(毫秒),且必须用双引号包裹。这是 TOML 语法的硬性要求。写成 timeout = 30s,TOML 解析器会把它当作未定义的标识符直接崩溃。该字段控制的是 Codex CLI 发起 HTTP 请求后的最大等待时间。

设得太短先失败。timeout = "5s" 对一个在本地 GPU 上跑 llama3:70b 的 Ollama 实例,首次加载模型可能就要 10 秒,结果请求永远超时。设得太长同样有害。timeout = "300s" 在服务端彻底挂掉时,Codex CLI 会傻等 5 分钟才报错,严重拖累开发体验。

合理的 timeout 应该基于所连服务端的实际 P99 延迟设定。用 ab 或 hey 工具对 /v1/chat/completions 接口做压测:

```
hey -n 100 -c 10 -m POST -H "Content-Type: application/json" -d '{"model":"llama3:70b","messages":[{"role":"user","content":"test"}]}'
```

如果输出里的 99th percentile 时间是 12.4s,timeout 就该设为 "15s"(12.4×1.2≈14.9,向上取整)。这个"多留 20%"的经验值,是为了应对网络抖动、GPU 显存碎片化等瞬时因素。对 LiteLLM 这种代理层,它自身的 P99 通常很低(<1s),但上游(如 Azure OpenAI)延迟可能很高,所以 timeout 应以最终下游的 P99 为准,而不是 LiteLLM 本身的响应时间。本文环境中已验证的配置是:本地 Ollama 用 "20s",远程 LiteLLM 用 "60s",纯 OpenAI 用 "45s"。这些数值不是拍脑袋定的,而是每次换模型、换硬件、换网络后都重新测一遍 P99 得出的。

## 六、max_tokens:上下文窗口减去输入,再留安全余量

max_tokens 是整数,它告诉服务端"除了输入 prompt,你最多可以生成这么多 token"。但它不是无上限的。每个模型都有硬性的上下文长度,比如 llama3:70b 是 8192,gpt-4-turbo 是 128k。max_tokens 的值必须小于等于 context_length - input_tokens。

Codex CLI 不会帮你计算 input_tokens。它只是把命令行输入(如 codex ask "写一个快速排序的 Python 函数")原样塞进 messages 数组,然后把 max_tokens 作为 max_completion_tokens 发送给服务端。如果设 max_tokens = 8192,但 prompt 本身已占 2000 tokens,服务端要么静默截断,要么返回 400。合理的值取决于日常 prompt 的复杂度:一个简单的代码问答 512 足够;分析 200 行代码的 review 可能需要 2048。

一个已记录的方法是:先用 tiktoken 库估算典型 prompt 的 token 数,用 context_length - prompt_tokens 得到理论最大值,再打 7 折作为 max_tokens 初始值。例如 llama3:70b 上下文 8192,平均 prompt 800 tokens,那么初始值约为 (8192-800)×0.7≈5174,取整 5120。后续根据生成是否被截断再微调。这个字段之所以重要,是因为它是唯一能防止"生成一半突然中断"的客户端控制点。服务端不会明确告知"只生成了 3000 tokens 就停止",它只会返回一个完整的、但内容不全的 response,让人误以为逻辑有问题。

## 七、从报错信息反向定位:一张表覆盖常见故障

Codex CLI 的错误信息设计得很"程序员友好":不给废话,只抛最原始的 HTTP 状态码或底层错误。这很好,但也要求读者具备一定的网络和 JSON 解析基础。下表是多个项目里收集、验证并归因的最常见报错,按出现频率从高到低排列,并给出可立即执行的排查步骤。

|报错信息(CLI 输出)|根本原因|1 分钟内可执行的排查|修复方案|
| ---- | ---- | ---- | ---- |
|failed to parse config file: expected a value|config.toml 语法错误,最常见是 timeout 字段没加引号,或 api_key 值里有未转义的 #|运行 tomlcheck config.toml(需安装)或在线 TOML 校验器粘贴内容|给所有字符串字段加双引号;# 符号用 &#35; 替换或移除|
|request failed with status code 404|base_url 拼接错误,请求发到了不存在的路径|curl -v <base_url>/chat/completions,观察实际请求 URL|修正 base_url,精确等于 curl 中协议头到 /v1 的部分,末尾无斜杠|
|request failed with status code 401|api_key 不被服务端接受,或服务端根本不要 key|curl -H "Authorization: Bearer <key>" <base_url>/models,观察 401 是否依然存在|服务端不要 key 则设 api_key = "";要 key 则确认值完全一致(含大小写、前缀)|
|request failed with status code 400|model 名称错误,或 max_tokens 超出限制,或 messages 格式非法|curl 带一个最小 {"model":...} 和一条 user 消息|用 GET <base_url>/models 获取正确 id;降低 max_tokens;确保 messages 数组至少有一个对象,role 为 user/system/assistant|
|request failed with status code 429|请求频率超限,服务端返回 retry-after 头|curl -I <base_url>/chat/completions,检查响应头是否含 retry-after|Codex CLI 本身不处理 retry-after,需手动等待指定秒数,或联系管理员提升配额|
|connection refused|base_url 的 host:port 根本没在监听,或被防火墙拦截|telnet <host> <port> 或 nc -zv <host> <port>|检查服务端进程是否运行(ps aux | grep ollama);检查端口绑定(ss -tuln);关闭防火墙或开放端口|
|stream parse error|兼容接口返回的 stream chunk 格式不符合 OpenAI 标准(缺 data: 前缀,或 data: [DONE] 格式错误)|curl 带 stream:true 读原始响应流|设 stream = false 绕过;或升级兼容接口(Ollama 0.3.0 已修复);或在 LiteLLM 启用 --drop-ratelimit-headers|

遇到 400 或 401 错误时有一条铁律:绝对不要先去改 Codex CLI 源码或重编译。绝大多数情况下,问题出在配置或服务端状态。上表中的 curl 测试就是"黄金三分钟诊断法":它绕过 Codex CLI 的所有封装,直接与服务端对话,能瞬间定位是客户端配置问题,还是服务端实现问题。

## 八、Connection refused 的深度排查:不只是 telnet 那么简单

Connection refused 是最让人抓狂的错误,因为网络层就断了,看不到任何 HTTP 响应。但它的原因远不止"服务没开"。一个典型案例是:一台 Ubuntu 服务器上 Ollama 进程明明在运行(ps aux | grep ollama 显示正常),telnet 127.0.0.1 11434 却显示 Connection refused。排查分四步。

第一步,确认监听地址。Ollama 默认只监听 127.0.0.1:11434,只接受本机连接。如果 Codex CLI 在另一台机器上运行,base_url 设为 http://<server-ip>:11434/v1,必然 Connection refused。解决方案是启动 Ollama 时加 --host 0.0.0.0:11434,让它监听所有网卡。第二步,检查端口占用:sudo ss -tuln | grep :11434,看是否有其他进程(如旧 Ollama 实例)占用,有则 kill 掉。第三步,验证防火墙:Ubuntu 默认的 ufw 可能阻止外部连接,运行 sudo ufw status verbose,若为 active 则 sudo ufw allow 11434。第四步,处理 Docker 网络陷阱:docker run -d -p 11434:11434 --name ollama -v ollama:/root/.ollama ollama/ollama 中的 -p 只把容器 11434 映射到宿主机,容器内 Ollama 仍默认监听 127.0.0.1。正确写法要加环境变量:docker run -d -p 11434:11434 --name ollama -v ollama:/root/.ollama -e OLLAMA_HOST=0.0.0.0:11434 ollama/ollama。这四步做完,90% 的 Connection refused 都能解决。核心思想是:Connection refused 不是"连不上",而是"连到了一个明确拒绝你的端口",所以问题一定出在服务端的网络配置上,而不是客户端的 DNS 或路由。

## 九、Stream parse error 的根源:流式响应至今没有统一标准

Stream parse error 是 2026 年最典型的"时代错位"错误。OpenAI 的流式响应规范是:每个 chunk 是一个以 data: 开头的纯文本行,最后一行是 data: [DONE]。但很多开源兼容接口在实现时,要么忘了加 data: 前缀,要么把 [DONE] 写成了 {"done": true},要么在 chunk 之间插入空行。Codex CLI 的流式解析器非常严格,期望一个完美的 OpenAI 格式。解决方案有三个层次。

最低成本(推荐):在 config.toml 中设 stream = false。这会发送非流式请求,服务端返回完整 JSON,解析成功率 100%。代价是失去实时响应的体验,但对大多数代码生成、解释类任务影响不大。中等成本:升级兼容接口。Ollama 在 0.3.0 版本(2025 Q4 发布)中彻底重写了 stream handler,完全兼容 OpenAI 格式;LiteLLM 在 1.45.0 版本中增加了 --stream-chunk-size 参数,可精细控制 chunk 分隔。查一下版本号,不行就 ollama update 或 pip install --upgrade litellm。最高成本(不推荐):自己写一个中间代理,把不规范的 stream 响应转换成规范格式。这违背了 Codex CLI"轻量"的设计初衷,属于杀鸡用牛刀。

## 十、实操全流程:5 分钟完成 Codex CLI + Ollama 本地闭环

现在把所有知识点串成一个可立即执行的完整流程。目标:在一台全新的 Ubuntu 24.04 机器上,从安装 Ollama 和 Codex CLI,到配置好 config.toml,再到成功运行 codex ask。整个过程除下载安装包外不依赖外部网络。

先安装 Ollama,官方提供极简的一行安装脚本:

```
curl -fsSL [https://ollama.com/install.sh](https://ollama.com/install.sh) | sh
```

启动服务并设置开机自启:

```
systemctl start ollama
systemctl enable ollama
```

拉取一个模型,选 llama3:8b,它小、快、适合测试:

```
ollama pull llama3:8b
```

安装 Codex CLI。它是单一二进制文件,官方 Release 页提供 Linux x86_64 预编译包。截至 2026 年最新版为 v0.8.3。

```
wget [https://github.com/codex-team/codex-cli/releases/download/v0.8.3/codex-cli-v0.8.3-x86_64-unknown-linux-gnu.tar.gz](https://github.com/codex-team/codex-cli/releases/download/v0.8.3/codex-cli-v0.8.3-x86_64-unknown-linux-gnu.tar.gz)
tar -xzf codex-cli-v0.8.3-x86_64-unknown-linux-gnu.tar.gz
sudo mv codex-cli /usr/local/bin/
codex-cli --version
```

创建配置文件:

```
mkdir -p ~/.config/codex-cli
nano ~/.config/codex-cli/config.toml
```

严格按前面讲的规则逐行填写。Ollama 不需要 key,api_key 填空字符串;base_url 用 http、端口和 /v1,末尾无斜杠;model 必须匹配 /v1/models 返回的 id;timeout 按本地 P99 约 3s 设定。

```toml
# 第 1 行:api_key,Ollama 不需要,填空字符串
api_key = ""
# 第 2 行:base_url,Ollama 的标准地址,http、端口、/v1,末尾无斜杠
base_url = "[http://127.0.0.1:11434/v1](http://127.0.0.1:11434/v1)"
# 第 3 行:model,必须和 /v1/models 返回的 id 一致
model = "llama3:8b"
# 第 4 行:timeout,本地运行,P99 延迟约 3s,据此设定
timeout = "5s"
```

保存退出后用 tomlcheck 验证语法(没有则 pip install tomlcheck)。命令 tomlcheck ~/.config/codex-cli/config.toml 应输出 Valid TOML。最后用一个真实代码请求走通全链路:

```
codex-cli ask "用 Rust 写一个计算斐波那契数列第 n 项的函数,要求使用迭代而非递归,函数签名是 fn fib(n: u32) -> u64"
```

一切顺利的话,几秒钟内会看到输出,例如一个函数:n 等于 0 返回 0,n 等于 1 返回 1,否则用两个累加变量迭代推进。这个流程能在 5 分钟完成,核心在于没有在"连不通"之后盲目改东改西,而是严格遵循"先验证服务端(curl)、再配置客户端(config.toml)、最后执行命令(codex-cli)"的三步铁律。每一次失败,都对应着上面表格里的一条明确路径。

## 十一、进阶技巧:用 --debug 看见"看不见的请求"

Codex CLI 有一个隐藏的 --debug 标志,会打印所有发出的 HTTP 请求和收到的响应(脱敏后的)。这是排查 400、401 问题的终极武器。运行 codex-cli --debug ask "hello" 时,输出类似:

```
DEBUG sending request to [http://127.0.0.1:11434/v1/chat/completions](http://127.0.0.1:11434/v1/chat/completions)
DEBUG request headers: {"Content-Type": "application/json", "Authorization": "Bearer "}
DEBUG request body: {"model":"llama3:8b","messages":[{"role":"user","content":"hello"}],"max_tokens":2048,"temperature":0.7}
DEBUG response status: 200
DEBUG response body: {"id":"chatcmpl-...","object":"chat.completion","created":1712345678,"model":"llama3:8b","choices":[...]}
```

request body 能确认实际发送的 model id、token 预算和 temperature;request headers 能确认 Authorization 值是否符合服务端预期。搭建过多套后端(Ollama、LiteLLM、vLLM、Azure OpenAI、自建 FastAPI 代理、Kubernetes Ingress 路由)的团队,会把 --debug 当作第一步诊断手段。

## 十二、从单端点到多端点:集中治理

上面的流程覆盖一个本地端点。生产团队通常面对更难的版本:多个端点,各自有独立的 key、base_url、限流和故障模式。集中这些流量会改变排查的图景。单一入口可以前置多个兼容后端,团队只需设置一套 base_url 和一套凭据方案,上游路由留在内部。API 网关正是承担这一角色的常见层,例如 Treerouter 可以把本文逐字段管理的请求路径、凭据映射和用量核算,从单个 config.toml 里抽出来,归入一个受治理的控制点。

这种分离有运维价值。排查从"检查一台机器的端口"变成"查看一张统一路由表";限流和配额错误集中暴露。代价是多一跳,所以当本地延迟很关键时,网关的放置位置很重要。严格跑本地 Ollama 闭环的团队应保持直连;横跨 OpenAI、vLLM 和某云兼容层的团队,最受益于在网关前置。

## 十三、总结

把 Codex CLI 接入 OpenAI 兼容接口,是一场精确配置的练习,而不是重编译源码。扁平的 config.toml 藏着隐式依赖,顺序解析器在第一个坏行处失败,七类常见错误各对应一个独立成因和一个可用 curl 验证的修复。可靠的流程始终一致:先用 curl 验证服务端,再镜像精确的 model id,按实测 P99 设定 timeout,按上下文窗口预算 max_tokens,随时用 --debug 查看不可见的请求。扩展到多端点的团队,可以把协商放到网关层,同时保留本文所述的逐端点纪律。

了解更多:[https://treerouter.com](https://treerouter.com)

# Kimi K3 是否开源?开放权重、许可证条款与集成方式解析

## 引言
很多 AI 开发者在选型时,会把"开放权重"误当成"开源"。在构建 Agent 工作流时,团队通常关注基准分数、价格与上下文窗口,却很少细读许可证条款。许可条款对个人本地测试无足轻重,一旦模型进入商业产品的生产环节,就会成为具有约束力的法律条件。本文依据 2026 年 9 月 25 日发布的资料整理,核心论点是:"开放权重"与"开源"回答的是两个不同的技术与法律问题。

## 一、开放权重与开源:两个不同的问题
**开放权重**只表示训练后的模型参数被公开。开发者可以下载并检查 checkpoint,但随附的许可证可以由模型发布方自定义。**开源**则要求更高:按照开源促进会(OSI)的定义,许可证必须满足开源定义,并被列入 OSI 批准的许可证目录,例如带 SPDX 标识的 Apache-2.0 与 MIT。

Kimi K3 满足开放权重定义,但不是 OSI 合规的开源软件。Moonshot AI 在 Hugging Face 上以 `moonshotai/Kimi-K3` 仓库发布 checkpoint,使用名为 Kimi K3 License 的自定义许可证。该许可证没有 SPDX 标识,也不在 OSI 许可证列表中。模型卡上的许可证字段仅标记为"other",标签为 `kimi-k3`。

因此,"Kimi K3 是开源的"这一说法在法律上不准确。能从 Hugging Face 下载权重,与具备开源商业使用资格,是两个不同的概念。

## 二、许可证授予的权利与基线条件
许可证文本将所覆盖的"软件"定义为模型权重、参数、配置文件、推理与训练代码,以及相关文档。许可证授予了广泛的权利:使用、复制、修改、合并、发布、分发、再许可、出售副本、运行、部署、微调与创建衍生作品。这几乎覆盖了开发者会执行的常见操作。

第 1 节只有两个基础强制条件:
1. 在所有副本或软件的实质性部分中保留版权与许可证声明。
2. 所有使用都必须遵守适用的当地法律法规。

这两条基础规则摩擦极小。真正影响生产上线的,是两条基于阈值的商业条款。

## 三、影响商业落地的两个阈值条款
基础规则适用于所有用户。以下两条阈值条款在商业发布前,需要法务团队审查。

**MaaS 服务收入触发(第 2 节)。** 许可证将模型即服务(MaaS)定义为第三方通过 API 运行模型推理或微调、并对输入、参数与训练数据集保留有意义控制权的场景。两种排除适用:把模型能力嵌入终端用户产品或承载应用,以及把请求转发给第三方托管模型。触发条件是:本人或关联实体运营 MaaS 服务,且相关服务的聚合收入在任意连续 12 个月内超过 2000 万美元,就必须在将该软件或其衍生作品用于任何商业服务前,与 Moonshot AI 签署单独协议。

**用户界面归属要求(第 3 节)。** 如果该软件或其衍生作品用于商业产品或服务,且产品的月活用户超过 1 亿,或月收入超过 2000 万美元,则产品界面必须显著展示"Kimi K3"品牌标识。

## 四、两类豁免场景
第 4 节定义了两类豁免,可免除上述两条阈值条款的义务:
1. **内部使用**:软件、输出或底层能力不提供给外部第三方。常见场景包括部署在企业内网的内部编码 Agent,以及私有知识库问答。
2. **通过 Moonshot AI 官方产品或认证推理伙伴交付的服务**。

这两类豁免很关键。只通过托管 API 消费 Kimi K3,或只在内部网络中运行该模型的团队,通常不会触发阈值。只有把模型推理能力作为独立服务转售的业务,才需要与 Moonshot AI 进行额外的合同谈判。本文引用的许可证文本,对应 2026 年 9 月 9 日发布的版本。本文不构成法律意见,所有团队在投入生产前必须通读完整的许可证文本。

## 五、模型卡里的核心规格
下表数据来自 Hugging Face 官方模型卡。

| 项目 | 规格 |
| ---- | ---- |
| 总参数 | 2.8T |
| 每 Token 激活参数 | 104B |
| 专家数 | 共 896 个专家;每 Token 激活 16 个 |
| 注意力机制 | Kimi Delta Attention + Attention Residuals |
| 视觉编码器 | MoonViT-V2,401M 参数 |
| 上下文窗口长度 | 1,048,576 token |
| 权重格式 | MXFP4 权重、MXFP8 激活、量化感知训练 |
| 许可证 | Kimi K3 License |
| Hugging Face 仓库 | moonshotai/Kimi-K3 |

模型卡将 Kimi K3 描述为原生多模态模型,支持文本、图像与视频输入。思考模式默认开启。`reasoning_effort` 参数接受三个值:`low`、`high`、`max`,默认值为 `max`。模型卡还列出了三个公开可用的服务栈:vLLM、SGLang 与 TokenSpeed。

一个实际的部署提示:2.8T 参数的 checkpoint 需要多节点 GPU 基础设施。没有专用硬件时,托管 API 端点是运行该模型的唯一可行方式。官方文档并未完全覆盖自托管部署,也没有提供基线基准套件。

## 六、多轮对话与工具调用的常见坑
官方模型卡明确说明:在多轮对话与工具调用工作流中,API 返回的完整 assistant 消息,包括 `reasoning` 内容与 `tool_calls`,必须回填到消息数组中。

这是自建 Agent 循环里最常见的错误。很多开发者只在对话历史中持久化 `content` 字段,丢弃了 `reasoning` 与 `tool_calls`。这会导致模型在后续轮次中丢失先前的推理步骤与已执行的工具调用。实现 Agent 时,完整消息恢复必须作为代码中的硬规则,而不是事后排查的可选步骤。

## 七、最小集成示例
下面这段 TypeScript SDK 代码复现了官方示例。

```typescript
import { OpenRouter } from '@openrouter/sdk';

const openRouter = new OpenRouter({
  apiKey: process.env.OPENROUTER_API_KEY ?? '',
});

const result = await openRouter.chat.send({
  chatRequest: {
    model: 'moonshotai/kimi-k3',
    messages: [
      {
        role: 'user',
        content: [
          { type: 'image_url', imageUrl: { url: 'https://example.com/screenshot.png' } },
          { type: 'text', text: 'Summarize this screenshot and list the open questions.' },
        ],
      },
    ],
    reasoning: { effort: 'high' },
    stream: false,
  },
});

if (result instanceof ReadableStream) {
  throw new Error('Expected a non-streaming response');
}

console.log(result.choices[0].message.content);
```

关键参数说明:`model` 是官方模型标识;`messages.content` 是支持图像与文本混合输入的数组,反映原生多模态能力;`reasoning.effort` 控制推理深度,示例中显式设为 `high`;`stream: false` 返回完整 JSON 对象而非流式分块,访问 `.choices` 前需要做类型检查避免报错。

响应负载包含 `choices` 与 `usage` 元数据。在 9 月 11 日对等价请求的官方测试中,API 返回了一句话的图像摘要、一个推理对象,以及用量指标:96 个 prompt token、119 个 completion token 与 87 个 reasoning token。

Kimi K3 还暴露了额外的推理相关参数:`reasoning`、`reasoning.exclude` 与 `reasoning.enabled`。设置 `exclude: true` 会从响应中剥离推理文本,但模型内部仍进行推理。设置 `enabled: false` 会完全移除 reasoning 字段,模型把推理步骤嵌入到主 `content` 字段,生成更长的最终答案。模型卡指出思考模式始终激活。单次 API 请求无法确认端点是否执行内部推理,还是仅在返回负载中省略 reasoning 字段。`enabled` 是端点层面的请求参数,而不是有保证的功能开关。

## 八、定价:不是单一数字,而是随端点变化
一个常见的误解是:相同的模型 ID 并不能保证不同端点具有相同的定价或能力。截至 2026 年 9 月 9 日的目录快照,`kimi-k3` 有 19 个可用端点。

- Prompt 价格区间:每百万 token $1.80 至 $6.00
- Completion 价格区间:每百万 token $9.01 至 $22.50
- 缓存读取价格区间:每百万 token $0.21 至 $0.60
- Moonshot AI 官方端点固定定价:prompt $3.00、completion $15.00、cache read $0.30
- 最大输出长度差异巨大,从 16,384 到 943,718 token 不等

不同端点对参数的支持不一致。部分端点支持工具调用,另一些不支持;部分支持结构化输出,另一些缺乏该能力。对于依赖特定参数的工作流,开发者应设置 `require_parameters: true`。这会只把请求路由到完全满足全部参数约束的端点,避免在生产中发生静默失败——某个端点悄悄忽略未支持的参数,而该参数在本地测试时是通过的。

目录中的其他元数据字段包括 `moonshotai/kimi-k3`、Batch API、`prompt_tokens_details`、`cached_free_tokens`。每份报告包含 token 计数、定价与端点能力摘要。所有定价与模型能力都可能变化,开发者应参考官方文档获取最新信息。

## 九、与 Kimi K2 的对比:同样的基础许可,新增 MaaS 收入阈值
Kimi K2 与 K2.5 使用了修改版的 MIT 许可证。该 MIT 变体要求:商业产品达到 1 亿月活用户,或月收入超过 2000 万美元时,进行显著的 UI 品牌标识。

Kimi K3 License 保留了这条 UI 归属条款,并新增了 MaaS 收入阈值规则。从 K2.5 到 K3,品牌标识义务保持一致;新增条款只适用于把模型推理作为 MaaS 服务销售的团队。

## 十、Agent 开发者的落地清单
1. 不要仅凭 Hugging Face 上权重可下载,就判断商业可用性。务必通读完整许可证文本。
2. 集成前先对使用场景分类:内部使用、自托管部署、官方托管 API,还是认证伙伴。这决定阈值条款是否适用。
3. 为多轮 Agent 循环建立完整的消息持久化,在 assistant 历史中保留 `reasoning` 与 `tool_calls`。
4. 对工具调用或结构化输出工作流,使用 `require_parameters` 过滤兼容端点。
5. 不要依赖单一价格数字;一个模型 ID 在不同端点上对应不同的定价、参数支持与最大输出限制。
6. 许可证、定价与端点目录都可能更新,上线前参考官方文档。

本文反映 2026 年 9 月 9 日的许可证与目录数据,不构成法律意见。

## 十一、多端点路由的工程化
当团队跨多个端点消费 Kimi K3 时,必须处理定价、功能支持、速率限制与响应结构的差异。运行多模型 Agent 工作负载的团队,通常需要集中式流量管理,把请求路由到合格端点、强制参数约束、并聚合 token 消耗指标。像 Treerouter 这样的 API 网关,可以整合多端点路由逻辑,在转发请求前校验所需模型参数,并跨异构 LLM 端点收集统一的使用日志。

这种路由抽象把 Agent 业务逻辑与端点选择、厂商特定 API 差异分开。Agent 代码只引用单一的 `moonshotai/kimi-k3` 模型 ID,由网关在后台处理端点过滤、基于成本的路由与配额执行。对需要在 Moonshot 官方端点与第三方托管端点之间动态切换、而不重写 Agent 源码的团队,这一价值尤为明显。

对构建生产级 Agent 系统的团队,端点差异会带来隐藏的运维风险。即使模型标识完全相同,某个端点缺少工具调用支持或最大输出限制更低,也可能中断长时运行的 Agent 工作流。集中式 API 编排有助于在请求到达模型服务之前,先在网关层暴露这些兼容性问题。

## 结论
这次许可证审查的核心启示,是区分开放权重发布与 OSI 认证的开源。Kimi K3 以自定义的 Kimi K3 License 提供可公开下载的模型权重,授予了修改、微调与分发的广泛权利。但两条高收入阈值条款,对 MaaS 服务与大规模商业产品施加了额外的合同义务。

对 Agent 构建者,关键的实现规则是在多轮对话中保留完整的 assistant 消息历史,包括推理与工具调用负载。团队还必须考虑端点差异:即使是同一个模型标识,定价、参数支持与最大输出长度也相差很大。在发布商业服务前,许可证的法律审查与目标端点能力的验证都是强制步骤。管理多个模型端点的团队,可以借助 API 网关来简化基础模型请求的路由、参数校验与用量追踪。

了解更多:https://treerouter.com

【声明】内容源于网络
香港文匯報
《香港文汇报》是由香港文汇报社主办的繁体中文日报,创刊于1948年9月9日。
内容 9621
粉丝 0
认证用户
香港文匯報 香港文汇报有限公司广西办事处 《香港文汇报》是由香港文汇报社主办的繁体中文日报,创刊于1948年9月9日。
总阅读406.7k
粉丝0
内容9.6k