1
0
Files
DiAL/openspec/specs/llm-checker/spec.md
lanyuanxiaoyao 375dd3492b feat: 结构化 observation 替代 statusDetail,API 层动态构造 detail
- CheckResult: statusDetail -> observation (持久化) + detail (API 动态派生)
- 存储: status_detail 列 -> observation TEXT (JSON)
- CheckerDefinition: 新增 buildDetail(observation) 方法
- 各 checker 返回结构化 observation,API 层通过 registry 调用 buildDetail
- HTTP: bodyPreview 在 status/header 失败时也提前采集
- UDP: observation 包含 durationMs,未响应归为 error failure
- CMD: 超时/输出超限时保留已收集 observation
- TCP: connectTimeMs 仅含连接建立耗时,不含 banner 等待
- 新增 buildDetail 单测和 mapCheckResult 覆盖测试
- 同步 openspec 主规范,归档 checker-observation 变更
2026-05-19 22:49:00 +08:00

16 KiB
Raw Blame History

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 返回的 checkerTypes SHALL 包含 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 使用现有 CheckResulttargetscheck_results 和 Dashboard API 结构,不新增 LLM 专用存储列或 Dashboard 指标字段

Requirement: LLM Provider 与调用模式

LLM checker SHALL 支持 openaiopenai-responsesanthropic 三类 provider。mode: http SHALL 调用 AI SDK generateTextmode: 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/openaiopenai.chat(model) 创建模型调用对象

Scenario: OpenAI Responses provider

  • WHEN target 配置 llm.provider: openai-responses
  • THEN LLM checker SHALL 使用 @ai-sdk/openaiopenai.responses(model) 创建模型调用对象

Scenario: Anthropic provider

  • WHEN target 配置 llm.provider: anthropic
  • THEN LLM checker SHALL 使用 @ai-sdk/anthropicanthropic.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.providerllm.urlllm.modelllm.promptllm.modellm.keyllm.authTokenllm.headersllm.ignoreSSLllm.optionsllm.providerOptionsllm.options SHALL 支持 maxOutputTokens(默认 16)、temperature(默认 0)、topPtopKpresencePenaltyfrequencyPenaltystopSequences(字符串数组)和 seedllm.mode 默认值 SHALL 为 httpllm.key 默认值 SHALL 为空字符串,llm.ignoreSSL 默认值 SHALL 为 false。LLM checker MUST NOT 隐式读取 AI SDK 默认环境变量。

Scenario: 最简 LLM target 解析

  • WHEN 系统读取只包含 type: llm 以及 llm.providerllm.urlllm.modelllm.prompt 的 target
  • THEN 系统 SHALL 解析为 LLM target并填充 mode=httpkey=""ignoreSSL=falseoptions.maxOutputTokens=16options.temperature=0

Scenario: headers 默认值合并

  • WHEN defaults.llm.headers 和 target llm.headers 同时配置同名 header
  • THEN LLM checker SHALL 按原始 header key 浅合并 headers并由 target llm.headers 覆盖 defaults 中同名 key

Scenario: options 默认值合并

  • WHEN defaults.llm.options 和 target llm.options 同时配置同名 option
  • THEN LLM checker SHALL 浅合并 options并由 target llm.options 覆盖 defaults 中同名字段

Scenario: providerOptions 默认值合并

  • WHEN defaults.llm.providerOptions 和 target llm.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: trueobserving fetch SHALL 仅对当前 target 的 provider 请求使用 Bun tls.rejectUnauthorized=false

Scenario: 捕获 HTTP metadata

  • WHEN AI SDK provider 发起模型 HTTP 请求并收到响应
  • THEN observing fetch SHALL 记录 status code 和响应 headersexpect.statusexpect.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: httpoutputText SHALL 来自 generateText.textmode: streamoutputText SHALL 来自 fullStreamtext-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.statusexpect.headersexpect.outputexpect.finishReasonexpect.rawFinishReasonexpect.usage.inputTokensexpect.usage.outputTokensexpect.usage.totalTokensexpect.stream.completedexpect.stream.firstTokenMsexpect.durationMsexpect.status SHALL 保持 HTTP 状态码数组语义并复用 HTTP status 断言。expect.headers SHALL 使用共享 KeyValueExpect 且 header key 大小写不敏感。expect.output MUST 使用共享 ContentRulesexpect.finishReasonexpect.rawFinishReason SHALL 使用共享 ValueMatcherexpect.usage.*expect.stream.firstTokenMsexpect.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,或 jsoncssxpath extractor 规则之一。直接 matcher SHALL 作用于原始输出字符串。equals SHALL 对原始输出字符串做严格相等比较。contains SHALL 判断原始输出是否包含子串。regex SHALL 对原始输出执行无 flags 正则匹配。json SHALL 将原始输出解析为 JSON并用现有 JSONPath 子集和 ValueMatcher 校验提取值。json.equals SHALL 支持任意 JSON value。cssxpath 在 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.streamexpect.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=true SHALL NOT 阻断基于 status 和 headers 的 APICallError 状态探测

Requirement: LLM Failure Phase 与状态摘要

LLM checker SHALL 使用 requeststatusheadersstreamoutputfinishReasonrawFinishReasonusageduration 作为第一版 failure phase。成功结果的 API detail SHALL 从持久化 observation 动态构造,简短描述 provider、mode、HTTP status、finish reason、raw finish reason、first token、输出长度和 token usage 中可用的信息。observation 和 detail 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 detail SHALL 使用类似 LLM openai http 200 finish=stop, output=2 chars, usage=12/2 tokens 的简短格式

Scenario: 流式成功摘要

  • WHEN provider: anthropic 的流式检查成功且存在 raw finish reason
  • THEN detail SHALL 使用类似 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、detail 和 observation

Scenario: 质量检查覆盖 LLM checker

  • WHEN 实现完成后执行质量检查
  • THEN bun run schema:checkbun run check SHALL 通过