Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions TELEMETRY-CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,16 @@ them so the graph span and the `invoke_agent` root share one name for the graph
everything LaunchDarkly owns on a span sits under `launchdarkly.`. `ld.ai.` stays reserved for
metric and event keys.

Every `launchdarkly.graph` span, from `graph()` and from each `native_graph` adapter, goes through
`set_ld_span_attributes`, so it carries the identity in section 2 and the `feature_flag` event.
Its config key is the graph key. The graph-level events (`$ld:ai:graph:invocation_success`,
`invocation_failure`, `duration:total` and `total_tokens`) use the same track data, so their
`configKey` is the graph key too, whichever runner produced them. Node-level events keep the node's
own config key.

A native adapter has no graph variation metadata, so its graph span and graph events report an
empty `variationKey` and version 1. `graph()` reports the real values.

Nothing else in `graph.py` or `native_graph.py` needs work.

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
get_client,
make_track_data,
report_usage,
set_ld_span_attributes,
to_ld_context,
)
from launchdarkly_ai_server.utils import make_graph_track_data

from ._version import PACKAGE_NAME, __version__

Expand Down Expand Up @@ -240,20 +242,30 @@ async def invoke(
)
raw_handlers: dict[str, Any] = _opts.get("tool_handlers") or {}

start_time = time.monotonic()
run_id = str(uuid.uuid4())
tracer_name = "@launchdarkly/ai-claude-agents"
if _HAS_OTEL:
span = trace.get_tracer(tracer_name).start_span("launchdarkly.graph")
span.set_attribute("launchdarkly.graph.key", def_obj.key)
else:
span = None

start_time = time.monotonic()
run_id = str(uuid.uuid4())
path: list[str] = []
total_usage = {"input": 0, "output": 0, "total": 0}
subagent_tool_ctx: dict[str, Any] = {}

# One try covers setup and the run, so a setup error (a sub-agent tool
# that fails to build) is recorded on the span and tracked as an
# invocation failure, like a run error. The span ends exactly once, in
# the finally.
try:
if span:
set_ld_span_attributes(
span,
{
"__ld": make_graph_track_data(def_obj.key, run_id),
"ldContext": raw_ld_context,
},
)

async def _build_node(node: GraphNode) -> None:
if node.key == root.key:
Expand Down Expand Up @@ -355,29 +367,17 @@ async def _subagent_execute(
)
root_start = time.monotonic()

try:
result = await _run_query(
root,
input_text,
vs,
raw_handlers,
ld_context,
def_obj.key,
run_id,
root_child_tools,
history,
)
except Exception as exc:
if span:
span.record_exception(exc)
span.set_status(SpanStatusCode.ERROR, str(exc))
span.end()
if ld_context:
td = make_track_data(root, def_obj.key, run_id)
get_client().track(
"$ld:ai:graph:invocation_failure", ld_context, td, 1
)
raise
result = await _run_query(
root,
input_text,
vs,
raw_handlers,
ld_context,
def_obj.key,
run_id,
root_child_tools,
history,
)

final_output = result["output"]
root_usage = result["usage"]
Expand Down Expand Up @@ -411,30 +411,38 @@ async def _subagent_execute(
span.set_attribute("gen_ai.usage.input_tokens", total_usage["input"])
span.set_attribute("gen_ai.usage.output_tokens", total_usage["output"])
span.set_attribute("gen_ai.usage.total_tokens", total_usage["total"])
span.set_status(SpanStatusCode.OK)
span.end()

if ld_context:
root_td = make_track_data(root, def_obj.key, run_id)
graph_td = make_graph_track_data(def_obj.key, run_id)
client = get_client()
client.track(
"$ld:ai:graph:duration:total", ld_context, root_td, graph_dur
"$ld:ai:graph:duration:total", ld_context, graph_td, graph_dur
)
client.track(
"$ld:ai:graph:total_tokens",
ld_context,
root_td,
graph_td,
total_usage["total"],
)
client.track("$ld:ai:graph:invocation_success", ld_context, root_td, 1)
client.track("$ld:ai:graph:invocation_success", ld_context, graph_td, 1)

if span:
span.set_status(SpanStatusCode.OK)
return {"response": final_output, "usage": total_usage}

except Exception as exc:
if span:
span.record_exception(exc)
span.set_status(SpanStatusCode.ERROR, str(exc))
span.end()
if ld_context:
get_client().track(
"$ld:ai:graph:invocation_failure",
ld_context,
make_graph_track_data(def_obj.key, run_id),
1,
)
raise
finally:
if span:
span.end()

return types.SimpleNamespace(invoke=invoke)
Loading
Loading