Files
forge/apps/api/tests/test_otel.py
T
nihalashetty ae67bff5a3 feat: deep-agent canvas, live observability, and multi-environment tooling
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.
2026-07-28 01:49:19 +05:30

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