o
    1j^p                     @   s  U 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Zd dlm	Z	 d dl
mZmZmZ d dlZd dlZd dlZi Zeedf ed< dadd Zded	efd
dZdeded	efddZd?ddZe  d dlmZmZ d dl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( ddl)m*Z* ddl+m,Z,m-Z-m.Z.m/Z/m0Z0m1Z1m2Z2m3Z3 e4dZ5e$  da6d	eeef fddZ7deeef d	eeef fdd Z8e	d!d"G d#d$ d$Z9d%e:d	efd&d'Z;d%e:d(e<d)e<d*e=d	e>e:e=f f
d+d,Z?ded-ed.ed	efd/d0Z@dd1ded2ed3ed4ed5ed6ejAded7ee< d	ee:df fd8d9ZBd?d:d;ZCd?d<d=ZDeEd>kr@e FeD  dS dS )@    N)	dataclass)AnyAsyncGeneratorOptionalChatContext_call_contextsc                  C   s.   t d u rtd} | stdtj| da t S )NOPENAI_API_KEYzOPENAI_API_KEY not setapi_key)_openai_clientosgetenvRuntimeErroropenaiAsyncOpenAIr	    r   T/var/www/html/livekitdocker/backend/agent_runtime/src/mcube_integration/ai_worker.pyget_openai_client$   s   
r   textreturnc                    s\   | sdS t dd | D }|dkrdS g d}|   t  fdd|D }|dkr,dS dS )	zq

    Detect if text is Hindi/Hinglish or English.

    Returns: 'hi' for Hindi/Hinglish, 'en' for English.

    enc                 s   s,    | ]}d |  krdkrn ndV  qdS )u   ऀu   ॿ   Nr   ).0charr   r   r   	<genexpr>C   s   * z"detect_language.<locals>.<genexpr>r   hi)"namastekaisehoaapmainhaikyakarrahar!   nahipleasethankyoujiyaardostachatheekr!   sahigalatbataosamajhr%   kripyakarein	dhanyawadshukriyahaannabolohindihinglishc                 3   s     | ]}|   v rd V  qdS )r   N)split)r   word
text_lowerr   r   r   _   s    r   )sumlower)r   hindi_charshinglish_wordshinglish_countr   r=   r   detect_language1   s   
rD   system_promptlanguagec                 C   s    |dkr
d}| | S d}| | S )z_

    Add language-specific instructions to the system prompt based on detected language.

    r   z

LANGUAGE INSTRUCTION: Respond in Hindi or Hinglish (Hindi written in English script). Use natural conversational Hindi as spoken in North India. Be warm, polite, and use appropriate honorifics like 'aap', 'ji', etc.zh

LANGUAGE INSTRUCTION: Respond in English. Use clear, professional, and natural conversational English.r   )rE   rF   language_instructionr   r   r   #augment_system_prompt_with_languageq   s
   rH   c                  C   sL   d} | t jv r	dS G dd d}t| }|jdg|d |t j| < dS )z

    Work around Windows WDAC/App Control blocking `lk_blingfire` native extension.

    `livekit.agents` imports `livekit.agents.tokenize.blingfire` unconditionally; we stub it.

    z!livekit.agents.tokenize.blingfireNc                	   @   sl   e Zd Zdddddedededd	fd
dZd	ddeded	B dee fddZd	dded	B fddZ	d	S )z:_install_livekit_blingfire_stub.<locals>.SentenceTokenizer   
   F)min_sentence_lenstream_context_lenretain_formatrK   rL   rM   r   Nc                S   s   t || _d S )N)int_min_sentence_len)selfrK   rL   rM   r   r   r   __init__   s   zC_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.__init__)rF   r   rF   c                   s,   dd t d|p	dD } fdd|D S )Nc                 S   s   g | ]
}|  r|  qS r   )stripr   pr   r   r   
<listcomp>   s    zW_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.tokenize.<locals>.<listcomp>z(?<=[.!?])\s+ c                    s   g | ]}t | jkr|qS r   )lenrO   rS   rP   r   r   rU      s    )rer;   )rP   r   rF   partsr   rX   r   tokenize   s   zC_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.tokenizec                S   s   t d)Nz2Streaming tokenizer not supported in fallback stub)NotImplementedError)rP   rF   r   r   r   stream   s   zA_install_livekit_blingfire_stub.<locals>.SentenceTokenizer.stream)
__name__
__module____qualname__rN   boolrQ   strlistr[   r]   r   r   r   r   SentenceTokenizer   s    

$rd   )__all__rd   )sysmodulestypes
ModuleType__dict__update)module_namerd   stubr   r   r   _install_livekit_blingfire_stub   s   

"rn   )	inferencefunction_tool)r   )cartesiadeepgram
elevenlabsr   )load_agent_runtime_dotenv)'get_runtime_overrides_from_agents_table)&get_runtime_overrides_from_cluster_bot)get_default_mcube_call_config)AI_UTTERANCES_QUEUERABBITMQ_URLcontrol_queue_namedeclare_durable_queueconnectget_channelpublish_jsontts_queue_namezmcube.ai_workerc                  C   sl   t  } | d | d | d | d | dpd| d | d | d	p!d
| dp'd| dp-d| dp3ddS )zSFallback only when DB + queue payload do not supply values (see process_utterance).rE   	llm_modelllm_provider	tts_modeltts_providerrs   tts_voice_idtts_encodingtts_chunk_ms200stt_language_coder   stt_model_idrV   stt_providerrE   r   r   r   r   r   r   r   r   r   r   )rw   get)dr   r   r   _env_fallback_runtime  s   r   payloadc              	      s:  t  }| d}|durrzt|}W n ty   d}Y nw |durrtjt|| d| d| dp8| d| dpA| dd	I dH }|| | d
}|durrzt|}|tt||I dH  W n	 tyq   Y nw d}t	|}|D ]@}	|	| vrqz| |	}
|
du rqzt
|
tr|	dkr|
 sqz|	dkrt
|
tr|
dd||	< qzt
|
trt|
 n|
||	< qz|dd |d|dd |d|dd |d|dd |d|dd |d|dd |d|dd |d|dd |d|dd |d|dd |S )a\  

    Merge precedence (later wins):

    1) `.env` defaults (MCUBE_* via mcube_defaults)

    2) `business_id_agents` row (+ JSON config) when `business_id` is present

    3) Cluster `{bid}_bots` + `business_id_bots` when `business_id` and `bot_id` are present

    4) Explicit fields on the RabbitMQ payload (Redis call config / ws_bridge)

    business_idNagent_iduser_idagent_emailemail
agent_namename)r   r   r   r   bot_idr   r   rE   z\n
r   rs   r   rV   r   r   r   r   r   r   r   r   r   )r   r   rN   	Exceptionasyncio	to_threadru   rk   rv   dict
isinstancerb   rR   replace
setdefault)r   basebidbid_intdb_ovbot_rawbot_intoverride_keysoutkvr   r   r   _resolve_runtime_from_payload%  sj   



 r   T)frozenc                   @   s.   e Zd ZU eed< eed< eed< eed< dS )PublishTtsChunkcall_idsequence_id	chunk_seqpcm16_8k_bytesN)r^   r_   r`   rb   __annotations__rN   bytesr   r   r   r   r     s
   
 r   pcm16_bytesc                 C   s   t | dS )Nascii)base64	b64encodedecode)r   r   r   r   _pcm16_to_b64  s   r   	from_rateto_ratestatec                C   s(   ddl }|| dd|||\}}||fS )z]

    audioop.ratecv works on raw int16 PCM.

    Returns (converted_bytes, new_state).

    r   N   r   )audioopratecv)r   r   r   r   r   	converted	new_stater   r   r   _resample_pcm16  s   
r   r   	user_textc                    st   t j|dd}t }|jd| d |jd|d |j|d}g }| 2 z3 d H W }|| q&6 d|	 S )Nx   )model
max_tokenssystemrolecontentuser)chat_ctxrV   )
ro   LLMr   emptyadd_messagechatto_str_iterableappendjoinrR   )rE   r   r   llmr   r]   r   tokenr   r   r   _run_llm  s   r   )chunk_msr   r   r   r   sessionr   c             	   C  s  ddl }|dkrtdpd}	tj|pd|||	dd|d	}
d}|dur&|nttd
d}t|d | }|d }t }|
| 2 zJ3 dH W }|j}|j	
 }|j}||krkd}||dd|||\}}|| n|| t||krt|d| }|d|= |V  t||ksvqB6 |rt||k r|d|t|   t|d| V  dS dS tdptdpd}|stdtj||||pdd|d}
d}|dur|nttd
d}t|d | d }t }|
| 2 zF3 dH W }t|dr|jj	
 }nt|dr|j	
 }nt|}|| t||kr5t|d| }|d|= |V  t||ksq6 |rSt||k rL|d|t|   t|V  dS dS )zNSynthesize TTS and yield chunks as they arrive for true streaming (Phase 6.3).r   Nrq   CARTESIA_API_KEYrV   z	sonic-3.5	pcm_s16lei>  )r   voicerF   r
   encodingsample_ratehttp_sessionMCUBE_TTS_CHUNK_MSr   g     @@r   r       ELEVENLABS_API_KEYELEVEN_API_KEYzELEVENLABS_API_KEY not set	pcm_16000)voice_idr   r
   r   streaming_latencyr   framedata)r   r   r   rq   TTSrN   	bytearray
synthesizer   r   tobytesr   r   extendrW   r   r   rs   hasattr)r   r   r   r   r   r   rF   r   r   cartesia_keyttstarget_ratechunk_ms_effchunk_sampleschunk_bytesbuffersynth_audior   
pcm16_fromr   resample_stater   chunkelevenlabs_keypcm_datar   r   r   _synthesize_tts_pcm16_8k
  s   



	

r   c           .         s<  | j dd4 I d H  t| jd}t|d }t|dd}|dp*d}|d	p1d }t	|I d H }|d
 }|d }t|dpJd}	|d }
t|dpWd}|d }|d }zt
dtdttt|dpod}W n ty   d}Y nw |dd}|r|st|}td||||d d  t||}t }td|||p|pdd d |||
||	 z?|r/|}t|}t}|d u rtdt 4 I d H I}t|||
|||||d}d}|2 z3 d H W }t|||||t|dd d!I d H  |d"7 }q6 t||||d#d$d!I d H  d }W d   I d H  n1 I d H s(w   Y  nd%d&d'd(i g dd)d*}d%d+d,d(d-d.d/d-d0d/d-d1d/d2dd3d*}d4tttf d5tfd6d7}d4tttf d5tfd8d9}t||d:t||d:g}d;}| | } tj|d<}!|t vrt!" t |< t | j#d=| d> t | }"|"j#d?|d> t }#|!j$|"|dd@}$g }%g }&|$2 z#3 d H W }'|'j%r|'j%j&r|&'|'j%j& |'j%j(r|%)|'j%j( q6 tt |# dA }(tdB|||( |%rst*|})t}|d u rtd|%D ]o}*zt|*j+pdC}+W n ty   i }+Y nw |*j,d&kr>t||)dD||dEd!I d H   W W d   I d H  d S |*j,d+krq|+dFpT|+dGpT|+dH},t||)dI|||,dJd!I d H   W W d   I d H  d S qd-|& }|sdK}|t v rt | j#dL|d> t|}t}|d u rtdt 4 I d H J}t|||
|||||d}d}|2 z3 d H W }t|||||t|dd d!I d H  |d"7 }q6 t||||d#d$d!I d H  d }W d   I d H  n1 I d H sw   Y  W n ty }- zt.dM||t|-  d }-~-ww |d uret|}t}|d u r(tdt/|D ]\}}t|||||t|dd d!I d H  q,t||||dNd$d!I d H  tdO||t0|tt | dA  |d u rtdP||tt | dA  W d   I d H  d S W d   I d H  d S 1 I d H sw   Y  d S )QNF)requeuezutf-8r   r   r   
transcriptrV   bot_say_textrE   r   r   r   r   rs   r   r      i  r   r      r   r   z7language_detected call_id=%s seq=%s language=%s text=%r2   zvutterance_received call_id=%s seq=%s stt_text=%r llm_model=%s tts_provider=%s tts_model=%s tts_voice_id=%s language=%sr   zpublish channel not initialized)r   r   r   r   r   r   rF   tts_audio_chunk)r   r   r   audio_pcm_b64type)channel
queue_namer   r   tts_end)r   r   r  functionend_callz-Terminate the current phone call immediately.object)r  
propertiesrequiredadditionalProperties)r  r   description
parameterstransfer_to_numberz*Transfer the call to another phone number.stringz0Target phone number in E.164 format (preferred).)r  r  zAlias for phone_number.z'Optional SIP URI like sip:+918...@host.)phone_numbertransfer_numbersip_uri)r  r
  r  raw_argumentsr   c                       dS Nokr   r  r   r   r   _end_call_toolB     z)process_utterance.<locals>._end_call_toolc                    r  r  r   r  r   r   r   _transfer_to_number_toolH  r  z3process_utterance.<locals>._transfer_to_number_tool)
raw_schemaa	  

Tool-use rules for MCube phone calls:
- Call `end_call` to terminate the current call.
- Call `transfer_to_number` to transfer; provide phone_number or sip_uri.
- When calling a tool, do not include additional spoken content; the caller side will act on the tool.)r   r   r   r   )r   toolsparallel_tool_callsi  z1llm_generated call_id=%s seq=%s llm_latency_ms=%sz{}mcube_terminate)r  r   r   r  r  r  mcube_transfer)r  r   r   transfer_tozOkay.	assistantz6utterance_processing_failed call_id=%s seq=%s error=%stts_donez>tts_published call_id=%s seq=%s chunks=%s tts_generation_ms=%sz>tts_published_streaming call_id=%s seq=%s tts_generation_ms=%s)1processjsonloadsbodyr   rb   rN   r   rR   r   maxminfloatr   rD   loginforH   timer   _PUBLISH_CHANNELr   aiohttpClientSessionr   r~   r   r   r	  rp   ro   r   r   r   r   r   r   deltar   r   
tool_callsr   rz   	argumentsr   r   error	enumeraterW   ).messager   r   r   r   r   rtrE   r   r   r   r   r   r   r   detected_languagesystem_prompt_with_langstart_tresponse_textconnection_queuer  r   chunks_generatorr   r   	chunks_8kend_call_schematransfer_to_number_schemar  r  r  tool_instructionssystem_prompt_with_toolsr   r   	llm_startr]   r2  
text_partsr   llm_latency_mscontrol_queuetc	tool_argsr!  er   r   r   process_utterancej  sD  *


*.

0


	


    d
     d$


*(


            0rJ  c                     s   t jt jd t I d H } t| I d H }|at|tI d H  t	dtt
 |jtddI d H }|jtddI d H  t I d H  d S )N)levelzai_worker: consuming %s on %sT)durableF)no_ack)loggingbasicConfigINFOr|   r}   r.  r{   rx   r+  r,  ry   declare_queueconsumerJ  r   Future)
connectionr  queuer   r   r   r      s   r    __main__)r   N)Gr   r/  r   r%  rN  r   rf   r-  dataclassesr   typingr   r   r   rh   rY   r   r   r   rb   r   r   r   rD   rH   rn   livekit.agentsro   rp   livekit.agents.llm.chat_contextr   livekit.pluginsrq   rr   rs   env_loadrt   business_id_agentsru   cluster_bot_runtimerv   mcube_defaultsrw   mqrx   ry   rz   r{   r|   r}   r~   r   	getLoggerr+  r.  r   r   r   r   r   rN   r	  tupler   r   r0  r   rJ  r    r^   runr   r   r   r   <module>   s   
 @
@(
"$ &$!	



`    
+
$