ZhuLinsen/daily_stock_analysis · error · ValueError

LLM route aliases cannot mix API surfaces: {sorted(surface_c

Error message

LLM route aliases cannot mix API surfaces: {sorted(surface_conflicts)}

What it means

Config-time validation in _channels_to_model_list (src/config.py): find_llm_channel_surface_conflicts detects that the same public route alias (the normalized model name LiteLLM Router routes on) is declared by more than one enabled channel with different api_surface values (e.g. 'responses' vs 'chat'). LiteLLM's Router keys deployments by model_name, so one alias with two surfaces is ambiguous — requests could hit either wire format nondeterministically. The conflicting alias names are embedded in the message.

Source

Thrown at src/config.py:2570

                        str(channel.get("base_url") or ""),
                    )
                    for model in channel.get("models") or []
                }.intersection(conflicting_models)
            ]

        return channels, issues, blocks_legacy_fallback, blocked_hermes_routes

    @classmethod
    def _channels_to_model_list(cls, channels: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
        """Convert parsed LLM channels to LiteLLM Router model_list format.

        Mapping follows:
        - LiteLLM providers: https://docs.litellm.ai/docs/providers
        - LiteLLM model_list 语义: https://docs.litellm.ai/docs/proxy/configs#the-model_list-key
        """
        surface_conflicts = find_llm_channel_surface_conflicts(channels)
        if surface_conflicts:
            raise ValueError(
                "LLM route aliases cannot mix API surfaces: "
                + ", ".join(sorted(surface_conflicts))
            )
        model_list: List[Dict[str, Any]] = []
        for ch in channels:
            hermes_refs = {
                str(ref.get("route_model") or ""): ref
                for ref in (ch.get("model_refs") or [])
                if isinstance(ref, dict)
            }
            for model_name in ch['models']:
                for api_key in ch['api_keys']:
                    model_ref = hermes_refs.get(str(model_name))
                    wire_model = str((model_ref or {}).get("wire_model") or model_name)
                    api_surface = normalize_llm_channel_api_surface(ch.get("api_surface"))
                    wire_model = apply_litellm_api_surface(wire_model, api_surface)
                    litellm_params: Dict[str, Any] = {
                        'model': wire_model,

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Give each surface a distinct route alias (e.g. 'openai/gpt-4o' for chat and 'openai/gpt-4o-responses' for responses) so no alias maps to two surfaces.
  2. Or remove/disable (enabled: false) the old-surface channel declaring the same alias.
  3. Search all channel configs for the aliases listed in the error message to find both declarations.

Example fix

# before (two channels share alias 'openai/gpt-4o')
channel A: models=["openai/gpt-4o"], api_surface: responses
channel B: models=["openai/gpt-4o"]

# after
channel A: models=["openai/gpt-4o-resp"], api_surface: responses
channel B: models=["openai/gpt-4o"]
Defensive patterns

Strategy: validation

Validate before calling

from src.config import find_llm_channel_surface_conflicts

conflicts = find_llm_channel_surface_conflicts(channels)
if conflicts:
    raise SystemExit(f"alias/surface conflicts: {sorted(conflicts)}")

Try / catch

try:
    model_list = Config._channels_to_model_list(channels)
except ValueError as exc:
    if "mix API surfaces" in str(exc):
        # rename or disable the conflicting alias
    raise

Prevention

When it happens

Trigger: Two enabled channels both listing model 'openai/gpt-4o' but one with api_surface: responses and the other without (or 'chat'); or a channel re-declaring an alias after protocol/base_url normalization with a different surface. Raised while building the Router model_list, i.e. at startup/config load.

Common situations: Adding a new Responses-API channel while keeping the old chat-completions channel for 'migration'; YAML merge duplicating a channel block with edited surface; copy-paste between staging and production configs.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/c98cdb384020305f. Report an issue: GitHub.