Skip to content

feat: add LiteLLM as unified LLM provider gateway - #134

Merged
MaleicAcid merged 3 commits into
zh-plus:masterfrom
RheagalFire:feat/add-litellm-provider
May 20, 2026
Merged

feat: add LiteLLM as unified LLM provider gateway#134
MaleicAcid merged 3 commits into
zh-plus:masterfrom
RheagalFire:feat/add-litellm-provider

Conversation

@RheagalFire

Copy link
Copy Markdown
Contributor

Summary

Adds LiteLLMBot as a fourth chatbot provider alongside GPTBot, ClaudeBot, and GeminiBot, enabling access to 100+ LLM providers (OpenAI, Anthropic, Google, AWS Bedrock, Azure, Groq, Mistral, Cohere, etc.) through a single litellm: model prefix.

Supersedes PR #12 (ishaan-jaff, Aug 2023, closed). The original was closed because "the translation prompt is only compatible with OpenAI's models" -- that limitation no longer applies since the repo now has native Anthropic and Google support. This PR builds on the current multi-provider architecture.

Changes

File What
openlrc/chatbot.py New LiteLLMBot(ChatBot) class (120 LOC): lazy litellm import, drop_params=True, temperature/top_p conflict guard, retry with provider-specific exception handling, fee tracking via OpenAI-format usage. Updated route_chatbot() to handle "litellm:" prefix. Updated provider2chatbot dict.
openlrc/models.py Added ModelProvider.LITELLM enum value and DefaultLiteLLMModelInfo fallback class for unknown LiteLLM-routed models.
pyproject.toml Added litellm = ["litellm>=1.60,<1.85"] optional dependency.
tests/test_litellm_bot.py 11 unit tests: dispatch, param forwarding, top_p omission/inclusion, fee tracking, null content, stop sequences, API key forwarding, auth error not retried, routing, extra_body forwarding.

Usage

Install:

pip install "openlrc[litellm]"

Set the credential for whatever backing provider you want:

export ANTHROPIC_API_KEY=sk-ant-...
# or OPENAI_API_KEY, GOOGLE_API_KEY, AWS_ACCESS_KEY_ID, etc.

Use the litellm: prefix when specifying the model:

from openlrc import LRCer

lrcer = LRCer(model="litellm:anthropic/claude-sonnet-4-6")
lrcer.run("audio.mp3")

# Or any of 100+ providers:
lrcer = LRCer(model="litellm:openai/gpt-4o")
lrcer = LRCer(model="litellm:vertex_ai/gemini-2.5-flash")
lrcer = LRCer(model="litellm:bedrock/anthropic.claude-sonnet-4-6-v2:0")
lrcer = LRCer(model="litellm:groq/llama-4-scout-17b-16e-instruct")

Direct chatbot usage:

from openlrc.chatbot import LiteLLMBot

bot = LiteLLMBot(model_name="anthropic/claude-sonnet-4-6", temperature=0.1)
results = bot.message([
    {"role": "system", "content": "Translate to French. Reply with only the translation."},
    {"role": "user", "content": "Hello, how are you?"},
])
print(bot.get_content(results[0]))  # "Bonjour, comment allez-vous ?"

Prior art

Tests

Unit tests: 24 passed, 5 skipped (pre-existing live API skips)

$ python -m pytest tests/test_litellm_bot.py tests/test_chatbot.py -v
tests/test_litellm_bot.py::test_api_key_forwarded PASSED
tests/test_litellm_bot.py::test_auth_error_not_retried PASSED
tests/test_litellm_bot.py::test_create_chat_dispatches_to_litellm PASSED
tests/test_litellm_bot.py::test_extra_body_forwarded PASSED
tests/test_litellm_bot.py::test_fee_tracking PASSED
tests/test_litellm_bot.py::test_null_content_returns_empty PASSED
tests/test_litellm_bot.py::test_params_forwarded PASSED
tests/test_litellm_bot.py::test_route_chatbot_litellm PASSED
tests/test_litellm_bot.py::test_stop_sequences_forwarded PASSED
tests/test_litellm_bot.py::test_top_p_included_when_temperature_default PASSED
tests/test_litellm_bot.py::test_top_p_omitted_when_temperature_set PASSED
tests/test_chatbot.py::... 13 passed, 5 skipped
======================== 24 passed, 5 skipped in 1.53s =========================

Lint: ruff check + format clean

$ ruff check openlrc/chatbot.py openlrc/models.py tests/test_litellm_bot.py
All checks passed!
$ ruff format --check openlrc/chatbot.py openlrc/models.py tests/test_litellm_bot.py
3 files already formatted

Live E2E: Anthropic Claude Sonnet 4-6

Content: Bonjour, comment allez-vous ?
Fee: 0.000560 USD
Usage: prompt=28, completion=14
PASS

Edge cases handled

  • temperature/top_p conflict: Anthropic Claude 4+ rejects requests with both set simultaneously. drop_params=True does not catch this (both are individually valid). Fix: only forward top_p when temperature is at its default (1.0).
  • Null content: Returns "" instead of crashing when response.choices[0].message.content is None.
  • Auth errors not retried: AuthenticationError and BadRequestError raise immediately; only transient errors (rate limit, timeout, 5xx) are retried with backoff.
  • Fee tracking: Uses OpenAI-format response.usage.prompt_tokens / completion_tokens from LiteLLM's normalized response.
  • Lazy import: litellm is imported inside _create_chat() only, so users who don't install openlrc[litellm] are unaffected.

@RheagalFire

Copy link
Copy Markdown
Contributor Author

cc @zh-plus

@MaleicAcid

Copy link
Copy Markdown
Collaborator

Thanks for putting this together — LiteLLM support is a great addition to the project!

The branch looks like it needs a rebase onto current master before merging, as the diff includes some unrelated changes from branch divergence.

On the implementation itself, a few suggestions:

1. Missing non-retryable error types. It would be nice to add PermissionDeniedError (403) and UnprocessableEntityError (422) to the no-retry exception block, alongside the existing AuthenticationError and BadRequestError. Per litellm's exception mapping docs, these are client errors that won't resolve on retry.

2. The top_p conditional filtering. Since drop_params=True is already set, litellm handles incompatible params automatically. The current logic silently drops user-specified top_p when temperature != 1.0, which could be surprising — especially for providers like OpenAI that accept both just fine. It might be worth removing the conditional and letting drop_params do its job, or at least logging a warning when top_p is dropped.

3. proxy is accepted but not used. The other bots (GPTBot, ClaudeBot) pass proxy to their underlying HTTP clients, but LiteLLMBot silently ignores it. Since litellm doesn't expose a per-call proxy parameter, there's not a clean way to wire it through. I'd suggest logging a warning in __init__ when proxy is not None, pointing users to set HTTP_PROXY/HTTPS_PROXY environment variables instead. Something like:

if proxy:
    logger.warning(
        "LiteLLMBot does not support per-instance proxy. "
        "Set HTTP_PROXY/HTTPS_PROXY environment variables instead."
    )

4. get_model() in models.py. There's no litellm-specific inference branch yet. Not a blocker — most litellm model names contain provider prefixes like "openai/" that match existing keyword checks — but adding one would make the fallback behavior more predictable.

Everything else looks clean.

@RheagalFire
RheagalFire force-pushed the feat/add-litellm-provider branch from 2effaef to 2ffd3e6 Compare May 18, 2026 21:04
@RheagalFire

Copy link
Copy Markdown
Contributor Author

@MaleicAcid

Thanks for the detailed review! Pushed fixes addressing all 4 points:

  1. Rebased onto current master
  2. Added PermissionDeniedError (403) and UnprocessableEntityError (422) to the no-retry block
  3. Removed top_p conditional filtering -- always passed now, drop_params=True handles incompatible providers
  4. Added proxy warning in __init__ pointing users to HTTP_PROXY/HTTPS_PROXY env vars
  5. Added litellm provider-prefix inference in get_model() -- model strings like openai/gpt-4o, anthropic/claude-sonnet-4-6, groq/llama-3.3-70b-versatile now route to the correct default ModelInfo

Comment thread openlrc/chatbot.py
def _get_sleep_time(error):
qualname = type(error).__name__
if qualname == "RateLimitError":
return random.randint(30, 60)
@MaleicAcid
MaleicAcid merged commit b23d5cb into zh-plus:master May 20, 2026
4 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants