mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
Add a live spend-tracking e2e that drives a streaming anthropic-format /v1/messages request through litellm's anthropic-messages -> OpenAI Responses adapter and asserts the consumed stream writes exactly one SpendLogs row with nonzero cost and token counts, attributed to the calling key under custom_llm_provider openai and the /v1/messages call_type. The deployment is a Responses-only OpenAI model (gpt-5.3-codex), so a served, costed row proves the Responses path was taken; the chat-completions bridge would have failed at OpenAI on an endpoint the model does not expose. Adds a streaming /v1/messages method to the shared Gateway and the suite client, the model to the inline compose config and driver-model registration, a coverage registry row (quota_management.spend_tracking.messages_bridge.logs_cost), and the matching variant vocab entry. The _summarize spend-row detail also gains call_type and custom_llm_provider so a failed assertion prints the fields it asserts on. Resolves LIT-4546
57 lines
2.2 KiB
Python
57 lines
2.2 KiB
Python
"""Spend-tracking suite's `client` fixture and driver-model registration.
|
|
|
|
The shared lifecycle (resources/scoped_key), proxy liveness gate, and e2e marker
|
|
live in the parent tests/e2e/conftest.py. SpendClient exposes the shared ProxyClient
|
|
(ProxyClientProvider), so the `resources` fixture cleans up keys and customers this
|
|
suite creates.
|
|
|
|
The suite drives real calls through three deployments. On the stage gateway they
|
|
are baked into the proxy config; on a local dev proxy they usually are not, so
|
|
`driver_models` registers whichever are missing via /model/new and deletes only
|
|
the ones it created, never a config-baked deployment. Each registration carries
|
|
the provider key from the test runner's env when set (so a local proxy whose
|
|
container env lacks the key still works); otherwise it falls back to an
|
|
os.environ reference resolved from the proxy's own env, the stage convention.
|
|
"""
|
|
|
|
import os
|
|
from typing import Iterator
|
|
|
|
import pytest
|
|
|
|
from models import LiteLLMParamsBody
|
|
from spend_e2e_client import SpendClient, build_client
|
|
from proxy_client import ProxyClient
|
|
|
|
|
|
def _driver_params(provider_model: str, env_var: str) -> LiteLLMParamsBody:
|
|
return LiteLLMParamsBody(
|
|
model=provider_model,
|
|
api_key=os.environ.get(env_var) or f"os.environ/{env_var}",
|
|
)
|
|
|
|
|
|
DRIVER_MODELS: tuple[tuple[str, str, str], ...] = (
|
|
("gemini-2.5-flash", "gemini/gemini-2.5-flash", "GEMINI_API_KEY"),
|
|
("claude-haiku-4-5", "anthropic/claude-haiku-4-5", "ANTHROPIC_API_KEY"),
|
|
("openai-text-embedding-3-small", "openai/text-embedding-3-small", "OPENAI_API_KEY"),
|
|
("openai-responses-codex", "openai/gpt-5.3-codex", "OPENAI_API_KEY"),
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def client(proxy: ProxyClient) -> SpendClient:
|
|
return build_client(proxy)
|
|
|
|
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def driver_models(client: SpendClient) -> Iterator[None]:
|
|
existing = frozenset(entry.model_name for entry in client.proxy.model_info())
|
|
created = tuple(
|
|
client.proxy.create_model(name, _driver_params(provider_model, env_var))
|
|
for name, provider_model, env_var in DRIVER_MODELS
|
|
if name not in existing
|
|
)
|
|
yield
|
|
for model_id in created:
|
|
client.proxy.delete_model(model_id)
|