Skip to content

feat: add LiteLLM as AI gateway provider#264

Open
RheagalFire wants to merge 2 commits into
EverMind-AI:mainfrom
RheagalFire:feat/add-litellm-provider
Open

feat: add LiteLLM as AI gateway provider#264
RheagalFire wants to merge 2 commits into
EverMind-AI:mainfrom
RheagalFire:feat/add-litellm-provider

Conversation

@RheagalFire
Copy link
Copy Markdown

@RheagalFire RheagalFire commented Jun 6, 2026

Summary

Adds LiteLLM as an optional AI gateway SDK for both LLM chat and embeddings. Users set EVEROS_LLM__PROVIDER=litellm and a provider-prefixed model id (e.g. anthropic/claude-sonnet-4-6) to access 100+ LLM providers through a unified Python SDK. drop_params=True is always passed so provider-unsupported kwargs (seed, frequency_penalty, strict) are silently dropped.

Follows the existing RerankSettings dispatch pattern: adds provider: Literal["openai", "litellm"] to LLMSettings and EmbeddingSettings. Default is "openai" - existing behavior is completely unchanged.

Area

  • Architecture method
  • Benchmark
  • Use case
  • Documentation
  • Developer experience
  • CI, build, or release

Verification

$ make test
1215 passed, 1 skipped in 10.26s

$ make integration
57 passed, 6 deselected in 26.97s

$ make lint
ruff check: All checks passed!
ruff format: 466 files already formatted
import-linter: 3 contracts kept, 0 broken
repo assets / deprecated names / datetime discipline / openapi drift: all clean

LLM provider tests (11 tests):

test_chat_dispatches_to_litellm PASSED
test_api_key_omitted_when_none PASSED
test_base_url_forwarded PASSED
test_litellm_error_wrapped_as_llm_error PASSED
test_import_error_raises_llm_error PASSED
test_empty_choices_raises_llm_error PASSED
test_null_usage_handled PASSED
test_null_content_coerced_to_empty PASSED
test_per_call_model_override PASSED
test_openai_exception_wrapped PASSED
test_litellm_timeout_wrapped PASSED

Embedding provider tests (9 tests):

test_embed_single PASSED
test_embed_batch_preserves_order PASSED
test_embed_batch_empty PASSED
test_dimension_truncation PASSED
test_api_key_forwarded PASSED
test_api_key_omitted_when_none PASSED
test_litellm_error_wrapped PASSED
test_import_error_raises_embedding_error PASSED
test_batching_chunks_correctly PASSED

Factory dispatch tests (10 tests):

test_builds_litellm_provider PASSED
test_litellm_provider_without_base_url PASSED
test_litellm_provider_without_api_key PASSED
test_builds_litellm_embedding_provider PASSED
test_litellm_embedding_without_base_url PASSED
test_litellm_embedding_raises_when_model_missing PASSED
(+ 4 existing openai factory tests PASSED)

Live E2E against Azure Foundry (Claude via LiteLLM SDK):

$ python3 -c "
provider = LiteLLMProvider(
    model='anthropic/claude-sonnet-4-6',
    api_key=os.environ['ANTHROPIC_FOUNDRY_API_KEY'],
    base_url=os.environ['ANTHROPIC_FOUNDRY_BASE_URL'],
)
resp = await provider.chat([ChatMessage(role='user', content='Say OK and nothing else.')])
"

content: 'OK'
model: claude-sonnet-4-6
usage: prompt_tokens=13 completion_tokens=4
finish_reason: stop

Checklist

  • I kept the change scoped to the relevant area.
  • I am opening this from a separate branch, not pushing directly to main.
  • I updated docs, examples, or setup notes when behavior changed.
  • I added or updated tests when the change affects behavior.
  • I did not commit secrets, .env files, dependency folders, or generated output.
  • Active relative links in Markdown files resolve.

Notes for Reviewers

Additive only. The default provider="openai" path is untouched - no existing code paths change. litellm is lazy-imported inside function bodies so users who never install it see no difference.

Bug fixes applied during review:

  • item["embedding"] -> item.embedding in embedding provider (attribute access matching OpenAI sibling)
  • Added guard for empty completion.choices list (was IndexError)
  • Widened exception prefix from litellm.exceptions.* to litellm.* (catches litellm.Timeout, litellm.BudgetExceededError)
  • api_key forwarding: truthiness check -> is not None (empty string now correctly sent rather than silently falling back to env vars)

Files created:

  • src/everos/component/llm/litellm_provider.py - LiteLLMProvider (async, satisfies LLMClient protocol)
  • src/everos/component/embedding/litellm_provider.py - LiteLLMEmbeddingProvider (async, satisfies EmbeddingProvider protocol, with batching + semaphore concurrency matching the OpenAI sibling)
  • tests/unit/test_component/test_llm/test_litellm_provider.py - 11 tests
  • tests/unit/test_component/test_embedding/test_litellm_provider.py - 9 tests

Files modified:

  • src/everos/config/settings.py - added provider field to LLMSettings and EmbeddingSettings
  • src/everos/component/llm/factory.py - dispatch on settings.provider
  • src/everos/component/llm/client.py - singleton accessor routes through factory when provider="litellm"
  • src/everos/component/embedding/factory.py - dispatch on settings.provider
  • src/everos/component/{llm,embedding}/__init__.py - re-export new classes
  • pyproject.toml - litellm>=1.80.0,<2.0 under [project.optional-dependencies].litellm
  • tests/unit/test_component/test_llm/test_factory.py - 3 new litellm dispatch tests
  • tests/unit/test_component/test_embedding/test_factory.py - 3 new litellm dispatch tests

Usage:

from everos.component.llm import build_llm_provider
from everos.component.llm.protocol import ChatMessage
from everos.config.settings import LLMSettings
from pydantic import SecretStr

provider = build_llm_provider(
    LLMSettings(
        provider="litellm",
        model="anthropic/claude-sonnet-4-6",
        api_key=SecretStr("sk-ant-..."),
    )
)

response = await provider.chat(
    [ChatMessage(role="user", content="What is 2+2?")],
    temperature=0.0,
)
print(response.content)   # "4"
print(response.usage)     # Usage(prompt_tokens=12, completion_tokens=1)

Or via env vars:

EVEROS_LLM__PROVIDER=litellm
EVEROS_LLM__MODEL=anthropic/claude-sonnet-4-6
# litellm reads ANTHROPIC_API_KEY from env automatically

By submitting this pull request, I agree that my contribution is licensed under
the Apache License 2.0.

@RheagalFire
Copy link
Copy Markdown
Author

cc @cyfyifanchen

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.

1 participant