diff --git a/docs/models.md b/docs/models.md index 7ad515bc..a49546be 100644 --- a/docs/models.md +++ b/docs/models.md @@ -71,3 +71,175 @@ spanish_agent = Agent( model_settings=ModelSettings(temperature=0.5), ) ``` + +## Using LiteLLM Provider + +The SDK includes built-in support for [LiteLLM](https://docs.litellm.ai/), a unified interface for multiple LLM providers. LiteLLM provides a proxy server that exposes an OpenAI-compatible API for various LLM providers including OpenAI, Anthropic, Azure, AWS Bedrock, Google, and more. + +### Basic Usage + +```python +from agents import Agent, Runner, LiteLLMProvider, RunConfig +import asyncio + +# Create a LiteLLM provider +provider = LiteLLMProvider( + api_key="your-litellm-api-key", # or set LITELLM_API_KEY env var + base_url="http://localhost:8000", # or set LITELLM_API_BASE env var +) + +# Create an agent using a specific model +agent = Agent( + name="Assistant", + instructions="You are a helpful assistant.", + model="claude-3", # Will be routed to Anthropic by the provider +) + +# Create a run configuration with the provider +run_config = RunConfig(model_provider=provider) + +async def main(): + result = await Runner.run( + agent, + input="Hello!", + run_config=run_config # Pass the provider through run_config + ) + print(result.final_output) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +### Environment Variables + +The LiteLLM provider supports configuration through environment variables: + +```bash +# LiteLLM configuration +export LITELLM_API_KEY="your-litellm-api-key" +export LITELLM_API_BASE="http://localhost:8000" +export LITELLM_MODEL="gpt-4" # Default model (optional) + +# Provider-specific keys (examples) +export OPENAI_API_KEY="sk-..." +export ANTHROPIC_API_KEY="sk-ant-..." +export AZURE_API_KEY="..." +export AWS_ACCESS_KEY_ID="..." +export AWS_SECRET_ACCESS_KEY="..." +``` + +### Model Routing + +The provider automatically routes model names to their appropriate providers: + +```python +# Create the LiteLLM provider +provider = LiteLLMProvider( + api_key="your-litellm-api-key", + base_url="http://localhost:8000" +) + +# Create a run configuration with the provider +run_config = RunConfig(model_provider=provider) + +# Models are automatically routed based on their names +openai_agent = Agent( + name="OpenAI Agent", + instructions="Using GPT-4", + model="gpt-4", # Will be routed to OpenAI +) + +anthropic_agent = Agent( + name="Anthropic Agent", + instructions="Using Claude", + model="claude-3", # Will be routed to Anthropic +) + +azure_agent = Agent( + name="Azure Agent", + instructions="Using Azure OpenAI", + model="azure/gpt-4", # Explicitly using Azure +) + +# Run any of the agents with the provider +result = await Runner.run(openai_agent, input="Hello!", run_config=run_config) +``` + +You can also explicitly specify providers using prefixes: + +- `openai/` - OpenAI models +- `anthropic/` - Anthropic models +- `azure/` - Azure OpenAI models +- `aws/` - AWS Bedrock models +- `cohere/` - Cohere models +- `replicate/` - Replicate models +- `huggingface/` - Hugging Face models +- `mistral/` - Mistral AI models +- `gemini/` - Google Gemini models +- `groq/` - Groq models + +### Advanced Configuration + +The provider supports additional configuration options: + +```python +provider = LiteLLMProvider( + api_key="your-litellm-api-key", + base_url="http://localhost:8000", + model_name="gpt-4", # Default model + use_responses=True, # Use OpenAI Responses API format + extra_headers={ # Additional headers + "x-custom-header": "value" + }, + drop_params=True, # Drop unsupported params for specific models +) +``` + +### Using Multiple Providers + +You can use different providers for different agents in your workflow: + +```python +from agents import Agent, Runner, OpenAIProvider, LiteLLMProvider, RunConfig +import asyncio + +# OpenAI provider for direct OpenAI API access +openai_provider = OpenAIProvider() + +# LiteLLM provider for other models +litellm_provider = LiteLLMProvider( + api_key="your-litellm-api-key", + base_url="http://localhost:8000" +) + +# Create agents with different model names +triage_agent = Agent( + name="Triage", + instructions="Route requests to appropriate agents", + model="gpt-3.5-turbo", # Will be routed by the provider +) + +analysis_agent = Agent( + name="Analysis", + instructions="Perform detailed analysis", + model="claude-3", # Will be routed by the provider +) + +# Run with OpenAI provider +openai_config = RunConfig(model_provider=openai_provider) +result_triage = await Runner.run( + triage_agent, + input="Analyze this data", + run_config=openai_config +) + +# Run with LiteLLM provider +litellm_config = RunConfig(model_provider=litellm_provider) +result_analysis = await Runner.run( + analysis_agent, + input="Perform detailed analysis of this data", + run_config=litellm_config +) +``` + +The LiteLLM provider makes it easy to use multiple LLM providers while maintaining a consistent interface and the full feature set of the Agents SDK including handoffs, tools, and tracing. diff --git a/examples/litellm/README.md b/examples/litellm/README.md new file mode 100644 index 00000000..20993a43 --- /dev/null +++ b/examples/litellm/README.md @@ -0,0 +1,65 @@ +# LiteLLM Provider Examples + +This directory contains examples demonstrating how to use the LiteLLM provider with the Agents SDK. + +## Prerequisites + +1. Install and run the LiteLLM proxy server: +```bash +pip install litellm +litellm --model ollama/llama2 --port 8000 +``` + +2. Set up environment variables: +```bash +# LiteLLM configuration +export LITELLM_API_KEY="your-litellm-api-key" # If required by your proxy +export LITELLM_API_BASE="http://localhost:8000" + +# Provider API keys (as needed) +export OPENAI_API_KEY="sk-..." +export ANTHROPIC_API_KEY="sk-ant-..." +export GEMINI_API_KEY="..." +``` + +## Examples + +### Multi-Provider Workflow (`multi_provider_workflow.py`) + +This example demonstrates using multiple LLM providers in a workflow: + +1. A triage agent (using OpenAI directly) determines the task type +2. Based on the task type, it routes to specialized agents: + - Summarization tasks → Claude (via LiteLLM) + - Coding tasks → GPT-4 (via LiteLLM) + - Creative tasks → Gemini (via LiteLLM) + +The example uses `RunConfig` to specify which provider to use for each agent: + +```python +# For OpenAI provider +openai_config = RunConfig(model_provider=openai_provider) +result = await Runner.run(triage_agent, input="...", run_config=openai_config) + +# For LiteLLM provider +litellm_config = RunConfig(model_provider=litellm_provider) +result = await Runner.run(target_agent, input="...", run_config=litellm_config) +``` + +To run: +```bash +python examples/litellm/multi_provider_workflow.py +``` + +The example will process three different types of requests to demonstrate the routing: +1. A summarization request about the French Revolution +2. A coding request to implement a Fibonacci sequence +3. A creative writing request about a time-traveling coffee cup + +## Notes + +- The LiteLLM provider automatically routes model names to their appropriate providers (e.g., `claude-3` → Anthropic, `gpt-4` → OpenAI) +- You can explicitly specify providers using prefixes (e.g., `anthropic/claude-3`, `openai/gpt-4`) +- The provider handles passing API keys and configuration through headers +- All Agents SDK features (handoffs, tools, tracing) work with the LiteLLM provider +- Use `RunConfig` to specify which provider to use when calling `Runner.run()` \ No newline at end of file diff --git a/examples/litellm/multi_provider_workflow.py b/examples/litellm/multi_provider_workflow.py new file mode 100644 index 00000000..00edae36 --- /dev/null +++ b/examples/litellm/multi_provider_workflow.py @@ -0,0 +1,140 @@ +""" +This example demonstrates using multiple LLM providers in a workflow using LiteLLM. +It creates a workflow where: +1. A triage agent (using OpenAI directly) determines the task type +2. Based on the task type, it routes to: + - A summarization agent using Claude via LiteLLM + - A coding agent using GPT-4 via LiteLLM + - A creative agent using Gemini via LiteLLM +""" + +import asyncio +import os +from typing import Literal + +from agents import Agent, Runner, OpenAIProvider, LiteLLMProvider, RunConfig +from agents.agent_output import AgentOutputSchema +from pydantic import BaseModel + + +class TaskType(BaseModel): + """The type of task to be performed.""" + task: Literal["summarize", "code", "creative"] + explanation: str + + +class TaskOutput(BaseModel): + """The output of the task.""" + result: str + provider_used: str + + +# Set up providers +openai_provider = OpenAIProvider( + api_key=os.getenv("OPENAI_API_KEY") +) + +litellm_provider = LiteLLMProvider( + api_key=os.getenv("LITELLM_API_KEY"), + base_url=os.getenv("LITELLM_API_BASE", "http://localhost:8000") +) + +# Create specialized agents for different tasks +triage_agent = Agent( + name="Triage Agent", + instructions=""" + You are a triage agent that determines the type of task needed. + - For text analysis, summarization, or understanding tasks, choose 'summarize' + - For programming, coding, or technical tasks, choose 'code' + - For creative writing, storytelling, or artistic tasks, choose 'creative' + """, + model="gpt-3.5-turbo", + output_schema=AgentOutputSchema(TaskType), +) + +summarize_agent = Agent( + name="Summarization Agent", + instructions=""" + You are a summarization expert using Claude's advanced comprehension capabilities. + Provide clear, concise summaries while preserving key information. + Always include "Provider Used: Anthropic Claude" in your response. + """, + model="claude-3", # Will be routed to Anthropic + output_schema=AgentOutputSchema(TaskOutput), +) + +code_agent = Agent( + name="Coding Agent", + instructions=""" + You are a coding expert using GPT-4's technical capabilities. + Provide clean, well-documented code solutions. + Always include "Provider Used: OpenAI GPT-4" in your response. + """, + model="gpt-4", # Will be routed to OpenAI + output_schema=AgentOutputSchema(TaskOutput), +) + +creative_agent = Agent( + name="Creative Agent", + instructions=""" + You are a creative writer using Gemini's creative capabilities. + Create engaging, imaginative content. + Always include "Provider Used: Google Gemini" in your response. + """, + model="gemini-pro", # Will be routed to Google + output_schema=AgentOutputSchema(TaskOutput), +) + + +async def process_request(user_input: str) -> str: + """Process a user request using the appropriate agent.""" + + # First, triage the request with OpenAI provider + openai_config = RunConfig(model_provider=openai_provider) + triage_result = await Runner.run( + triage_agent, + input=f"What type of task is this request? {user_input}", + run_config=openai_config + ) + task_type = triage_result.output + + # Route to the appropriate agent with LiteLLM provider + target_agent = { + "summarize": summarize_agent, + "code": code_agent, + "creative": creative_agent, + }[task_type.task] + + # Process with the specialized agent using LiteLLM provider + litellm_config = RunConfig(model_provider=litellm_provider) + result = await Runner.run( + target_agent, + input=user_input, + run_config=litellm_config + ) + + return f""" +Task Type: {task_type.task} +Reason: {task_type.explanation} +Result: {result.output.result} +Provider Used: {result.output.provider_used} +""" + + +async def main(): + """Run example requests through the workflow.""" + requests = [ + "Can you summarize the key points of the French Revolution?", + "Write a Python function to calculate the Fibonacci sequence.", + "Write a short story about a time-traveling coffee cup.", + ] + + for request in requests: + print(f"\nProcessing request: {request}") + print("-" * 80) + result = await process_request(request) + print(result) + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/src/agents/__init__.py b/src/agents/__init__.py index 69c500ab..34acfe6d 100644 --- a/src/agents/__init__.py +++ b/src/agents/__init__.py @@ -44,6 +44,7 @@ from .models.openai_chatcompletions import OpenAIChatCompletionsModel from .models.openai_provider import OpenAIProvider from .models.openai_responses import OpenAIResponsesModel +from .models.litellm_provider import LiteLLMProvider from .result import RunResult, RunResultStreaming from .run import RunConfig, Runner from .run_context import RunContextWrapper, TContext @@ -139,6 +140,7 @@ def enable_verbose_stdout_logging(): "OpenAIChatCompletionsModel", "OpenAIProvider", "OpenAIResponsesModel", + "LiteLLMProvider", "AgentOutputSchema", "Computer", "AsyncComputer", diff --git a/src/agents/models/__init__.py b/src/agents/models/__init__.py index e69de29b..57b65ce8 100644 --- a/src/agents/models/__init__.py +++ b/src/agents/models/__init__.py @@ -0,0 +1,5 @@ +from .interface import Model, ModelProvider, ModelTracing +from .litellm_provider import LiteLLMProvider +from .openai_provider import OpenAIProvider + +__all__ = ["Model", "ModelProvider", "ModelTracing", "OpenAIProvider", "LiteLLMProvider"] diff --git a/src/agents/models/litellm_provider.py b/src/agents/models/litellm_provider.py new file mode 100644 index 00000000..4aedb8a1 --- /dev/null +++ b/src/agents/models/litellm_provider.py @@ -0,0 +1,164 @@ +from __future__ import annotations + +import os +import httpx +from openai import AsyncOpenAI +from typing import Any, Dict + +from . import _openai_shared +from .interface import Model, ModelProvider +from .openai_chatcompletions import OpenAIChatCompletionsModel +from .openai_responses import OpenAIResponsesModel + +DEFAULT_MODEL: str = "gpt-4" + +_http_client: httpx.AsyncClient | None = None + + +def shared_http_client() -> httpx.AsyncClient: + global _http_client + if _http_client is None: + _http_client = httpx.AsyncClient() + return _http_client + + +class LiteLLMProvider(ModelProvider): + """A provider that connects to a LiteLLM API server. + + Environment Variables: + LITELLM_API_BASE: The base URL of the LiteLLM API server (e.g. "http://localhost:8000") + LITELLM_API_KEY: The API key for authentication with the LiteLLM server + LITELLM_MODEL: The default model to use (optional, defaults to gpt-4) + + Model-specific keys (examples): + OPENAI_API_KEY: OpenAI API key + ANTHROPIC_API_KEY: Anthropic API key + AZURE_API_KEY: Azure OpenAI API key + AZURE_API_BASE: Azure OpenAI API base URL + AZURE_API_VERSION: Azure OpenAI API version + AWS_ACCESS_KEY_ID: AWS access key for Bedrock + AWS_SECRET_ACCESS_KEY: AWS secret key for Bedrock + AWS_REGION_NAME: AWS region for Bedrock + + See LiteLLM documentation for all supported environment variables: + https://docs.litellm.ai/docs/proxy/environment_variables + """ + + def __init__( + self, + *, + api_key: str | None = None, + base_url: str | None = None, + model_name: str | None = None, + use_responses: bool | None = None, + extra_headers: Dict[str, str] | None = None, + drop_params: bool = False, # Whether to drop unsupported params for specific models + ) -> None: + # Get configuration from environment variables with fallbacks + self._api_key = api_key or os.getenv("LITELLM_API_KEY") + if not self._api_key: + raise ValueError("LITELLM_API_KEY environment variable or api_key parameter must be set") + + self._base_url = base_url or os.getenv("LITELLM_API_BASE") + if not self._base_url: + raise ValueError("LITELLM_API_BASE environment variable or base_url parameter must be set") + + self._model_name = model_name or os.getenv("LITELLM_MODEL", DEFAULT_MODEL) + self._drop_params = drop_params + + # Collect all environment variables that start with known prefixes + # These will be passed through headers to the LiteLLM proxy + self._env_headers = { + "Content-Type": "application/json", # Always set content type + } + + # Add LiteLLM-specific headers + if self._drop_params: + self._env_headers["litellm-drop-params"] = "true" + + env_prefixes = [ + "OPENAI_", "ANTHROPIC_", "AZURE_", "AWS_", "COHERE_", + "REPLICATE_", "HUGGINGFACE_", "TOGETHERAI_", "VERTEX_", + "PALM_", "CLAUDE_", "GEMINI_", "MISTRAL_", "GROQ_" + ] + + for key, value in os.environ.items(): + if any(key.startswith(prefix) for prefix in env_prefixes): + # Convert environment variables to headers + header_key = f"x-{key.lower().replace('_', '-')}" + self._env_headers[header_key] = value + + # Merge any extra headers provided + if extra_headers: + self._env_headers.update(extra_headers) + + # Create an OpenAI client configured to use the LiteLLM API server + # LiteLLM server provides an OpenAI-compatible API + self._openai_client = AsyncOpenAI( + api_key=self._api_key, + base_url=self._base_url, + http_client=shared_http_client(), + default_headers=self._env_headers # Pass all provider keys as headers + ) + + if use_responses is not None: + self._use_responses = use_responses + else: + self._use_responses = _openai_shared.get_use_responses_by_default() + + def get_model(self, model_name: str | None) -> Model: + """Get a model by name. + + Args: + model_name: The name of the model to get. If None, uses the default model + configured through LITELLM_MODEL or the constructor. + + For OpenAI-compatible endpoints, prefix with 'openai/' + For completion endpoints, prefix with 'text-completion-openai/' + + Returns: + The model implementation, either using OpenAI Responses or Chat Completions format. + The response format is identical to OpenAI's API as LiteLLM provides full compatibility. + """ + if model_name is None: + model_name = self._model_name + + # If model doesn't have a provider prefix, add the appropriate one + if not any(model_name.startswith(prefix) for prefix in [ + "openai/", "anthropic/", "azure/", "aws/", "cohere/", + "replicate/", "huggingface/", "together/", "vertex_ai/", + "palm/", "claude/", "gemini/", "mistral/", "groq/" + ]): + # Map model names to their providers + provider_prefixes = { + "gpt-": "openai/", + "claude-": "anthropic/", + "mistral-": "mistral/", + "gemini-": "gemini/", + "j2-": "aws/", + "command-": "cohere/", + "llama-": "replicate/", + "palm-": "palm/", + "groq-": "groq/", + } + + # Find the matching provider prefix + prefix = next( + (prefix for name_prefix, prefix in provider_prefixes.items() + if model_name.startswith(name_prefix)), + "openai/" # Default to OpenAI if no match + ) + model_name = f"{prefix}{model_name}" + + # LiteLLM server provides an OpenAI-compatible API, so we can reuse the OpenAI models + return ( + OpenAIResponsesModel(model=model_name, openai_client=self._openai_client) + if self._use_responses + else OpenAIChatCompletionsModel(model=model_name, openai_client=self._openai_client) + ) + + async def __aenter__(self) -> LiteLLMProvider: + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: + await self._openai_client.close() \ No newline at end of file diff --git a/tests/test_litellm_provider.py b/tests/test_litellm_provider.py new file mode 100644 index 00000000..93e86ad1 --- /dev/null +++ b/tests/test_litellm_provider.py @@ -0,0 +1,220 @@ +from __future__ import annotations + +import os +import pytest +from openai import AsyncOpenAI, OpenAIError +from openai.types.chat.chat_completion import ChatCompletion, Choice +from openai.types.chat.chat_completion_message import ChatCompletionMessage +from openai.types.completion_usage import CompletionUsage + +from agents import ModelSettings, ModelTracing +from agents.models.litellm_provider import LiteLLMProvider +from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel +from agents.models.openai_responses import OpenAIResponsesModel + + +def test_no_api_key_errors(monkeypatch): + """Test that provider raises error when no API key is provided.""" + monkeypatch.delenv("LITELLM_API_KEY", raising=False) + with pytest.raises(ValueError, match="LITELLM_API_KEY.*must be set"): + LiteLLMProvider() + + +def test_no_base_url_errors(monkeypatch): + """Test that provider raises error when no base URL is provided.""" + monkeypatch.delenv("LITELLM_API_BASE", raising=False) + with pytest.raises(ValueError, match="LITELLM_API_BASE.*must be set"): + LiteLLMProvider(api_key="test-key") + + +def test_provider_initialization(): + """Test that provider initializes correctly with proper configuration.""" + provider = LiteLLMProvider( + api_key="test-key", + base_url="http://localhost:8000", + model_name="gpt-4" + ) + + assert provider._api_key == "test-key" + assert provider._base_url == "http://localhost:8000" + assert provider._model_name == "gpt-4" + assert isinstance(provider._openai_client, AsyncOpenAI) + + +def test_environment_variables(monkeypatch): + """Test that provider correctly reads from environment variables.""" + monkeypatch.setenv("LITELLM_API_KEY", "env-key") + monkeypatch.setenv("LITELLM_API_BASE", "http://env-url") + monkeypatch.setenv("LITELLM_MODEL", "env-model") + monkeypatch.setenv("OPENAI_API_KEY", "openai-key") + monkeypatch.setenv("ANTHROPIC_API_KEY", "anthropic-key") + + provider = LiteLLMProvider() + + assert provider._api_key == "env-key" + assert provider._base_url == "http://env-url" + assert provider._model_name == "env-model" + + # Check that provider keys are passed as headers + assert provider._env_headers["x-openai-api-key"] == "openai-key" + assert provider._env_headers["x-anthropic-api-key"] == "anthropic-key" + + +def test_model_name_routing(): + """Test that provider correctly handles model name routing.""" + provider = LiteLLMProvider( + api_key="test-key", + base_url="http://localhost:8000" + ) + + # Test default model + model = provider.get_model(None) + assert isinstance(model, (OpenAIResponsesModel, OpenAIChatCompletionsModel)) + + # Test OpenAI model (should not add prefix) + model = provider.get_model("gpt-4") + assert isinstance(model, (OpenAIResponsesModel, OpenAIChatCompletionsModel)) + assert model.model == "openai/gpt-4" + + # Test Anthropic model + model = provider.get_model("claude-3") + assert isinstance(model, (OpenAIResponsesModel, OpenAIChatCompletionsModel)) + assert model.model == "anthropic/claude-3" + + # Test model with existing prefix + model = provider.get_model("anthropic/claude-3") + assert model.model == "anthropic/claude-3" + + +@pytest.mark.allow_call_model_methods +@pytest.mark.asyncio +async def test_chat_completion_response(monkeypatch): + """Test that provider correctly handles chat completion responses.""" + msg = ChatCompletionMessage(role="assistant", content="Hello from LiteLLM!") + choice = Choice(index=0, finish_reason="stop", message=msg) + chat = ChatCompletion( + id="test-id", + created=0, + model="gpt-4", + object="chat.completion", + choices=[choice], + usage=CompletionUsage(completion_tokens=5, prompt_tokens=7, total_tokens=12), + ) + + async def mock_create(*args, **kwargs): + return chat + + # Create a mock OpenAI client + class MockCompletions: + create = mock_create + + class MockChat: + def __init__(self): + self.completions = MockCompletions() + + class MockClient: + def __init__(self): + self.chat = MockChat() + self.base_url = "http://localhost:8000" # Add base_url for tracing + + provider = LiteLLMProvider( + api_key="test-key", + base_url="http://localhost:8000", + use_responses=False + ) + provider._openai_client = MockClient() + + model = provider.get_model("gpt-4") + response = await model.get_response( + system_instructions=None, + input="test input", + model_settings=ModelSettings(), + tools=[], + output_schema=None, + handoffs=[], + tracing=ModelTracing.DISABLED, + ) + + assert response.output[0].content[0].text == "Hello from LiteLLM!" + assert response.usage.input_tokens == 7 + assert response.usage.output_tokens == 5 + assert response.usage.total_tokens == 12 + + +@pytest.mark.asyncio +async def test_provider_cleanup(): + """Test that provider properly cleans up resources.""" + provider = LiteLLMProvider( + api_key="test-key", + base_url="http://localhost:8000" + ) + + # Track if close was called + close_called = False + + async def mock_close(): + nonlocal close_called + close_called = True + + provider._openai_client.close = mock_close + + async with provider: + pass + + assert close_called, "Provider should call close on exit" + + +@pytest.mark.asyncio +async def test_litellm_provider_with_run_config(): + """Test that LiteLLMProvider works correctly with RunConfig.""" + from agents import Agent, RunConfig, Runner + from .fake_model import FakeModel + from .test_responses import get_text_message + + # Create a mock LiteLLMProvider that returns a FakeModel + class MockLiteLLMProvider(LiteLLMProvider): + def __init__(self): + # Skip the actual initialization to avoid API calls + self.model_requested = None + self.fake_model = FakeModel(initial_output=[get_text_message("Hello from LiteLLM via RunConfig!")]) + + def get_model(self, model_name: str | None) -> FakeModel: + self.model_requested = model_name + return self.fake_model + + # Create the mock provider + provider = MockLiteLLMProvider() + + # Create an agent with a model name + agent = Agent( + name="Test Agent", + instructions="You are a test agent.", + model="claude-3" # This should be passed to the provider + ) + + # Create a run configuration with the provider + run_config = RunConfig(model_provider=provider) + + # Run the agent + result = await Runner.run(agent, input="Test input", run_config=run_config) + + # Verify that the provider was used correctly + assert provider.model_requested == "claude-3" + assert result.final_output == "Hello from LiteLLM via RunConfig!" + + # Test with model override in RunConfig + run_config_with_override = RunConfig( + model="gpt-4", # This should override the agent's model + model_provider=provider + ) + + # Reset the provider's state + provider.model_requested = None + provider.fake_model = FakeModel(initial_output=[get_text_message("Hello from LiteLLM via RunConfig!")]) + + # Run the agent with the override + result = await Runner.run(agent, input="Test input", run_config=run_config_with_override) + + # Verify that the override was used + assert provider.model_requested == "gpt-4" + assert result.final_output == "Hello from LiteLLM via RunConfig!" \ No newline at end of file