Why one interface: every provider speaks differently
Two model providers can do the same job and still disagree on where instructions go and where the answer is. Code written for one breaks on the other.
A request and its answer in the shape of OpenAI's Chat Completions API:
openai_request = {
"model": "gpt-4o-mini",
"messages": [
{"role": "system", "content": "You sort support tickets."},
{"role": "user", "content": "I was charged twice"},
],
}
openai_reply = {"choices": [{"message": {"role": "assistant", "content": "billing"}}]}The same request for Anthropic's Messages API. The system instruction moves out of messages into its own field, max_tokens is required, and the answer comes back as a list of content blocks:
anthropic_request = {
"model": "claude-sonnet-4-5",
"max_tokens": 100,
"system": "You sort support tickets.",
"messages": [{"role": "user", "content": "I was charged twice"}],
}
anthropic_reply = {"content": [{"type": "text", "text": "billing"}]}print(openai_reply["choices"][0]["message"]["content"])
print(anthropic_reply["content"][0]["text"])Same answer, two different paths to reach it. Multiply that by request fields, streaming formats, error types, token counting and prices, and an app that can switch between providers carries a translation layer for each one.
What LiteLLM does
LiteLLM takes every request in the OpenAI shape and returns every answer in it, translating to and from each provider underneath. Changing provider becomes changing a string: "openai/gpt-4o-mini" to "anthropic/claude-sonnet-4-5". On top of that one interface it adds what every production app ends up writing: retries, fallbacks, caching, cost tracking and a shared gateway.
- Write the path to the answer for a reply shaped like
{"output": [{"content": [{"text": "billing"}]}]}. - List three fields a provider could rename that would break the code above.
- Write a function
answer_text(reply)that handles both shapes. How long would it get with ten providers?
Little by little, you're building something great.