o
    ưiL                     @   s   d Z ddlZddlZddlmZmZmZmZmZ ddl	m
Z
 ddlmZ ddlmZ ddlmZ ddlmZmZ dd	lmZmZ G d
d deZe ZddlZeje dS )a  
Skills Injection Hook for LiteLLM Proxy

Main hook that orchestrates skill processing:
- Fetches skills from LiteLLM DB
- Injects SKILL.md content into system prompt
- Adds litellm_code_execution tool for automatic code execution
- Handles agentic loop internally when litellm_code_execution is called

For non-Anthropic models (e.g., Bedrock, OpenAI, etc.):
- Skills are converted to OpenAI-style tools
- Skill file content (SKILL.md) is extracted and injected into the system prompt
- litellm_code_execution tool is added - when model calls it, LiteLLM handles
  execution automatically and returns final response with file_ids

Usage:
    # Simple - LiteLLM handles everything automatically via proxy
    # The container parameter triggers the SkillsInjectionHook
    response = await litellm.acompletion(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Create a bouncing ball GIF"}],
        container={"skills": [{"skill_id": "litellm:skill_abc123"}]},
    )
    # Response includes file_ids for generated files
    N)AnyDictListOptionalUnion)verbose_proxy_logger)	DualCache)CustomLogger)SkillPromptInjectionHandler)LiteLLM_SkillsTableUserAPIKeyAuth)	CallTypesCallTypesLiteralc                       s  e Zd ZdZ fddZdedededede	e
eeef  f
d	d
Z	d1dedee dedefddZdedee defddZdede	e fddZdedefddZdedede	e de	e fddZdedeeeef  fddZdededeeef defdd Zd!edeeef d"ed#eeeef  def
d$d%Zd&ed'eeef deeef d"ed#eeeef  defd(d)Zdededeeef defd*d+Zd,edeeef d"ed#eeeef  def
d-d.Zded#eeeef  defd/d0Z   Z!S )2SkillsInjectionHooka  
    Pre/Post-call hook that processes skills from container.skills parameter.

    Pre-call (async_pre_call_hook):
    - Skills with 'litellm:' prefix are fetched from LiteLLM DB
    - For Anthropic models: native skills pass through, LiteLLM skills converted to tools
    - For non-Anthropic models: LiteLLM skills are converted to tools + execute_code tool
    
    Post-call (async_post_call_success_deployment_hook):
    - If response has litellm_code_execution tool call, automatically execute code
    - Continue conversation loop until model gives final response
    - Return response with generated files inline
    
    This hook is called automatically by litellm during completion calls.
    c                    sP   ddl m}m} || _t | _|d|| _|d|| _t	 j
di | d S )Nr   )DEFAULT_MAX_ITERATIONSDEFAULT_SANDBOX_TIMEOUTmax_iterationssandbox_timeout )Z+litellm.llms.litellm_proxy.skills.constantsr   r   Zoptional_paramsr
   prompt_handlergetr   r   super__init__)selfkwargsr   r   	__class__r   ^/home/app/Keep/.python/lib/python3.10/site-packages/litellm/proxy/hooks/litellm_skills/main.pyr   :   s   zSkillsInjectionHook.__init__user_api_key_dictcachedata	call_typereturnc                    s   |dvr|S | d}|rt|ts|S | d}|r!t|ts#|S tdt| d g }g }|D ]2}	t|	ts<q4|	 dd}
|
dra| |
I d	H }|rW|	| q4t
d
|
 d q4|	|	 q4|dk}t|dkry| j|||d}|S )a  
        Process skills from container.skills before the LLM call.

        1. Check if container.skills exists in request
        2. Separate skills by prefix (litellm: vs native)
        3. Fetch LiteLLM skills from database
        4. For Anthropic: keep native skills in container
        5. For non-Anthropic: convert LiteLLM skills to tools, inject content, add execute_code
        )
completionZacompletionanthropic_messages	containerskillsz SkillsInjectionHook: Processing z skillsskill_id Zlitellm_NzSkillsInjectionHook: Skill 'z' not found in LiteLLM DBr$   r   )r    litellm_skillsuse_anthropic_format)r   
isinstancedictlistr   debuglen
startswith_fetch_skill_from_dbappendwarning_process_for_messages_api)r   r   r   r    r!   r%   r&   r)   Zanthropic_skillsskillr'   Zdb_skillr*   r   r   r   async_pre_call_hookF   s@   




z'SkillsInjectionHook.async_pre_call_hookTr)   r*   c              	   C   s:  ddl m} |dg }g }i }g }|D ]6}	|| j|	 | j|	}
|
r,||
 | j|	}|rJ|||	j< |	 D ]}|
drI|| q=q|rQ||d< |r\| jj|||d}|r| }|dg |g |d< |di |d< ||d d< d|d d	< |d
d tdt| dt| dt| d |S )ai  
        Process skills for messages API (Anthropic format tools).
        
        - Converts skills to Anthropic-style tools (name, description, input_schema)
        - Extracts and injects SKILL.md content into system prompt
        - Adds litellm_code_execution tool for code execution
        - Stores skill files in metadata for sandbox execution
        r   ))get_litellm_code_execution_tool_anthropictools.py)r*   litellm_metadata_skill_filesT_litellm_code_execution_enabledr%   Nz.SkillsInjectionHook: Messages API - converted z% skills to Anthropic tools, injected z8 skill contents, added litellm_code_execution tool with  modules)0litellm.llms.litellm_proxy.skills.code_executionr7   r   r2   r   Zconvert_skill_to_anthropic_toolextract_skill_contentextract_all_filesr'   keysendswith inject_skill_content_to_messagespopr   r.   r/   )r   r    r)   r*   r7   r8   skill_contentsall_skill_filesall_module_pathsr5   contentskill_filespathZcode_exec_toolr   r   r   r4      sL   



z-SkillsInjectionHook._process_for_messages_apic              	   C   s2  | dg }g }i }g }|D ]6}|| j| | j|}|r&|| | j|}	|	rD|	||j< |	 D ]}
|
drC||
 q7q|rK||d< |rT| j	||}|r|ddl
m} | dg | g |d< | di |d< ||d d< d|d d< |d	d
 tdt| dt| dt| d |S )a,  
        Process skills for non-Anthropic models (OpenAI format tools).
        
        - Converts skills to OpenAI-style tools
        - Extracts and injects SKILL.md content
        - Adds execute_code tool for code execution
        - Stores skill files in metadata for sandbox execution
        r8   r9   r   )get_litellm_code_execution_toolr:   r;   Tr<   r%   Nz5SkillsInjectionHook: Non-Anthropic model - converted z skills to tools, injected z. skill contents, added execute_code tool with r=   )r   r2   r   Zconvert_skill_to_toolr?   r@   r'   rA   rB   rC   r>   rK   rD   r   r.   r/   )r   r    r)   r8   rE   rF   rG   r5   rH   rI   rJ   rK   r   r   r   _process_non_anthropic_model   sF   



z0SkillsInjectionHook._process_non_anthropic_modelr'   c              
      s^   zddl m} ||I dH W S  ty. } ztd| d|  W Y d}~dS d}~ww )z
        Fetch a skill from the LiteLLM database.

        Args:
            skill_id: The skill ID (without 'litellm:' prefix)

        Returns:
            LiteLLM_SkillsTable or None if not found
        r   )LiteLLMSkillsHandlerNz*SkillsInjectionHook: Error fetching skill z: )Z)litellm.llms.litellm_proxy.skills.handlerrM   Zfetch_skill_from_db	Exceptionr   r3   )r   r'   rM   er   r   r   r1     s   
z(SkillsInjectionHook._fetch_skill_from_dbmodelc                 C   sV   zddl m} ||d\}}}}|dkW S  ty*   d| v p'| d Y S w )z
        Check if the model is an Anthropic model using get_llm_provider.

        Args:
            model: The model name/identifier

        Returns:
            True if Anthropic model, False otherwise
        r   )get_llm_provider)rP   	anthropicZclaudez
anthropic/)Z1litellm.litellm_core_utils.get_llm_provider_logicrQ   rN   lowerr0   )r   rP   rQ   _Zcustom_llm_providerr   r   r   _is_anthropic_model&  s   

z'SkillsInjectionHook._is_anthropic_modelrequest_dataresponsec                    s   ddl m} |dpi }|dpi }|dp|d}|s#dS |dp-|di }i }	| D ]}
|	|
 q4|	sEtd dS | |}|sNdS d	}|D ]}|d
d}||jj	kse|
drid} nqR|sndS td | j|||	dI dH S )a  
        Post-call hook to handle automatic code execution.
        
        Handles both OpenAI format (response.choices) and Anthropic/messages API 
        format (response["content"]).
        
        If the response contains a tool call (litellm_code_execution or skill tool):
        1. Execute the code in sandbox
        2. Add result to messages
        3. Make another LLM call
        4. Repeat until model gives final response
        5. Return modified response with generated files
        r   LiteLLMInternalToolsr:   metadatar<   Nr;   z>SkillsInjectionHook: No skill files found, cannot execute codeFnamer(   skill_Tz@SkillsInjectionHook: Detected tool call, starting execution loop)r    rW   rI   )r>   rY   r   valuesupdater   r3   _extract_tool_callsCODE_EXECUTIONvaluer0   r.   _execute_code_loop_messages_api)r   rV   rW   r!   rY   r:   rZ   Zcode_exec_enabledZskill_files_by_idrF   Z
files_dict
tool_callsZhas_executable_tooltc	tool_namer   r   r   'async_post_call_success_deployment_hook;  sP   



z;SkillsInjectionHook.async_post_call_success_deployment_hookc              	   C   s0  g }d}t |tr|dg }nt|dr|j}|ra|D ]D}t |tr>|ddkr>||d|d|di d qt|dr`t|dddkr`|t|ddt|ddt|di d q|st|d	r|jr|jd
 j}t|dr|j	r|j	D ]}||j
|jj|jjrt|jjni d q||S )z8Extract tool calls from response, handling both formats.NrH   typetool_useidr[   input)ri   r[   rj   choicesr   rc   )r+   r,   r   hasattrrH   r2   getattrrk   messagerc   ri   functionr[   	argumentsjsonloads)r   rW   rc   rH   blockmsgrd   r   r   r   r_     s>   








z'SkillsInjectionHook._extract_tool_callsrI   c                    s  ddl }ddlm} ddlm} |du rtd dS |dd}t|dg }|d	g }	|d
d}
|| j	d}g }|}t
| jD ]3}| |}t|trW|dnt|dd}t|trh|dg nt|dg }g }|psg D ]2}t|tr|| qtt|dr||  qtt|dr|t|j qt|dt|d qtd|d}|| |dks|std|d  dt| d | ||  S g }|D ]N}|dd}|dd}|di }||jjkr|dd}| ||||I dH }n|dr| |||||I dH }nd| d }|d!||d" q|d#|d td$|d%   z"|jj|||	|
d&I dH }|du rUtd' | ||W   S W qE tyy } ztd(|  | ||W  Y d}~  S d}~ww t d)| j d* | ||S )+z
        Execute the code execution loop for messages API (Anthropic format).
        
        Returns the final response with generated files inline.
        r   NrX   SkillsSandboxExecutorz?SkillsInjectionHook: Response is None, cannot execute code looprP   r(   messagesr8   
max_tokens   timeoutstop_reasonrH   
model_dump__dict__text)rg   r   	assistantrolerH   rh   z*SkillsInjectionHook: Loop completed after     iterations,  files generatedr[   ri   rj   coder\   Tool 'z' not handledtool_result)rg   Ztool_use_idrH   user/SkillsInjectionHook: Making LLM call iteration    rP   rw   r8   rx   z+SkillsInjectionHook: LLM call returned Nonez&SkillsInjectionHook: LLM call failed: %SkillsInjectionHook: Max iterations (	) reached)!litellmr>   rY   2litellm.llms.litellm_proxy.skills.sandbox_executorrv   r   errorr   r-   r   ranger   r_   r+   r,   rm   r2   rl   r}   r~   strr.   r/   _attach_files_to_responser`   ra   _execute_coder0   _execute_skill_toolrR   acreaterN   r3   )r   r    rW   rI   r   rY   rv   rP   rw   r8   rx   executorgenerated_filescurrent_response	iterationrc   r|   Zraw_contentZcontent_blocksrs   Zassistant_msgZtool_resultsrd   re   Ztool_id
tool_inputr   resultrO   r   r   r   rb     s   
 "






z3SkillsInjectionHook._execute_code_loop_messages_apir   r   r   c           	         s   zYt dt| d |j||d}|ddpd}|drH|d D ]"}||d |d |d	 tt|d	 d
 |d|d  7 }q%|drV|d|d  7 }|pYdW S  tys } zdt	| W  Y d}~S d}~ww )z1Execute code in sandbox and return result string.%SkillsInjectionHook: Executing code ( chars)r   rI   outputr(   filesr[   	mime_typecontent_base64r[   r   r   sizez

Generated file: r   z	

Error: zCode executed successfullyCode execution failed: N)
r   r.   r/   executer   r2   base64	b64decoderN   r   )	r   r   rI   r   r   exec_resultr   frO   r   r   r   r   &  s*   


z!SkillsInjectionHook._execute_codere   r   c                    s   dd |  D }d}|D ]}d| v s"d| v s"d| v r&|} nq|s/|r/|d }|rE|dd	d
d}	d|	 d|	 d}
nd}
| |
|||I dH S )zKExecute a skill tool by generating and running code based on skill content.c                 S   s$   g | ]}| d r| ds|qS )r9   z__init__.py)rB   ).0pr   r   r   
<listcomp>R  s   $ z;SkillsInjectionHook._execute_skill_tool.<locals>.<listcomp>NbuilderZcreator	generatorr   /.r9   r(   zX
# Auto-generated code to execute skill
import sys
sys.path.insert(0, '/sandbox')

from z] import *

# Try to find and use a Builder/Creator class
import inspect
module = __import__('aE  ', fromlist=[''])

for name, obj in inspect.getmembers(module):
    if inspect.isclass(obj) and name != 'object':
        try:
            instance = obj()
            # Try common methods
            if hasattr(instance, 'create'):
                result = instance.create()
            elif hasattr(instance, 'build'):
                result = instance.build()
            elif hasattr(instance, 'generate'):
                result = instance.generate()
            elif hasattr(instance, 'save'):
                instance.save('output.gif')
            print(f'Used {name} class')
            break
        except Exception as e:
            print(f'Error with {name}: {e}')
            continue

# List generated files
import os
for f in os.listdir('.'):
    if f.endswith(('.gif', '.png', '.jpg')):
        print(f'Generated: {f}')
z+
print('No executable skill module found')
)rA   rS   replacer   )r   re   r   rI   r   r   Zpython_modulesZmain_modulemodZimport_pathr   r   r   r   r   G  s&   $	
&z'SkillsInjectionHook._execute_skill_toolc              	      s  ddl }ddlm} ddlm} |dd}t|dg }|dg }	th d	  fd
d| D }
|| j	d}g }|}t
| jD ]}|jd j}|jd j}d|jd}|jrfdd |jD |d< || |dksr|jstd|d  dt| d | ||  S |jD ])}|jj}||jjkr| j||||dI dH }nd| d}|d|j|d qtd|d   |jj|||	|
dddI dH }qEtd | j d! | ||S )"z
        Execute the code execution loop until model gives final response.
        
        Returns the final response with generated files inline.
        r   NrX   ru   rP   r(   rw   r8   >   rw   r%   rP   r:   rZ   r8   c                    s   i | ]\}}| vr||qS r   r   )r   kvZ_EXCLUDED_ACOMPLETION_KEYSr   r   
<dictcomp>  s
    z:SkillsInjectionHook._execute_code_loop.<locals>.<dictcomp>rz   r   r   c                 S   s(   g | ]}|j d |jj|jjddqS )ro   )r[   rp   )ri   rg   ro   )ri   ro   r[   rp   )r   rd   r   r   r   r     s    	z:SkillsInjectionHook._execute_code_loop.<locals>.<listcomp>rc   z9SkillsInjectionHook: Code execution loop completed after r   r   r   )	tool_callrI   r   r   r   z' not handled automaticallyZtool)r   Ztool_call_idrH   r   r   rx   ry   r   r   r   )r   r>   rY   r   rv   r   r-   	frozensetitemsr   r   r   rk   rn   Zfinish_reasonrH   rc   r2   r   r.   r/   r   ro   r[   r`   ra   _execute_code_toolri   rR   r   r3   )r   r    rW   rI   r   rY   rv   rP   rw   r8   r   r   r   r   r   Zassistant_messager|   Zassistant_msg_dictr   re   r   r   r   r   _execute_code_loop  s|   
		




z&SkillsInjectionHook._execute_code_loopr   c              
      sF  zt |jj}|dd}tdt| d |j||d}|ddp(d}|drq|d7 }|d D ]:}	t	
|	d	 }
||	d
 |	d |	d	 t|
d |d|	d
  dt|
 d7 }td|	d
  dt|
 d q6|dr|d|d  7 }|W S  ty } ztd|  dt| W  Y d}~S d}~ww )zDExecute a litellm_code_execution tool call and return result string.r   r(   r   r   r   r   r   z

Generated files:r   r[   r   r   z
- z (z bytes)z$SkillsInjectionHook: Generated file r   z	

Error:
z,SkillsInjectionHook: Code execution failed: r   N)rq   rr   ro   rp   r   r   r.   r/   r   r   r   r2   rN   r   r   )r   r   rI   r   r   argsr   r   r   r   Zfile_contentrO   r   r   r   r     sL   

z&SkillsInjectionHook._execute_code_toolc                 C   s   |s|S t |tr||d< tdt| d |S z||_W n	 ty(   Y nw t|dr;|jdu r6i |_||jd< tdt| d |S )z
        Attach generated files to the response object.
        
        Files are added to response._litellm_generated_files for easy access.
        For dict responses, files are added as a key.
        _litellm_generated_fileszSkillsInjectionHook: Attached z files to dict responsemodel_extraNz files to response)	r+   r,   r   r.   r/   r   AttributeErrorrl   r   )r   rW   r   r   r   r   r   7  s*   




z-SkillsInjectionHook._attach_files_to_response)T)"__name__
__module____qualname____doc__r   r   r   r,   r   r   r   rN   r   r6   r   r   boolr4   rL   r1   rU   r   r   rf   r   r_   bytesrb   r   r   r   r   r   __classcell__r   r   r   r   r   )   s    
F
G
C
M(

v

!


I

r

5r   )r   r   rq   typingr   r   r   r   r   Zlitellm._loggingr   Zlitellm.caching.cachingr   Z"litellm.integrations.custom_loggerr	   Z2litellm.llms.litellm_proxy.skills.prompt_injectionr
   Zlitellm.proxy._typesr   r   Zlitellm.types.utilsr   r   r   Zskills_injection_hookr   Zlogging_callback_managerZadd_litellm_callbackr   r   r   r   <module>   s(          >