跳到主要内容
译文日期 2026-10-15;原文此后已有更改。

使用真实凭据对 LLM provider 进行验收

ADR 0043 交付了九个 provider,契约 测试套件全绿——针对 mock。它自己在“留待以后”中记录了:六个 smoke 使用真实凭据的验收仍未完成。 本文件就是追踪这个“以后”的地方:每个 provider 一行,每次出现一个 key 时都更新。ADR 不作编辑; 它是决策的记录,这里是证明的记录。

本项目的金科玉律在这里完全适用:能力只有在被证明后才被声明。在一个 smoke 针对真实 key 运行之前, 关于该 provider 存在的只是对官方文档的阅读——很好,在阶段 11 中逐行核对过,但仍然不是执行。

2026-09-25 的状态——在 user/tool 之后的 system 消息(AT-161)​

另一个维度,同一把尺子。契约测试套件现在证明了每个适配器把一条在 user 或 tool 之后到达的 role: "system" 放在哪里(在八个中放在 messages 的末尾;在 Anthropic 中提升到顶部的 system——见 分歧表)。 provider 是否接受这样的请求体,是 sistema-tardio.smoke.spec.ts 所问的,而只有真实调用能回答:

OLLAMA_SISTEMA_TARDIO_SMOKE=1 pnpm --filter api exec vitest run test/infrastructure/llm/sistema-tardio
# providers de nuvem: exporte a mesma *_TEST_KEY dos smokes de aceite (ANTHROPIC_TEST_KEY para o Anthropic)
provider真实 provider 接受迟到的 system 吗?方式
Ollama✅ 是,在 user 之后和在 tool 之后都接受本地 daemon 0.32.15,qwen2.5-coder:7b;两个回合都以文本结束,没有错误 chunk
Anthropic❌ 未证明缺少 ANTHROPIC_TEST_KEY——而且它发送的请求体无论如何都没有迟到的 system(被提升了)
OpenAI❌ 未证明缺少 OPENAI_TEST_KEY
OpenRouter❌ 未证明本环境缺少 OPENROUTER_TEST_KEY;在 hub 中,响应取决于上游,因此一轮只能证明为它提供服务的那个上游
NVIDIA NIM❌ 未证明缺少 NVIDIA_NIM_TEST_KEY
Together AI❌ 未证明缺少 TOGETHER_TEST_KEY
DeepInfra❌ 未证明缺少 DEEPINFRA_TEST_KEY
Bitdeer❌ 未证明缺少 BITDEER_TEST_KEY
Vultr❌ 未证明缺少 VULTR_TEST_KEY

smoke 只证明接受(回合以文本结束,没有错误 chunk)。模型是否遵从这个指引是质量问题,属于 AT-082。

2026-08-14 的状态——embedding 能力(ADR 0075)​

这个列表中的新维度。到此为止,“验收”指的是聊天脚本; ADR 0075 把 embed 加入了契约,同一把尺子 适用:只有有执行证明时,能力才是 true。

Ollama 已关闭,而且是唯一一个。针对真实的本地 daemon(0.32.1)运行,模型当场拉取:

docker exec brabo-dev-ollama-1 ollama pull nomic-embed-text
OLLAMA_EMBEDDING_SMOKE=1 pnpm --filter api test ollama-provider.embeddings
内容结果
POST /api/embed,2 个输入2 个向量,每个 768 维
prompt_eval_count10——estimated: false,来自 daemon
同一 daemon 的 /api/tagsnomic-embed-text 带 capabilities: ["embedding"] 和 embedding_length: 768;llama3.2:1b 带 ["completion","tools"]
聊天模型请求 embedding501——“This server does not support embeddings”

501 是最有价值的发现,它在两个层面都变成了测试:这是能力的模型层在尽可能晚的地方失败,也是 assertCanEmbed 提前拒绝的原因(RN-190)。实际成本: US$ 0,00——本地模型没有价格,因此这是本页唯一可以随意重复的验收。

providerembeddings运行了?原因
Ollama✅ 是✅ 是本地 daemon,没有 key,没有成本
Anthropic❌ 否—没有自己的 embedding 端点;文档指向一个第三方,那是另一个 provider,有另一个 key 和另一种方言
OpenAI❌ 否❌ 跳过缺少 OPENAI_TEST_KEY
OpenRouter❌ 否❌ 跳过有 key,但运行的 smoke 是聊天的——在 hub 中,embedding 会路由到不同的提供方,一个端点的证明不是另一个端点的证明
Together AI❌ 否❌ 跳过缺少 TOGETHER_TEST_KEY
DeepInfra❌ 否❌ 跳过缺少 DEEPINFRA_TEST_KEY
NVIDIA NIM❌ 否❌ 跳过缺少 NVIDIA_NIM_TEST_KEY
Bitdeer❌ 否❌ 跳过缺少 BITDEER_TEST_KEY——颇具讽刺,因为他们的 embedding 文档是阶段 11b 找到的唯一经认证的来源
Vultr❌ 否❌ 跳过缺少 VULTR_TEST_KEY

在没有任何凭据的情况下已经证明的是方言:契约测试套件在开启该能力的 OpenAI 兼容基础上 第二次运行,覆盖按 index 排序、读取 usage.prompt_tokens、拒绝不完整的批次以及按 code 归一化的 错误。当 key 存在时,把一个 provider 改为 true 只需修改字面量中的一行并运行 smoke——而不用编写 解析代码。

2026-08-07 的状态​

这个列表中第一次付费执行。OpenRouter 已关闭;另外五个因缺少 key 仍被跳过——环境中任何地方都 没有它们的凭据,产品只登记了 OpenRouter 的(与 hello-limpo 执行中使用的相同)。

provider运行了?结果
OpenRouter✅ 是完整脚本通过,一次付费调用
Together AI❌ 跳过缺少 TOGETHER_TEST_KEY
DeepInfra❌ 跳过缺少 DEEPINFRA_TEST_KEY
NVIDIA NIM❌ 跳过缺少 NVIDIA_NIM_TEST_KEY
Bitdeer❌ 跳过缺少 BITDEER_TEST_KEY
Vultr❌ 跳过缺少 VULTR_TEST_KEY

实际成本,从测试数据库的 token_usage 读取:

provider模型inoutcost_micros冻结的价格estimated
openrouteropenai/gpt-4o-mini2035150.000 /百万false

五个微美元——US$ 0,000005,比下文“< US$ 0,001”的估算低两个数量级。这里重要的是 estimated: false:用量来自 provider,而不是本地估算器,而记录的价格是使用时刻冻结的价格 (RN-044)——而不是今天 models 表中的价格。

smoke 在路上发现了什么​

它从未运行过,因此已经悄无声息地相对 ADR 0049 腐烂了,后者把策展从 models.is_active 移到了 workspace_models:

  • 它在全局目录上断言 alvo.isActive === false——这个字段已不存在,得到的是 undefined。 RN-043 如今正确的断言是 workspaceModels.isActive(...), 因为没有行就是关闭;
  • 它用旧的签名组装 SetModelsActiveUseCase 和 SetModelBindingUseCase,没有 workspaceId/curatedBy,也没有策展的仓库。

这些都无法被 CI 发现:没有 key 时,describe.skipIf 会跳过整个文件,而 api 的类型检查基于 tsconfig.build.json 运行,它排除了 test/。一个从不运行的验收不是验收——这正是让阶段 10 的 表格停留在“未测量”的同一个机制,教训在这里同样适用。

随执行一起修正:smoke 现在通过 ADR 0049 的路径断言 RN-043。当其他五个 key 存在时,剩下的五个 smoke 很可能以同样的方式、出于同样的原因已经腐烂了。

2026-08-03 的状态​

这一天对环境的扫描:六个变量一个都没有导出,.env 中也没有以其他名称存放的 provider key。没有 该变量时,describe.skipIf(!apiKey) 会带着警告跳过整个测试套件——这是正确的行为,而不是失败。没有 进行任何付费调用;这一轮的成本是 US$ 0,00。

providersmoke变量默认模型运行了?原因实际成本(token_usage)
OpenRouteropenrouter-provider.smoke.spec.tsOPENROUTER_TEST_KEYopenai/gpt-4o-mini❌ 跳过环境中缺少 key—
Together AItogether-provider.smoke.spec.tsTOGETHER_TEST_KEYmeta-llama/Llama-3.3-70B-Instruct-Turbo❌ 跳过环境中缺少 key—
DeepInfradeepinfra-provider.smoke.spec.tsDEEPINFRA_TEST_KEYdeepseek-ai/DeepSeek-V3❌ 跳过环境中缺少 key—
NVIDIA NIMnvidia-nim-provider.smoke.spec.tsNVIDIA_NIM_TEST_KEYmeta/llama-3.2-3b-instruct❌ 跳过环境中缺少 key—
Bitdeerbitdeer-provider.smoke.spec.tsBITDEER_TEST_KEYmoonshotai/Kimi-K2.5❌ 跳过环境中缺少 key—
Vultrvultr-provider.smoke.spec.tsVULTR_TEST_KEYkimi-k2-instruct❌ 跳过环境中缺少 key—

这些文件位于 apps/api/test/infrastructure/llm/。默认模型可以通过 <PROVIDER>_TEST_MODEL 覆盖—— 在账户无权访问该列中的模型时很有用。

关闭每一行要花多少钱​

刻意做得便宜。每个 smoke 恰好做一个聊天回合,prompt 为 Responda só a palavra "ok", sem mais nada.——大约 20 个输入 token 和少量输出。脚本中的其他一切都是 免费的:凭据校验是只看状态的 GET /v1/models(或 GET /key),而目录同步(在三个具有 listModels: true 的 provider 中)是读取操作。

付费调用免费调用数量级
每个 smoke1 个聊天回合(约 20 token 输入)连接测试 + 同步< US$ 0,001
六个合计6 个聊天回合—< US$ 0,01

即使用表中最贵的模型,每次执行也不到一美分。花钱的不是 token:而是拥有 key。

每个 smoke 证明了什么——而且并非对每个都一样​

划分依据是能力,正如 ADR 0041 所设计的:

有 listModels: true 的——OpenRouter、Together、DeepInfra。 同步会访问真实的 API 并填充目录。 真实价格就在这里与解析器相遇。最重要的情形是 Together:ADR 0043 记录了价格的单位(每百万 token 的 USD 还是每 token)是通过与市场价格比较推断出来的,因为官方文档没有声明——而 smoke 恰恰为此有一个 合理性区间(inputPricePerMillionMicros 在 10.000 和 1.000.000.000 之间;超出范围时,诊断是“单位 错误”,而不是“模型昂贵”)。如果哪天出了错,那是要在解析器和这里修正的发现,绝不能被压下。smoke 还 确认了 RN-043:发现的模型以停用状态进入,激活是手动策展。

有 listModels: false 的——NVIDIA NIM、Bitdeer、Vultr。 三者都没有在可访问的文档中公布每 token 的价格(NIM 甚至不按 token 收费:商业单位是 GPU/小时)。因此同步必须跳过,返回 pulado: 'sem_capability'——而 smoke 正是断言这一点,这让声明的能力变得可验证,而不是被信任。模型 通过 seed / 手动策展进入,带 manual_pricing = true,smoke 真正校验的是对照真实 API 的模型 id—— 如今是从文档读来的估计——以及带冻结价格的端到端计量路径。

在六个 provider 中,步骤 1b 都通过 TestStoredCredentialUseCase 针对真实 API 校验真实的 key—— 五个返回 ok,DeepInfra 返回 nao_suportado,它是唯一没有声明测试端点的。在 ADR 0050 之前,这项证明内嵌在登记中; 它没有消失,而是换了位置并变得显式。

在六个 provider 中,最后一步都一样,也是产品关心的:一个完整的聊天会话,没有 error 或 metering_failed 事件,并在 token_usage 中有一行带着使用时刻冻结的价格 (RN-044)——而不是今天 models 表中的价格。

如何关闭一个待办​

export OPENROUTER_TEST_KEY=... # a variável do provider em questão
# opcional, se a conta não tiver o modelo default:
# export OPENROUTER_TEST_MODEL=...

pnpm --filter api test -- openrouter-provider.smoke.spec.ts

要求:

  • 测试数据库处于运行状态——TEST_DATABASE_URL,默认 postgres://brabo:brabo@localhost:5432/brabo_test(与 api 的任何集成测试相同;smoke 在开始前会调用 truncateAll);
  • provider 账户中有余额。自 ADR 0050 起,登记不测试任何东西—— 校验 key 的是 smoke 的步骤 1b(TestStoredCredentialUseCase,只看状态),而一个有效但没有余额的 key 只会在聊天回合时才真正失败。

运行之后,更新上表中的那一行,写上日期、裁决和实际成本。成本来自数据库,而不是估算:

SELECT provider, model_name, input_tokens, output_tokens, cost_micros
FROM token_usage ORDER BY created_at DESC LIMIT 1;

如果 smoke 失败,该行变为 ❌ reprovado 并附上原因,而发现会变成 provider 中的修正——绝不是基于信任 声明的能力。

为什么这是一份活文档​

这张表不是某个日期的报告:它是验收的当前状态。每出现一个 key 就关闭一行,其他的保持开放、可见。本文件 存在要避免的失败模式是阶段 10 的那种,描述在 第一次 dogfooding 的收获 中:一张没人填写、永远变成 não medido 的观察表。 在这里,数字通过查询来自 token_usage,没有运行的就写明没有运行。