|
| 1 | +"""LiteLLM backend — routes requests through ``litellm.completion()`` to 100+ providers |
| 2 | +(OpenAI, Anthropic, Bedrock, Vertex, Gemini, Ollama, OpenRouter, Groq, DeepSeek, etc.) |
| 3 | +using provider-native API keys. |
| 4 | +
|
| 5 | +Reuses the ``OpenAICompatible`` simple-backend implementation end-to-end by swapping |
| 6 | +``self.client`` for a thin duck-typed shim that dispatches ``chat.completions.create`` |
| 7 | +to ``litellm.completion``. All retry/concurrency/media-encoding logic is inherited |
| 8 | +unchanged. |
| 9 | +
|
| 10 | +See https://docs.litellm.ai/docs/providers for the full provider list and model-name |
| 11 | +prefix convention (e.g. ``anthropic/claude-3-5-sonnet-20241022``). |
| 12 | +""" |
| 13 | + |
| 14 | +from __future__ import annotations |
| 15 | + |
| 16 | +import os |
| 17 | +from typing import Any, Optional |
| 18 | + |
| 19 | +from lmms_eval.api.registry import register_model |
| 20 | +from lmms_eval.models.simple.openai import OpenAICompatible as OpenAICompatibleBase |
| 21 | + |
| 22 | +# Placeholder key passed to ``openai.OpenAI(api_key=...)`` inside the super().__init__() |
| 23 | +# call. openai-python raises at construction if both the argument and OPENAI_API_KEY env |
| 24 | +# var are unset. We replace ``self.client`` with a LiteLLM-backed shim immediately after, |
| 25 | +# so this placeholder is never used on the wire. |
| 26 | +_PLACEHOLDER_API_KEY = "sk-litellm-placeholder" |
| 27 | + |
| 28 | + |
| 29 | +class _LiteLLMChatCompletions: |
| 30 | + """Duck-typed ``openai.OpenAI().chat.completions`` surface backed by ``litellm.completion``.""" |
| 31 | + |
| 32 | + def __init__(self, api_key: Optional[str], base_url: Optional[str]) -> None: |
| 33 | + self._api_key = api_key |
| 34 | + self._base_url = base_url |
| 35 | + |
| 36 | + def create(self, **kwargs: Any) -> Any: |
| 37 | + import litellm # lazy import — ``litellm`` is an optional extra |
| 38 | + |
| 39 | + if self._api_key is not None: |
| 40 | + kwargs.setdefault("api_key", self._api_key) |
| 41 | + if self._base_url is not None: |
| 42 | + kwargs.setdefault("api_base", self._base_url) |
| 43 | + return litellm.completion(**kwargs) |
| 44 | + |
| 45 | + |
| 46 | +class _LiteLLMChat: |
| 47 | + def __init__(self, completions: _LiteLLMChatCompletions) -> None: |
| 48 | + self.completions = completions |
| 49 | + |
| 50 | + |
| 51 | +class _LiteLLMClientShim: |
| 52 | + """Minimal OpenAI-client shape used by lmms-eval's ``generate_until`` call path. |
| 53 | +
|
| 54 | + lmms-eval only calls ``self.client.chat.completions.create(**payload)``; this shim |
| 55 | + dispatches that single method to ``litellm.completion``. API key / base URL are |
| 56 | + forwarded per-call; when they are None, LiteLLM resolves credentials from |
| 57 | + provider-specific env vars (``ANTHROPIC_API_KEY``, ``GEMINI_API_KEY``, ``AWS_*``, ...). |
| 58 | + """ |
| 59 | + |
| 60 | + def __init__(self, api_key: Optional[str], base_url: Optional[str]) -> None: |
| 61 | + self.chat = _LiteLLMChat(_LiteLLMChatCompletions(api_key=api_key, base_url=base_url)) |
| 62 | + |
| 63 | + |
| 64 | +@register_model("litellm") |
| 65 | +class LiteLLMCompatible(OpenAICompatibleBase): |
| 66 | + """LiteLLM-backed backend that inherits OpenAI-compatible batching/retry logic. |
| 67 | +
|
| 68 | + Users select this backend via ``--model litellm --model_args model=<prefixed_name>``. |
| 69 | + Provider API keys come from the user's environment (``ANTHROPIC_API_KEY``, ...) or |
| 70 | + can be passed explicitly via ``--model_args api_key=...``. |
| 71 | + """ |
| 72 | + |
| 73 | + def __init__( |
| 74 | + self, |
| 75 | + model_version: str = "openai/gpt-4o-mini", |
| 76 | + model: Optional[str] = None, |
| 77 | + base_url: Optional[str] = None, |
| 78 | + api_key: Optional[str] = None, |
| 79 | + **kwargs: Any, |
| 80 | + ) -> None: |
| 81 | + resolved_api_key = api_key or os.getenv("OPENAI_API_KEY") |
| 82 | + resolved_base_url = base_url or os.getenv("OPENAI_API_BASE") |
| 83 | + |
| 84 | + # The parent __init__ builds an openai.OpenAI(...) client and raises if no API |
| 85 | + # key is resolvable. Hand it a placeholder so construction succeeds; we swap |
| 86 | + # self.client immediately afterward with a LiteLLM-backed shim that never uses |
| 87 | + # the OpenAI client. |
| 88 | + super().__init__( |
| 89 | + model_version=model_version, |
| 90 | + model=model, |
| 91 | + base_url=resolved_base_url, |
| 92 | + api_key=resolved_api_key or _PLACEHOLDER_API_KEY, |
| 93 | + azure_openai=False, |
| 94 | + **kwargs, |
| 95 | + ) |
| 96 | + self.client = _LiteLLMClientShim(api_key=resolved_api_key, base_url=resolved_base_url) |
0 commit comments