Skip trace work when disabled; share the agent span builder

With TRACES_ENABLED off, LLM calls record no GenAI metrics, and a streamed
answer is joined into a preview only when a span keeps it. Agent runs and
continuations build their invoke_agent span in one place, which now reads
the model from model_id. The trace panel shows a stream's model time next
to how long it was open. Adds missing type hints.
This commit is contained in:
arc53-machine committed 2026-09-23 22:09:58 +01:00
1 parent 699906a015
commit 0bbbac0eb2
18 files changed
+88 -35

No files matched your search

+12 -6
View File
@@ -7,8 +7,9 @@ fallback each get their own span with the provider that actually ran.
from __future__ import annotations
from typing import Any, Dict, Iterable, Optional
from typing import Any, Dict, Iterable, Optional, Union
from docsgpt.core.settings import settings
from docsgpt.tracing import core
from docsgpt.tracing.otel import provider_name, record_llm_metrics
@@ -16,7 +17,9 @@ from docsgpt.tracing.otel import provider_name, record_llm_metrics
CACHE_HIT_ATTR = "_trace_cache_hit"
def start_llm_span(llm: Any, model: Optional[str], *, stream: bool, tools: Any = None):
def start_llm_span(
llm: Any, model: Optional[str], *, stream: bool, tools: Any = None
) -> Union[core.Span, Any]:
"""Open a ``chat {model}`` span for a call on ``llm`` (no-op without a trace)."""
if core.current_trace() is None:
return core.NOOP_SPAN
@@ -54,7 +57,7 @@ def finish_llm_call(
ttft_ms: Optional[int] = None,
cost_usd: Optional[float] = None,
estimated: bool = True,
output: Optional[str] = None,
output: Union[str, Iterable[Any], None] = None,
) -> None:
"""Close the call's span and record the GenAI client metrics.
@@ -69,13 +72,16 @@ def finish_llm_call(
ttft_ms: Time to first streamed chunk.
cost_usd: Cost priced for the call, when known.
estimated: True when token counts are local estimates.
output: Response text for the preview.
output: The response text, or the streamed chunks to join into it;
joined only when the span keeps a preview.
"""
cache_hit = bool(getattr(llm, CACHE_HIT_ATTR, False))
try:
setattr(llm, CACHE_HIT_ATTR, False)
except AttributeError:
pass
if not settings.TRACES_ENABLED:
return
record_llm_metrics(
provider=getattr(llm, "provider_name", None),
model=str(model) if model else None,
@@ -86,8 +92,8 @@ def finish_llm_call(
)
if not span:
return
if output:
span.preview("output", output)
if output and settings.TRACES_CAPTURE_CONTENT:
span.preview("output", output_text(output))
span.end(
None if completed or error is not None else core.STATUS_CANCELLED,
error=error,
+7 -3
View File
@@ -12,7 +12,9 @@ _SNIPPET_CHARS = 240
_MAX_PREVIEW_CHUNKS = 20
def start_retrieval_span(name: str, *, sources: Optional[Iterable[Any]] = None, **attributes: Any):
def start_retrieval_span(
name: str, *, sources: Optional[Iterable[Any]] = None, **attributes: Any
) -> Any:
"""Open a ``retrieval`` span (a container: embeddings and searches nest under it)."""
source_ids = [str(s) for s in (sources or []) if s]
base = {
@@ -64,7 +66,7 @@ def describe_documents(span: Any, docs: Optional[List[Dict[str, Any]]], *, query
)
def start_embedding_span(model: Optional[str], *, inputs: int = 1, **attributes: Any):
def start_embedding_span(model: Optional[str], *, inputs: int = 1, **attributes: Any) -> Any:
"""Open an ``embeddings`` span for a query embedding (leaf)."""
base = {
"gen_ai.operation.name": "embeddings",
@@ -79,7 +81,9 @@ def start_embedding_span(model: Optional[str], *, inputs: int = 1, **attributes:
)
def start_source_search_span(source_id: Any, *, top_k: Optional[int] = None, **attributes: Any):
def start_source_search_span(
source_id: Any, *, top_k: Optional[int] = None, **attributes: Any
) -> Any:
"""Open a per-source vector ``search`` span (leaf)."""
base = {
"gen_ai.operation.name": "search",