11from __future__ import annotations
22
3+ import asyncio
4+ import time
5+
36import structlog
47from aio_pika .abc import AbstractIncomingMessage
58
9+ from ai_server .core .client import CoreClient
610from ai_server .messaging .idempotency import LruIdempotencyStore
711from ai_server .messaging .publisher import CallbackPublisher
812from ai_server .model .envelope import Envelope
@@ -37,20 +41,28 @@ def __init__(
3741 idempotency : LruIdempotencyStore ,
3842 callback_routing_key : str ,
3943 filler_pattern : str ,
44+ core_client : CoreClient | None = None ,
4045 ) -> None :
4146 self ._stt = stt
4247 self ._storage = storage
4348 self ._publisher = publisher
4449 self ._idempotency = idempotency
4550 self ._callback_routing_key = callback_routing_key
4651 self ._filler_pattern = filler_pattern
52+ self ._core_client = core_client
4753
4854 async def handle (self , message : AbstractIncomingMessage ) -> None :
4955 async with message .process (requeue = False ):
5056 try :
51- envelope = Envelope [AnalyzeVoiceRequest ].model_validate_json (message .body )
57+ envelope = Envelope [AnalyzeVoiceRequest ].model_validate_json (
58+ message .body
59+ )
5260 except Exception as exc :
53- log .error ("voice.parse.failed" , error = str (exc ), delivery_tag = message .delivery_tag )
61+ log .error (
62+ "voice.parse.failed" ,
63+ error = str (exc ),
64+ delivery_tag = message .delivery_tag ,
65+ )
5466 raise
5567
5668 if self ._idempotency .is_seen_then_mark (envelope .message_id ):
@@ -74,13 +86,29 @@ async def handle(self, message: AbstractIncomingMessage) -> None:
7486 await self ._publish_failed (envelope , req , code = "AUDIO_FETCH_FAILED" )
7587 return
7688
89+ started = None
7790 try :
91+ started = time .perf_counter ()
7892 result = await self ._stt .transcribe (
7993 audio_bytes = audio_bytes ,
8094 content_type = req .content_type ,
8195 hint = req .previous_question_text ,
8296 )
97+ self ._record_stt_log (
98+ req ,
99+ envelope ,
100+ latency_ms = _elapsed_ms (started ),
101+ status = "SUCCESS" ,
102+ error_message = None ,
103+ )
83104 except SttError as exc :
105+ self ._record_stt_log (
106+ req ,
107+ envelope ,
108+ latency_ms = _elapsed_ms (started ),
109+ status = "FAILED" ,
110+ error_message = exc .message ,
111+ )
84112 log .error (
85113 "voice.stt.failed" ,
86114 error = str (exc ),
@@ -90,7 +118,16 @@ async def handle(self, message: AbstractIncomingMessage) -> None:
90118 await self ._publish_failed (envelope , req , code = exc .code )
91119 return
92120 except Exception as exc :
93- log .error ("voice.stt.unexpected" , error = str (exc ), session_id = req .session_id )
121+ self ._record_stt_log (
122+ req ,
123+ envelope ,
124+ latency_ms = _elapsed_ms (started ),
125+ status = "FAILED" ,
126+ error_message = str (exc ),
127+ )
128+ log .error (
129+ "voice.stt.unexpected" , error = str (exc ), session_id = req .session_id
130+ )
94131 await self ._publish_failed (envelope , req , code = "TRANSCRIPTION_FAILED" )
95132 return
96133
@@ -123,7 +160,9 @@ async def handle(self, message: AbstractIncomingMessage) -> None:
123160 trace_id = envelope .trace_id ,
124161 )
125162
126- async def _publish_failed (self , envelope , req : AnalyzeVoiceRequest , * , code : str ) -> None :
163+ async def _publish_failed (
164+ self , envelope , req : AnalyzeVoiceRequest , * , code : str
165+ ) -> None :
127166 payload = VoiceCallbackPayload (
128167 session_id = req .session_id ,
129168 interview_message_id = req .message_id ,
@@ -142,3 +181,48 @@ async def _publish_failed(self, envelope, req: AnalyzeVoiceRequest, *, code: str
142181 correlation_id = envelope .message_id ,
143182 context = envelope .context ,
144183 )
184+
185+ def _record_stt_log (
186+ self ,
187+ req : AnalyzeVoiceRequest ,
188+ envelope ,
189+ * ,
190+ latency_ms : int | None ,
191+ status : str ,
192+ error_message : str | None ,
193+ ) -> None :
194+ if self ._core_client is None :
195+ return
196+
197+ async def _do () -> None :
198+ try :
199+ await self ._core_client .record_ai_log (
200+ request_type = "stt.transcribe" ,
201+ model_name = _provider_model_name (self ._stt ),
202+ input_tokens = None ,
203+ output_tokens = None ,
204+ latency_ms = latency_ms ,
205+ status = status ,
206+ user_id = envelope .context .user_id ,
207+ session_id = req .session_id ,
208+ error_message = (error_message [:1000 ] if error_message else None ),
209+ )
210+ except Exception as exc :
211+ log .warn (
212+ "voice.ai_log.failed" , error = str (exc ), session_id = req .session_id
213+ )
214+
215+ asyncio .create_task (_do ())
216+
217+
218+ def _elapsed_ms (started : float | None ) -> int | None :
219+ if started is None :
220+ return None
221+ return int ((time .perf_counter () - started ) * 1000 )
222+
223+
224+ def _provider_model_name (provider ) -> str | None :
225+ value = getattr (provider , "model_name" , None )
226+ if isinstance (value , str ):
227+ return value
228+ return None
0 commit comments