Files
openobserve/web/src
Ashish Kolhe 57caf3894c fix(agent-graph): correct stream gating, independent selector, and agent→model/tool edge attribution (#13404)
Fixes the Agent Graph end-to-end — UI (openobserve/web) + service-graph
backend (openobserve/core). Root causes were found and validated against
live trace data.

---

## 1. UI: wrong graph until refresh + no independent graph-type selector

The Agent Graph page embeds Traces' `ServiceGraph`, which read its
viz/layout type from the shared `searchObj.meta` singleton and
auto-loads on mount.

- **Wrong graph on return (from Agent Behavior):** the effective stream
fell back to the literal `default` trace stream before `loadAgents()`
resolved, so ServiceGraph rendered that whole stream's service topology
instead of the agent graph. Fixed: no `default` fallback in agent mode;
render is gated behind `graphReady` (a resolved agent stream), with a
spinner / "no agents" empty state otherwise; stale agent/stream
selections reconciled on reload.
- **No graph-type selector / inherited the Traces tab's type:** added
`vizType`/`layoutType` prop overrides on `ServiceGraph` (computeds
prefer props, else the shared store — Traces behavior unchanged), and
gave the Agent Graph page its own Tree/Graph + layout selector with its
own state and `agentGraph_*` localStorage keys.
- **Stream picker** now lists only the distinct `source_stream`s of
discovered agents (not agentless streams), annotated with per-stream
agent counts, with an option tooltip and wider control.

New `AgentGraphPage.spec.ts` (5 tests): gating, empty state, viz/layout
independence, stream-picker scoping. `vue-tsc` app + vitest typecheck
clean.

## 2. Backend: agent→model/tool edges mis-attributed to the service

Model/tool spans (`chat`, `execute_tool`) don't carry
`gen_ai.agent.name` — it lives on an ancestor `invoke_agent` span
(confirmed as the norm across the OTel GenAI spec, Google ADK, OpenAI
Agents SDK, OpenInference, CrewAI, LangGraph). The edge builder climbs
≤4 parent hops to find it, else falls back to `service_name`. When an
intermediate span is missing (the spec permits omitting `invoke_agent`;
inconsistent sampling / async context loss produce orphan LLM spans),
the climb fails and the span mis-attributes to the service — a spurious
`service → model` / `service → tool` edge.

Fix: add a per-trace agent CTE (`MAX(gen_ai_agent_name) GROUP BY
trace_id`) as a fallback level in the COALESCE, between the ancestor
climb and the `service_name` fallback, for both edge queries. This
matches the industry-recommended fallback (group by trace when the chain
breaks).

### Validation (live introspect data, 2 streams, 7-day window)

| stream | before | after |
|---|---|---|
| `sre_agent_traces_production` | `service → model` (47+3 reqs) |
eliminated → o2_ai_agent (247) / sre_rca_agent (29) |
| `sre_agent_traces_production_us2` | `service → model` (20 reqs) |
eliminated → o2_ai_agent (279) |

- No cross-agent contamination in the multi-agent stream (traces are
single-agent). No tool-edge regression.
- Non-agent traces: CTE returns nothing → NULL join → falls to
`service_name` exactly as before (no behavior change).
- Perf: single-column `GROUP BY trace_id` aggregate, ~+25% wallclock on
the batch edge query; runs in the hourly job, not a hot path.

Unit tests cover the COALESCE position and CTE builder.

## Not included / follow-up
- **env/version in the agent dropdown** requires backend extractor + DB
migration + API changes. Tracked separately.

## Note
Full enterprise `cargo build` isn't runnable in the dev environment (the
`o2_enterprise` crate is a resolution stub); the SQL-builder logic is
verified via standalone rustc and the runtime behavior via the live
dry-run above. CI runs the real enterprise build.
2026-07-23 10:56:29 +00:00
..
2025-12-05 15:43:51 +05:30