From 3e49cf5d443316b49e7c781230af98a3d853726e Mon Sep 17 00:00:00 2001 From: cthomas Date: Fri, 23 Jan 2026 11:15:12 -0800 Subject: [PATCH] =?UTF-8?q?fix:=20load=20default=20provider=20config=20whe?= =?UTF-8?q?n=20summarizer=20uses=20different=20prov=E2=80=A6=20(#9051)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: load default provider config when summarizer uses different provider **Problem:** Summarization failed when agent used one provider (e.g., Google AI) but summarizer config specified a different provider (e.g., Anthropic): ```python # Agent LLM config model_endpoint_type='google_ai', handle='gemini-something/gemini-2.5-pro', context_window=100000 # Summarizer config model='anthropic/claude-haiku-4-5-20251001' # Bug: Resulting summarizer_llm_config mixed Google + Anthropic settings model='claude-haiku-4-5-20251001', model_endpoint_type='google_ai', # ❌ Wrong endpoint! context_window=100000 # ❌ Google's context window, not Anthropic's default! ``` This sent Claude requests to Google AI endpoints with incorrect parameters. **Root Cause:** `_build_summarizer_llm_config()` always copied the agent's LLM config as base, then patched model/provider fields. But this kept all provider-specific settings (endpoint, context_window, etc.) from the wrong provider. **Fix:** 1. Parse provider_name from summarizer handle 2. Check if it matches agent's model_endpoint_type (or provider_name for custom) 3. **If YES** → Use agent config as base, override model/handle (same provider) 4. **If NO** → Load default config via `provider_manager.get_llm_config_from_handle()` (new provider) **Example Flow:** ```python # Agent: google_ai/gemini-2.5-pro # Summarizer: anthropic/claude-haiku provider_name = "anthropic" # Parsed from handle provider_matches = ("anthropic" == "google_ai") # False ❌ # Different provider → load default Anthropic config base = await provider_manager.get_llm_config_from_handle( handle="anthropic/claude-haiku", actor=self.actor ) # Returns: model_endpoint_type='anthropic', endpoint='https://api.anthropic.com', etc. ✅ ``` **Result:** - Summarizer with different provider gets correct default config - No more mixing Google endpoints with Anthropic models - Same-provider summarizers still inherit agent settings efficiently 👾 Generated with [Letta Code](https://letta.com) Co-authored-by: Letta --- letta/agents/letta_agent_v3.py | 47 ++++++++++++++++++++++------ tests/managers/test_agent_manager.py | 10 ++++-- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/letta/agents/letta_agent_v3.py b/letta/agents/letta_agent_v3.py index 098e21b0..b8b13fe6 100644 --- a/letta/agents/letta_agent_v3.py +++ b/letta/agents/letta_agent_v3.py @@ -1513,7 +1513,7 @@ class LettaAgentV3(LettaAgentV2): summarizer_config = CompactionSettings(model=handle) # Build the LLMConfig used for summarization - summarizer_llm_config = self._build_summarizer_llm_config( + summarizer_llm_config = await self._build_summarizer_llm_config( agent_llm_config=self.agent_state.llm_config, summarizer_config=summarizer_config, ) @@ -1641,8 +1641,8 @@ class LettaAgentV3(LettaAgentV2): return summary_message_obj, final_messages, summary - @staticmethod - def _build_summarizer_llm_config( + async def _build_summarizer_llm_config( + self, agent_llm_config: LLMConfig, summarizer_config: CompactionSettings, ) -> LLMConfig: @@ -1668,12 +1668,41 @@ class LettaAgentV3(LettaAgentV2): model_name = summarizer_config.model # Start from the agent's config and override model + provider_name + handle - # Note: model_endpoint_type is NOT overridden - the parsed provider_name - # is a custom label (e.g. "claude-pro-max"), not the endpoint type (e.g. "anthropic") - base = agent_llm_config.model_copy() - base.provider_name = provider_name - base.model = model_name - base.handle = summarizer_config.model + # Check if the summarizer's provider matches the agent's provider + # If they match, we can safely use the agent's config as a base + # If they don't match, we need to load the default config for the new provider + from letta.schemas.enums import ProviderType + + provider_matches = False + try: + # Check if provider_name is a valid ProviderType that matches agent's endpoint type + provider_type = ProviderType(provider_name) + provider_matches = provider_type.value == agent_llm_config.model_endpoint_type + except ValueError: + # provider_name is a custom label - check if it matches agent's provider_name + provider_matches = provider_name == agent_llm_config.provider_name + + if provider_matches: + # Same provider - use agent's config as base and override model/handle + base = agent_llm_config.model_copy() + base.model = model_name + base.handle = summarizer_config.model + else: + # Different provider - load default config for this handle + from letta.services.provider_manager import ProviderManager + + provider_manager = ProviderManager() + try: + base = await provider_manager.get_llm_config_from_handle( + handle=summarizer_config.model, + actor=self.actor, + ) + except Exception as e: + self.logger.warning( + f"Failed to load LLM config for summarizer handle '{summarizer_config.model}': {e}. " + f"Falling back to agent's LLM config." + ) + return agent_llm_config # If explicit model_settings are provided for the summarizer, apply # them just like server.create_agent_async does for agents. diff --git a/tests/managers/test_agent_manager.py b/tests/managers/test_agent_manager.py index fb9c134f..4a9e8598 100644 --- a/tests/managers/test_agent_manager.py +++ b/tests/managers/test_agent_manager.py @@ -406,8 +406,14 @@ async def test_compaction_settings_model_uses_separate_llm_config_for_summarizat tool_rules=None, ) - # Use the static helper on LettaAgentV3 to derive summarizer llm_config - summarizer_llm_config = LettaAgentV3._build_summarizer_llm_config( + # Create a mock agent instance to call the instance method + mock_agent = Mock(spec=LettaAgentV3) + mock_agent.actor = default_user + mock_agent.logger = Mock() + + # Use the instance method to derive summarizer llm_config + summarizer_llm_config = await LettaAgentV3._build_summarizer_llm_config( + mock_agent, agent_llm_config=agent_state.llm_config, summarizer_config=agent_state.compaction_settings, )