1. 从 requirements.md 到代码:opencode 在 spec coding 下的真实卡点
opencode 是一个开源优先的终端 AI 编程工具,支持在项目目录里用自然语言驱动代码生成、修改和测试。它适合谁?适合已经习惯命令行、愿意把需求先写成文档再交给模型执行的开发者。spec coding 的核心思路是:先把需求沉淀成requirements.md,再让模型按规格逐条实现,而不是一句一句地 vibe coding。
我试过把一份真实项目的总体要求直接丢给模型生成requirements.md,结果洋洋洒洒几千字,看起来结构完整,实际开篇就埋了一个大而全的功能类,后面每改一处都牵动全局。问题不在于 opencode 本身,而在于多模型切换时 Key 和通道管理混乱:今天用这个模型的 Key,明天换那个模型的 Base URL,配置文件改来改去,requirements.md里的规格还没跑通,环境先崩了。
这篇要解决的就是这个痛点:用 TaoToken 统一 Key 和 API 通道,把 opencode 的 spec coding 工作流固定下来。你会看到可复制的 opencode 配置片段、一份能直接用的requirements.md模板,以及从需求到代码生成的完整验证步骤。核心检索词就是 opencode spec coding 多模型统一 Key 配置,下面按可跟做的顺序展开。
先说清楚场景边界。opencode 负责在本地项目里执行读写文件、跑命令、生成代码;TaoToken 负责把多家模型的调用收敛到一个 Base URL 和一把 Key 上。两者分工明确,不重叠。你不需要在 opencode 里为每个模型单独维护一套鉴权信息,只需要在配置文件里指向同一个 API 地址,模型 ID 按需切换。
为什么强调 spec coding 而不是纯 vibe coding?因为真实交付项目里,需求会变、客户会补文档、测试会暴露边界条件。vibe coding 适合原型,spec coding 适合要进版本库的代码。requirements.md就是那个把口头需求变成可执行规格的中间层。它不需要写得多漂亮,但必须让模型能逐条对应到函数、类和测试用例。
我踩过的坑是:一开始把业务人员几句话生成的需求直接喂给模型,模型自由发挥出一堆客户根本没提的功能,后面删都删不干净。后来改成先写requirements.md,把输入、输出、边界、异常都列清楚,再让 opencode 按条目实现,返工率明显下降。这个转变的关键,就是统一模型通道,让每次生成的上下文一致,不会因为换了模型就换了理解方式。
2. TaoToken 前置:统一 Key 与 API 通道的准备
TaoToken 在这里的角色是统一入口:你拿到一把 Key,配一个 Base URL,就能在 opencode 里调用多家模型。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置时直接写这个。
前置准备分三步。第一步,注册并登录后进入控制台,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在里面创建 API Key。第二步,打开 API Keys 管理页 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,复制生成的 Key,注意只显示一次,先存到本地密码管理器。第三步,确认你要用的模型 ID,可以在模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 里先试一次对话,确认通道可用。
这里要强调一个原则:opencode 的配置文件里只写 Base URL 和 Key,不写具体厂商的域名。模型 ID 作为参数传入,切换模型时只改模型 ID,不改鉴权信息。这样做的直接好处是,requirements.md里的规格不会因为换模型而重新解释一遍,上下文保持一致。
如果你打算长期跑编码和 Agent 任务,可以了解 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它面向的是持续性的编码场景,不是一次性试用。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,配置字段和参数说明以文档为准。
关于安全,这里只做正常 API 调用,不涉及任何网络通道改造。你只需要保证本地能正常访问 https://taotoken.net/api 即可。如果公司网络有白名单,把域名加进去,不要用任何非官方方式绕过。这一点在团队协作里尤其重要,统一走一个入口,审计和配额都好管理。
准备阶段还要确认 opencode 的版本。不同版本配置文件路径可能不同,常见的是项目根目录下的opencode.json或用户目录下的配置。下面第三节给的片段以项目根目录opencode.json为例,路径和字段名以你本地实际版本为准。如果字段对不上,先看接入文档,不要凭记忆改。
最后提醒一句:Key 不要提交到 git。把opencode.json里涉及 Key 的部分用环境变量引用,或者把配置文件加入.gitignore。真实项目里因为 Key 泄露被刷配额的事不少,别在这上面省事。
3. 可复制配置:opencode.json 与 requirements.md 模板
这一节给两份可直接复制的内容。第一份是 opencode 的配置文件片段,第二份是requirements.md模板。两份配合使用,配置负责通道,模板负责规格。
先看opencode.json。路径是项目根目录下的opencode.json,如果你用的是全局配置,路径可能是~/.config/opencode/opencode.json,以本地实际为准。字段结构如下:
{ "provider": { "taotoken": { "type": "openai-compatible", "baseURL": "https://taotoken.net/api", "apiKey": "${TAOTOKEN_API_KEY}", "models": { "default": { "id": "claude-sonnet-4-20250514", "name": "Claude Sonnet 4" }, "fast": { "id": "gpt-4o-mini", "name": "GPT-4o mini" }, "reasoning": { "id": "deepseek-chat", "name": "DeepSeek Chat" } } } }, "defaultModel": "taotoken/default", "agent": { "build": { "model": "taotoken/reasoning" }, "plan": { "model": "taotoken/default" } } }这里 Base URL 写https://taotoken.net/api,Key 用环境变量TAOTOKEN_API_KEY引用。你在终端里执行export TAOTOKEN_API_KEY="你的Key",或者在.env文件里写,注意.env要进.gitignore。模型 ID 按你实际可用的填,上面三个只是示例,具体以模型对话页里能跑通的为准。
如果你用的是 TOML 格式的配置,等价片段如下:
[provider.taotoken] type = "openai-compatible" baseURL = "https://taotoken.net/api" apiKey = "${TAOTOKEN_API_KEY}" [provider.taotoken.models.default] id = "claude-sonnet-4-20250514" name = "Claude Sonnet 4" [provider.taotoken.models.reasoning] id = "deepseek-chat" name = "DeepSeek Chat" [agent.build] model = "taotoken/reasoning" [agent.plan] model = "taotoken/default"三件套记牢:Base URL 是https://taotoken.net/api,Key 是你在 API Keys 页拿到的那串,Model ID 是模型对话页里验证过能用的那个。任何一处对不上,都会在第五节报错。
再看requirements.md模板。这份模板的目标是让模型能逐条对应到实现,而不是自由发挥。放在项目根目录,命名固定为requirements.md,opencode 在 spec coding 模式下会优先读它。
# 项目需求规格 ## 1. 背景 简述业务背景,一句话说清这个项目解决什么问题。 ## 2. 输入 - 输入文件:data.xlsx,包含列:日期、地区、销售额、成本、数量 - 输入频率:每月一次,文件名格式 data_YYYYMM.xlsx - 编码:UTF-8,首行为表头 ## 3. 输出 - 输出文件:report_YYYYMM.docx - 内容:环比、同比、趋势分析,含表格和文字说明 - 文字说明用中文,每段不超过 200 字 ## 4. 功能点 ### 4.1 读取输入 - 读取 data.xlsx,校验列名是否齐全 - 缺失列时抛出明确错误,不静默跳过 ### 4.2 计算环比 - 公式:(本月 - 上月) / 上月 * 100% - 上月为空时输出 "N/A" ### 4.3 计算同比 - 公式:(本月 - 去年同月) / 去年同月 * 100% - 去年同月为空时输出 "N/A" ### 4.4 生成报告 - 按地区分组,输出表格 - 每个地区附一段趋势文字说明 ## 5. 边界与异常 - 空文件:报错并退出,退出码 1 - 列名不匹配:列出缺失列名 - 数值列含非数字:记录行号并跳过 ## 6. 测试要求 - 每个功能点至少一个 pytest 用例 - 测试数据放在 tests/fixtures/ 下 - 不修改生产配置文件这份模板的关键在第四、五、六节。功能点拆到可独立实现,边界写清楚,测试要求明确禁止修改生产配置。我踩过的坑就是没写第六条,模型在跑 pytest 时自动去读配置文件,遇到编码错误直接把内容删了填垃圾数据,最后连源文件都删了。写清楚「不修改生产配置文件」之后,这类事故基本没再出现。
4. 验证请求:从需求到代码生成的完整步骤
配置和模板就位后,走一遍完整验证。目标是确认通道通、规格被正确读取、代码能生成、测试能跑。
第一步,确认环境变量生效。在终端执行:
echo $TAOTOKEN_API_KEY能打印出 Key 就说明环境变量没问题。如果为空,回到上一节检查 export 或 .env 加载。
第二步,在项目根目录启动 opencode。执行:
opencode进入交互界面后,先发一条简单指令验证通道:
请用中文回复:当前使用的模型 ID 是什么?如果返回正常中文,说明 Base URL 和 Key 都对。如果报 401,看第五节。
第三步,让 opencode 读取requirements.md并生成实现计划。输入:
请读取项目根目录的 requirements.md,按功能点 4.1 到 4.4 生成实现计划,不要写代码,只列步骤。这一步是 spec coding 的关键:先要计划,再要代码。计划里应该逐条对应功能点,如果模型开始自由发挥加功能,说明requirements.md的边界写得不够紧,回去补第五节。
第四步,按计划生成代码。输入:
按上面的计划实现功能点 4.1 和 4.2,生成 Python 文件 src/report.py,并写对应的 pytest 用例到 tests/test_report.py。生成完成后,检查文件是否落在指定路径。如果模型把文件写到别处,说明项目根目录不对,或者配置里的工作目录没设对。
第五步,跑测试。执行:
pytest tests/test_report.py -v预期结果是用例通过。如果失败,先看报错是逻辑错误还是环境错误。逻辑错误回到requirements.md补边界,环境错误看第五节。
第六步,验证多模型切换。把opencode.json里的defaultModel从taotoken/default改成taotoken/reasoning,重启 opencode,重复第三步。观察同一个requirements.md是否被一致理解。如果两个模型给出的计划结构一致,说明规格写得够明确,通道也稳定。
整个流程走下来,从需求到代码生成大约十几分钟,比手工搭环境快得多。关键是每一步都有明确的输入和输出,出问题能定位到具体环节,而不是一团乱麻。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节对照真实报错,逐个排查。每个报错都给出触发条件和处理方式。
401 Unauthorized。触发条件:Key 错误、Key 过期、环境变量没加载。排查顺序:先echo $TAOTOKEN_API_KEY确认变量有值,再确认opencode.json里apiKey字段引用的是${TAOTOKEN_API_KEY}而不是硬编码的旧 Key。如果都对,去 API Keys 页重新生成一把 Key,替换后重启 opencode。注意 Key 只显示一次,复制时不要带空格。
local proxy failed。触发条件:本地网络无法访问https://taotoken.net/api,或者配置里 Base URL 写错。排查:先用 curl 测一下:
curl -I https://taotoken.net/api能返回 HTTP 状态码说明网络通。如果超时,检查公司网络白名单,把域名加进去。不要用任何非官方方式绕过网络限制。如果 curl 通但 opencode 报这个错,检查opencode.json里baseURL是否写成了带路径的地址,正确写法就是https://taotoken.net/api,不要多加/v1之类。
reading choices 报错。触发条件:模型返回格式不符合 opencode 预期,常见于模型 ID 填错或该模型不支持当前调用方式。排查:去模型对话页确认这个模型 ID 能正常对话,然后检查opencode.json里id字段是否和对话页里的一致。如果模型 ID 对但还报错,换一个模型 ID 试,排除是单个模型的问题。
OAuth 相关报错。触发条件:配置里混入了需要 OAuth 的 provider 字段,或者 opencode 版本默认走了 OAuth 流程。排查:确认opencode.json里type是openai-compatible,不要写oauth。如果配置文件里有旧的 OAuth 字段,删掉。重启 opencode 后如果还提示 OAuth,检查是否有全局配置覆盖了项目配置,全局配置路径通常在~/.config/opencode/下。
还有一个高频问题:模型返回英文。触发条件:AGENTS.md或命令行里要求中文,但多轮对话后模型切回英文。处理:在requirements.md开头加一行「所有输出用中文」,并在opencode.json的 agent 配置里固定模型,不要频繁切换。如果还不行,在每次指令末尾加「用中文回复」。
最后是文件被误删的问题。触发条件:模型跑测试时自动读取生产配置文件,遇到编码错误后删除内容。处理:在requirements.md的测试要求里明确写「不修改生产配置文件」,并把生产配置目录加入.gitignore之外的只读保护。更稳妥的做法是,跑测试前先git commit,出问题能回滚。我踩过的坑就是没先提交,模型删了源文件还问我要 git,那次之后我养成了先提交再让模型跑测试的习惯。
6. 固定工作流:把统一 Key 接入日常编码
走到这里,通道、配置、模板、验证、排错都齐了。剩下的就是把它变成日常习惯。
日常流程建议固定成四步:写requirements.md、让 opencode 出计划、按计划生成代码、跑测试并提交。每一步都有明确的输入输出,出问题能定位。多模型切换时只改opencode.json里的模型 ID,不改 Base URL 和 Key,保证上下文一致。
如果你要长期跑编码和 Agent 任务,Coding Plan 地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,API Keys 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。验证模型是否可用,去模型对话页 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 先试一次。
最后给一个实用技巧:把requirements.md按功能点编号,每次只让模型实现一个编号,实现完提交一次 git。这样即使模型中途出错,回滚成本也低。真实项目里,稳定比快更重要。