mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-07-10 11:12:13 +02:00
chore: fix tracing for text chat mode
This commit is contained in:
parent
e23cce444f
commit
08a2435ba5
31 changed files with 1753 additions and 597 deletions
|
|
@ -27,6 +27,15 @@ from typing import TYPE_CHECKING, Awaitable, Callable, Optional, Set
|
|||
|
||||
from loguru import logger
|
||||
|
||||
from api.services.pipecat.realtime_feedback_events import (
|
||||
build_bot_text_event,
|
||||
build_function_call_end_event,
|
||||
build_function_call_start_event,
|
||||
build_pipeline_error_event,
|
||||
build_ttfb_metric_event,
|
||||
build_user_transcription_event,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from api.services.pipecat.in_memory_buffers import InMemoryLogsBuffer
|
||||
|
||||
|
|
@ -211,29 +220,23 @@ class RealtimeFeedbackObserver(BaseObserver):
|
|||
# Handle user transcriptions (interim) - WebSocket only
|
||||
elif isinstance(frame, InterimTranscriptionFrame):
|
||||
await self._send_ws(
|
||||
{
|
||||
"type": RealtimeFeedbackType.USER_TRANSCRIPTION.value,
|
||||
"payload": {
|
||||
"text": frame.text,
|
||||
"final": False,
|
||||
"user_id": frame.user_id,
|
||||
"timestamp": frame.timestamp,
|
||||
},
|
||||
}
|
||||
build_user_transcription_event(
|
||||
text=frame.text,
|
||||
final=False,
|
||||
user_id=frame.user_id,
|
||||
timestamp=frame.timestamp,
|
||||
)
|
||||
)
|
||||
# Handle user transcriptions (final) - WebSocket only
|
||||
# Complete turn text is persisted via register_turn_handlers
|
||||
elif isinstance(frame, TranscriptionFrame):
|
||||
await self._send_ws(
|
||||
{
|
||||
"type": RealtimeFeedbackType.USER_TRANSCRIPTION.value,
|
||||
"payload": {
|
||||
"text": frame.text,
|
||||
"final": True,
|
||||
"user_id": frame.user_id,
|
||||
"timestamp": frame.timestamp,
|
||||
},
|
||||
}
|
||||
build_user_transcription_event(
|
||||
text=frame.text,
|
||||
final=True,
|
||||
user_id=frame.user_id,
|
||||
timestamp=frame.timestamp,
|
||||
)
|
||||
)
|
||||
# Handle engine-queued speech (transition/tool messages) marked for
|
||||
# log persistence. The downstream TTSTextFrame(s) from the TTS service
|
||||
|
|
@ -241,23 +244,13 @@ class RealtimeFeedbackObserver(BaseObserver):
|
|||
# to avoid word-level log entries from word-timestamp providers.
|
||||
elif isinstance(frame, TTSSpeakFrame):
|
||||
if getattr(frame, "persist_to_logs", False):
|
||||
await self._append_to_buffer(
|
||||
{
|
||||
"type": RealtimeFeedbackType.BOT_TEXT.value,
|
||||
"payload": {"text": frame.text},
|
||||
}
|
||||
)
|
||||
await self._append_to_buffer(build_bot_text_event(text=frame.text))
|
||||
# Handle bot TTS text - respect pts timing, WebSocket only
|
||||
# Complete turn text is persisted via register_turn_handlers,
|
||||
# except for frames explicitly flagged persist_to_logs (e.g. recording
|
||||
# transcripts from play_audio) which bypass the aggregator path.
|
||||
elif isinstance(frame, TTSTextFrame):
|
||||
message = {
|
||||
"type": RealtimeFeedbackType.BOT_TEXT.value,
|
||||
"payload": {
|
||||
"text": frame.text,
|
||||
},
|
||||
}
|
||||
message = build_bot_text_event(text=frame.text)
|
||||
|
||||
# If frame has pts, queue it for timed delivery
|
||||
if frame.pts:
|
||||
|
|
@ -280,13 +273,10 @@ class RealtimeFeedbackObserver(BaseObserver):
|
|||
and frame_direction == FrameDirection.DOWNSTREAM
|
||||
):
|
||||
await self._send_message(
|
||||
{
|
||||
"type": RealtimeFeedbackType.FUNCTION_CALL_START.value,
|
||||
"payload": {
|
||||
"function_name": frame.function_name,
|
||||
"tool_call_id": frame.tool_call_id,
|
||||
},
|
||||
}
|
||||
build_function_call_start_event(
|
||||
function_name=frame.function_name,
|
||||
tool_call_id=frame.tool_call_id,
|
||||
)
|
||||
)
|
||||
# Handle function call result
|
||||
elif (
|
||||
|
|
@ -294,14 +284,11 @@ class RealtimeFeedbackObserver(BaseObserver):
|
|||
and frame_direction == FrameDirection.DOWNSTREAM
|
||||
):
|
||||
await self._send_message(
|
||||
{
|
||||
"type": RealtimeFeedbackType.FUNCTION_CALL_END.value,
|
||||
"payload": {
|
||||
"function_name": frame.function_name,
|
||||
"tool_call_id": frame.tool_call_id,
|
||||
"result": str(frame.result) if frame.result else None,
|
||||
},
|
||||
}
|
||||
build_function_call_end_event(
|
||||
function_name=frame.function_name,
|
||||
tool_call_id=frame.tool_call_id,
|
||||
result=frame.result,
|
||||
)
|
||||
)
|
||||
# Handle TTFB metrics - capture LLM generation time only
|
||||
elif isinstance(frame, MetricsFrame):
|
||||
|
|
@ -311,47 +298,42 @@ class RealtimeFeedbackObserver(BaseObserver):
|
|||
# Only send TTFB if it's from an LLM processor
|
||||
if metric_data.processor and "LLM" in metric_data.processor:
|
||||
await self._send_message(
|
||||
{
|
||||
"type": RealtimeFeedbackType.TTFB_METRIC.value,
|
||||
"payload": {
|
||||
"ttfb_seconds": metric_data.value,
|
||||
"processor": metric_data.processor,
|
||||
"model": metric_data.model,
|
||||
},
|
||||
}
|
||||
build_ttfb_metric_event(
|
||||
ttfb_seconds=metric_data.value,
|
||||
processor=metric_data.processor,
|
||||
model=metric_data.model,
|
||||
)
|
||||
)
|
||||
# Handle pipeline errors
|
||||
elif isinstance(frame, ErrorFrame):
|
||||
processor_name = str(frame.processor) if frame.processor else None
|
||||
payload = {
|
||||
"error": frame.error,
|
||||
"fatal": frame.fatal,
|
||||
"processor": processor_name,
|
||||
}
|
||||
extra_payload: dict[str, object] = {}
|
||||
# Surface structured fields when the underlying exception carries
|
||||
# them (e.g. google.genai APIError: code=1008, status=None,
|
||||
# message="Your project has been denied access...").
|
||||
exc = frame.exception
|
||||
if exc is not None:
|
||||
exc_type = type(exc).__name__
|
||||
payload["exception_type"] = exc_type
|
||||
payload["exception_message"] = str(exc)
|
||||
extra_payload["exception_type"] = exc_type
|
||||
extra_payload["exception_message"] = str(exc)
|
||||
for attr in ("code", "status", "message", "details"):
|
||||
value = getattr(exc, attr, None)
|
||||
if value is None or attr in payload:
|
||||
if value is None or attr in extra_payload:
|
||||
continue
|
||||
try:
|
||||
# Ensure the value is JSON-serializable; fall back
|
||||
# to str() for opaque objects (e.g. raw response).
|
||||
json.dumps(value)
|
||||
payload[attr] = value
|
||||
extra_payload[attr] = value
|
||||
except (TypeError, ValueError):
|
||||
payload[attr] = str(value)
|
||||
extra_payload[attr] = str(value)
|
||||
await self._send_message(
|
||||
{
|
||||
"type": RealtimeFeedbackType.PIPELINE_ERROR.value,
|
||||
"payload": payload,
|
||||
}
|
||||
build_pipeline_error_event(
|
||||
error=frame.error,
|
||||
fatal=frame.fatal,
|
||||
processor=processor_name,
|
||||
extra_payload=extra_payload or None,
|
||||
)
|
||||
)
|
||||
|
||||
async def _send_ws(self, message: dict):
|
||||
|
|
@ -401,14 +383,11 @@ def register_turn_log_handlers(
|
|||
logs_buffer.increment_turn()
|
||||
try:
|
||||
await logs_buffer.append(
|
||||
{
|
||||
"type": RealtimeFeedbackType.USER_TRANSCRIPTION.value,
|
||||
"payload": {
|
||||
"text": message.content,
|
||||
"final": True,
|
||||
"timestamp": message.timestamp,
|
||||
},
|
||||
}
|
||||
build_user_transcription_event(
|
||||
text=message.content,
|
||||
final=True,
|
||||
timestamp=message.timestamp,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to append user turn to logs buffer: {e}")
|
||||
|
|
@ -418,13 +397,10 @@ def register_turn_log_handlers(
|
|||
if message.content:
|
||||
try:
|
||||
await logs_buffer.append(
|
||||
{
|
||||
"type": RealtimeFeedbackType.BOT_TEXT.value,
|
||||
"payload": {
|
||||
"text": message.content,
|
||||
"timestamp": message.timestamp,
|
||||
},
|
||||
}
|
||||
build_bot_text_event(
|
||||
text=message.content,
|
||||
timestamp=message.timestamp,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to append assistant turn to logs buffer: {e}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue