mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-28 08:49:42 +02:00
feat: run per node QA
This commit is contained in:
parent
a836825b83
commit
c8742dbdc0
13 changed files with 1012 additions and 373 deletions
154
api/services/workflow/qa/tracing.py
Normal file
154
api/services/workflow/qa/tracing.py
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
"""Langfuse / OpenTelemetry tracing helpers for QA analysis."""
|
||||
|
||||
import re
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from api.db.models import WorkflowRunModel
|
||||
|
||||
|
||||
def extract_trace_id(gathered_context: dict) -> str | None:
|
||||
"""Extract Langfuse trace_id from gathered_context trace_url.
|
||||
|
||||
URL format: https://langfuse.dograh.com/project/<project_id>/traces/<trace_id>
|
||||
"""
|
||||
trace_url = gathered_context.get("trace_url")
|
||||
if not trace_url:
|
||||
return None
|
||||
try:
|
||||
match = re.search(r"/traces/([a-fA-F0-9]+)$", trace_url)
|
||||
if match:
|
||||
return match.group(1)
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def setup_langfuse_parent_context(workflow_run: WorkflowRunModel):
|
||||
"""Set up OTEL parent context from the workflow run's Langfuse trace.
|
||||
|
||||
Returns the parent context object, or None if tracing is unavailable.
|
||||
"""
|
||||
try:
|
||||
from opentelemetry.trace import (
|
||||
NonRecordingSpan,
|
||||
SpanContext,
|
||||
TraceFlags,
|
||||
set_span_in_context,
|
||||
)
|
||||
|
||||
from api.services.pipecat.tracing_config import (
|
||||
is_tracing_enabled,
|
||||
setup_tracing_exporter,
|
||||
)
|
||||
|
||||
if not is_tracing_enabled():
|
||||
return None
|
||||
|
||||
setup_tracing_exporter()
|
||||
|
||||
gathered_context = workflow_run.gathered_context or {}
|
||||
trace_id = extract_trace_id(gathered_context)
|
||||
if not trace_id:
|
||||
logger.debug("No trace_id found, skipping Langfuse tracing")
|
||||
return None
|
||||
|
||||
parent_span_ctx = SpanContext(
|
||||
trace_id=int(trace_id, 16),
|
||||
span_id=0x1,
|
||||
is_remote=True,
|
||||
trace_flags=TraceFlags(0x01),
|
||||
)
|
||||
return set_span_in_context(NonRecordingSpan(parent_span_ctx))
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to set up Langfuse parent context: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def add_qa_span_to_trace(
|
||||
parent_ctx,
|
||||
model: str,
|
||||
messages: list[dict],
|
||||
output: str,
|
||||
span_name: str,
|
||||
) -> None:
|
||||
"""Create a child span under the conversation trace."""
|
||||
if parent_ctx is None:
|
||||
return
|
||||
try:
|
||||
from opentelemetry import trace as otel_trace
|
||||
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
tracer = otel_trace.get_tracer("pipecat")
|
||||
with tracer.start_as_current_span(
|
||||
span_name,
|
||||
context=parent_ctx,
|
||||
) as span:
|
||||
add_llm_span_attributes(
|
||||
span,
|
||||
service_name="OpenAILLMService",
|
||||
model=model,
|
||||
operation_name=span_name,
|
||||
messages=messages,
|
||||
output=output,
|
||||
stream=False,
|
||||
parameters={"temperature": 0},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to trace span '{span_name}' to Langfuse: {e}")
|
||||
|
||||
|
||||
def create_node_summary_trace(
|
||||
model: str,
|
||||
messages: list[dict],
|
||||
output: str,
|
||||
node_name: str,
|
||||
) -> str | None:
|
||||
"""Create a standalone Langfuse trace for a node summary generation.
|
||||
|
||||
Returns the trace URL, or None if tracing is unavailable.
|
||||
"""
|
||||
try:
|
||||
from opentelemetry import trace as otel_trace
|
||||
from opentelemetry.context import Context
|
||||
|
||||
from api.services.pipecat.tracing_config import (
|
||||
is_tracing_enabled,
|
||||
setup_tracing_exporter,
|
||||
)
|
||||
from pipecat.utils.tracing.service_attributes import add_llm_span_attributes
|
||||
|
||||
if not is_tracing_enabled():
|
||||
return None
|
||||
|
||||
setup_tracing_exporter()
|
||||
|
||||
tracer = otel_trace.get_tracer("pipecat")
|
||||
|
||||
# Create a root span (new trace) for this node summary generation
|
||||
with tracer.start_as_current_span(
|
||||
f"node-summary-{node_name}",
|
||||
context=Context(),
|
||||
) as span:
|
||||
add_llm_span_attributes(
|
||||
span,
|
||||
service_name="OpenAILLMService",
|
||||
model=model,
|
||||
operation_name=f"node-summary-{node_name}",
|
||||
messages=messages,
|
||||
output=output,
|
||||
stream=False,
|
||||
parameters={"temperature": 0},
|
||||
)
|
||||
trace_id = format(span.get_span_context().trace_id, "032x")
|
||||
|
||||
from langfuse import get_client
|
||||
|
||||
langfuse = get_client()
|
||||
return langfuse.get_trace_url(trace_id=trace_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to create node summary trace for '{node_name}': {e}")
|
||||
return None
|
||||
Loading…
Add table
Add a link
Reference in a new issue