
    uj!                         d 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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Zd	e	eef   d
efdZ G d de      Zd
efdZy)uG   OpenAI API 클라이언트 — urllib 기반 구현 + 자동 DB 로깅.    N)AnyDictList)settings)traced_call)BaseLLMClient)log_llm_callz#https://api.openai.com/v1/responsespayloadreturnc                    | j                  d      }t        |t              r|j                         r|S | j                  d      }t        |t              r|D ]  }t        |t
              s|j                  d      }t        |t              s6|D ]`  }t        |t
              s|j                  d      dk(  s)|j                  d      }t        |t              sK|j                         s\|c c S   t        d      )z/Extract text from OpenAI Responses API payload.output_textoutputcontenttypetextz,OpenAI response did not include output_text.)get
isinstancestrstriplistdictRuntimeError)r
   r   r   itemr   partr   s          S/Users/manta/Documents/Projects/TheRoad-I1/backend/app/modules/llm/openai_client.py_response_text_from_payloadr      s    ++m,K+s#(9(9(;[["F&$DdD)hhy)Ggt,dD)dhhv.>-.O88F+D!$,#	    E
FF    c                       e Zd ZdZ	 	 ddedz  dedz  ddfdZddZ edd	d
 d d      	 	 	 ddededeee	f   dede
dedz  defd       Zy)OpenAIClientuD   OpenAI Responses API client using urllib.request + 자동 DB 로깅.Napi_keymodelr   c                 R    || _         |xs t        j                  | _        i | _        y N)_explicit_keyr   openai_model_model_ctx)selfr    r!   s      r   __init__zOpenAIClient.__init__,   s%     %4x44$&	r   c                 <    | j                   j                  |       | S )uN   로깅 컨텍스트 설정: project_id, episode_id, operation_type, step_name.)r'   update)r(   kwargss     r   set_contextzOpenAIClient.set_context8   s    		 r   structured_outputopenaic                     t        | dd      S )Nr&   unknown)getattr)r(   aks      r   <lambda>zOpenAIClient.<lambda>>   s    h	0Rr   c                     |S r#    )r(   system_promptuser_promptr3   r4   s        r   r5   zOpenAIClient.<lambda>?   s    %r   z[structured output])	operationprovidermodel_of	prompt_ofr   r8   r9   response_schemaschema_name
max_tokens_api_keyc                     |#ddl m}  | fdd j                        S t        j                  |st        d       j                  dd	d
dddid}|d<   t        j                  j                  t        t        j                  |      j                  d      d| ddd      }	t        j                  }
t        j                  }d}t!        j"                         }t%        d|dz         D ]`  }	 t        j                  j'                  |	|
      5 }t        j(                  |j+                         j-                  d            }ddd        na t;        t!        j"                         |z
  d!z        }t=        d- j                  d"t?        |      |d# j@                   t        d&|       t;        t!        j"                         |z
  d!z        }tI              }|jK                  d'i       }|jK                  d(      }|jK                  d)      }t=        d- j                  ||||d*d+ j@                   	 t        j(                  |      S # 1 sw Y   xY w# t        j.                  j0                  $ r}|j+                         j-                  dd      }t        d|j2                   d|       }ddl m}  |||j2                  |       ddl m} |j2                  d v r+||k  r& ||      st!        j8                  d|z         Y d}~#t;        t!        j"                         |z
  d!z        }t=        d- j                  d"t?        |      |d# j@                   ||d}~wt        j.                  jB                  tD        jF                  f$ r}|}||k  rt!        j8                  d|z         Y d}~t;        t!        j"                         |z
  d!z        }t=        d- j                  d"t?        |      |d# j@                   t        d$| d%|       |d}~ww xY w# t        jL                  $ r}t        d,|       |d}~ww xY w).uL   ★기록: urllib 직접 호출이라 litellm 콜백 밖이다 (2026-08-07).Nr   )call_with_key_failoverc                 2    j                  |       S )N)r?   r@   rA   )generate_structured)r4   r@   r>   r?   r(   r8   r9   s    r   r5   z2OpenAIClient.generate_structured.<locals>.<lambda>R   s$    $22!; +
 3  r   zopenai_client.responses)where	fixed_keyz!OpenAI API key is not configured.g?Fformatjson_schemaT)r   namestrictschema)r!   instructionsinputtemperaturestorer   max_output_tokenszutf-8zBearer zapplication/json)AuthorizationzContent-TypePOST)dataheadersmethod      )timeoutreplace)errorszOpenAI API error z: )mark_openai_failure)statusbody)is_key_level_failure>             i  error)
model_namer9   r8   r]   error_messageduration_mszOpenAI API failed after z
 retries: zOpenAI API failed: usageinput_tokensoutput_tokenssuccess)rf   r8   r9   r   rh   rj   rk   r]   z&Structured output was not valid JSON:
r7   )'app.core.openai_keysrC   r$   r   llm_max_output_tokensr   r&   urllibrequestRequestOPENAI_API_URLjsondumpsencodellm_timeout_textllm_max_retriestime	monotonicrangeurlopenloadsreaddecodere   	HTTPErrorcoder\   r_   sleepintr	   r   r'   URLErrorsocketrY   r   r   JSONDecodeError)r(   r8   r9   r>   r?   r@   rA   rC   r^   rp   rY   max_retries
last_errorstart_msattemptrespr
   exc
error_textr\   r_   durationr   ri   rj   rk   s   ``````                    r   rE   z OpenAIClient.generate_structured=   sS     D)    0,,. . !77JBCC [[) )'"-	 
 !(2D$%..((D!((1#*8*!5 2  ) 
 ++..'+
>>#Qa0G)e^^++GW+E"jj););G)DEG F	 1X DNN,x74?@H ;;K+G!*o8 ))	 !4ZLABB(83t;<*73 GR(yy0		/2 	 
	
{{'# %'
	
 ii
	
	Z::d##S FE <<)) * XXZ..wy.I
)'zJ<@B
 E#JsxxjQ FHH 99#{2 4Z @JJq7{+ 08 ;tCD #{{"/"%j/x ii	 !c)LL))6>>: e 
k)JJq7{+ 08 ;tCD #{{"/"%j/x ii	 #%=k]*UXTY#Z[adde\ ## 	Z!HOPVYY	Zsc   !I0%3I#I0P9 #I-	(I00P6BM8#AM880P6(P1A$P11P69Q QQ )NN)r   r   )responseNN)__name__
__module____qualname____doc__r   r)   r-   r   r   r   r   r   rE   r7   r   r   r   r   )   s    N # 
't
' Tz
' 
	
'
 .R&2	4 &#IZIZ IZ c3h	IZ
 IZ IZ *IZ 
IZ4
IZr   r   c                      ddl m}   |        S )uO   활성 키 슬롯 — 1차 필드를 직접 보면 보조 키가 안 쓰인다.r   
active_key)rm   r   r   s    r   _active_openai_keyr      s    /<r   )r   rs   r   rx   urllib.errorro   urllib.requesttypingr   r   r   app.core.configr   app.modules.llm.image_tracerr   app.modules.llm.baser   app.modules.llm.llm_loggerr	   rr   r   r   r   r   r7   r   r   <module>r      si    M      " " $ 4 . 36Gc3h GC G.bZ= bZHC r   