crewai - ✅(Solved) Fix [BUG] fix: CrewAI 1.12.x LLM routing - litellm does not receive api_base/api_key for multi-provider setups [1 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
crewAIInc/crewAI#5139Fetched 2026-04-08 01:44:56
View on GitHub
Comments
0
Participants
1
Timeline
3
Reactions
0
Participants
Timeline (top)
cross-referenced ×1labeled ×1referenced ×1

Root Cause

When using CrewAI 1.12.x with provider="litellm" and multiple cloud providers (Scaleway + Nebius), LLM calls fail with litellm.AuthenticationError because:

Fix Action

Fix

Three changes in model_service.py:

PR fix notes

PR #5140: fix: sync base_url and api_base for litellm multi-provider routing

Description (problem / solution / changelog)

Summary

Fixes #5139. When LLM(base_url=...) is used without explicitly setting api_base, litellm never receives the custom endpoint because it reads api_base, not base_url. This causes requests to fall back to api.openai.com, breaking multi-provider setups (e.g. Scaleway + Nebius with different API keys and endpoints).

The fix adds a 4-line sync in LLM.__init__ so that whichever parameter the caller provides, both fields are populated:

  • Only base_url provided → api_base is set to match
  • Only api_base provided → base_url is set to match
  • Both provided → both keep their explicit values
  • Neither provided → both stay None

This ensures _prepare_completion_params always includes api_base in the kwargs passed to litellm.completion().

Review & Testing Checklist for Human

  • Verify all internal litellm call sites go through _prepare_completion_params: This fix works because _prepare_completion_params passes api_base/api_key to litellm.completion(). If any code path (e.g. agent reasoning loops, tool selection) calls litellm without going through _prepare_completion_params, those calls would still lack the custom endpoint. Grep for direct litellm.completion / litellm.acompletion calls outside this path.
  • End-to-end test with a real multi-provider crew: Create two LLM instances with different base_url/api_key values, assign them to different agents in the same crew, and run a task. Confirm each agent's requests go to the correct endpoint (e.g. via debug logging or network inspection).
  • Check native provider paths are unaffected: The sync only runs in LLM.__init__ (the litellm fallback path). Native providers (OpenAI, Anthropic, Gemini, etc.) use their own __init__ via BaseLLM. Verify no regressions for LLM(model="gpt-4o") or LLM(model="anthropic/claude-3-sonnet").

Notes

  • The issue also suggests a monkey-patch approach for litellm.completion/litellm.acompletion. This PR intentionally does not implement that — the simpler param-syncing fix should be sufficient since _prepare_completion_params already passes api_base/api_key to every litellm call.
  • 9 new unit tests added covering all sync scenarios including _prepare_completion_params, multi-provider independence, litellm.completion mock verification, and copy/deepcopy preservation.
  • tests (3.13) CI job was cancelled (infrastructure, not a failure); all other required checks (lint, type-checker, tests on 3.10/3.11/3.12) passed.

Link to Devin session: https://app.devin.ai/sessions/3ccb7f0ac5ba4e0c95dc59b9556576a3

Changed files

  • lib/crewai/src/crewai/llm.py (modified, +8/-0)
  • lib/crewai/tests/test_llm.py (modified, +142/-0)

Code Example

LLM(
    model="openai/model-name",
    api_key=api_key,
    base_url=base_url,
    api_base=base_url,  # litellm reads this field
    provider="litellm",
    ...
)

---

_MODEL_PROVIDER_ROUTING: dict[str, dict[str, str]] = {}

def _install_litellm_routing():
    _orig = litellm.completion
    def _patched(*args, **kwargs):
        model = kwargs.get("model", "")
        for pattern, cfg in _MODEL_PROVIDER_ROUTING.items():
            if pattern in model:
                kwargs.setdefault("api_key", cfg["api_key"])
                kwargs.setdefault("api_base", cfg["api_base"])
                break
        return _orig(*args, **kwargs)
    litellm.completion = _patched
RAW_BUFFERClick to expand / collapse

Description

Problem

When using CrewAI 1.12.x with provider="litellm" and multiple cloud providers (Scaleway + Nebius), LLM calls fail with litellm.AuthenticationError because:

  1. CrewAI's LLM class does not map base_url to api_base: The LLM(base_url=...) constructor stores the URL in base_url but litellm reads from api_base, which remains None. This causes litellm to fall back to the default OpenAI endpoint (api.openai.com), sending the Scaleway/Nebius API key to OpenAI.

  2. CrewAI agent internals bypass LLM object params: When a CrewAI Agent executes (tool selection, reasoning loops), its internal litellm calls do not pass api_key or api_base from the LLM object. Instead, litellm falls back to OPENAI_API_KEY / OPENAI_API_BASE environment variables. With a multi-provider setup (Scaleway for mistral-small, Nebius for qwen3), global env vars cannot serve both providers simultaneously.

Root Causes

IssueImpact
LLM(base_url=X) sets self.base_url=X but self.api_base=NoneDirect llm.call() sends requests to api.openai.com instead of custom endpoint
CrewAI agent internal litellm calls don't inherit LLM object paramsAgent execution fails when OPENAI_API_KEY/OPENAI_API_BASE env vars are not set or point to the wrong provider
Global OPENAI_API_BASE env var conflicts with multi-provider setupSetting it to Scaleway breaks Nebius calls and vice versa

Fix

Three changes in model_service.py:

1. Pass both base_url and api_base to LLM constructors

LLM(
    model="openai/model-name",
    api_key=api_key,
    base_url=base_url,
    api_base=base_url,  # litellm reads this field
    provider="litellm",
    ...
)

2. Add provider="litellm" to all LLM constructors

Required in CrewAI 1.12.x to ensure litellm is used as the backend (without it, CrewAI may use a default OpenAI provider that ignores base_url).

3. Thread-safe litellm routing via monkey-patch

Monkey-patch litellm.completion and litellm.acompletion to inject the correct api_key and api_base per model name. This ensures CrewAI's internal agent calls (which bypass the LLM object) are routed to the correct provider.

_MODEL_PROVIDER_ROUTING: dict[str, dict[str, str]] = {}

def _install_litellm_routing():
    _orig = litellm.completion
    def _patched(*args, **kwargs):
        model = kwargs.get("model", "")
        for pattern, cfg in _MODEL_PROVIDER_ROUTING.items():
            if pattern in model:
                kwargs.setdefault("api_key", cfg["api_key"])
                kwargs.setdefault("api_base", cfg["api_base"])
                break
        return _orig(*args, **kwargs)
    litellm.completion = _patched

Thread-safety: Each litellm call resolves credentials from its own model kwarg. The routing table is read-only after initialization. No shared mutable state is modified at call time.

Environment

  • crewai[litellm]~=1.12.0
  • Multiple providers: Scaleway (mistral-small, llama3, deepseek-r1) + Nebius (qwen3)

Steps to Reproduce

  1. Install crewai[litellm]~=1.12.0
  2. Configure two cloud providers with different api_key / base_url: Scaleway (for mistral-small, llama3, deepseek-r1) Nebius (for qwen3)
  3. Create an LLM instance using base_url and api_key: `from crewai import LLM

llm = LLM( model="openai/mistral-small-3.2-24b-instruct-2506", api_key="scw-xxx", base_url="https://api.scaleway.ai/v1", temperature=0.2, )4. Use this LLM inside a CrewAI Agent and run a Crew:from crewai import Agent, Crew, Task, Process agent = Agent( role="Assistant", goal="Answer the user's question", backstory="You are a helpful assistant.", llm=llm, ) task = Task( description="What is the capital of France?", expected_output="A short answer.", agent=agent, ) crew = Crew(agents=[agent], tasks=[task], process=Process.sequential) result = crew.kickoff()` 5. Observe a litellm.AuthenticationError because the request was sent to api.openai.com instead of the Scaleway endpoint.

Expected behavior

  • LLM(base_url="https://api.scaleway.ai/v1") should route all litellm calls (both direct llm.call() and internal agent reasoning/tool-selection calls) to https://api.scaleway.ai/v1 using the provided api_key.

  • In a multi-provider setup, each LLM instance should use its own api_key / base_url independently, without relying on global OPENAI_API_KEY / OPENAI_API_BASE environment variables.

Screenshots/Code snippets

Problem 1 -- base_url not mapped to api_base: The LLM class stores base_url but litellm reads from api_base, which remains None: # Before fix: only api_base was set, base_url was missing LLM( model="openai/mistral-small-3.2-24b-instruct-2506", api_key=self.scaleway_api_key, api_base=self.scaleway_base_url, # litellm reads this # base_url is missing -> CrewAI may not propagate api_base correctly temperature=0.2, timeout=120, ) Fix: pass both base_url and api_base, and add provider="litellm": LLM( model="openai/mistral-small-3.2-24b-instruct-2506", api_key=self.scaleway_api_key, base_url=self.scaleway_base_url, api_base=self.scaleway_base_url, # litellm reads this field provider="litellm", temperature=0.2, timeout=120, )

Problem 2 -- Agent internals bypass LLM object params: CrewAI agent internal litellm calls (tool selection, reasoning loops) do not pass api_key / api_base from the LLM object. They fall back to OPENAI_API_KEY / OPENAI_API_BASE env vars, which cannot serve two providers at once. Fix: monkey-patch litellm.completion / litellm.acompletion to inject the correct credentials based on model name: `_MODEL_PROVIDER_ROUTING: dict[str, dict[str, str]] = {}

def _install_litellm_routing(): _orig_completion = litellm.completion _orig_acompletion = litellm.acompletion

def _inject(kwargs):
    model = kwargs.get("model", "")
    for pattern, cfg in _MODEL_PROVIDER_ROUTING.items():
        if pattern in model:
            kwargs.setdefault("api_key", cfg["api_key"])
            kwargs.setdefault("api_base", cfg["api_base"])
            break

def _patched_completion(*args, **kwargs):
    _inject(kwargs)
    return _orig_completion(*args, **kwargs)

async def _patched_acompletion(*args, **kwargs):
    _inject(kwargs)
    return await _orig_acompletion(*args, **kwargs)

litellm.completion = _patched_completion
litellm.acompletion = _patched_acompletion`

Thread-safety: each call resolves credentials from its own model kwarg. The routing table (_MODEL_PROVIDER_ROUTING) is read-only after initialization.

Operating System

Ubuntu 20.04

Python Version

3.10

crewAI Version

1.12.2

crewAI Tools Version

1.12.2

Virtual Environment

Venv

Evidence

  • litellm.AuthenticationError on first LLM call -> LLM(base_url=X) sets self.base_url=X but self.api_base=None -- litellm falls back to api.openai.com (Requests logged against OpenAI endpoint instead of Scaleway/Nebius; the Scaleway API key is rejected by OpenAI)

  • Agent tool-calling / reasoning steps fail even when llm.call() works -> CrewAI agent internals call litellm.completion() without passing api_key / api_base from the LLM object

(Adding debug logging to litellm.completion shows api_key=None, api_base=None on agent-internal calls)

  • Setting OPENAI_API_BASE fixes one provider but breaks the other -> Global env var cannot serve Scaleway and Nebius simultaneously

(Setting OPENAI_API_BASE to Scaleway makes qwen3 (Nebius) fail, and vice versa)

Possible Solution

None

Additional context

None

extent analysis

Fix Plan

To resolve the issue, follow these steps:

  1. Update LLM constructors: Pass both base_url and api_base to the LLM constructors and add provider="litellm":

LLM( model="openai/model-name", api_key=api_key, base_url=base_url, api_base=base_url,
provider="litellm", ... )


2. **Monkey-patch litellm.completion and litellm.acompletion**: Inject the correct `api_key` and `api_base` per model name:
   ```python
_MODEL_PROVIDER_ROUTING: dict[str, dict[str, str]] = {}

def _install_litellm_routing():
    _orig_completion = litellm.completion
    _orig_acompletion = litellm.acompletion

    def _inject(kwargs):
        model = kwargs.get("model", "")
        for pattern, cfg in _MODEL_PROVIDER_ROUTING.items():
            if pattern in model:
                kwargs.setdefault("api_key", cfg["api_key"])
                kwargs.setdefault("api_base", cfg["api_base"])
                break

    def _patched_completion(*args, **kwargs):
        _inject(kwargs)
        return _orig_completion(*args, **kwargs)

    async def _patched_acompletion(*args, **kwargs):
        _inject(kwargs)
        return await _orig_acompletion(*args, **kwargs)

    litellm.completion = _patched_completion
    litellm.acompletion = _patched_acompletion

Verification

To verify the fix, follow these steps:

  • Create an LLM instance with the updated constructor.
  • Use the LLM instance inside a CrewAI Agent and run a Crew.
  • Check that the LLM calls are routed to the correct provider endpoint.
  • Verify that the agent internal calls are also routed correctly.

Extra Tips

  • Ensure that the _MODEL_PROVIDER_ROUTING dictionary is properly configured to map model names to their corresponding api_key and api_base.
  • Be cautious when using monkey patches, as they can have unintended consequences if not properly tested.
  • Consider submitting a pull request to the CrewAI repository to include this fix in the main codebase.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

  • LLM(base_url="https://api.scaleway.ai/v1") should route all litellm calls (both direct llm.call() and internal agent reasoning/tool-selection calls) to https://api.scaleway.ai/v1 using the provided api_key.

  • In a multi-provider setup, each LLM instance should use its own api_key / base_url independently, without relying on global OPENAI_API_KEY / OPENAI_API_BASE environment variables.

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING