o
    pj                     @  s  d Z ddlm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 ddlmZmZ dd	lmZmZ ed
Zee jd Zed d d Zd9ddZd:ddZddd;ddZd<d!d"Zd=d%d&Zd>d(d)Zd?d.d/Z d@d1d2Z!dAd4d5Z"dBd7d8Z#dS )Cz1Centralized OpenAI client for the platform layer.    )annotationsN)Path)Any)OpenAI)convert_from_bytes)get_platform_loggerlog_structured)consumerecord_observed_usageai   platformsecretsopenai_keysgpt-4o-minic                 C  s,  |pi }t | pi dtr| pi dpi dnd}|r"t|nd}|r7t||d|d| p/i ddd	 t||d
}t|d}t|||d}	z|jj	j
di |	}
W nL ty } z@ttd|||t | tro| pki dndt|jt|ddt|ddt|ddd
 |r|rt||d| d| pi ddd	  d}~ww d}|
jr|
jd jr|
jd jjpd}t||d}t|
}|rt||d|dd| d|t | tr| pi dnd|dd|dddd	 ttd||||dt | tr| pi dndd |||t|
ddt|
dddS ) zBExecute an OpenAI request and return normalized output plus usage.tenant	tenant_idNr   	ai_tokensprovider_reservation
request_id)kindr   )r   app_idmetricamountmetadatar   )api_key)promptmodeloptionsai_provider_request_failedstatus_codecodeparam)r   r   r   r   exception_typehttp_statusprovider_codeprovider_paramprovider_reservation_release contentr   total_tokens#provider_reservation_reconciliationinput_tokensoutput_tokens)r   r   r   r.   r/   ai_usage)r   r   r   tokensr   _request_idid)outputusage
raw_outputprovider_request_idprovider_response_id )
isinstancegetdict_estimated_capacityr	   _resolve_api_keyr   _build_request_kwargschatcompletionscreate	Exceptionr   loggertype__name__getattrr
   choicesmessager+   _normalize_output_extract_usage)contextr   r   r   r   r   reservationr   clientrequest_kwargsresponseexcr+   r4   r5   r9   r9   #bridge_platform/ai/openai_client.pyrun_llm   s   0








rS   r   dict[str, Any]returnintc                 C  s:   |  drtdt|  dpdS tdt|  dpdS )zFConservative reservation; actual usage is recorded after the response.
file_bytes   capacity_reservation_tokensiP  i  )r;   maxrV   )r   r9   r9   rR   r=   f   s   
r=   r   r   strr   
str | Nonec                C  s   t ddt| pd d}t ddt| pd d}t ddt|p(d d}|rA|rAttd | | d nd }tt|  d }ttd	 }t	d
| }t	dpet	d}	|po|po|po|po|	}
|
syt
d| |
S )Nz
[^A-Z0-9]+_r)   z[^a-z0-9_]+z[^a-z0-9_-]+z_-tenantsz.keyzdev.keyOPENAI_API_KEY_OPENAI_API_KEY_DEVOPENAI_API_KEYz"Missing OpenAI API key for app_id=)resubr[   upperstriplower_read_key_file	_KEYS_DIRosgetenvRuntimeError)r   r   
normalizedsafe_appsafe_tenant
tenant_keyapp_keydev_keyenv_app_keyenv_dev_keyr   r9   r9   rR   r>   m   s      &r>   pathr   c                 C  sD   z
| j dd }W n ty   Y d S  ty   Y d S w |p!d S )Nutf-8)encoding)	read_textre   FileNotFoundErrorOSError)rt   valuer9   r9   rR   rg   ~   s   rg   r   r   c                 C  s   |dt | |ddgd}|d}|dkrddi|d< |S |dkrB|d}t|ts0td	dt|d
p8dd|dd|d< |S )Nuser)r   r   )roler+   )r   messagesresponse_formatjson_objectrE   json_schemaz4json_schema response format requires a schema objectschema_namestructured_outputT)namestrictschema)rE   r   )_build_contentr;   r:   r<   
ValueErrorr[   )r   r   r   rO   r~   r   r9   r9   rR   r?      s*   





r?   list[dict[str, Any]]c                 C  sp   d| dg}| d}|r6t|| dpdd\}}dd l}||d}|d	d
d| d| id |S )Ntext)rE   r   rW   mimetypezapplication/octet-streamrW   r   r   ru   	image_urlurlzdata:z;base64,)rE   r   )r;   _prepare_visual_inputbase64	b64encodedecodeappend)r   r   r+   rW   r   r   b64r9   r9   rR   r      s   

r   rW   bytesr   tuple[bytes, str]c                 C  s   |dkr
t | dfS | |fS )Nzapplication/pdfz	image/png)_pdf_first_page_to_pngr   r9   r9   rR   r      s   r   	pdf_bytesc                 C  sJ   t | ddddd}|stdddlm} | }|d j|dd	 | S )
N   pngrX   )dpifmt
first_page	last_pagez(PDF conversion failed: no pages renderedr   )BytesIOPNG)format)r   rk   ior   savegetvalue)r   pagesr   bufr9   r9   rR   r      s   r   r+   c                 C  s:   | ddv rzt| W S  ty   d| i Y S w | S )Nr~   >   r   r   _raw)r;   jsonloadsrC   r*   r9   r9   rR   rJ      s   rJ   dict[str, int]c                 C  s\   t | dd }|d u rddddS tt |ddpdtt |ddp!dtt |ddp*ddS )Nr5   r   )r.   r/   r,   prompt_tokenscompletion_tokensr,   )rG   rV   )rP   r5   r9   r9   rR   rK      s   rK   )r   N)r   rT   rU   rV   )r   r[   r   r\   rU   r[   )rt   r   rU   r\   )r   r[   r   r[   r   rT   rU   rT   )r   r[   r   rT   rU   r   )rW   r   r   r[   rU   r   )r   r   rU   r   )r+   r[   r   rT   )rU   r   )$__doc__
__future__r   r   ri   rb   pathlibr   typingr   openair   	pdf2imager   'bridge_platform.logging.platform_loggerr   r   bridge_platform.quotas.servicer	   r
   rD   __file__resolveparents_PROJECT_ROOTrh   rS   r=   r>   rg   r?   r   r   r   rJ   rK   r9   r9   r9   rR   <module>   s0    

O






	