From a58726cb93e466380cd6e9a7c025aceec01fae7e Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Fri, 16 Jan 2026 19:50:46 -0800 Subject: [PATCH] async_run_agentic_loop --- .../websearch_interception/handler.py | 16 +++++++++++++++- .../messages/handler.py | 12 ++++++++++++ litellm/llms/custom_httpx/llm_http_handler.py | 5 ++++- litellm/types/utils.py | 14 ++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/litellm/integrations/websearch_interception/handler.py b/litellm/integrations/websearch_interception/handler.py index 0d32d7cb87b..0b08bc2312a 100644 --- a/litellm/integrations/websearch_interception/handler.py +++ b/litellm/integrations/websearch_interception/handler.py @@ -120,6 +120,8 @@ class WebSearchInterceptionLogger(CustomLogger): verbose_logger.debug(f"WebSearchInterception: Response type: {type(response)}") # Check if provider should be intercepted + # Note: custom_llm_provider is already normalized by get_llm_provider() + # (e.g., "bedrock/invoke/..." -> "bedrock") if custom_llm_provider not in self.enabled_providers: verbose_logger.debug( f"WebSearchInterception: Skipping provider {custom_llm_provider} (not in enabled list: {self.enabled_providers})" @@ -183,6 +185,7 @@ class WebSearchInterceptionLogger(CustomLogger): messages=messages, tool_calls=tool_calls, anthropic_messages_optional_request_params=anthropic_messages_optional_request_params, + logging_obj=logging_obj, stream=stream, kwargs=kwargs, ) @@ -193,6 +196,7 @@ class WebSearchInterceptionLogger(CustomLogger): messages: List[Dict], tool_calls: List[Dict], anthropic_messages_optional_request_params: Dict, + logging_obj: Any, stream: bool, kwargs: Dict, ) -> Any: @@ -278,10 +282,20 @@ class WebSearchInterceptionLogger(CustomLogger): if k != 'max_tokens' } + # Get model from logging_obj.model_call_details["agentic_loop_params"] + # This preserves the full model name with provider prefix (e.g., "bedrock/invoke/...") + full_model_name = model + if logging_obj is not None: + agentic_params = logging_obj.model_call_details.get("agentic_loop_params", {}) + full_model_name = agentic_params.get("model", model) + verbose_logger.debug( + f"WebSearchInterception: Using model name: {full_model_name}" + ) + final_response = await anthropic_messages.acreate( max_tokens=max_tokens, messages=follow_up_messages, - model=model, + model=full_model_name, **optional_params_without_max_tokens, **kwargs, ) diff --git a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py index 571a2729d45..11245b1bdba 100644 --- a/litellm/llms/anthropic/experimental_pass_through/messages/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/messages/handler.py @@ -177,6 +177,10 @@ def anthropic_messages_handler( # Use provided client or create a new one litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + # Store original model name before get_llm_provider strips the provider prefix + # This is needed by agentic hooks (e.g., websearch_interception) to make follow-up requests + original_model = model + litellm_params = GenericLiteLLMParams( **kwargs, api_key=api_key, @@ -194,6 +198,14 @@ def anthropic_messages_handler( api_base=litellm_params.api_base, api_key=litellm_params.api_key, ) + + # Store agentic loop params in logging object for agentic hooks + # This provides original request context needed for follow-up calls + if litellm_logging_obj is not None: + litellm_logging_obj.model_call_details["agentic_loop_params"] = { + "model": original_model, + "custom_llm_provider": custom_llm_provider, + } if litellm_params.mock_response and isinstance(litellm_params.mock_response, str): diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index c7c1ebc7056..e591f156b26 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -4395,6 +4395,9 @@ class BaseLLMHTTPHandler: if should_run: # Second: Execute agentic loop + # Add custom_llm_provider to kwargs so the agentic loop can reconstruct the full model name + kwargs_with_provider = kwargs.copy() if kwargs else {} + kwargs_with_provider["custom_llm_provider"] = custom_llm_provider agentic_response = await callback.async_run_agentic_loop( tools=tool_calls, model=model, @@ -4404,7 +4407,7 @@ class BaseLLMHTTPHandler: anthropic_messages_optional_request_params=anthropic_messages_optional_request_params, logging_obj=logging_obj, stream=stream, - kwargs=kwargs, + kwargs=kwargs_with_provider, ) # First hook that runs agentic loop wins return agentic_response diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 8301a6da2d9..8c30e4d7e80 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -109,6 +109,20 @@ class SearchContextCostPerQuery(TypedDict, total=False): search_context_size_high: float +class AgenticLoopParams(TypedDict, total=False): + """ + Parameters passed to agentic loop hooks (e.g., WebSearch interception). + + Stored in logging_obj.model_call_details["agentic_loop_params"] to provide + agentic hooks with the original request context needed for follow-up calls. + """ + model: str + """The model string with provider prefix (e.g., 'bedrock/invoke/...')""" + + custom_llm_provider: str + """The LLM provider name (e.g., 'bedrock', 'anthropic')""" + + class ModelInfoBase(ProviderSpecificModelInfo, total=False): key: Required[str] # the key in litellm.model_cost which is returned