feat: 新增 LLM checker 支持大模型服务应用层拨测
基于 AI SDK v6 实现 openai/openai-responses/anthropic 三类 provider 的 http/stream 模式调用 支持 output/finishReason/usage/stream 等完整 expect 断言链路 新增 9 个源文件和 5 个测试文件共 78 个测试 更新 README/DEVELOPMENT/probes.example.yaml 和 probe-config.schema.json
This commit is contained in:
223
openspec/specs/llm-checker/spec.md
Normal file
223
openspec/specs/llm-checker/spec.md
Normal file
@@ -0,0 +1,223 @@
|
||||
## 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 使用现有 `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` 和 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: 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.maxDurationMs`。`expect.status` 和 `expect.headers` 的运行期断言 SHALL 复用 `src/server/checker/runner/http/expect.ts` 中的 `checkStatus` 和 `checkHeaders` 函数。LLM checker MUST 按固定顺序快速失败,非流式顺序为 status、headers、output、finishReason、rawFinishReason、usage、duration;流式顺序为 status、headers、stream.completed、stream.firstTokenMs、output、finishReason、rawFinishReason、usage、duration。
|
||||
|
||||
#### 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: expect headers 不匹配
|
||||
- **WHEN** observing fetch 捕获的响应 headers 不满足 `expect.headers` 中的某项配置
|
||||
- **THEN** LLM checker SHALL 返回 `phase: "headers"` 的 mismatch failure
|
||||
|
||||
#### Scenario: 全部 expect 通过
|
||||
- **WHEN** LLM checker 构建出的 observation 满足所有已配置 expect
|
||||
- **THEN** 检查结果 SHALL 为 `matched=true` 且 `failure=null`
|
||||
|
||||
#### 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 支持 `expect.output` 有序规则数组,每个规则 MUST 仅包含 `equals`、`contains`、`regex` 或 `json` 中的一种。`equals` SHALL 对原始输出字符串做严格相等比较。`contains` SHALL 判断原始输出是否包含子串。`regex` SHALL 对原始输出执行正则匹配。`json` SHALL 将原始输出解析为 JSON,并用现有 JSONPath 子集和 operator 校验提取值。
|
||||
|
||||
#### 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` 匹配配置的合法正则
|
||||
- **THEN** LLM checker SHALL 判定该 output regex 规则通过
|
||||
|
||||
#### Scenario: output JSONPath 通过
|
||||
- **WHEN** `outputText` 是 JSON 字符串且 JSONPath 提取值满足 operator
|
||||
- **THEN** LLM checker SHALL 判定该 output json 规则通过
|
||||
|
||||
#### 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 仅统计第一个非空 `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` 且首个非空 text delta 耗时满足 operator
|
||||
- **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 使用 `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** `statusDetail` SHALL 使用类似 `LLM openai http 200 finish=stop, output=2 chars, usage=12/2 tokens` 的简短格式
|
||||
|
||||
#### Scenario: 流式成功摘要
|
||||
- **WHEN** `provider: anthropic` 的流式检查成功且存在 raw finish reason
|
||||
- **THEN** `statusDetail` 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 和 statusDetail
|
||||
|
||||
#### Scenario: 质量检查覆盖 LLM checker
|
||||
- **WHEN** 实现完成后执行质量检查
|
||||
- **THEN** `bun run schema:check`、`bun run check` SHALL 通过
|
||||
@@ -5,9 +5,9 @@
|
||||
## Requirements
|
||||
|
||||
### Requirement: YAML 配置文件格式
|
||||
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、runtime 配置、可选的 variables 段、checker 默认值和 typed target 列表(含可选 group 字段)。target MUST 使用 `id` 字段作为唯一标识符,MUST 使用 `type` 字段声明 checker 类型,SHALL 支持可选的 `name` 字段作为展示名称元信息,SHALL 支持可选的 `description` 字段作为目标说明。`name` 和 `description` 均 SHALL 允许省略或显式配置为 `null`;省略或显式 null 时解析结果 SHALL 保留为 null。HTTP 领域字段 MUST 放在 `http` 分组,cmd 领域字段 MUST 放在 `cmd` 分组,db 领域字段 MUST 放在 `db` 分组,tcp 领域字段 MUST 放在 `tcp` 分组,ping 领域字段 MUST 放在 `ping` 分组,udp 领域字段 MUST 放在 `udp` 分组。HTTP target 的 `http` 分组 SHALL 支持可选的 `ignoreSSL`(布尔值)和 `maxRedirects`(非负整数)字段。Db target 的 `db` 分组 SHALL 支持 `url`(必填)和 `query`(可选)字段。Tcp target 的 `tcp` 分组 SHALL 支持 `host`(必填)、`port`(必填)、`readBanner`(可选)、`bannerReadTimeout`(可选)和 `maxBannerBytes`(可选)字段。Ping target 的 `ping` 分组 SHALL 支持 `host`(必填)、`count`(可选,默认 3)和 `packetSize`(可选,默认 56)字段。Udp target 的 `udp` 分组 SHALL 支持 `host`(必填)、`port`(必填)、`payload`(可选,默认空字符串)、`encoding`(可选,默认 `text`)、`responseEncoding`(可选,默认 `text`)和 `maxResponseBytes`(可选,默认 4096)字段。
|
||||
系统 SHALL 支持通过 YAML 配置文件定义全部运行参数,包括 server 配置、runtime 配置、可选的 variables 段、checker 默认值和 typed target 列表(含可选 group 字段)。target MUST 使用 `id` 字段作为唯一标识符,MUST 使用 `type` 字段声明 checker 类型,SHALL 支持可选的 `name` 字段作为展示名称元信息,SHALL 支持可选的 `description` 字段作为目标说明。`name` 和 `description` 均 SHALL 允许省略或显式配置为 `null`;省略或显式 null 时解析结果 SHALL 保留为 null。HTTP 领域字段 MUST 放在 `http` 分组,cmd 领域字段 MUST 放在 `cmd` 分组,db 领域字段 MUST 放在 `db` 分组,tcp 领域字段 MUST 放在 `tcp` 分组,ping 领域字段 MUST 放在 `ping` 分组,udp 领域字段 MUST 放在 `udp` 分组,LLM 领域字段 MUST 放在 `llm` 分组。HTTP target 的 `http` 分组 SHALL 支持可选的 `ignoreSSL`(布尔值)和 `maxRedirects`(非负整数)字段。Db target 的 `db` 分组 SHALL 支持 `url`(必填)和 `query`(可选)字段。Tcp target 的 `tcp` 分组 SHALL 支持 `host`(必填)、`port`(必填)、`readBanner`(可选)、`bannerReadTimeout`(可选)和 `maxBannerBytes`(可选)字段。Ping target 的 `ping` 分组 SHALL 支持 `host`(必填)、`count`(可选,默认 3)和 `packetSize`(可选,默认 56)字段。Udp target 的 `udp` 分组 SHALL 支持 `host`(必填)、`port`(必填)、`payload`(可选,默认空字符串)、`encoding`(可选,默认 `text`)、`responseEncoding`(可选,默认 `text`)和 `maxResponseBytes`(可选,默认 4096)字段。LLM target 的 `llm` 分组 SHALL 支持 `provider`(必填)、`url`(必填)、`model`(必填)、`prompt`(必填)、`mode`(可选,默认 `http`)、`key`(可选,默认空字符串)、`authToken`(可选)、`headers`(可选)、`ignoreSSL`(可选,默认 `false`)、`options`(可选)和 `providerOptions`(可选)字段。
|
||||
|
||||
`defaults.http` 分组 SHALL 仅支持 `headers`(可选)和 `maxBodyBytes`(可选)字段。`defaults.http` 分组 MUST NOT 支持 `method` 字段。`defaults.tcp` 分组 SHALL 仅支持 `bannerReadTimeout`(可选)和 `maxBannerBytes`(可选)字段。`defaults.udp` 分组 SHALL 仅支持 `encoding`(可选)、`responseEncoding`(可选)和 `maxResponseBytes`(可选)字段。
|
||||
`defaults.http` 分组 SHALL 仅支持 `headers`(可选)和 `maxBodyBytes`(可选)字段。`defaults.http` 分组 MUST NOT 支持 `method` 字段。`defaults.tcp` 分组 SHALL 仅支持 `bannerReadTimeout`(可选)和 `maxBannerBytes`(可选)字段。`defaults.udp` 分组 SHALL 仅支持 `encoding`(可选)、`responseEncoding`(可选)和 `maxResponseBytes`(可选)字段。`defaults.llm` 分组 SHALL 仅支持 `mode`(可选)、`headers`(可选)、`ignoreSSL`(可选)、`options`(可选)和 `providerOptions`(可选)字段。
|
||||
|
||||
#### Scenario: 完整配置文件解析
|
||||
- **WHEN** 系统启动并读取包含 server、runtime、variables、defaults、targets(含 id、group 字段)的 YAML 配置文件
|
||||
@@ -69,6 +69,14 @@
|
||||
- **WHEN** YAML 配置中 defaults.udp 设置 `encoding`、`responseEncoding` 和 `maxResponseBytes`
|
||||
- **THEN** 未显式覆盖对应字段的 udp target SHALL 使用 defaults.udp 中的值
|
||||
|
||||
#### Scenario: 最简 llm 配置文件解析
|
||||
- **WHEN** 系统读取只包含一个 `type: llm` target(含 `id`、`llm.provider`、`llm.url`、`llm.model` 和 `llm.prompt`)的 YAML 配置文件
|
||||
- **THEN** 系统 SHALL 使用内置默认值填充未指定字段(interval=30s, timeout=10s, group="default", llm.mode="http", llm.key="", llm.ignoreSSL=false, llm.options.maxOutputTokens=16, llm.options.temperature=0),并保留 name=null、description=null
|
||||
|
||||
#### Scenario: defaults.llm 配置默认值
|
||||
- **WHEN** YAML 配置中 defaults.llm 设置 `mode`、`headers`、`ignoreSSL`、`options` 或 `providerOptions`
|
||||
- **THEN** 未显式覆盖对应字段的 llm target SHALL 使用 defaults.llm 中的值
|
||||
|
||||
### Requirement: CLI 参数
|
||||
系统 SHALL 通过单一命令行参数接受 YAML 配置文件路径。
|
||||
|
||||
@@ -293,7 +301,7 @@
|
||||
- **THEN** 系统 SHALL 调用 `Bun.YAML.parse()` 将内容解析为配置对象
|
||||
|
||||
### Requirement: expect 配置增强
|
||||
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status`(支持精确数字和范围模式)、`headers`、`body`,cmd 的 `exitCode`、`stdout`、`stderr`,tcp 的 `connected`、`banner`,ping 的 `alive`、`maxPacketLoss`、`maxAvgLatencyMs`、`maxMaxLatencyMs`,以及 udp 的 `responded`、`response`、`responseSize`、`sourceHost`、`sourcePort` 和 `maxDurationMs`。内容类 expect MUST 使用数组表达配置顺序。
|
||||
系统 SHALL 支持 typed target 的领域专用 expect 配置,包括 HTTP 的 `status`(支持精确数字和范围模式)、`headers`、`body`,cmd 的 `exitCode`、`stdout`、`stderr`,tcp 的 `connected`、`banner`,ping 的 `alive`、`maxPacketLoss`、`maxAvgLatencyMs`、`maxMaxLatencyMs`,udp 的 `responded`、`response`、`responseSize`、`sourceHost`、`sourcePort` 和 `maxDurationMs`,以及 llm 的 `status`、`headers`、`output`、`finishReason`、`rawFinishReason`、`usage`、`stream` 和 `maxDurationMs`。内容类 expect MUST 使用数组表达配置顺序。
|
||||
|
||||
#### Scenario: 解析 HTTP expect 配置
|
||||
- **WHEN** YAML 配置文件中 HTTP target 的 expect 包含 status、headers、body 规则数组及内部方法
|
||||
@@ -343,6 +351,14 @@
|
||||
- **WHEN** udp target 未配置任何 expect 规则
|
||||
- **THEN** 系统 SHALL 正常处理,expect 字段为 undefined,执行时使用默认 responded=true 语义
|
||||
|
||||
#### Scenario: 解析 llm expect 配置
|
||||
- **WHEN** YAML 配置文件中 llm target 的 expect 包含 status、headers、output、finishReason、rawFinishReason、usage、stream 和 maxDurationMs
|
||||
- **THEN** 系统 SHALL 正确解析并存储为 llm target 的 expect 字段,并保留 output 规则数组顺序
|
||||
|
||||
#### Scenario: 不配置 llm expect
|
||||
- **WHEN** llm target 未配置任何 expect 规则
|
||||
- **THEN** 系统 SHALL 正常处理,expect 字段为 undefined,执行时使用默认 status=[200] 语义
|
||||
|
||||
### Requirement: 数据保留配置字段
|
||||
配置 schema 的 `runtime` 段 SHALL 支持 `retention` 字段,类型为字符串,格式为 `<数字><单位>`(单位:`d` 天、`h` 小时、`m` 分钟),用于指定历史数据保留时长。
|
||||
|
||||
@@ -469,3 +485,82 @@
|
||||
#### Scenario: defaults.tcp 未知字段失败
|
||||
- **WHEN** YAML 中 defaults.tcp 包含 `host` 或其他非默认字段
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 defaults.tcp 包含未知字段
|
||||
|
||||
### Requirement: LLM 配置校验
|
||||
系统 SHALL 在启动期对 llm checker 的配置契约和语义执行严格校验。LLM target 的 `llm` 分组 SHALL 只允许 `provider`、`url`、`model`、`prompt`、`mode`、`key`、`authToken`、`headers`、`ignoreSSL`、`options` 和 `providerOptions` 字段。`defaults.llm` 分组 SHALL 只允许 `mode`、`headers`、`ignoreSSL`、`options` 和 `providerOptions` 字段。LLM expect SHALL 只允许 `status`、`headers`、`output`、`finishReason`、`rawFinishReason`、`usage`、`stream` 和 `maxDurationMs` 字段。未知字段、非法 provider、非法 URL、非法 mode、非法认证组合、非法 options、非法 output 规则和 `mode: http` 下配置 `expect.stream` MUST 导致启动期配置错误。
|
||||
|
||||
#### Scenario: llm provider 非法
|
||||
- **WHEN** YAML 中 llm target 的 `llm.provider` 不是 `openai`、`openai-responses` 或 `anthropic`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.provider 不合法
|
||||
|
||||
#### Scenario: llm url 非法
|
||||
- **WHEN** YAML 中 llm target 的 `llm.url` 不是 `http://` 或 `https://` URL
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.url 格式不合法
|
||||
|
||||
#### Scenario: llm model 为空
|
||||
- **WHEN** YAML 中 llm target 的 `llm.model` 不是非空字符串
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.model 必须为非空字符串
|
||||
|
||||
#### Scenario: llm prompt 为空
|
||||
- **WHEN** YAML 中 llm target 的 `llm.prompt` 不是非空字符串
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.prompt 必须为非空字符串
|
||||
|
||||
#### Scenario: llm mode 非法
|
||||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `mode` 不是 `http` 或 `stream`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.mode 不合法
|
||||
|
||||
#### Scenario: llm headers 类型非法
|
||||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `headers` 不是对象,或任一 header 值不是字符串
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.headers 格式错误
|
||||
|
||||
#### Scenario: llm ignoreSSL 类型非法
|
||||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `ignoreSSL` 不是布尔值
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.ignoreSSL 必须为布尔值
|
||||
|
||||
#### Scenario: llm authToken provider 非法
|
||||
- **WHEN** YAML 中 `provider: openai` 或 `provider: openai-responses` 的 llm target 配置 `authToken`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 authToken 仅支持 anthropic provider
|
||||
|
||||
#### Scenario: Anthropic key 与 authToken 冲突
|
||||
- **WHEN** YAML 中 `provider: anthropic` 的 llm target 同时配置非空 `key` 和非空 `authToken`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 key 与 authToken 不能同时配置
|
||||
|
||||
#### Scenario: llm options 非法
|
||||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `options.maxOutputTokens` 不是正整数,`options.temperature`/`topP`/`topK`/`presencePenalty`/`frequencyPenalty`/`seed` 类型不合法,或 `options.stopSequences` 不是字符串数组
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.options 格式错误
|
||||
|
||||
#### Scenario: llm providerOptions 非法
|
||||
- **WHEN** YAML 中 llm target 或 defaults.llm 的 `providerOptions` 不是 JSON object
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm.providerOptions 格式错误
|
||||
|
||||
#### Scenario: llm 禁止字段失败
|
||||
- **WHEN** YAML 中 llm target 配置 `api`、`providerName`、`baseURL`、`apiKey`、`messages`、`maxRetries`、`request`、`maxBodyBytes` 或 `maxStreamBytes`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 llm 分组包含未知字段
|
||||
|
||||
#### Scenario: llm output 规则缺少支持字段
|
||||
- **WHEN** YAML 中 llm target 的 `expect.output` 数组项未包含 equals、contains、regex、json 任一支持字段
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 output rule 缺少支持的规则类型
|
||||
|
||||
#### Scenario: llm output 规则同时配置多个支持字段
|
||||
- **WHEN** YAML 中 llm target 的同一条 output rule 同时包含 equals、contains、regex、json 中的多个支持字段
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示每条 output rule 只能配置一种规则类型
|
||||
|
||||
#### Scenario: llm output regex 非法
|
||||
- **WHEN** YAML 中 llm target 的 output regex 规则不是字符串、不是可编译正则表达式或存在 ReDoS 风险
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示该 output regex 不合法
|
||||
|
||||
#### Scenario: llm output json path 非法
|
||||
- **WHEN** YAML 中 llm target 的 output json 规则缺少 path,或 path 不符合系统支持的 JSONPath 子集
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示该 output json path 不合法
|
||||
|
||||
#### Scenario: llm expect usage 非法
|
||||
- **WHEN** YAML 中 llm target 的 `expect.usage.inputTokens`、`expect.usage.outputTokens` 或 `expect.usage.totalTokens` 不是合法 operator 对象
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.usage 格式错误
|
||||
|
||||
#### Scenario: llm expect stream 仅允许 stream mode
|
||||
- **WHEN** YAML 中 llm target 配置 `llm.mode: http` 且配置 `expect.stream`
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.stream 仅支持 stream mode
|
||||
|
||||
#### Scenario: llm expect stream firstTokenMs 非法
|
||||
- **WHEN** YAML 中 llm target 的 `expect.stream.firstTokenMs` 不是合法 operator 对象
|
||||
- **THEN** 系统 SHALL 以配置错误退出,提示 expect.stream.firstTokenMs 格式错误
|
||||
|
||||
Reference in New Issue
Block a user