U
    hX                     @   s  d dl Z d dlZd dl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 d dl	m
Z
 d dlmZmZmZmZmZmZmZmZmZ d dlmZmZ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"m#Z#m$Z$m%Z%m&Z&m'Z'm(Z(m)Z)m*Z*m+Z+ d dl,m-Z-m.Z. d dl/m0Z0m1Z1m2Z2 d dl3m4Z4m5Z5 d dl6m7Z7m8Z8 d dl9m:Z: e;e<Z=dZ>dZ?e#e@dddZAeeBef e#dddZCeeBef ee$ e$dddZDG dd deZEG dd dZFdS )    N)datetime)Queue)mktime)	AnyDict	GeneratorIteratorListMappingOptionalTypecast)	urlencodeurlparse
urlunparse)format_date_time)CallbackManagerForLLMRun)BaseChatModelgenerate_from_stream)	AIMessageAIMessageChunkBaseMessageBaseMessageChunkChatMessageChatMessageChunkFunctionMessageChunkHumanMessageHumanMessageChunkSystemMessageToolMessageChunk)make_invalid_tool_callparse_tool_call)ChatGenerationChatGenerationChunk
ChatResult)Fieldroot_validator)get_from_dict_or_envget_pydantic_field_names)
get_fieldsz$wss://spark-api.xf-yun.com/v3.5/chatzgeneralv3.5)messagereturnc                 C   s   t | trd| jd}nt | tr0d| jd}nt | trd| jd}d| jkrr| jd |d< |d dkrrd |d< d| jkr| jd |d< |d dkrd |d< n&t | trd| jd}ntd	|  |S )
Nuser)rolecontent	assistantfunction_callr.    
tool_callssystemzGot unknown type )
isinstancer   r.   r   r   additional_kwargsr   
ValueError)r*   Zmessage_dict r7   L/tmp/pip-unpacked-wheel-9gdii04g/langchain_community/chat_models/sparkllm.pyconvert_message_to_dict9   s$    






r9   )_dictr+   c                 C   s  | d }| d }|dkr"t |dS |dkrg }i }| d }rLt||d< g }| d }r||d< | d D ]R}z|t|dd	 W qn tk
r }	 z|t|t|	 W 5 d }	~	X Y qnX qnni }|pd
}
t|
|||dS |dkrt	|dS t
||dS d S )Nr-   r.   r,   r.   r/   r0   r2   T)Z	return_idr1   )r.   r5   r2   invalid_tool_callsr3   r.   r-   )r   getdictappendr!   	Exceptionr    strr   r   r   )r:   msg_rolemsg_contentr<   r5   r0   r2   Zraw_tool_callsZraw_tool_caller.   r7   r7   r8   convert_dict_to_messageS   s<    

rF   )r:   default_classr+   c                 C   s  t t| d}t t| dp d}i }| drbt| d }d|krZ|d d krZd|d< ||d< | drx| d |d< |dks|tkrt|dS |d	ks|tkrt||d
S |dks|tkrt|| d dS |dks|tkrt|| d dS | s|tkr
t||dS ||dS d S )Nr-   r.   r1   r0   namer2   r,   r;   r/   )r.   r5   function)r.   rH   Ztooltool_call_id)r.   rJ   r=   )	r   rB   r>   r?   r   r   r   r   r   )r:   rG   rC   rD   r5   r0   r7   r7   r8   _convert_delta_to_message_chunkv   s*    


rK   c                   @   s  e Zd ZU dZeedddZeee	e	f dddZ
dZeed< edd	d
Zee	 ed< eddd
Zee	 ed< eddd
Zee	 ed< eddd
Zee	 ed< eddd
Zee	 ed< dZe	ed< dZeed< edddZeed< eddZeed< dZeed < eed!Zee	ef ed"< G d#d$ d$Zed%d&ee	ef ee	ef d'd(d)Z ed%d&eed'd*d+Z!d4e"e# ee"e	  ee$ ee%e& d,d-d.Z'd5e"e# ee"e	  ee$ ee ee(d/d0d1Z)ee	dd2d3Z*dS )6ChatSparkLLMu-  IFlyTek Spark chat model integration.

    Setup:
        To use, you should have the environment variable``IFLYTEK_SPARK_API_KEY``,
        ``IFLYTEK_SPARK_API_SECRET`` and ``IFLYTEK_SPARK_APP_ID``.

    Key init args — completion params:
        model: Optional[str]
            Name of IFLYTEK SPARK model to use.
        temperature: Optional[float]
            Sampling temperature.
        top_k: Optional[float]
            What search sampling control to use.
        streaming: Optional[bool]
             Whether to stream the results or not.

    Key init args — client params:
        api_key: Optional[str]
            IFLYTEK SPARK API KEY. If not passed in will be read from env var IFLYTEK_SPARK_API_KEY.
        api_secret: Optional[str]
            IFLYTEK SPARK API SECRET. If not passed in will be read from env var IFLYTEK_SPARK_API_SECRET.
        api_url: Optional[str]
            Base URL for API requests.
        timeout: Optional[int]
            Timeout for requests.

    See full list of supported init args and their descriptions in the params section.

    Instantiate:
        .. code-block:: python

            from langchain_community.chat_models import ChatSparkLLM

            chat = ChatSparkLLM(
                api_key="your-api-key",
                api_secret="your-api-secret",
                model='Spark4.0 Ultra',
                # temperature=...,
                # other params...
            )

    Invoke:
        .. code-block:: python

            messages = [
                ("system", "你是一名专业的翻译家，可以将用户的中文翻译为英文。"),
                ("human", "我喜欢编程。"),
            ]
            chat.invoke(messages)

        .. code-block:: python

            AIMessage(
                content='I like programming.',
                response_metadata={
                    'token_usage': {
                        'question_tokens': 3,
                        'prompt_tokens': 16,
                        'completion_tokens': 4,
                        'total_tokens': 20
                    }
                },
                id='run-af8b3531-7bf7-47f0-bfe8-9262cb2a9d47-0'
            )

    Stream:
        .. code-block:: python

            for chunk in chat.stream(messages):
                print(chunk)

        .. code-block:: python

            content='I' id='run-fdbb57c2-2d32-4516-b894-6c5a67605d83'
            content=' like programming' id='run-fdbb57c2-2d32-4516-b894-6c5a67605d83'
            content='.' id='run-fdbb57c2-2d32-4516-b894-6c5a67605d83'

        .. code-block:: python

            stream = chat.stream(messages)
            full = next(stream)
            for chunk in stream:
                full += chunk
            full

        .. code-block:: python

            AIMessageChunk(
                content='I like programming.',
                id='run-aca2fa82-c2e4-4835-b7e2-865ddd3c46cb'
            )

    Response metadata
        .. code-block:: python

            ai_msg = chat.invoke(messages)
            ai_msg.response_metadata

        .. code-block:: python

            {
                'token_usage': {
                    'question_tokens': 3,
                    'prompt_tokens': 16,
                    'completion_tokens': 4,
                    'total_tokens': 20
                }
            }

    )r+   c                 C   s   dS )z9Return whether this model can be serialized by Langchain.Fr7   )clsr7   r7   r8   is_lc_serializable  s    zChatSparkLLM.is_lc_serializablec                 C   s   ddddddS )NIFLYTEK_SPARK_APP_IDIFLYTEK_SPARK_API_KEYIFLYTEK_SPARK_API_SECRETIFLYTEK_SPARK_API_URLIFLYTEK_SPARK_LLM_DOMAIN)spark_app_idspark_api_keyspark_api_secretspark_api_urlspark_llm_domainr7   selfr7   r7   r8   
lc_secrets  s    zChatSparkLLM.lc_secretsNclientapp_id)defaultaliasrT   api_keyrU   
api_secretrV   api_urlrW   modelrX   Zlc_userspark_user_idF	streaming   timeout)r_   request_timeoutg      ?r^   temperature   top_k)default_factorymodel_kwargsc                   @   s   e Zd ZdZdS )zChatSparkLLM.ConfigTN)__name__
__module____qualname__Zallow_population_by_field_namer7   r7   r7   r8   Config+  s   rr   T)pre)valuesr+   c              
   C   s   t | }|di }t|D ]P}||kr8td| d||krtd| d| d| d ||||< q|| }|rtd| d	||d< |S )
z>Build extra kwargs from additional params that were passed in.rn   zFound z supplied twice.z	WARNING! z/ is not default parameter.
                    zJ was transferred to model_kwargs.
                    Please confirm that z is what you intended.zParameters za should be specified explicitly. Instead they were passed in as part of `model_kwargs` parameter.)	r(   r>   listr6   loggerwarningpopintersectionkeys)rM   rt   Zall_required_field_namesextra
field_nameZinvalid_model_kwargsr7   r7   r8   build_extra.  s"    
zChatSparkLLM.build_extrac                 C   s   t |ddgd|d< t |ddgd|d< t |ddgd	|d< t |d
dt|d
< t |ddt|d< dd t|  D }|d|d d< |d|d d< t|d |d |d |d
 |d |d d|d< |S )NrT   r]   rO   rU   r`   rP   rV   ra   rQ   rW   rR   rX   rS   c                 S   s"   i | ]\}}|j d k	r||j qS )Nri   ).0rH   fieldr7   r7   r8   
<dictcomp>h  s   
 z5ChatSparkLLM.validate_environment.<locals>.<dictcomp>rj   rn   rl   r]   r`   ra   rb   spark_domainrn   r\   )r'   SPARK_API_URLSPARK_LLM_DOMAINr)   itemsr>   _SparkLLMClient)rM   rt   Zdefault_valuesr7   r7   r8   validate_environmentI  sR    

z!ChatSparkLLM.validate_environment)messagesstoprun_managerkwargsr+   c           
      k   s   t }| jjdd |D | j| jdd | jj| jdD ]H}d|krDq6|d }t||}t|d}	|rx|j	t
|j|	d |	V  q6d S )	Nc                 S   s   g | ]}t |qS r7   r9   r~   mr7   r7   r8   
<listcomp>  s     z(ChatSparkLLM._stream.<locals>.<listcomp>T)re   rg   datar*   )chunk)r   r\   arunrd   rn   	subscriberh   rK   r#   Zon_llm_new_tokenrB   r.   )
rZ   r   r   r   r   Zdefault_chunk_classr.   deltar   Zcg_chunkr7   r7   r8   _streamz  s     

zChatSparkLLM._stream)r   r   r   streamr   r+   c                 K   s   |s
| j r*| jf |||d|}t|S | jdd |D | j| jd i }i }| jj| jdD ]*}	d|	krz|	d |d< d|	krqb|	d }qbt	|}
t
|
d	g}t||d
S )N)r   r   r   c                 S   s   g | ]}t |qS r7   r   r   r7   r7   r8   r     s     z*ChatSparkLLM._generate.<locals>.<listcomp>Fr   usageZtoken_usager   r   )generations
llm_output)re   r   r   r\   r   rd   rn   r   rh   rF   r"   r$   )rZ   r   r   r   r   r   Zstream_iter
completionr   r.   r*   r   r7   r7   r8   	_generate  s4    
  
zChatSparkLLM._generatec                 C   s   dS )Nzspark-llm-chatr7   rY   r7   r7   r8   	_llm_type  s    zChatSparkLLM._llm_type)NN)NNN)+ro   rp   rq   __doc__classmethodboolrN   propertyr   rB   r[   r\   r   __annotations__r%   rT   r   rU   rV   rW   rX   rd   re   rh   intrj   floatrl   r?   rn   rr   r&   r}   r   r	   r   r   r   r#   r   r$   r   r   r7   r7   r7   r8   rL      sX   
o	"3  
   
 rL   c                   @   s  e Zd ZdZd"eeeee ee ee dddZeeeeedddZ	d#e
e eee edd
ddZd$e
e eee eejd
ddZeee ddddZeeeddddZeddddZeeddddZd%eeee edddZd&ee eeddf dd d!ZdS )'r   z
    Use websocket-client to call the SparkLLM interface provided by Xfyun,
    which is the iFlyTek's open platform for AI capabilities
    Nr   c                 C   sx   zdd l }|| _W n tk
r.   tdY nX |s8tn|| _|| _|| _|pPt| _t	 | _
ddd| _|| _|| _d S )Nr   zhCould not import websocket client python package. Please install it with `pip install websocket-client`.r1   r/   r=   )	websocketwebsocket_clientImportErrorr   rb   r]   rn   r   r   r   queueblocking_messager`   ra   )rZ   r]   r`   ra   rb   r   rn   r   r7   r7   r8   __init__  s    	


z_SparkLLMClient.__init__)rb   r`   ra   r+   c                 C   s   t tt  }t| }|j}|j}d| d| d| d}tj	|
d|
dtjd }t|jdd}	d| d	|	 d
}
t|

djdd}|||d}t|}t|j|j|j|j||jf}|S )zK
        Generate a request url with an api key and an api secret.
        zhost: z
date: z
GET z	 HTTP/1.1zutf-8)	digestmod)encodingz	api_key="zQ", algorithm="hmac-sha256",         headers="host date request-line", signature="")authorizationdatehost)r   r   r   now	timetupler   netlocpathhmacnewencodehashlibsha256digestbase64	b64encodedecoder   r   schemeparamsfragment)rb   r`   ra   r   
parsed_urlr   r   Zsignature_originZsignature_shaZsignature_sha_base64Zauthorization_originr   params_dictZencoded_paramsurlr7   r7   r8   _create_url  s:    

z_SparkLLMClient._create_urlF)r   user_idrn   re   r+   c                 C   sl   | j d | j jt| j| j| j| j| j	| j
| jd}||_||_|d krT| jn||_||_|  d S )NF)
on_messageon_erroron_closeon_open)r   ZenableTraceZWebSocketAppr   r   rb   r`   ra   r   r   r   r   r   r   rn   re   Zrun_forever)rZ   r   r   rn   re   wsr7   r7   r8   run  s"    z_SparkLLMClient.runc                 C   s$   t j| j||||fd}|  |S )N)targetargs)	threadingThreadr   start)rZ   r   r   rn   re   Z	ws_threadr7   r7   r8   r      s    	z_SparkLLMClient.arun)r   errorr+   c                 C   s   | j d|i |  d S )Nr   )r   putclose)rZ   r   r   r7   r7   r8   r   3  s    z_SparkLLMClient.on_error)r   close_status_codeclose_reasonr+   c                 C   s(   t d||di | jddi d S )Nlog)r   r   doneT)rv   debugr   r   )rZ   r   r   r   r7   r7   r8   r   7  s    z_SparkLLMClient.on_close)r   r+   c                 C   s6   ddd| _ t| j|j|j|jd}|| d S )Nr1   r/   r=   )r   r   rn   )r   jsondumps
gen_paramsr   r   rn   send)rZ   r   r   r7   r7   r8   r   B  s      z_SparkLLMClient.on_open)r   r*   r+   c           	      C   s  t |}|d d }|dkrL| jdd| d|d d  i |  n|d d	 }|d
 }|d d d }|jr| jd|d d i n| jd  |7  < |dkr|js| jd| ji |r|di di di ni }| jd|i |  d S )Nheadercoder   r   zCode: z	, Error: r*   payloadchoicesstatustextr.   r      r   )r   loadsr   r   r   re   r   r>   )	rZ   r   r*   r   r   r   r   r.   Z
usage_datar7   r7   r8   r   K  s,    

z_SparkLLMClient.on_message)r   r   rn   r+   c                 C   sP   | j |ddd| jiidd|iid}|r<|d d | td|  |S )	N)r]   uidZchatdomainr*   r   )r   	parameterr   r   zSpark Request Parameters: )r]   r   updaterv   r   )rZ   r   r   rn   r   r7   r7   r8   r   f  s    

z_SparkLLMClient.gen_paramsrf   )rg   r+   c              
   c   s   z| j j|d}W n4 t jk
rF } ztd| dW 5 d }~X Y nX d|kr\t|d d|krl|V  q d|krvqd|krq|V  q d S )Nr   z-SparkLLMClient wait LLM api response timeout z secondsr   r   r   r   )r   r>   EmptyTimeoutErrorConnectionError)rZ   rg   r.   _r7   r7   r8   r   t  s     
z_SparkLLMClient.subscribe)NNN)NF)NF)N)rf   )ro   rp   rq   r   rB   r   r?   r   staticmethodr   r	   r   r   r   r   r   r   r   r   r   r   r   r   ru   r   r   r   r7   r7   r7   r8   r     sX   
   0    	   r   )Gr   r   r   r   loggingr   r   r   r   timer   typingr   r   r   r   r	   r
   r   r   r   urllib.parser   r   r   Zwsgiref.handlersr   Zlangchain_core.callbacksr   Z*langchain_core.language_models.chat_modelsr   r   Zlangchain_core.messagesr   r   r   r   r   r   r   r   r   r   r   Z*langchain_core.output_parsers.openai_toolsr    r!   Zlangchain_core.outputsr"   r#   r$   Zlangchain_core.pydantic_v1r%   r&   Zlangchain_core.utilsr'   r(   Zlangchain_core.utils.pydanticr)   	getLoggerro   rv   r   r   r?   r9   rB   rF   rK   rL   r   r7   r7   r7   r8   <module>   sB   ,4
$
   )