
    |j                        S r SSKJr  SSKrSSKJr  SSKJr  SSKrSSK	J
r
  SSKJr  SrSSS	 jjrSS
 jrSS jrSS jrSSSSSS.             SS jjrg)uB   Cliente LLM genérico (chat completions estilo OpenAI, streaming).    )annotationsN)Iterator)Any)Session)settingszdeepseek-v4-proc                    U b'   SSK Jn  U" U 5      R                  5       nU(       a  U$  [        R
                  =(       d    SR                  5       $ ! [         a     N3f = f)uG   Prioridad: clave en BD (DeepSeek Token) → AGENTS_LLM_API_KEY en .env.r   )AgentsLlmModelsClass )+app.backend.classes.agents_llm_models_classr	   get_llm_api_key	Exceptionr   agents_llm_api_keystrip)dbr	   keys      UC:\Users\jesus\Desktop\proyecto_pie360\backend\app\backend\utils\agents_llm_client.pyresolve_llm_api_keyr      s_    	~	X&r*::<C
  ''-24466  		s   $A 
AAc                 \    [         R                  =(       d    SR                  S5      n U  S3$ )Nzhttps://api.deepseek.com/z/chat/completions)r   agents_llm_api_baserstrip)bases    r   llm_chat_completions_urlr      s,    ((F,FNNsSDV$%%    c                   [        U [        5      (       a  U (       d  g[        U R                  S5      =(       d2    U R                  S5      =(       d    U R                  S5      =(       d    S5      n[        U R                  S5      =(       d2    U R                  S5      =(       d    U R                  S5      =(       d    S5      n[        U R                  S	5      =(       d    U R                  S
5      =(       d    S5      n[        U R                  S5      =(       d2    U R                  S5      =(       d    U R                  S5      =(       d    S5      n[        U R                  S5      =(       d2    U R                  S5      =(       d    U R                  S5      =(       d    S5      nU R                  S5      n[        U[        5      (       aa  US::  a;  [        UR                  S5      =(       d    UR                  S5      =(       d    S5      nUS::  a  US:  a  US:  a  [	        SX-
  5      nUS::  a  US::  a  US::  a  US::  a  US::  a  gUS::  a  X-   nUS:  a  US::  a	  US::  a  UnOAUS:  a  US::  a  US:  a  [	        SX-
  5      nO US:  a  US::  a  US:  a  [	        SX-
  5      n[	        SU5      [	        SU5      [	        SU5      [	        SU5      [	        SU5      S.$ )zNNormaliza usage de DeepSeek/OpenAI a prompt/completion/total + cache hit/miss.Nprompt_tokensinput_tokenspromptTokensr   completion_tokensoutput_tokenscompletionTokenstotal_tokenstotalTokensprompt_cache_hit_tokenscache_hit_tokenspromptCacheHitTokensprompt_cache_miss_tokenscache_miss_tokenspromptCacheMissTokensprompt_tokens_detailscached_tokens)r   r   r"   r$   r'   )
isinstancedictintgetmax)rawprompt
completiontotal	cache_hit
cache_missdetailss          r   normalize_usager8   $   s   c4    	77>"	77>"	 	F #$ 	77?#	77%&	 	J 'F377=+AFQGE)* 	77%&	77)*	 	I *+ 	77&'	77*+	 	J gg-.G'4  >GKK8`GKKHZ<[`_`aI?vzi!mQ 23J{zQ5A:)q.Z[\_z#zi1nq
	!
aIMF./
	!	Q:>6./	Q J/Au#&q)#4$':$6 r   c                J    [        S[        U =(       d    S5      S-   S-  5      $ )uC   Estimación rough (~4 chars/token) si el proveedor no envía usage.r   r
         )r0   len)texts    r   estimate_tokens_from_textr>   \   s!    q3tzr?Q&1,--r   x   )modelapi_keyr   timeout
max_tokensc             #  .  #    U=(       d    SR                  5       =(       d    [        U5      nU(       d	  SSSS.v   gU=(       d    [        R                  5       =(       d    [        nUU SSS0S	.nUb  [        U5      S
:  a  [        U5      US'    [        R
                  " [        5       SU 3SS.USUS9n	U	R                  S:  a{  U	R                  SS n U	R                  5       nUR                  S0 5      R                  S5      =(       d    UR                  S5      =(       d    UnSSU	R                   SU 3SS.v   gSnSn U	R                  SS9 GH  nUc  M	  UR                  5       nU(       a  UR                  S5      (       d  M8  USS R                  5       nU(       d  MT  US:X  a    O [        R                  " U5      nUR                  S5      n[#        U[$        5      (       a	  U(       a  UnUR                  S5      =(       d    / nU(       d  M  US
   R                  S5      =(       d    0 R                  S5      =(       d    SnU(       d  GM  UU-  nSUS .v   GM      S#U0n['        U5      nU(       a  UUS'   S$US%.v   g! [        R                   a  n
SSU
 3SS.v    Sn
A
gSn
A
ff = f! [         a     GNf = f! [        R                    a     GM  f = f! [        R                   a  n
SS!U
 3S"S.v    Sn
A
gSn
A
ff = f7f)&z
Yields:
  {"type": "text_delta", "delta": "..."}
  {"type": "done", "data": {"reply": "...", "usage": {...}}}
  {"type": "error", "message": "...", "code": "..."}
r
   erroruD   Falta el DeepSeek Token. Configúralo en Agentes → DeepSeek Token.missing_api_key)typemessagecodeNTinclude_usage)r@   messagesstreamstream_optionsr   rC   zBearer zapplication/json)AuthorizationzContent-Type)headersjsonrL   rB   z*No se pudo conectar con el proveedor LLM: llm_networki  rH   zError del proveedor LLM (z): llm_http)decode_unicodezdata:   z[DONE]usagechoicesdeltacontent
text_delta)rG   rW   zError leyendo stream del LLM: 
llm_streamreplydone)rG   data)r   r   DEFAULT_MODEL_CODEr.   requestspostr   RequestExceptionstatus_coder=   rP   r/   r   
iter_lines
startswithloadsJSONDecodeErrorr,   r-   r8   )rK   r@   rA   r   rB   rC   r   
model_codepayloadresponseexcdetail
full_reply	usage_rawraw_lineliner]   chunkchunk_usagerV   rW   done_payload
normalizeds                          r   stream_chat_completionrt   a   s.     =b


!
<%8%<C> &
 	
 	--446L:LJ*D1	G #j/A"5 #J==$&#*3% 2 	
$ s"t$	mmoGGR(,,Y7 ;;y)  283G3G2HF8T
 	

 	J'+I! ++4+@H>>#Dtw778>>#Dx

4(  ))G,K+t,,'	ii	*0bGQZ^^G,277	BHbE%J'%883 AD %,Z#8L +J *W<
00G $$ CC5I!
 	

 	"  		2 ''  $$ 7u= 
 	

 	s   BL&J 8LAJ< *LA*K) 2L4K
B"K) ,$LJ9$J4/L4J99L<
K
L	K

LK&!K) %K&&K) )L=LLLL)N)r   Session | Nonereturnstr)rv   rw   )r1   zdict[str, Any] | Nonerv   zdict[str, int] | None)r=   rw   rv   r.   )rK   zlist[dict[str, str]]r@   
str | NonerA   rx   r   ru   rB   r.   rC   z
int | Nonerv   zIterator[dict[str, Any]])__doc__
__future__r   rP   collections.abcr   typingr   r_   sqlalchemy.ormr   app.backend.core.configr   r^   r   r   r8   r>   rt    r   r   <module>r      s    H "  $   " ,& 7&
5p. !r1"r1 r1 	r1
 	r1 r1 r1 r1r   