Ë
    tPêjM  ã                   óX   — d Z ddlZddlmZmZ ddlmZ ddlmZ ddl	m
Z
  G d„ d«      Zy)	zo
WebSearch Tool Transformation

Transforms between Anthropic/OpenAI tool_use format and LiteLLM search format.
é    N)ÚAnyÚFinal)Úverbose_logger)ÚLITELLM_WEB_SEARCH_TOOL_NAME)ÚSearchResponsec                   óè  — e Zd ZdZe	 ddedededeee	e
   f   fd„«       Zededeee	e
   f   fd„«       Zededeee	e
   f   fd„«       Zededeee	e
   f   fd	„«       Ze	 	 dde	e
   de	e   dede	e
   d
z  dee
e
e	e
   z  f   f
d„«       Ze	 dde	e
   de	e   de	e
   d
z  dee
e
f   fd„«       Zede	e
   de	e   dee
e	e
   f   fd„«       Zededed
z  de
eef   fd„«       Zededefd„«       Zy
)ÚWebSearchTransformationu   
    Transformation class for WebSearch tool interception.

    Handles transformation between:
    - Anthropic tool_use format â†’ LiteLLM search requests
    - OpenAI tool_calls format â†’ LiteLLM search requests
    - LiteLLM SearchResponse â†’ Anthropic/OpenAI tool_result format
    ÚresponseÚstreamÚresponse_formatÚreturnc                 óÊ   — |rt        j                  d«       dg fS |dk(  rt        j                  | «      S |dk(  rt        j	                  | «      S t        j                  | «      S )aò  
        Transform model response to extract WebSearch tool calls.

        Detects if response contains WebSearch tool_use/tool_calls blocks and extracts
        the search queries for execution.

        Args:
            response: Model response (dict, AnthropicMessagesResponse, or ModelResponse)
            stream: Whether response is streaming
            response_format: Response format - "anthropic" or "openai" (default: "anthropic")

        Returns:
            (has_websearch, tool_calls):
                has_websearch: True if WebSearch tool_use found
                tool_calls: List of tool_use/tool_calls dicts with id, name, input/function

        Note:
            Streaming requests are handled by converting stream=True to stream=False
            in the WebSearchInterceptionLogger.async_log_pre_api_call hook before
            the API request is made. This means by the time this method is called,
            streaming requests have already been converted to non-streaming.
        zKWebSearchInterception: Unexpected streaming response, skipping interceptionFÚopenaiÚ	responses)r   Úwarningr	   Ú_detect_from_openai_responseÚ_detect_from_responses_responseÚ#_detect_from_non_streaming_response)r
   r   r   s      ú/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/integrations/websearch_interception/transformation.pyÚtransform_requestz)WebSearchTransformation.transform_request   sg   € ñ8 ô ×"Ñ"Ð#pÔqØ˜"�9Ðð ˜hÒ&Ü*×GÑGÈÓQÐQØ Ò+Ü*×JÑJÈ8ÓTÐTä*×NÑNÈxÓXÐXó    c           
      óˆ  — t        | t        «      r| j                  dg «      }nt        | dd«      xs g }t        |t        «      sdg fS g }|D �]2  }t        |t        «      rF|j                  d«      }|j                  d«      }|j                  d«      }|j                  dd«      }n4t        |dd«      }t        |dd«      }t        |dd«      }t        |dd«      }|d	k7  s	|t
        k7  rŒ�t        |t        «      r	 |rt        j                  |«      ni }nt        |t        «      r|}ni }t        |t        «      r|nt        j                  |«      }	|j                  ||d	||	|dœ«       t        j                  d||«       �Œ5 t        |«      dkD  |fS # t        j                  $ r t        j                  d
|«       i }Y Œ–w xY w)aE  Parse a Responses API response for ``litellm_web_search`` function calls.

        After pre-request conversion the native web search tool is replaced by a
        ``litellm_web_search`` function tool, so the model emits ``function_call``
        items in ``response.output`` instead of a native ``web_search_call``.
        ÚoutputNFÚtypeÚnameÚcall_idÚ	argumentsÚ Úfunction_callzBWebSearchInterception: Failed to parse function_call arguments: %s)Úidr   r   r   r   Úinputz=WebSearchInterception: Found %s function_call with call_id=%sr   )Ú
isinstanceÚdictÚgetÚgetattrÚlistr   ÚstrÚjsonÚloadsÚJSONDecodeErrorr   r   ÚdumpsÚappendÚdebugÚlen)
r
   r   Ú
tool_callsÚitemÚ	item_typeÚ	item_namer   r   Úparsed_inputÚarguments_strs
             r   r   z7WebSearchTransformation._detect_from_responses_responseC   sÁ  € ô �h¤Ô%Ø—\‘\ (¨BÓ/‰Fä˜X x°Ó6Ò<¸"ˆFä˜&¤$Ô'Ø˜"�9Ðà(*ˆ
Øó '	vˆDÜ˜$¤Ô%Ø ŸH™H VÓ,�	Ø ŸH™H VÓ,�	ØŸ(™( 9Ó-�Ø ŸH™H [°"Ó5‘	ä# D¨&°$Ó7�	Ü# D¨&°$Ó7�	Ü! $¨	°4Ó8�Ü# D¨+°rÓ:�	à˜OÒ+¨yÔ<XÒ/XØä˜)¤SÔ)ð&Ù<E¤4§:¡:¨iÔ#8È2‘Lô ˜I¤tÔ,Ø(‘à!�ä)3°I¼sÔ)C™IÌÏÉÐT`ÓIaˆMØ×Ñà!Ø&Ø+Ø%Ø!.Ø)ñô	ô × Ñ Ð!`ÐbkÐmtÖuðO'	vôR �:‹ Ñ" JÐ.Ð.øô/ ×+Ñ+ò &Ü"×*Ñ*Ø\Ð^gôð $&’Lð	&ús   Ã;FÆ+GÇ Gc                 ó´  — t        | t        «      r| j                  dg «      }n5t        | d«      st	        j
                  d«       dg fS | j                  xs g }|st	        j
                  d«       dg fS g }|D ]Ï  }t        |t        «      rF|j                  d«      }|j                  d«      }|j                  d«      }|j                  di «      }n4t        |dd	«      }t        |dd	«      }t        |dd	«      }t        |di «      }|d
k(  sŒ“|t        dfv sŒž|d
||dœ}|j                  |«       t	        j
                  d||d   «       ŒÑ t        |«      dkD  |fS )z3Parse non-streaming response for WebSearch tool_useÚcontentz8WebSearchInterception: Response has no content attributeFz1WebSearchInterception: Response has empty contentr   r   r    r!   NÚtool_useÚ
web_search)r    r   r   r!   z3WebSearchInterception: Found %s tool_use with id=%sr   )r"   r#   r$   Úhasattrr   r-   r6   r%   r   r,   r.   )	r
   r6   r/   ÚblockÚ
block_typeÚ
block_nameÚblock_idÚblock_inputÚ	tool_calls	            r   r   z;WebSearchTransformation._detect_from_non_streaming_response�   ss  € ô �h¤Ô%Ø—l‘l 9¨bÓ1‰Gä˜8 YÔ/Ü×$Ñ$Ð%_Ô`Ø˜b�yÐ Ø×&Ñ&Ò,¨"ˆGáÜ× Ñ Ð!TÔUØ˜"�9Ðð ˆ
Øò 	yˆEä˜%¤Ô&Ø"ŸY™Y vÓ.�
Ø"ŸY™Y vÓ.�
Ø Ÿ9™9 T›?�Ø#Ÿi™i¨°Ó4‘ä$ U¨F°DÓ9�
Ü$ U¨F°DÓ9�
Ü" 5¨$°Ó5�Ü% e¨W°bÓ9�ð ˜ZÓ'¨JÜ,Øð;ò -ð #Ø&Ø&Ø(ñ	�	ð ×!Ñ! )Ô,Ü×$Ñ$Ð%ZÐ\fÐhqÐrvÑhwÕxð?	yôB �:‹ Ñ" JÐ.Ð.r   c                 ót  — t        | t        «      r| j                  dg «      }n5t        | d«      st	        j
                  d«       dg fS | j                  xs g }|st	        j
                  d«       dg fS |d   }t        |t        «      r|j                  di «      }nt        |dd«      }|st	        j
                  d«       dg fS t        |t        «      r|j                  d	g «      }nt        |d	d«      xs g }|st	        j
                  d
«       dg fS g }|D �]^  }t        |t        «      r‘|j                  d«      }|j                  d«      }|j                  di «      }	t        |	t        «      r|	j                  d«      nt        |	dd«      }
t        |	t        «      r|	j                  d«      nt        |	dd«      }nIt        |dd«      }t        |dd«      }t        |dd«      }	|	rt        |	dd«      nd}
|	rt        |	dd«      nd}|dk(  sŒô|
t        dfv sŒÿt        |t        «      r	 t        j                  |«      }n|xs i }|d|
|
|dœ|dœ}|j                  |«       t	        j
                  d|
|«       �Œa t        |«      dkD  |fS # t        j                  $ r t	        j                  d|«       i }Y Œsw xY w)z4Parse OpenAI-style response for WebSearch tool_callsÚchoicesz8WebSearchInterception: Response has no choices attributeFz1WebSearchInterception: Response has empty choicesr   ÚmessageNz2WebSearchInterception: First choice has no messager/   z0WebSearchInterception: Message has no tool_callsr    r   Úfunctionr   r   r8   z=WebSearchInterception: Failed to parse function arguments: %s©r   r   )r    r   r   rC   r!   z4WebSearchInterception: Found %s tool_call with id=%s)r"   r#   r$   r9   r   r-   rA   r%   r   r'   r(   r)   r*   r   r,   r.   )r
   rA   Úfirst_choicerB   Úopenai_tool_callsr/   r?   Útool_idÚ	tool_typerC   Úfunction_nameÚfunction_argumentsr   Útool_call_dicts                 r   r   z4WebSearchTransformation._detect_from_openai_response¹   sÕ  € ô �h¤Ô%Ø—l‘l 9¨bÓ1‰Gä˜8 YÔ/Ü×$Ñ$Ð%_Ô`Ø˜b�yÐ Ø×&Ñ&Ò,¨"ˆGáÜ× Ñ Ð!TÔUØ˜"�9Ðð & a™jˆÜ�l¤DÔ)Ø"×&Ñ& y°"Ó5‰Gä˜l¨I°tÓ<ˆGáÜ× Ñ Ð!UÔVØ˜"�9Ðô �gœtÔ$Ø '§¡¨L¸"Ó =Ñä '¨°¸tÓ DÒ JÈÐá Ü× Ñ Ð!SÔTØ˜"�9Ðð ˆ
Ø*ó 1	uˆIä˜)¤TÔ*Ø#Ÿ-™-¨Ó-�Ø%ŸM™M¨&Ó1�	Ø$Ÿ=™=¨°RÓ8�Ü8BÀ8ÌTÔ8R §¡¨VÔ 4ÔX_Ð`hÐjpÐrvÓXw�ä1;¸HÄdÔ1K�H—L‘L Ô-ÔQXÐYaÐcnÐptÓQuñ #ô " )¨T°4Ó8�Ü# I¨v°tÓ<�	Ü" 9¨j¸$Ó?�ÙCK¤¨°&¸$Ô ?ÐQU�ÙMU¤W¨X°{ÀDÔ%IÐ[_Ð"ð ˜JÓ&¨=Ü,Øð=ò ,ô
 Ð0´#Ô6ð'Ü$(§J¡JÐ/AÓ$B™	ð !3Ò 8°b�Ið "Ø&Ø)à -Ø%.ñ!ð 'ñ	"�ð ×!Ñ! .Ô1Ü×$Ñ$Ð%[Ð]jÐlsÖtðc1	uôf �:‹ Ñ" JÐ.Ð.øô-  ×/Ñ/ò 'Ü&×.Ñ.Ø[Ð]oôð %'š	ð	'ús   È'J	Ê	+J7Ê6J7Nr/   Úsearch_resultsÚthinking_blocksc                 óh   — |dk(  rt         j                  | |«      S t         j                  | ||¬«      S )aö  
        Transform LiteLLM search results to Anthropic/OpenAI tool_result format.

        Builds the assistant and user/tool messages needed for the agentic loop
        follow-up request.

        Args:
            tool_calls: List of tool_use/tool_calls dicts from transform_request
            search_results: List of search result strings (one per tool_call)
            response_format: Response format - "anthropic" or "openai" (default: "anthropic")
            thinking_blocks: Optional list of thinking/redacted_thinking blocks
                from the model's response. When present, prepended to the
                assistant message content (required by Anthropic API when
                thinking is enabled).

        Returns:
            (assistant_message, user_or_tool_messages):
                For Anthropic: assistant_message with tool_use blocks, user_message with tool_result blocks
                For OpenAI: assistant_message with tool_calls, tool_messages list with tool results
        r   )rM   )r	   Ú_transform_response_openaiÚ_transform_response_anthropic)r/   rL   r   rM   s       r   Útransform_responsez*WebSearchTransformation.transform_response  s@   € ð6 ˜hÒ&Ü*×EÑEÀjÐR`ÓaÐaä*×HÑHØ˜N¸Oð Ió ð r   c           
      ó  — g }|r|j                  |«       |j                  | D �cg c]  }d|d   |d   |d   dœ‘Œ c}«       d|dœ}dt        t        | «      «      D �cg c]  }d	| |   d   ||   d
œ‘Œ c}dœ}||fS c c}w c c}w )zKTransform to Anthropic format (single user message with tool_result blocks)r7   r    r   r!   )r   r    r   r!   Ú	assistant)Úroler6   ÚuserÚtool_result©r   Útool_use_idr6   )ÚextendÚranger.   )r/   rL   rM   Úassistant_contentÚtcÚassistant_messageÚiÚuser_messages           r   rP   z5WebSearchTransformation._transform_response_anthropic:  sÐ   € ð 02Ðñ Ø×$Ñ$ _Ô5ð 	× Ñ ð %öð ð 'Ø˜T™(Ø˜v™JØ ™[ó	òô
	
ð  Ø(ñ$
Ðð ô œs :›Ó/öð ð	 *Ø#-¨a¡=°Ñ#6Ø-¨aÑ0óòñ

ˆð ! ,Ð.Ð.ùò;ùò&s   ¥A>ÁBc                 ó(  — d| D �cg c]H  }|d   d|d   t        |d   t        «      rt        j                  |d   «      nt	        |d   «      dœdœ‘ŒJ c}dœ}t        t        | «      «      D �cg c]  }d	| |   d   ||   d
œ‘Œ }}||fS c c}w c c}w )zNTransform to OpenAI format (assistant with tool_calls, separate tool messages)rS   r    rC   r   r!   rD   )r    r   rC   )rT   r/   Útool)rT   Útool_call_idr6   )r"   r#   r(   r+   r'   rZ   r.   )r/   rL   r\   r]   r^   Útool_messagess         r   rO   z2WebSearchTransformation._transform_response_openail  sÈ   € ð  ð %ö
ð ð ˜T™(Ø&à " 6¡
ÜAKÈBÈwÉKÔY]ÔA^¤d§j¡j°°G±Ô&=ÔdgÐhjÐkrÑhsÓdtñ!óò
ñ$
Ðô, œ3˜z›?Ó+ö 
ð ð	 Ø *¨1¡¨dÑ 3Ø)¨!Ñ,óð 
ˆð  
ð ! -Ð/Ð/ùò/
ùò 
s   †AB
Á.BrX   Úsearch_responsec                 ó  — g }|�}t        |dd«      xs g }|D ]g  }t        |dd«      xs d}t        |dd«      xs d}t        |dd«      xs t        |dd«      }|j                  d|||dt        |d	d«      xs dd
œ«       Œi d| |dœS )u.  
        Build an Anthropic-native ``web_search_tool_result`` content block.

        Native Anthropic clients (Claude Desktop, the Anthropic SDK, the
        Anthropic Console) expect search-tool results to be returned as
        structured ``web_search_tool_result`` blocks so that citations and
        source links can be rendered. The agentic loop currently feeds the
        model a flat text blob in the follow-up call (which is correct â€” the
        model needs readable evidence). This helper produces the *additional*
        block that should accompany the model's text reply when the original
        request used a native ``web_search_*`` tool.

        The spec'd shape carries page text only in ``encrypted_content``, an
        opaque server-issued blob that we cannot mint. Emitting the four spec
        fields alone would drop the snippet entirely, leaving the client (and
        the model, on any replayed follow-up turn) with URLs and titles but no
        evidence to answer from, forcing a fetch per result. So the snippet is
        carried in an additive ``snippet`` key alongside the spec fields.
        ``encrypted_content`` stays empty rather than holding plaintext, which
        would assert encryption semantics that do not hold.

        Spec reference:
        https://docs.anthropic.com/en/api/web-search-tool

        Args:
            tool_use_id: The ``tool_use_id`` the model emitted on the first
                turn. Must match exactly so the client can pair the result
                with its tool_use block.
            search_response: Structured ``SearchResponse`` from
                ``litellm.asearch()``. If None or empty, the block is still
                emitted with an empty result list (signals "search ran, no
                results" rather than "search did not run").
        NÚresultsÚurlr   ÚtitleÚdateÚlast_updatedÚweb_search_resultÚsnippet)r   rg   rh   Úpage_ageÚencrypted_contentrl   Úweb_search_tool_resultrW   )r%   r,   )rX   rd   Úitemsrf   Úrrg   rh   rm   s           r   Ú"build_web_search_tool_result_blockz:WebSearchTransformation.build_web_search_tool_result_blockŽ  s»   € ðL .0ˆØÐ&Ü$ _°iÀÓFÒLÈ"ˆGØò �Ü˜a ¨Ó+Ò1¨r�Ü  7¨BÓ/Ò5°2�Ü" 1 f¨dÓ3ÒW´w¸qÀ.ÐRVÓ7W�Ø—‘à 3Ø"Ø!&Ø$,Ø-/Ü#*¨1¨i¸Ó#<Ò#BÀñõ	ð	ð -Ø&Øñ
ð 	
r   Úresultc                 óú   — t        | d«      r^| j                  rRdj                  | j                  D �cg c]+  }d|j                  › d|j                  › d|j
                  › �‘Œ- c}«      }|S t        | «      }|S c c}w )zç
        Format SearchResponse as text for tool_result content.

        Args:
            result: SearchResponse from litellm.asearch()

        Returns:
            Formatted text with Title, URL, Snippet for each result
        rf   z

zTitle: z
URL: z

Snippet: )r9   rf   Újoinrh   rg   rl   r'   )rs   rq   Úsearch_result_texts      r   Úformat_search_responsez.WebSearchTransformation.format_search_responseË  sv   € ô �6˜9Ô%¨&¯.ª.à!'§¡ØRX×R`ÑR`ÖaÈQ�7˜1Ÿ7™7˜) 7¨1¯5©5¨'°¸Q¿Y¹Y¸KÒHÒaó"Ðð "Ð!ô "% V£Ðà!Ð!ùò bs   ²0A8)Ú	anthropic)rx   N)N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__Ústaticmethodr   Úboolr'   Útupler&   r#   r   r   r   r   rQ   rP   rO   r   rr   rw   © r   r   r	   r	      s`  „ ñð ð  +ñ'YØð'Yàð'Yð ð'Yð 
ˆt�T˜$‘ZÐÑ	 ò	'Yó ð'YðR ð;/Øð;/à	ˆt�T˜$‘ZÐÑ	 ò;/ó ð;/ðz ð5/Øð5/à	ˆt�T˜$‘ZÐÑ	 ò5/ó ð5/ðn ð\/Øð\/à	ˆt�T˜$‘ZÐÑ	 ò\/ó ð\/ð| ð  +Ø-1ñ	Ø˜‘Jðà˜S™	ðð ðð ˜d™ dÑ*ð	ð
 
ˆt�T˜D ™JÑ&Ð&Ñ	'òó ððB ð .2ñ//Ø˜‘Jð//à˜S™	ð//ð ˜d™ dÑ*ð//ð 
ˆt�TˆzÑ	ò	//ó ð//ðb ð0Ø˜‘Jð0à˜S™	ð0ð 
ˆt�T˜$‘ZÐÑ	 ò0ó ð0ðB ð:
Øð:
à'¨$Ñ.ð:
ð 
ˆc�3ˆh‰ò:
ó ð:
ðx ð" ~ð "¸#ò "ó ñ"r   r	   )r|   r(   Útypingr   r   Úlitellm._loggingr   Úlitellm.constantsr   Ú+litellm.llms.base_llm.search.transformationr   r	   r€   r   r   ú<module>r…      s'   ðñó ß å +Ý :Ý F÷P"ò P"r   