{"record":{"id":"529a673743ead1e4","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-529a67","errorCode":null,"errorMessage":"测试模型不能为空","messagePattern":"测试模型不能为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":544,"sourceCode":"    let base_url = relay_profile_base_url(profile);\n    let base_url = base_url.trim().trim_end_matches('/');\n    if base_url.is_empty() {\n        anyhow::bail!(\"Base URL 不能为空\");\n    }\n    let api_key = relay_profile_api_key(profile);\n    let api_key = api_key.trim();\n    if api_key.is_empty() {\n        anyhow::bail!(\"API Key 不能为空\");\n    }\n\n    let client = crate::http_client::proxied_client(\"CodexPlusPlus/RelayTest\")?;\n    let endpoint = match profile.protocol {\n        RelayProtocol::Responses => format!(\"{base_url}/responses\"),\n        RelayProtocol::ChatCompletions => format!(\"{base_url}/chat/completions\"),\n    };\n    let test_model = model.trim();\n    if test_model.is_empty() {\n        anyhow::bail!(\"测试模型不能为空\");\n    }\n\n    let payload = relay_profile_test_payload(profile.protocol, test_model);\n    let response = client\n        .post(&endpoint)\n        .bearer_auth(api_key)\n        .header(reqwest::header::CONTENT_TYPE, \"application/json\")\n        .json(&payload)\n        .send()\n        .await?;\n    let http_status = response.status().as_u16();\n\n    // 如果 404 且 base_url 末尾没有 /v1，尝试自动补 /v1 后再发一次。\n    // 许多上游（中转站、自建代理）暴露的路径以 /v1/ 开头，\n    // 用户容易遗漏这个前缀，导致 /responses 或 /chat/completions 404。\n    if http_status == 404 && !base_url.ends_with(\"/v1\") {\n        let v1_url = format!(\"{base_url}/v1\");\n        let v1_endpoint = match profile.protocol {","sourceCodeStart":526,"sourceCodeEnd":562,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_config.rs#L526-L562","documentation":"test_relay_profile 的第三个守卫：测试用的 model 参数 trim 后为空则 bail。该 model 会填入请求 payload（relay_profile_test_payload 按协议生成最小请求体），为空说明调用方没传有效模型名。前两个守卫（base_url、API Key）已通过才会走到这里。","triggerScenarios":"调用 test_relay_profile(profile, model) 时 model 是空串或纯空白，例如 UI 上模型选择框未选中、settings 的默认测试模型（default_relay_test_model 相关字段）被清空。","commonSituations":"测试按钮没绑定模型选择值；profile.model_list 为空导致模型下拉无默认项；把模型名误当可选项省略。","solutions":["传入非空模型名（如 profile 的默认模型或用户选中的模型）","调用前检查 model.trim() 非空，空时用 profile 默认模型回填","UI 层在未选模型时禁用测试按钮"],"exampleFix":"// before\nlet r = test_relay_profile(&profile, \"  \").await?;\n\n// after\nlet model = model.trim();\nlet model = if model.is_empty() { relay_profile_model(&profile) } else { model.to_string() };\nanyhow::ensure!(!model.trim().is_empty(), \"测试模型不能为空\");\nlet r = test_relay_profile(&profile, &model).await?;","handlingStrategy":"validation","validationCode":"let model = model.trim();\nlet model = if model.is_empty() { relay_profile_model(profile) } else { model };\nanyhow::ensure!(!model.trim().is_empty(), \"测试模型不能为空\");\ntest_relay_profile(profile, &model).await?;","typeGuard":null,"tryCatchPattern":"if let Err(err) = test_relay_profile(&profile, &model).await {\n    if err.to_string().contains(\"测试模型不能为空\") {\n        // 模型下拉未选中：给默认值后让用户重试\n    } else { return Err(err); }\n}","preventionTips":["模型选择框空值时用 profile 默认模型回填","profile.model_list 为空时在 UI 上禁止发起测试","把 model 参数纳入与 base_url/key 相同的提交前校验"],"tags":["relay","model-name","connectivity-test","validation"],"backgroundTag":"missing-model-name","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}