修复真实验收协议名解析
Project CI / Backend tests (pull_request) Failing after 11s
Project CI / Repository checks (pull_request) Failing after 11s
Project CI / Frontend tests (pull_request) Failing after 22s
Project CI / Native shell tests (pull_request) Successful in 9m32s

仅允许空值默认 openai_responses

显式校验 openai_responses、openai_chat 和 anthropic

为未知协议名补充失败用例并同步验收说明
This commit is contained in:
2026-07-27 10:38:01 +00:00
parent 872a2f6454
commit f3e9fb4508
2 changed files with 59 additions and 7 deletions
+1 -1
View File
@@ -88,5 +88,5 @@ Responses 如果只发送 `response.completed` 或 `response.incomplete`,解
## 9. 验收证据边界
1. `cargo test -p platform-llm` 的确定性用例把 checked-in SSE fixture 交给 parser,验证归一后的文本、工具调用、slot 聚合、Responses 仅有 completed / incomplete 终态事件时的恢复、参数 JSON 完整性和错误边界。fixture 可以来源于真实端点抓包,但测试不保存原始 SSE,也不逐事件与端点报文比较,因此不能证明抓包转录无偏差。
2. `tests/live_stream_tool_calls.rs` 是默认 `#[ignore]` 的真实端点工具调用 smoke。它只验证最终归一结果中的工具名、id 和完整参数 JSON;文本增量字符数仅用于打印观测,工具调用不进入 `on_delta`,也没有原始 SSE 录制或逐事件对比能力。
2. `tests/live_stream_tool_calls.rs` 是默认 `#[ignore]` 的真实端点工具调用 smoke。`PLATFORM_LLM_LIVE_API_KIND` 支持 `openai_responses``openai_chat``anthropic`,未设置或空白时默认 `openai_responses`,未知非空值会直接使验收失败。它只验证最终归一结果中的工具名、id 和完整参数 JSON;文本增量字符数仅用于打印观测,工具调用不进入 `on_delta`,也没有原始 SSE 录制或逐事件对比能力。
3. 因此验收应分别称为“固定 SSE fixture parser 覆盖”和“真实端点归一工具调用 smoke”,不能把后者描述为原始 SSE fidelity 或转录一致性证明。
@@ -26,11 +26,58 @@ fn env_var(name: &str) -> Option<String> {
.filter(|value| !value.trim().is_empty())
}
fn parse_api_kind(value: &str) -> LlmApiKind {
match value.trim().to_ascii_lowercase().replace('-', "_").as_str() {
"anthropic" => LlmApiKind::Anthropic,
"openai_chat" => LlmApiKind::OpenAiChat,
_ => LlmApiKind::OpenAiResponses,
fn parse_api_kind(value: &str) -> Result<LlmApiKind, String> {
let normalized = value.trim().to_ascii_lowercase().replace('-', "_");
if normalized.is_empty() {
return Ok(LlmApiKind::OpenAiResponses);
}
match normalized.as_str() {
"anthropic" => Ok(LlmApiKind::Anthropic),
"openai_chat" => Ok(LlmApiKind::OpenAiChat),
"openai_responses" => Ok(LlmApiKind::OpenAiResponses),
value => Err(format!(
"PLATFORM_LLM_LIVE_API_KIND 无效:{value},请使用 openai_responses、openai_chat 或 anthropic"
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_api_kind_defaults_only_for_empty_value() {
assert_eq!(
parse_api_kind("").expect("empty api kind should default"),
LlmApiKind::OpenAiResponses
);
assert_eq!(
parse_api_kind(" ").expect("whitespace api kind should default"),
LlmApiKind::OpenAiResponses
);
}
#[test]
fn parse_api_kind_accepts_supported_values() {
assert_eq!(
parse_api_kind("anthropic").expect("anthropic should parse"),
LlmApiKind::Anthropic
);
assert_eq!(
parse_api_kind("OPENAI-CHAT").expect("openai chat should parse"),
LlmApiKind::OpenAiChat
);
assert_eq!(
parse_api_kind("openai_responses").expect("openai responses should parse"),
LlmApiKind::OpenAiResponses
);
}
#[test]
fn parse_api_kind_rejects_unknown_non_empty_value() {
let error = parse_api_kind("anthopic").expect_err("misspelled api kind must fail");
assert!(error.contains("anthopic"));
}
}
@@ -44,7 +91,12 @@ async fn live_stream_run_returns_native_tool_calls() {
) else {
panic!("缺少 PLATFORM_LLM_LIVE_BASE_URL / _API_KEY / _MODEL");
};
let api_kind = parse_api_kind(&env_var("PLATFORM_LLM_LIVE_API_KIND").unwrap_or_default());
let api_kind = parse_api_kind(
env_var("PLATFORM_LLM_LIVE_API_KIND")
.as_deref()
.unwrap_or_default(),
)
.unwrap_or_else(|error| panic!("{error}"));
let config = LlmConfig::new(
LlmProvider::OpenAiCompatible,