fix(apodex): harden provider routing metadata

This commit is contained in:
zhanghanduo 2026-08-16 18:01:27 +08:00
parent 64dcb95268
commit 190a3e01ec
5 changed files with 98 additions and 6 deletions

View file

@ -15,6 +15,7 @@ from collections.abc import Mapping
from typing import Final
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
from litellm.types.llms.openai import AllMessageValues
from ..common_utils import (
APODEX_API_BASE_URL,
@ -46,6 +47,8 @@ _CORE_PARAMS: Final = (
"parallel_tool_calls",
)
_PIN_NON_STREAMING: Final = "_apodex_pin_non_streaming"
class ApodexChatConfig(OpenAIGPTConfig):
"""
@ -116,5 +119,35 @@ class ApodexChatConfig(OpenAIGPTConfig):
)
return { # mutable-ok: JSON request body
**renamed,
"extra_body": {"stream": False, **extra_body}, # mutable-ok: JSON request body
_PIN_NON_STREAMING: True,
"extra_body": {**extra_body, "stream": False}, # mutable-ok: JSON request body
}
def transform_request(
self,
model: str,
messages: list[AllMessageValues],
optional_params: dict, # mutable-ok: matches the base-class signature
litellm_params: dict, # mutable-ok: matches the base-class signature
headers: dict, # mutable-ok: matches the base-class signature
) -> dict: # mutable-ok: JSON request body
"""Apply the non-streaming pin after LiteLLM merges caller ``extra_body``."""
pin_non_streaming: Final = bool(optional_params.pop(_PIN_NON_STREAMING, False))
transformed: Final = super().transform_request(
model=model,
messages=messages,
optional_params=optional_params,
litellm_params=litellm_params,
headers=headers,
)
if not pin_non_streaming:
return transformed
requested_extra_body: Final = transformed.get("extra_body")
extra_body: Final = (
requested_extra_body if isinstance(requested_extra_body, Mapping) else {} # mutable-ok: JSON request body
)
return { # mutable-ok: JSON request body
**transformed,
"extra_body": {**extra_body, "stream": False}, # mutable-ok: JSON request body
}

View file

@ -177,6 +177,23 @@
"interactions": true
}
},
"apodex": {
"display_name": "Apodex (`apodex`)",
"url": "https://docs.litellm.ai/docs/providers/apodex",
"endpoints": {
"chat_completions": true,
"messages": true,
"responses": true,
"embeddings": false,
"image_generations": false,
"audio_transcriptions": false,
"audio_speech": false,
"moderations": false,
"batches": false,
"rerank": false,
"a2a": false
}
},
"apertis": {
"display_name": "Apertis (`apertis`)",
"endpoints": {

View file

@ -156,6 +156,17 @@ class TestStreamDefault:
assert captured["body"]["stream"] is False
assert captured["body"]["mcp_servers"] == mcp_servers
def test_extra_body_cannot_override_non_streaming_pin(self):
captured: dict = {}
litellm.completion(
model=DEEP_RESEARCH_MODEL,
messages=[{"role": "user", "content": "hi"}],
extra_body={"stream": True},
client=_client(captured),
)
assert captured["body"]["stream"] is False
class TestSupportedParams:
def test_core_models_support_tools(self):

View file

@ -82,6 +82,12 @@ class TestRegistration:
assert litellm.ApodexChatConfig().custom_llm_provider == "apodex"
assert litellm.ApodexResponsesConfig().custom_llm_provider == LlmProviders.APODEX
def test_packaged_endpoint_matrix_matches_the_source(self):
source = json.loads((REPO_ROOT / "provider_endpoints_support.json").read_text())
backup = json.loads((REPO_ROOT / "litellm" / "provider_endpoints_support_backup.json").read_text())
assert backup["providers"]["apodex"] == source["providers"]["apodex"]
class TestModelMetadata:
@pytest.fixture(scope="class")

View file

@ -62,12 +62,37 @@ class TestNativePassthroughRouting:
forwarding to a path Apodex does not serve for these tiers."""
assert _messages_config(model) is None
def test_deep_research_translation_uses_responses_api(self):
from litellm.llms.anthropic.experimental_pass_through.messages.handler import (
_should_route_to_responses_api,
)
@pytest.mark.parametrize("stream", (False, True), ids=("non-streaming", "streaming"))
def test_deep_research_translation_uses_responses_api(self, monkeypatch: pytest.MonkeyPatch, stream: bool):
from litellm.llms.anthropic.experimental_pass_through.messages import handler
assert _should_route_to_responses_api("apodex") is True
captured: dict = {}
class ResponsesRouteSelected(Exception):
pass
def capture_responses_translation(**kwargs):
captured.update(kwargs)
raise ResponsesRouteSelected
def reject_chat_translation(**kwargs):
pytest.fail("Apodex Deep Research messages must not route through chat completions")
monkeypatch.setattr(litellm, "responses", capture_responses_translation)
monkeypatch.setattr(litellm, "completion", reject_chat_translation)
with pytest.raises(ResponsesRouteSelected):
handler.anthropic_messages_handler(
max_tokens=256,
messages=[{"role": "user", "content": "hi"}],
model="apodex/apodex-1-1-deep-research",
custom_llm_provider="apodex",
stream=stream,
)
assert captured["model"] == "apodex-1-1-deep-research"
assert captured["custom_llm_provider"] == "apodex"
assert captured.get("stream", False) is stream
class TestNativePassthroughRequest: