- 引入共享 ValueMatcher(equals/contains/regex/exists/empty/gt/gte/lt/lte) - 引入共享 ContentRules 数组(direct/json/css/xpath 提取器) - 引入共享 KeyValueExpect(动态键值断言,字面量等价 equals) - maxDurationMs → durationMs: ValueMatcher(所有 checker) - match → regex(固定无 flags) - Ping max* → packetLossPercent/avgLatencyMs/maxLatencyMs(ValueMatcher) - LLM finishReason/rawFinishReason → ValueMatcher - DB 新增 result: ContentRules - TCP banner → ContentRules 数组 - 删除旧模块:operator.ts、validate-operator.ts、duration.ts、body.ts、text.ts、output.ts - 更新全部 checker schema/validate/expect/execute - 更新 probe-config.schema.json、probes.example.yaml - 更新 README.md、DEVELOPMENT.md(含 expect 字段选择规范) - 同步 10 个 delta specs 到主 specs,归档 change
16 KiB
Purpose
定义 LLM checker 的配置模型、Provider/Mode 支持、执行观测、Expect 断言、失败 Phase 和测试行为。
Requirements
Requirement: LLM Checker 注册与模块结构
系统 SHALL 提供 type: llm checker,用于大模型服务的应用层拨测。LLM checker MUST 位于 src/server/checker/runner/llm/ 自包含目录,并通过 src/server/checker/runner/index.ts 注册到 CheckerRegistry。LLM checker SHALL 复用现有 checker 抽象、配置 schema 组装、启动期语义校验、引擎调度、存储序列化和共享 expect 基础设施。
Scenario: 注册 LLM checker
- WHEN 系统初始化默认 checker registry
- THEN registry SHALL 包含
llm类型,且/api/meta返回的checkerTypesSHALL 包含llm
Scenario: LLM checker 目录自包含
- WHEN 开发者查看
src/server/checker/runner/llm/目录 - THEN 该目录 SHALL 包含 LLM checker 的类型、schema、语义校验、provider 创建、observation 构建、expect 断言、执行逻辑和模块入口
Scenario: 不扩展存储和 API 结构
- WHEN LLM checker 写入检查结果
- THEN 系统 SHALL 使用现有
CheckResult、targets、check_results和 Dashboard API 结构,不新增 LLM 专用存储列或 Dashboard 指标字段
Requirement: LLM Provider 与调用模式
LLM checker SHALL 支持 openai、openai-responses、anthropic 三类 provider。mode: http SHALL 调用 AI SDK generateText。mode: stream SHALL 调用 AI SDK streamText。所有模型调用 MUST 将 maxRetries 固定为 0,并 MUST 使用引擎注入的 ctx.signal 响应超时和取消。
Scenario: OpenAI Chat Completions provider
- WHEN target 配置
llm.provider: openai - THEN LLM checker SHALL 使用
@ai-sdk/openai的openai.chat(model)创建模型调用对象
Scenario: OpenAI Responses provider
- WHEN target 配置
llm.provider: openai-responses - THEN LLM checker SHALL 使用
@ai-sdk/openai的openai.responses(model)创建模型调用对象
Scenario: Anthropic provider
- WHEN target 配置
llm.provider: anthropic - THEN LLM checker SHALL 使用
@ai-sdk/anthropic的anthropic.messages(model)创建模型调用对象
Scenario: 非流式调用模式
- WHEN target 配置
llm.mode: http或省略llm.mode - THEN LLM checker SHALL 调用
generateText并从返回结果构建非流式 observation
Scenario: 流式调用模式
- WHEN target 配置
llm.mode: stream - THEN LLM checker SHALL 调用
streamText并消费fullStream构建流式 observation
Scenario: 超时取消传递给 SDK
- WHEN 引擎注入的
ctx.signal被 abort - THEN LLM checker SHALL 将该 signal 传递给 AI SDK 调用并将取消或超时结果记录为检查失败
Requirement: LLM 配置解析与默认值
LLM checker SHALL 解析 llm.provider、llm.url、llm.model、llm.prompt、llm.mode、llm.key、llm.authToken、llm.headers、llm.ignoreSSL、llm.options 和 llm.providerOptions。llm.options SHALL 支持 maxOutputTokens(默认 16)、temperature(默认 0)、topP、topK、presencePenalty、frequencyPenalty、stopSequences(字符串数组)和 seed。llm.mode 默认值 SHALL 为 http,llm.key 默认值 SHALL 为空字符串,llm.ignoreSSL 默认值 SHALL 为 false。LLM checker MUST NOT 隐式读取 AI SDK 默认环境变量。
Scenario: 最简 LLM target 解析
- WHEN 系统读取只包含
type: llm以及llm.provider、llm.url、llm.model、llm.prompt的 target - THEN 系统 SHALL 解析为 LLM target,并填充
mode=http、key=""、ignoreSSL=false、options.maxOutputTokens=16、options.temperature=0
Scenario: headers 默认值合并
- WHEN
defaults.llm.headers和 targetllm.headers同时配置同名 header - THEN LLM checker SHALL 按原始 header key 浅合并 headers,并由 target
llm.headers覆盖 defaults 中同名 key
Scenario: options 默认值合并
- WHEN
defaults.llm.options和 targetllm.options同时配置同名 option - THEN LLM checker SHALL 浅合并 options,并由 target
llm.options覆盖 defaults 中同名字段
Scenario: providerOptions 默认值合并
- WHEN
defaults.llm.providerOptions和 targetllm.providerOptions同时配置同名 provider namespace - THEN LLM checker SHALL 按 provider namespace 浅合并 providerOptions,并由 target namespace 覆盖 defaults 中同名 namespace
Scenario: Anthropic Bearer token
- WHEN target 配置
llm.provider: anthropic和非空llm.authToken - THEN LLM checker SHALL 将
authToken映射到 Anthropic SDK 的 Bearer token 认证字段
Scenario: key 不隐式读取环境变量
- WHEN target 未配置
llm.key - THEN LLM checker SHALL 将 SDK provider 的 api key 设置为空字符串,而不是隐式读取 SDK 默认环境变量
Requirement: LLM HTTP Metadata 与 TLS
LLM checker SHALL 通过 AI SDK provider 的 custom fetch 注入 observing fetch。observing fetch SHALL 调用 Bun fetch,在不消费 response body 的前提下记录 HTTP status、statusText 和 headers。llm.ignoreSSL: true 时,observing fetch SHALL 仅对当前 target 的 provider 请求使用 Bun tls.rejectUnauthorized=false。
Scenario: 捕获 HTTP metadata
- WHEN AI SDK provider 发起模型 HTTP 请求并收到响应
- THEN observing fetch SHALL 记录 status code 和响应 headers,供
expect.status与expect.headers使用
Scenario: 不消费响应体
- WHEN observing fetch 捕获 HTTP metadata
- THEN observing fetch SHALL 返回原始 response 给 AI SDK,不提前读取或克隆消费 body
Scenario: 忽略证书校验
- WHEN target 配置
llm.ignoreSSL: true - THEN observing fetch SHALL 对当前 target 的 provider 请求设置
tls.rejectUnauthorized=false
Requirement: LLM Observation
LLM checker SHALL 在 SDK 调用结果和 expect 断言之间构建 LlmCheckObservation。observation SHALL 包含 provider、model、mode、outputText、finishReason、rawFinishReason、usage、stream、http 和 warnings 中可观测的字段。mode: http 的 outputText SHALL 来自 generateText.text。mode: stream 的 outputText SHALL 来自 fullStream 中 text-delta 的原始文本聚合。
Scenario: 非流式 observation
- WHEN
generateText调用成功 - THEN LLM checker SHALL 从 SDK result 中提取 outputText、finishReason、rawFinishReason、usage、response headers 和 HTTP metadata
Scenario: 流式 observation
- WHEN
streamText调用成功且 stream 正常完成 - THEN LLM checker SHALL 从
fullStream聚合 outputText,并记录 stream.completed、firstTokenMs、finishReason、rawFinishReason、usage 和 HTTP metadata
Scenario: APICallError observation
- WHEN AI SDK 抛出带 statusCode 或 responseHeaders 的
APICallError - THEN LLM checker SHALL 构建包含可用 HTTP metadata 的 observation,并继续执行可执行的 status、headers 和 duration 断言
Scenario: 无 HTTP metadata 的 SDK 错误
- WHEN AI SDK 抛出不带 statusCode 和 responseHeaders 的错误
- THEN LLM checker SHALL 返回
phase: "request"的 error failure
Requirement: LLM Expect 断言
LLM checker SHALL 支持 expect.status、expect.headers、expect.output、expect.finishReason、expect.rawFinishReason、expect.usage.inputTokens、expect.usage.outputTokens、expect.usage.totalTokens、expect.stream.completed、expect.stream.firstTokenMs 和 expect.durationMs。expect.status SHALL 保持 HTTP 状态码数组语义并复用 HTTP status 断言。expect.headers SHALL 使用共享 KeyValueExpect 且 header key 大小写不敏感。expect.output MUST 使用共享 ContentRules。expect.finishReason 和 expect.rawFinishReason SHALL 使用共享 ValueMatcher。expect.usage.*、expect.stream.firstTokenMs 和 expect.durationMs SHALL 使用共享 ValueMatcher。LLM checker MUST 按固定顺序快速失败,非流式顺序为 status、headers、output、finishReason、rawFinishReason、usage、durationMs;流式顺序为 status、headers、stream.completed、stream.firstTokenMs、output、finishReason、rawFinishReason、usage、durationMs。
Scenario: 默认 status 断言
- WHEN LLM target 未配置
expect.status - THEN LLM checker SHALL 使用默认
status: [200]语义
Scenario: expect headers 通过
- WHEN observing fetch 捕获的响应 headers 满足
expect.headers配置 - THEN LLM checker SHALL 判定 headers 断言通过
Scenario: output ContentRules 通过
- WHEN LLM 输出文本满足
expect.output中配置的全部 ContentRules - THEN LLM checker SHALL 判定 output 阶段通过
Scenario: finishReason ValueMatcher 通过
- WHEN observation.finishReason 为
stop且 target 配置expect.finishReason: {equals: "stop"} - THEN LLM checker SHALL 判定 finishReason 阶段通过
Scenario: rawFinishReason regex 通过
- WHEN observation.rawFinishReason 为
end_turn且 target 配置expect.rawFinishReason: {regex: "^(stop|end_turn)$"} - THEN LLM checker SHALL 判定 rawFinishReason 阶段通过
Scenario: usage matcher 通过
- WHEN observation.usage.totalTokens 为 14 且 target 配置
expect.usage.totalTokens: {lte: 20} - THEN LLM checker SHALL 判定 usage 阶段通过
Scenario: durationMs matcher 失败
- WHEN LLM target 配置
expect.durationMs: {lte: 1000}且实际执行耗时为 1500ms - THEN LLM checker SHALL 返回 phase=
duration的 mismatch failure
Scenario: 首个 expect 失败
- WHEN 多个 LLM expect 中某个较早顺序的断言失败
- THEN LLM checker SHALL 立即返回该断言对应的 mismatch failure,不继续执行后续断言
Scenario: 期望认证失败状态
- WHEN AI SDK 抛出带 HTTP status 401 的
APICallError,且 target 仅配置expect.status: [401] - THEN LLM checker SHALL 判定本次检查为
matched=true
Scenario: APICallError 缺失模型输出
- WHEN AI SDK 抛出带 HTTP status 的
APICallError,且 target 同时配置需要模型结果的expect.output - THEN LLM checker SHALL 因
outputText缺失返回phase: "output"的 mismatch failure
Requirement: LLM Output 规则
LLM checker SHALL 使用共享 ContentRules 校验 expect.output。每个 output rule SHALL 为直接 ValueMatcher,或 json、css、xpath extractor 规则之一。直接 matcher SHALL 作用于原始输出字符串。equals SHALL 对原始输出字符串做严格相等比较。contains SHALL 判断原始输出是否包含子串。regex SHALL 对原始输出执行无 flags 正则匹配。json SHALL 将原始输出解析为 JSON,并用现有 JSONPath 子集和 ValueMatcher 校验提取值。json.equals SHALL 支持任意 JSON value。css 和 xpath 在 schema 层面可用,但 LLM 输出通常为纯文本或 JSON,实际场景中仅 json 提取器有意义。
Scenario: 原始输出严格相等
- WHEN
outputText为"OK\n"且 target 配置expect.output: [{ equals: "OK" }] - THEN LLM checker SHALL 判定 output 断言失败,因为 equals 不自动 trim
Scenario: output contains 通过
- WHEN
outputText包含配置的子串 - THEN LLM checker SHALL 判定该 output contains 规则通过
Scenario: output regex 通过
- WHEN
outputText匹配配置的合法 regex - THEN LLM checker SHALL 判定该 output regex 规则通过
Scenario: output JSONPath 字符串 equals 通过
- WHEN
outputText是 JSON 字符串且 JSONPath 提取字符串值满足equals - THEN LLM checker SHALL 判定该 output json 规则通过
Scenario: output JSONPath 对象 equals 通过
- WHEN
outputText是 JSON 字符串且 JSONPath 提取对象值满足equals - THEN LLM checker SHALL 使用深度相等判定该 output json 规则通过
Scenario: output JSONPath 存在性默认语义
- WHEN
outputText是 JSON 字符串且 target 配置expect.output: [{json: {path: "$.status"}}] - THEN LLM checker SHALL 将该规则按
exists: true语义执行
Scenario: output 规则按顺序快速失败
- WHEN
expect.output包含多个规则且第一条规则失败 - THEN LLM checker SHALL 返回第一条失败规则的 mismatch failure,不继续校验后续 output 规则
Requirement: LLM Stream 断言
LLM checker SHALL 仅允许 mode: stream 使用 expect.stream。expect.stream.completed 未配置时,LLM checker SHALL 在 stream observation 路径使用默认 true 语义。expect.stream.firstTokenMs SHALL 使用共享 ValueMatcher,并仅统计第一个非空 text-delta 事件耗时,不统计 reasoning、tool call 或 source 事件。
Scenario: stream completed 默认值
- WHEN target 配置
llm.mode: stream且未配置expect.stream.completed - THEN LLM checker SHALL 要求 SDK stream 正常完成
Scenario: stream error
- WHEN
fullStream产生 error part - THEN LLM checker SHALL 返回
phase: "stream"的 failure
Scenario: firstTokenMs 达标
- WHEN target 配置
expect.stream.firstTokenMs: {lte: 1000}且首个非空 text delta 耗时满足 matcher - THEN LLM checker SHALL 判定 firstTokenMs 断言通过
Scenario: firstTokenMs 缺失
- WHEN target 配置
expect.stream.firstTokenMs但 stream 未产生非空 text delta - THEN LLM checker SHALL 返回
phase: "stream"的 mismatch failure
Scenario: APICallError 不被默认 completed 阻断
- WHEN
mode: stream的 SDK 调用在 stream 启动前抛出带 HTTP status 的APICallError - THEN 默认
stream.completed=trueSHALL NOT 阻断基于 status 和 headers 的 APICallError 状态探测
Requirement: LLM Failure Phase 与状态摘要
LLM checker SHALL 使用 request、status、headers、stream、output、finishReason、rawFinishReason、usage、duration 作为第一版 failure phase。成功结果的 statusDetail SHALL 简短描述 provider、mode、HTTP status、finish reason、raw finish reason、first token、输出长度和 token usage 中可用的信息。statusDetail MUST NOT 写入完整 prompt、完整输出或 key。
Scenario: request failure
- WHEN 模型请求因网络错误、认证调用异常、AbortSignal 或无 HTTP metadata 的 SDK 错误失败
- THEN LLM checker SHALL 返回
phase: "request"的 error failure
Scenario: output mismatch failure
- WHEN 模型输出不满足
expect.output - THEN LLM checker SHALL 返回
phase: "output"的 mismatch failure
Scenario: 非流式成功摘要
- WHEN
provider: openai的非流式检查成功 - THEN
statusDetailSHALL 使用类似LLM openai http 200 finish=stop, output=2 chars, usage=12/2 tokens的简短格式
Scenario: 流式成功摘要
- WHEN
provider: anthropic的流式检查成功且存在 raw finish reason - THEN
statusDetailSHALL 使用类似LLM anthropic stream 200 finish=stop raw=end_turn, firstToken=624ms, output=2 chars的简短格式
Scenario: serialize 展示文本
- WHEN store 同步 LLM target
- THEN LLM checker
serialize()SHALL 返回类似openai:gpt-4o-mini @ https://api.openai.com/v1的 target 展示文本和 resolved config JSON
Requirement: LLM Checker 测试策略
LLM checker 的自动化测试 MUST 不访问真实外部模型服务。测试 SHALL 使用本地 mock HTTP/SSE 服务模拟 OpenAI Chat Completions、OpenAI Responses 和 Anthropic Messages 的成功、错误和流式响应。测试 SHALL 覆盖 schema、语义校验、defaults 合并、变量替换、provider factory、observation、expect、execute、registry 注册、配置加载和 JSON Schema 导出。
Scenario: 本地 mock provider 测试成功路径
- WHEN 测试运行 LLM checker 的 OpenAI、OpenAI Responses 和 Anthropic 成功路径
- THEN 测试 SHALL 使用本地 mock 服务返回 provider 响应,不依赖外部网络或真实 API key
Scenario: 本地 mock provider 测试错误路径
- WHEN 测试运行 401、429、500、超时、stream error、stream abort、缺 usage 或无文本输出路径
- THEN 测试 SHALL 断言 LLM checker 返回符合 spec 的 matched、failure phase、actual 和 statusDetail
Scenario: 质量检查覆盖 LLM checker
- WHEN 实现完成后执行质量检查
- THEN
bun run schema:check、bun run checkSHALL 通过