{"record":{"id":"37f27090abf5a28f","repo":"BigPizzaV3/CodexPlusPlus","slug":"error-37f270","errorCode":null,"errorMessage":"模型路由目标不能是聚合供应商：{}","messagePattern":"模型路由目标不能是聚合供应商：(.+?)","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"crates/codex-plus-core/src/protocol_proxy.rs","lineNumber":743,"sourceCode":"    let Some(route) = source\n        .model_routes\n        .iter()\n        .find(|route| route.model.trim() == model)\n    else {\n        return Ok(None);\n    };\n    let target_relay_id = route.target_relay_id.trim();\n    if target_relay_id == source.id {\n        anyhow::bail!(\"模型路由不能指向当前供应商自身：{model}\");\n    }\n    let target = settings\n        .relay_profiles\n        .iter()\n        .find(|profile| profile.id == target_relay_id)\n        .cloned()\n        .with_context(|| format!(\"模型路由目标供应商不存在：{target_relay_id}\"))?;\n    if target.relay_mode == crate::settings::RelayMode::Aggregate {\n        anyhow::bail!(\"模型路由目标不能是聚合供应商：{}\", target.name);\n    }\n    if target.protocol != RelayProtocol::Responses {\n        anyhow::bail!(\"模型路由目标必须使用 Responses API：{}\", target.name);\n    }\n\n    let upstream_model = if route.target_model.trim().is_empty() {\n        model.to_string()\n    } else {\n        route.target_model.trim().to_string()\n    };\n    Ok(Some(ModelRouteSelection {\n        relay: target,\n        source_relay_id: source.id,\n        source_model: model.to_string(),\n        upstream_model,\n    }))\n}\n","sourceCodeStart":725,"sourceCodeEnd":761,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/protocol_proxy.rs#L725-L761","documentation":"Thrown by select_model_route in crates/codex-plus-core/src/protocol_proxy.rs when a model route's target relay resolves to a profile whose relay_mode is RelayMode::Aggregate. The model-route mechanism forwards a single request directly to the target provider; an aggregate target would need its own member rotation and is not supported, so the proxy refuses. The placeholder is the target aggregate's display name.","triggerScenarios":"A model_routes entry whose target_relay_id points at an aggregate provider profile; user re-designated the route target from a concrete relay to an aggregate while keeping the route.","commonSituations":"Reorganizing providers: a concrete relay used as a route target got converted into an aggregate; user assumes routing can load-balance through an aggregate; settings imported from another machine where the id now belongs to an aggregate.","solutions":["Edit the failing model route and set target_relay_id to a concrete (non-aggregate) provider profile","If you want load balancing for that model, instead make the aggregate the active provider and let its rotation handle members","If routing through a pool is required, route to one specific member relay of the pool rather than the aggregate itself"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn route_target_is_concrete(settings: &BackendSettings, target_id: &str) -> bool {\n    settings\n        .relay_profiles\n        .iter()\n        .any(|p| p.id == target_id && p.relay_mode != RelayMode::Aggregate)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Restrict the route-target picker to non-aggregate profiles in the UI","After converting a relay to aggregate, sweep model_routes that pointed at it and repoint them","Validate route targets on settings load and warn about aggregate targets"],"tags":["model-routing","aggregate-relay","configuration","relay-profile"],"backgroundTag":"invalid-routing-config","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}