ae67bff5a3
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
47 lines
2.1 KiB
Python
47 lines
2.1 KiB
Python
"""OpenTelemetry export emits GenAI-semconv spans from run SpanRecords."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from forge.tracing import otel
|
|
from forge.tracing.tracer import SpanRecord
|
|
|
|
|
|
def test_export_emits_spans_with_genai_attributes():
|
|
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
|
|
|
|
exporter = InMemorySpanExporter()
|
|
assert otel.configure(exporter=exporter) is True
|
|
assert otel.enabled() is True
|
|
|
|
records = [
|
|
SpanRecord(id="s1", parent_id=None, name="agent_1", kind="llm", start=1000.0, end=1000.5,
|
|
start_wall=1_700_000_000.0, end_wall=1_700_000_000.5,
|
|
model="openai:gpt-4o-mini", input_tokens=120, output_tokens=30, cost_usd=0.0001),
|
|
SpanRecord(id="s2", parent_id="s1", name="get_order", kind="tool", start=1000.5, end=1000.6,
|
|
start_wall=1_700_000_000.5, end_wall=1_700_000_000.6, error="boom"),
|
|
]
|
|
otel.export(records, trace_name="run")
|
|
|
|
spans = exporter.get_finished_spans()
|
|
# One root span ("run") now groups the child spans into a single trace with a real hierarchy.
|
|
assert len(spans) == 3
|
|
by_name = {s.name: s for s in spans}
|
|
root, llm, tool = by_name["run"], by_name["agent_1"], by_name["get_order"]
|
|
assert llm.attributes["gen_ai.request.model"] == "openai:gpt-4o-mini"
|
|
assert llm.attributes["gen_ai.system"] == "openai"
|
|
assert llm.attributes["gen_ai.usage.input_tokens"] == 120
|
|
assert tool.attributes["error"] is True
|
|
# Wall-clock start time (post-2020 in ns), NOT the ~1970 that exporting monotonic seconds gave.
|
|
assert llm.start_time > 1_600_000_000_000_000_000
|
|
# Single trace, correct parent/child nesting: run -> agent_1 -> get_order.
|
|
assert root.context.trace_id == llm.context.trace_id == tool.context.trace_id
|
|
assert root.parent is None
|
|
assert llm.parent.span_id == root.context.span_id
|
|
assert tool.parent.span_id == llm.context.span_id
|
|
|
|
|
|
def test_export_noop_when_unconfigured():
|
|
# reset to unconfigured state
|
|
otel._tracer = None
|
|
otel.export([SpanRecord(id="x", parent_id=None, name="n", kind="node", start=0.0, end=1.0)]) # must not raise
|