From ef2291a84fa3e899c466d6bf517009feb95e8654 Mon Sep 17 00:00:00 2001 From: Sarmad Qadri Date: Fri, 19 Sep 2025 11:32:45 -0400 Subject: [PATCH 1/2] Fix router constructor and generally how params are set --- examples/basic/agent_factory/requirements.txt | 6 +++ .../mcp_basic_google_agent/README.md | 2 +- src/mcp_agent/config.py | 7 +++ .../artificial_analysis_llm_benchmarks.json | 18 +++---- src/mcp_agent/workflows/factory.py | 54 +++++++++++++++++-- src/mcp_agent/workflows/llm/augmented_llm.py | 6 +++ .../workflows/llm/augmented_llm_anthropic.py | 4 ++ .../workflows/llm/augmented_llm_azure.py | 4 ++ .../workflows/llm/augmented_llm_bedrock.py | 4 ++ .../workflows/llm/augmented_llm_google.py | 8 ++- .../workflows/llm/augmented_llm_ollama.py | 5 ++ .../workflows/llm/augmented_llm_openai.py | 4 ++ 12 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 examples/basic/agent_factory/requirements.txt diff --git a/examples/basic/agent_factory/requirements.txt b/examples/basic/agent_factory/requirements.txt new file mode 100644 index 000000000..07907e9bb --- /dev/null +++ b/examples/basic/agent_factory/requirements.txt @@ -0,0 +1,6 @@ +# Core framework dependency +mcp-agent @ file://../../../ # Link to the local mcp-agent project root + +# Additional dependencies specific to this example +anthropic +openai \ No newline at end of file diff --git a/examples/model_providers/mcp_basic_google_agent/README.md b/examples/model_providers/mcp_basic_google_agent/README.md index 3f1f51fe3..3f09aa201 100644 --- a/examples/model_providers/mcp_basic_google_agent/README.md +++ b/examples/model_providers/mcp_basic_google_agent/README.md @@ -42,7 +42,7 @@ Before running the agent, ensure you have your Gemini Developer API or Vertex AI - `vertexai`: Boolean flag to enable VertexAI integration (default: false) - `project`: Google Cloud project ID (required if using VertexAI) - `location`: Google Cloud location (required if using VertexAI) -- `default_model`: Defaults to "gemini-2.0-flash" but can be customized in your config +- `default_model`: Defaults to "gemini-2.5-flash" but can be customized in your config You can provide these in one of the following ways: diff --git a/src/mcp_agent/config.py b/src/mcp_agent/config.py index 82e1f007e..638698b4b 100644 --- a/src/mcp_agent/config.py +++ b/src/mcp_agent/config.py @@ -341,6 +341,13 @@ class GoogleSettings(BaseSettings, VertexAIMixin): ), ) + default_model: str | None = Field( + default=None, + validation_alias=AliasChoices( + "default_model", "GOOGLE_DEFAULT_MODEL", "google__default_model" + ), + ) + model_config = SettingsConfigDict( env_prefix="GOOGLE_", extra="allow", diff --git a/src/mcp_agent/data/artificial_analysis_llm_benchmarks.json b/src/mcp_agent/data/artificial_analysis_llm_benchmarks.json index 4ac0cfac0..87b0bf00c 100644 --- a/src/mcp_agent/data/artificial_analysis_llm_benchmarks.json +++ b/src/mcp_agent/data/artificial_analysis_llm_benchmarks.json @@ -8350,7 +8350,7 @@ } }, { - "name": "gemini-2-5-pro", + "name": "gemini-2.5-pro", "description": "Gemini 2.5 Pro (AI_Studio)", "provider": "Google (AI_Studio)", "context_window": 1000000, @@ -8375,7 +8375,7 @@ } }, { - "name": "gemini-2-5-pro", + "name": "gemini-2.5-pro", "description": "Gemini 2.5 Pro Vertex", "provider": "Google Vertex", "context_window": 1000000, @@ -9025,7 +9025,7 @@ } }, { - "name": "gemini-2-5-flash-reasoning", + "name": "gemini-2.5-flash-reasoning", "description": "Gemini 2.5 Flash (Reasoning) (AI_Studio)", "provider": "Google (AI_Studio)", "context_window": 1000000, @@ -9050,7 +9050,7 @@ } }, { - "name": "gemini-2-5-flash-reasoning", + "name": "gemini-2.5-flash-reasoning", "description": "Gemini 2.5 Flash (Reasoning) (Vertex)", "provider": "Google (Vertex)", "context_window": 1000000, @@ -9675,7 +9675,7 @@ } }, { - "name": "gemini-2-5-flash", + "name": "gemini-2.5-flash", "description": "Gemini 2.5 Flash (AI_Studio)", "provider": "Google (AI_Studio)", "context_window": 1000000, @@ -9700,7 +9700,7 @@ } }, { - "name": "gemini-2-5-flash", + "name": "gemini-2.5-flash", "description": "Gemini 2.5 Flash (Vertex)", "provider": "Google (Vertex)", "context_window": 1000000, @@ -12375,7 +12375,7 @@ } }, { - "name": "gemini-2-5-flash-lite", + "name": "gemini-2.5-flash-lite", "description": "Gemini 2.5 Flash-Lite (AI Studio)", "provider": "Google (AI Studio)", "context_window": 1000000, @@ -12400,7 +12400,7 @@ } }, { - "name": "gemini-2-5-flash-lite-reasoning", + "name": "gemini-2.5-flash-lite-reasoning", "description": "Gemini 2.5 Flash-Lite (Reasoning) (AI\n Studio)", "provider": "Google (AI Studio)", "context_window": 1000000, @@ -13149,4 +13149,4 @@ } } } -] \ No newline at end of file +] diff --git a/src/mcp_agent/workflows/factory.py b/src/mcp_agent/workflows/factory.py index df22718db..28dd3dd51 100644 --- a/src/mcp_agent/workflows/factory.py +++ b/src/mcp_agent/workflows/factory.py @@ -123,7 +123,7 @@ async def create_router_llm( functions: List[Callable] | None = None, routing_instruction: str | None = None, name: str | None = None, - provider: SupportedRoutingProviders = "openai", + provider: SupportedLLMProviders = "openai", model: str | ModelPreferences | None = None, request_params: RequestParams | None = None, context: Context | None = None, @@ -185,8 +185,22 @@ async def create_router_llm( **kwargs, ) else: - raise ValueError( - f"Unsupported routing provider: {provider}. Currently supported providers are: ['openai', 'anthropic']. To request support, please create an issue at https://github.com/lastmile-ai/mcp-agent/issues" + factory = _llm_factory( + provider=provider, + model=model, + request_params=request_params, + context=context, + ) + + return await LLMRouter.create( + name=name, + llm_factory=factory, + server_names=server_names, + agents=normalized_agents, + functions=functions, + routing_instruction=routing_instruction, + context=context, + **kwargs, ) @@ -974,9 +988,20 @@ def _llm_factory( request_params: RequestParams | None = None, context: Context | None = None, ) -> Callable[[Agent], AugmentedLLM]: + # Allow model to come from an explicit string, request_params.model, + # or request_params.modelPreferences (to run selection) in that order. + # Compute the chosen model by precedence: + # 1) explicit model_name from _select_provider_and_model (includes ModelPreferences) + # 2) provider default from provider_cls.get_provider_config(context) + # 3) provider hardcoded fallback + model_selector_input = ( + model + or getattr(request_params, "model", None) + or getattr(request_params, "modelPreferences", None) + ) prov, model_name = _select_provider_and_model( provider=provider, - model=model or getattr(request_params, "model", None), + model=model_selector_input, context=context, ) provider_cls = _get_provider_class(prov) @@ -990,9 +1015,28 @@ def _default_params() -> RequestParams | None: return RequestParams(modelPreferences=model) return None + # Merge provider-selected or configured default model into RequestParams if missing. + effective_params: RequestParams | None = request_params + if effective_params is not None: + chosen_model: str | None = model_name + + if not chosen_model: + cfg_obj = None + try: + cfg_obj = provider_cls.get_provider_config(context) + except Exception: + cfg_obj = None + if cfg_obj is not None: + chosen_model = getattr(cfg_obj, "default_model", None) + + # If the user did not specify a model in RequestParams, but provided other + # overrides (maxTokens, temperature, etc.), fill in the model only. + if getattr(effective_params, "model", None) is None and chosen_model: + effective_params.model = chosen_model + return lambda agent: provider_cls( agent=agent, - default_request_params=request_params or _default_params(), + default_request_params=effective_params or _default_params(), context=context, ) diff --git a/src/mcp_agent/workflows/llm/augmented_llm.py b/src/mcp_agent/workflows/llm/augmented_llm.py index 9b775724e..f5472aaaa 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm.py +++ b/src/mcp_agent/workflows/llm/augmented_llm.py @@ -354,6 +354,12 @@ async def generate_structured( ) -> ModelT: """Request a structured LLM generation and return the result as a Pydantic model.""" + # Provider configuration access + @classmethod + @abstractmethod + def get_provider_config(cls, context: Optional["Context"]): + """Return the provider-specific settings object from the app context, or None.""" + async def select_model( self, request_params: RequestParams | None = None ) -> str | None: diff --git a/src/mcp_agent/workflows/llm/augmented_llm_anthropic.py b/src/mcp_agent/workflows/llm/augmented_llm_anthropic.py index 71bedac80..d6522af5a 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm_anthropic.py +++ b/src/mcp_agent/workflows/llm/augmented_llm_anthropic.py @@ -149,6 +149,10 @@ def __init__(self, *args, **kwargs): use_history=True, ) + @classmethod + def get_provider_config(cls, context): + return getattr(getattr(context, "config", None), "anthropic", None) + @track_tokens() async def generate( self, diff --git a/src/mcp_agent/workflows/llm/augmented_llm_azure.py b/src/mcp_agent/workflows/llm/augmented_llm_azure.py index 6c39ad168..63e70e980 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm_azure.py +++ b/src/mcp_agent/workflows/llm/augmented_llm_azure.py @@ -128,6 +128,10 @@ def __init__(self, *args, **kwargs): use_history=True, ) + @classmethod + def get_provider_config(cls, context): + return getattr(getattr(context, "config", None), "azure", None) + @track_tokens() async def generate(self, message, request_params: RequestParams | None = None): """ diff --git a/src/mcp_agent/workflows/llm/augmented_llm_bedrock.py b/src/mcp_agent/workflows/llm/augmented_llm_bedrock.py index a3b385b9e..a9189b3a4 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm_bedrock.py +++ b/src/mcp_agent/workflows/llm/augmented_llm_bedrock.py @@ -91,6 +91,10 @@ def __init__(self, *args, **kwargs): use_history=True, ) + @classmethod + def get_provider_config(cls, context): + return getattr(getattr(context, "config", None), "bedrock", None) + @track_tokens() async def generate(self, message, request_params: RequestParams | None = None): """ diff --git a/src/mcp_agent/workflows/llm/augmented_llm_google.py b/src/mcp_agent/workflows/llm/augmented_llm_google.py index 10d516a29..cd238d495 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm_google.py +++ b/src/mcp_agent/workflows/llm/augmented_llm_google.py @@ -57,7 +57,7 @@ def __init__(self, *args, **kwargs): intelligencePriority=0.3, ) # Get default model from config if available - default_model = "gemini-2.0-flash" # Fallback default + default_model = "gemini-2.5-flash" # Fallback default if self.context.config.google: if hasattr(self.context.config.google, "default_model"): @@ -238,6 +238,10 @@ async def generate_str( return response.text or "" + @classmethod + def get_provider_config(cls, context): + return getattr(getattr(context, "config", None), "google", None) + async def generate_structured( self, message, @@ -250,7 +254,7 @@ async def generate_structured( import json params = self.get_request_params(request_params) - model = await self.select_model(params) or (params.model or "gemini-2.0-flash") + model = await self.select_model(params) or (params.model or "gemini-2.5-flash") # Convert input messages and build config messages = GoogleConverter.convert_mixed_messages_to_google(message) diff --git a/src/mcp_agent/workflows/llm/augmented_llm_ollama.py b/src/mcp_agent/workflows/llm/augmented_llm_ollama.py index df0dd289d..e8d608ef5 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm_ollama.py +++ b/src/mcp_agent/workflows/llm/augmented_llm_ollama.py @@ -33,6 +33,11 @@ def __init__(self, *args, **kwargs): self.provider = "Ollama" + @classmethod + def get_provider_config(cls, context): + # Uses the OpenAI-compatible config (base_url, api_key) for Ollama + return getattr(getattr(context, "config", None), "openai", None) + async def generate_structured( self, message, diff --git a/src/mcp_agent/workflows/llm/augmented_llm_openai.py b/src/mcp_agent/workflows/llm/augmented_llm_openai.py index be9f99fac..e3193c08e 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm_openai.py +++ b/src/mcp_agent/workflows/llm/augmented_llm_openai.py @@ -134,6 +134,10 @@ def __init__(self, *args, **kwargs): use_history=True, ) + @classmethod + def get_provider_config(cls, context): + return getattr(getattr(context, "config", None), "openai", None) + @classmethod def convert_message_to_message_param( cls, message: ChatCompletionMessage, **kwargs From d17600ea6d1fdc74e13410e0c145442c496106f7 Mon Sep 17 00:00:00 2001 From: Sarmad Qadri Date: Fri, 19 Sep 2025 16:43:13 -0400 Subject: [PATCH 2/2] Add gemini example to agent_factory --- examples/basic/agent_factory/mcp_agent.config.yaml | 5 ++++- src/mcp_agent/workflows/llm/augmented_llm.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/basic/agent_factory/mcp_agent.config.yaml b/examples/basic/agent_factory/mcp_agent.config.yaml index 38e0ccad8..369b6b0c3 100644 --- a/examples/basic/agent_factory/mcp_agent.config.yaml +++ b/examples/basic/agent_factory/mcp_agent.config.yaml @@ -4,7 +4,7 @@ execution_engine: asyncio logger: type: console - level: info + level: debug mcp: servers: @@ -19,6 +19,9 @@ openai: # API keys and secrets go in mcp_agent.secrets.yaml; this file is safe to check in. default_model: gpt-4o-mini +google: + default_model: gemini-2_5-pro + agents: enabled: true # Search paths are evaluated in order of precedence: earlier entries have higher precedence diff --git a/src/mcp_agent/workflows/llm/augmented_llm.py b/src/mcp_agent/workflows/llm/augmented_llm.py index f5472aaaa..35f8eec7d 100644 --- a/src/mcp_agent/workflows/llm/augmented_llm.py +++ b/src/mcp_agent/workflows/llm/augmented_llm.py @@ -356,9 +356,9 @@ async def generate_structured( # Provider configuration access @classmethod - @abstractmethod def get_provider_config(cls, context: Optional["Context"]): """Return the provider-specific settings object from the app context, or None.""" + return None async def select_model( self, request_params: RequestParams | None = None