o
    +ih                     @  sN  d dl mZ d dlZd dlZd dlZd dlZd dlmZ d dlm	Z	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mZ e	rVd d
lmZ edede
f dZeeZ d<ddZ!d=ddZ"d>ddZ#d?ddZ$d@ddZ%dAd"d#Z&dBd&d'Z'		(dCdDd1d2Z(G d3d4 d4ed(d5Z)edd6d7dEd:d;Z*dS )F    )annotationsN)Mapping)TYPE_CHECKINGAnyCallableOptionalTypeVarUnion)	TypedDict)client)run_helpers)	warn_beta)InputTokenDetailsOutputTokenDetailsUsageMetadata)genaiCzgenai.Client)boundddictreturnc                 C  s   dd |   D S )z%Remove `None` values from dictionary.c                 S     i | ]\}}|d ur||qS N .0kvr   r   W/var/www/html/psymed-ai/venv/lib/python3.10/site-packages/langsmith/wrappers/_gemini.py
<dictcomp>!       z_strip_none.<locals>.<dictcomp>)items)r   r   r   r   _strip_none   s   r"   kwargsNonec                 C  s2   d| v rt | d tst| d | d< dS dS dS )zFConvert `GenerateContentConfig` to `dict` for LangSmith compatibility.configN)
isinstancer   vars)r#   r   r   r   _convert_config_for_tracing$   s   r(   inputsc                 C  s  |  d}|s	| S t|tr#d|dg|  dddd |  D S t|trYtdd	 |D rHd
d |D |  dddd |  D S g }|D ]}t|trG| dd}| dg }g }g }|D ]}t|trd|v r|d r||d  |d|d d qfd|v r|d }	|	 dd}
|	 dd}t|trt	
|d}n|}|dd|
 d| ddd qfd|v r|d }|d| d| d i d!d" qfd#|v sd$|v r| d#p| d$}|d%urt|ts| }|d#| d&| d| d'i d(d) qft|tr$|| |d|d qf|r8td*d	 |D r8d+|}n|r=|nd,}|||d qL||  ddd-d |  D S | S ).u  Process Gemini inputs to normalize them for LangSmith tracing.

    Example:
        ```txt
        {"contents": "Hello", "model": "gemini-pro"}
        → {"messages": [{"role": "user", "content": "Hello"}], "model": "gemini-pro"}
        {"contents": [{"role": "user", "parts": [{"text": "What is AI?"}]}], "model": "gemini-pro"}
        → {"messages": [{"role": "user", "content": "What is AI?"}], "model": "gemini-pro"}
        ```
    contentsuserrolecontentmodel)messagesr/   c                 S     i | ]\}}|d vr||qS )r*   r/   r   r   r   r   r   r   ?   r    z*_process_gemini_inputs.<locals>.<dictcomp>c                 s  s    | ]}t |tV  qd S r   )r&   strr   itemr   r   r   	<genexpr>E   s    z)_process_gemini_inputs.<locals>.<genexpr>c                 S  s   g | ]}d |dqS )r+   r,   r   r4   r   r   r   
<listcomp>H   s    z*_process_gemini_inputs.<locals>.<listcomp>c                 S  r1   r2   r   r   r   r   r   r   J   r    r-   partstexttyper9   inline_data	mime_type
image/jpegdata    utf-8	image_urldata:;base64,highurldetailr;   rB   functionResponsefunction_responsenameresponse)rL   rM   )r;   rK   function_callfunctionCallNidargsrP   rL   	argumentsr;   rN   c                 s  s    | ]
}| d dkV  qdS )r;   r9   Ngetr   pr   r   r   r6      s    

 c                 S  r1   r2   r   r   r   r   r   r      r    )rV   r&   r3   r!   listallr   appendbytesbase64	b64encodedecodeto_dictjoin)r)   r*   r0   r.   r-   r8   
text_partscontent_partspartr<   r=   r?   data_b64rK   rN   message_contentr   r   r   _process_gemini_inputs*   s   










ri   c                 C  st   t | }|di }t|dr|j}t|dd}t|dd}n|d}|d}|d}dd|d|||d	S )
z*Extract invocation parameters for tracing.r%   temperaturemax_output_tokensNstop_sequencesgooglechatr/   )ls_providerls_model_typels_model_namels_temperaturels_max_tokensls_stop)r"   rV   hasattrrj   getattr)r#   strippedr%   rj   
max_tokensstopr   r   r   _infer_invocation_params   s    



rz   gemini_usage_metadatar   c                 C  s   |  dpd}|  dpd}|  dpd}|  dpd}|  dp$|| }i }|r-||d< i }|r5||d< t|||tdi d	d
 | D tdi dd
 | D dS )z2Convert Gemini usage metadata to LangSmith format.prompt_token_countr   candidates_token_countcached_content_token_countthoughts_token_counttotal_token_count
cache_read	reasoningc                 S  r   r   r   r   r   r   r   r      r    z*_create_usage_metadata.<locals>.<dictcomp>c                 S  r   r   r   r   r   r   r   r      r    )input_tokensoutput_tokenstotal_tokensinput_token_detailsoutput_token_detailsNr   )rV   r   r   r!   r   )r{   r|   r}   r~   r   r   r   r   r   r   r   _create_usage_metadata   s2   
r   rM   r   c              
   C  s  zt | dr|  }nt | dr|  }n
dt| dt| i}d}g }d}d|v r|d r|d d }d|v r|d }d	|v r|d	 r|d	 D ]}d|v rc|d rc||d 7 }|d|d d
 qHd|v r|d dur|d }|dd}	|dd}
t|
trt	
|
d}n|
}|dd|	 d| ddd qHd|v sd|v r|dp|d}|durt|ts| }|d|d|d|di dd qHd|v r|d r|d }nd|v r|d }|d|d
 dd  |D }|r|pdd!|d"d  t|D d#}n t|d$ks |r'|d d% dkr'|d!|d&}n|d!|d&}|d'}tdddd(}|ryt|}t }|ryz|jd)i d'i }|| |  W n tyx } ztd*|  W Y d}~nd}~ww |d+r|d d!||d+ |d,W S t|d tr|d d!||d-W S |d d!||d-W S  ty } ztd.|  d/| iW  Y d}~S d}~ww )0z$Process Gemini response for tracing.rb   
model_dumpr9   rZ   N
candidatesr   r.   r8   r:   r<   r=   r>   r?   r@   rA   rB   rC   rD   rE   rF   rI   rN   rO   rP   rL   rQ   rR   rT   finish_reasonc                 S  s   g | ]}| d dkr|qS rT   rU   rW   r   r   r   r7   4  r    z6_process_generate_content_response.<locals>.<listcomp>	assistantc              
   S  sN   g | ]#\}}|d   dpd| d||d  d t|d  d ddqS )rN   rP   call_functionrL   rS   )rL   rS   )rP   r;   indexr   )rV   jsondumps)r   itcr   r   r   r7   ;  s    

)r.   r-   r   
tool_calls   r;   )r.   r-   r   usage_metadatar   r   r   metadata!Failed to update usage metadata: r   )r.   r-   r   r   r   )r.   r-   r   r   z"Error processing Gemini response: output)ru   rb   r   rv   r3   r]   rV   r&   r^   r_   r`   ra   r   	enumeratelenr   r   r   get_current_run_treeextra
setdefaultupdatepatch	Exceptionloggerwarningdebug)rM   rdictcontent_resultre   r   	candidater.   rf   r<   r=   r?   rg   rN   r   resultr   
usage_dictcurrent_runmetaer   r   r   "_process_generate_content_response   s   











		r   
all_chunksr[   c           	      C  s  | sdt dddddS d}d}| D ]-}zt|dr"|jr"||j7 }|}W q ty? } ztd|  W Y d}~qd}~ww t dddd}|rz~t|dr|jrt|jd	r^|j }n-t|jd
rj|j }n!t	|jddt	|jddt	|jddt	|jddt	|jddd}t
|}t }|rz|jdi di }|| |  W n ty } ztd|  W Y d}~nd}~ww W n ty } ztd|  W Y d}~nd}~ww ||dS )z/Reduce streaming chunks into a single response.rZ   r   r   )r.   r   Nr9   zError processing chunk: r   rb   r   r|   r}   r~   r   r   )r|   r}   r~   r   r   r   r   z+Error extracting metadata from last chunk: )r   ru   r9   r   r   r   r   rb   r   rv   r   r   r   r   r   r   r   r   )	r   	full_text
last_chunkchunkr   r   r   r   r   r   r   r   _reduce_generate_content_chunks  s   

r   Foriginal_generater   rL   r3   tracing_extraOptional[TracingExtra]is_streamingboolc                   sR   |pi t  fdd}t  fdd}tr'|S |S )z9Create a wrapper for Gemini's `generate_content` methods.c               	     sH   t | tjdd rtnd t stnd td}|| i |S Nllm)rL   run_type	reduce_fnprocess_inputsprocess_outputs_invocation_params_fnr   r(   r   	traceabler   ri   r   rz   rQ   r#   	decoratorr   rL   r   textrar   r   generate  s   

	z_get_wrapper.<locals>.generatec               	     sP   t | tjdd rtnd t stnd td}|| i |I d H S r   r   r   r   r   r   	agenerate  s   

	z_get_wrapper.<locals>.agenerate)	functoolswrapsr   is_async)r   rL   r   r   r   r   r   r   r   _get_wrapper  s   r   c                   @  s&   e Zd ZU ded< ded< ded< dS )TracingExtrazOptional[Mapping[str, Any]]r   zOptional[list[str]]tagszOptional[ls_client.Client]r   N)__name__
__module____qualname____annotations__r   r   r   r   r     s   
 r   )totalChatGoogleGenerativeAI)r   	chat_namer   r   c                C  s  |pi }t | drt | jdrt | jjdrtdt | dr1t | jdr1t| jj||dd| j_t | drHt | jdrHt| jj||dd| j_t | d	rht | jdrht | jjdrht| jjj||dd| jj_t | d	rt | jdrt | jjdrt| jjj||dd| jj_| S )
a  Patch the Google Gen AI client to make it traceable.

    !!! warning

        **BETA**: This wrapper is in beta.

    Supports:
        - `generate_content` and `generate_content_stream` methods
        - Sync and async clients
        - Streaming and non-streaming responses
        - Tool/function calling with proper UI rendering
        - Multimodal inputs (text + images)
        - Image generation with `inline_data` support
        - Token usage tracking including reasoning tokens

    Args:
        client: The Google Gen AI client to patch.
        tracing_extra: Extra tracing information.
        chat_name: The run name for the chat endpoint.

    Returns:
        The patched client.

    Example:
        ```python
        from google import genai
        from google.genai import types
        from langsmith import wrappers

        # Use Google Gen AI client same as you normally would.
        client = wrappers.wrap_gemini(genai.Client(api_key="your-api-key"))

        # Basic text generation:
        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents="Why is the sky blue?",
        )
        print(response.text)

        # Streaming:
        for chunk in client.models.generate_content_stream(
            model="gemini-2.5-flash",
            contents="Tell me a story",
        ):
            print(chunk.text, end="")

        # Tool/Function calling:
        schedule_meeting_function = {
            "name": "schedule_meeting",
            "description": "Schedules a meeting with specified attendees.",
            "parameters": {
                "type": "object",
                "properties": {
                    "attendees": {"type": "array", "items": {"type": "string"}},
                    "date": {"type": "string"},
                    "time": {"type": "string"},
                    "topic": {"type": "string"},
                },
                "required": ["attendees", "date", "time", "topic"],
            },
        }

        tools = types.Tool(function_declarations=[schedule_meeting_function])
        config = types.GenerateContentConfig(tools=[tools])

        response = client.models.generate_content(
            model="gemini-2.5-flash",
            contents="Schedule a meeting with Bob and Alice tomorrow at 2 PM.",
            config=config,
        )

        # Image generation:
        response = client.models.generate_content(
            model="gemini-2.5-flash-image",
            contents=["Create a picture of a futuristic city"],
        )

        # Save generated image
        from io import BytesIO
        from PIL import Image

        for part in response.candidates[0].content.parts:
            if part.inline_data is not None:
                image = Image.open(BytesIO(part.inline_data.data))
                image.save("generated_image.png")
        ```

    !!! version-added "Added in `langsmith` 0.4.33"

        Initial beta release of Google Gemini wrapper.

    modelsgenerate_content__wrapped__zfThis Google Gen AI client has already been wrapped. Wrapping a client multiple times is not supported.F)r   r   generate_content_streamTaio)ru   r   r   
ValueErrorr   r   r   )r   r   r   r   r   r   wrap_gemini  sb   c


	

r   )r   r   r   r   )r#   r   r   r$   )r)   r   r   r   )r#   r   r   r   )r{   r   r   r   )rM   r   r   r   )r   r[   r   r   )NF)
r   r   rL   r3   r   r   r   r   r   r   )r   r   r   r   r   r3   r   r   )+
__future__r   r_   r   r   loggingcollections.abcr   typingr   r   r   r   r   r	   typing_extensionsr
   	langsmithr   	ls_clientr   #langsmith._internal._beta_decoratorr   langsmith.schemasr   r   r   rm   r   r   	getLoggerr   r   r"   r(   ri   rz   r   r   r   r   r   r   r   r   r   r   <module>   sB     	



 


" 
#L2