OpenSpec 最重要的工程价值,不是生成 Markdown,而是让一次变化能够从“为什么做”一路追到“哪段代码兑现、哪组测试证明、当前规格怎样更新、历史记录在哪里”。如果任何一层断开,文档数量再多也只是形式完整。

📷 [图片 token=FrvobgHLsoxDK7xKK0Dc591Rnod(未能下载,见飞书原文)]

追踪链到底在追什么

问题与边界
  → 可观察行为
  → 技术决策
  → 实施任务
  → 生产代码
  → 测试与运行证据
  → 当前主规格
  → 归档决策历史
  → 可浏览 WIKI

这里追踪的是语义,不是简单的文件链接。proposal 中的一条目标可能对应多个 Scenario;一个 Scenario 可能需要契约测试、服务测试和前端测试共同证明;一项 design 决策也可能被多个模块复用。因此追踪关系通常是一对多或多对多,不应强行要求“一条需求等于一个函数”。

📷 [图片 token=Ckseblw7qokwVKxyvanc0MWsnye(未能下载,见飞书原文)]

用 Embedding 批量限制案例建立矩阵

案例目录是 openspec/changes/archive/2026-07-11-limit-qwen-embedding-batch-size/。它的语义链可以整理为:

层次核心问题仓库证据
Proposal为什么要改,边界在哪里proposal.md:百炼单次最多 10 条,范围限定为 Provider 与测试
Delta spec系统必须表现成什么样specs/qwen-openai-provider/spec.md:1 条 Requirement、3 个 Scenario
Design为什么由这一层实现design.md:在 Provider 设置 chunk_size=10,业务层保持完整列表调用
Tasks交付要产生哪些证据tasks.md:实现、客户端测试、索引回归和质量门禁
实现技术决策落在哪里apps/backend/src/super_ai/llm/provider.py
边界测试Provider 是否真的按 10 条分批并保序apps/backend/tests/test_llm_provider.py
业务回归大文档是否仍完整索引apps/backend/tests/test_document_indexing.py
Main spec当前系统正式承诺什么openspec/specs/qwen-openai-provider/spec.md
Archive这次为什么这样决策带日期的完整归档 change
WIKI如何让历史便于浏览docs/changes/archive/.../index.md 通过 @include 引用原文件

第一段:从 proposal 锁定问题与范围

proposal 说明:较大文档会产生 11 个以上 chunk,默认 Embedding 客户端把它们作为一个请求发送,超过百炼 text-embedding-v4 的 10 条上限并得到 HTTP 400。它同时把影响范围限定为 Qwen Provider 构造、Provider 测试和文档索引回归。

📷 [图片 token=CjMObidddoEe8Mx6GeqcGHsJneb(未能下载,见飞书原文)]

这个边界很重要。没有 proposal,开发者可能去修改 chunking 策略、给索引服务加入厂商判断、调整 Milvus schema,甚至改变前端错误提示。proposal 中的非影响范围明确排除了 HTTP、SSE、Milvus schema 和前端,从一开始就减少无关改动。

追踪审查的第一个问题是:后续 spec、design 和代码有没有超出这一范围。如果出现数据库迁移或前端改动,就必须解释它为何必要,并回到 proposal 更新影响分析。

📷 [图片 token=IoLvbqn1PoA9KMxEJMlcf6uCnvc(未能下载,见飞书原文)]

第二段:把目标改写为可验收 Scenario

delta spec 没有写“修复 Embedding 报错”这种不可验收描述,而是拆成三个场景:

Scenario需要证明的行为主要证据
输入不超过 10 条单个请求仍符合上限Provider 客户端配置与调用测试
输入超过 10 条拆成每批最多 10 条,完整返回且顺序一致11 条输入形成 10+1 请求,输出仍按原序
文档超过 10 个 chunk索引任务成功并写入全部 chunk索引服务回归测试

📷 [图片 token=KYR2bQsUroEPpZxqF0lcn5xznud(未能下载,见飞书原文)]

这里能看到“一个测试无法证明全部设计”。Provider 测试负责证明真实客户端配置、10+1 分批和顺序保持;索引回归使用 FakeEmbeddingModel 与 FakeVectorStore,证明业务层仍提交 11 个 chunk 并完整写入。后者没有真正执行 10+1 请求,前者也没有覆盖整个索引编排,两项证据组合后才与三个 Scenario 对齐。

📷 [图片 token=I8CIbW0kiof2hVxIwUlcnQ2Rnwb(未能下载,见飞书原文)]

第三段:让 design 解释责任归属

design 的关键决策是把提供商上限放在 OpenAIEmbeddings Provider 配置,而不是文档索引服务。其理由可以从三个方向追踪:

**抽象边界。**批量上限来自模型提供商,应由 Provider 隐藏;业务服务只关心“为这组文本生成向量”。

**复用范围。**未来其他调用方使用同一 Embedding Provider 时,无须重复实现拆批。

**变化成本。**提供商未来调整上限时,只修改 Provider 配置或配置项,而不是修改所有业务调用路径。

📷 [图片 token=MbNwbnyA3olsGdxEq8gcCadrnkh(未能下载,见飞书原文)]

代码中的 QWEN_EMBEDDING_BATCH_SIZE = 10OpenAIEmbeddings(chunk_size=...) 正是这项决策的落点。如果代码改成索引服务手写分批循环,即使测试通过,也会违反 design 的责任边界,verify 应把它识别为一致性问题。

📷 [图片 token=QMxqbIQCJoLnkhxFhaxcfja5nyb(未能下载,见飞书原文)]

第四段:tasks 把行为与证据排成执行顺序

本案例的 tasks 没有按“后端开发、测试、结束”粗略分组,而是依次要求:设置默认 Qwen Embedding 的单批上限;验证真实客户端配置与超过上限的行为;验证 11 个以上 chunk 的索引回归;运行 Ruff、Pyright、Pytest 与 OpenSpec 校验。

📷 [图片 token=QDCNbE2bgowRH3x3cskc2l5OnSf(未能下载,见飞书原文)]

这种拆法让每个 checkbox 都有对象和证据。apply 可以按顺序推进,verify 也能反查“任务是否只勾选但没有对应实现或输出”。不过复选框仍不是证据本身,历史 tasks 中的 [x] 不能代替当前会话的测试结果。

📷 [图片 token=ZMRebzxUQoOslQxH4cHcFX7Nndb(未能下载,见飞书原文)]

第五段:从代码反向追踪到需求

正向追踪用于交付,反向追踪用于维护。假设未来 test_default_embedding_model_preserves_raw_qwen_inputs_and_batches_by_ten 失败,可以沿下面路径反查:

失败测试
  → provider.py 的 chunk_size 配置
  → design 中的 Provider 责任归属
  → delta spec 的大批量拆分 Scenario
  → proposal 中的百炼 10 条限制与范围

📷 [图片 token=AE7nbg7w7ou9E7xYDt3cqXNwncc(未能下载,见飞书原文)]

这样维护者不只知道“哪个断言坏了”,还能知道这个断言保护的是哪项系统承诺,以及为什么不能随意删除。反向链也能判断需求是否已经变化:如果提供商官方上限改变,应创建新 change 更新当前规格,而不是简单删掉测试。

Main spec、archive 与 WIKI 各自保存什么

Main spec 保存当前事实。openspec/specs/qwen-openai-provider/spec.md 已包含批量兼容 Requirement 和三个 Scenario。日常开发需要确认系统现在承诺什么,应先看这里。

**Archive 保存变化过程。**归档目录保留当时的 proposal、design、tasks、delta spec 和元数据。需要回答“为什么选择 Provider 层”“当时排除了哪些范围”,应查 archive。

WIKI 提供浏览入口。docs/changes/archive/2026-07-11-limit-qwen-embedding-batch-size/index.md 不复制正文,而是通过 @include 引用归档文件。它改善导航,但不成为第二套事实来源。

📷 [图片 token=JLHDb5Bm3ovVaax6XVzcNCIinng(未能下载,见飞书原文)]

OpenSpec 追踪与 Git 追踪并不重复

该案例的 archive、实现、测试和 main spec 可以在 Git 历史中找到,但相关提交同时包含很多其他文件。Git 能精确显示文本差异,却不天然提供一个聚焦于本变更的语义边界。OpenSpec change 把这次需求的意图、行为和取舍单独组织起来,二者互补。

也不能反过来夸大 OpenSpec:当前仓库没有把 Requirement ID 自动写进代码和测试,也没有自动生成形式化 trace matrix。现有追踪依赖稳定的 capability/Requirement 名称、文件路径、测试名称,以及开发者或 Agent 的语义核对。它是可审查的工程追踪,不是数学证明。

📷 [图片 token=XeWNbqPevoYayDxjS0RcEK4cnbb(未能下载,见飞书原文)]

审查一条追踪链的实用方法

  1. 从 proposal 选一条目标,确认 delta spec 中有对应 Requirement 或 Scenario。

  2. 确认 design 解释了实现责任、关键取舍和非目标。

  3. 从 Scenario 反推出 tasks,检查实现、测试和验证是否都被安排。

  4. 在生产代码中找到 design 决策的落点,而不只搜索相同关键词。

  5. 检查测试是否覆盖条件、结果和重要失败边界,并说明 fake 与真实集成的差别。

  6. 确认 delta 已正确进入 main spec,未覆盖无关 Requirement。

  7. 确认 archive 保留完整产物,WIKI include 指向归档后的真实路径。

📷 [图片 token=AGbObyD0YoKHNXx2WaHcqp1bnef(未能下载,见飞书原文)]

一段可直接使用的技术表达

我不会把 OpenSpec 理解成模板生成器,而是把它当作需求到证据的追踪链。在 Embedding 批量限制案例里,proposal 先锁定百炼 10 条上限和 Provider 层范围;delta spec 把小批量、大批量保序和完整索引写成三个 Scenario;design 决定由 Provider 配置 chunk_size;tasks 安排实现、边界测试、业务回归和质量门禁;代码与两层测试分别证明拆批和索引完整性;最后 delta 合入 main spec,完整 change 归档,WIKI 通过 include 提供浏览入口。这样任何人都能从当前行为反查当时的决策,也能从失败测试定位它保护的业务承诺。

📷 [图片 token=DmHfboHlwoVRv6xbpv7c2eJ3ndm(未能下载,见飞书原文)]