From b741329f1850e3735019b72417566e132c8e2cbf Mon Sep 17 00:00:00 2001 From: Adarsh Prashar Date: Mon, 15 Jun 2026 00:48:21 +0530 Subject: [PATCH] docs(otel): importable SigNoz dashboard for governed runs Add examples/otel/signoz with an importable SigNoz dashboard JSON and a short setup README, mirroring the existing Grafana + Tempo example. The dashboard is built from the OpenTelemetry GenAI spans RiskKernel already exports, so SigNoz users get the same cost/governance view as one JSON import: spend per run, budget halts by reason, tool-call outcomes, and latency and token burn by model. Targets SigNoz Query Builder v5 (dot-preserving attribute schema), so it references the gen_ai.* / riskkernel.* attributes by their pinned dotted names from api/v1/otel-genai.md. Cross-link from the Grafana example and record it in the changelog. --- CHANGELOG.md | 8 + examples/otel/grafana/README.md | 2 + examples/otel/signoz/README.md | 93 ++ .../dashboards/riskkernel-agent-runs.json | 934 ++++++++++++++++++ 4 files changed, 1037 insertions(+) create mode 100644 examples/otel/signoz/README.md create mode 100644 examples/otel/signoz/dashboards/riskkernel-agent-runs.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ec1e0c..fbd6283 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,14 @@ surface is governed by [`COMPATIBILITY.md`](COMPATIBILITY.md). ## [Unreleased] ### Added +- **Importable SigNoz dashboard.** [`examples/otel/signoz`](examples/otel/signoz) + ships a ready-made SigNoz dashboard (spend per run, budget halts by reason, + tool-call outcomes, latency and token burn by model) built from the same + OpenTelemetry GenAI spans RiskKernel already exports — so teams on SigNoz get the + "feed your existing observability" story as one JSON import, matching the existing + Grafana + Tempo example. Queries reference only the attribute names pinned in + [`api/v1/otel-genai.md`](api/v1/otel-genai.md); a short README covers pointing the + OTLP exporter at SigNoz (self-hosted and Cloud). - **One-command docker-compose quickstart.** [`examples/quickstart-compose`](examples/quickstart-compose) brings up the daemon, a stand-in mock LLM, and a tiny looping agent with a single `docker compose up` — so a newcomer watches the deterministic loop budget hard-stop diff --git a/examples/otel/grafana/README.md b/examples/otel/grafana/README.md index d57977b..b82fb2c 100644 --- a/examples/otel/grafana/README.md +++ b/examples/otel/grafana/README.md @@ -8,6 +8,8 @@ TraceQL metrics, and the dashboard is provisioned so it shows up on first load. > The [Jaeger quick-look](../docker-compose.yaml) one directory up is for reading > individual traces. This is the aggregate view — the panels a platform team watches. +> On [SigNoz](https://signoz.io) instead? [`../signoz/`](../signoz/) is the same +> dashboard, importable there. ![panels: total spend, output tokens, model calls, tool calls refused, spend over time, spend by run, p95 latency by model, model-call rate, tool calls by outcome, diff --git a/examples/otel/signoz/README.md b/examples/otel/signoz/README.md new file mode 100644 index 0000000..8b06559 --- /dev/null +++ b/examples/otel/signoz/README.md @@ -0,0 +1,93 @@ +# Cost & governance dashboard (SigNoz) + +An importable [SigNoz](https://signoz.io) dashboard for your governed runs — +**spend, token burn, budget halts, tool-call outcomes, and latency by model** — +built entirely from the OpenTelemetry spans RiskKernel already emits. No extra +instrumentation, no metrics pipeline: SigNoz aggregates the spans, and you import +one JSON file. + +> Already on Grafana + Tempo? [`../grafana/`](../grafana/) ships the same panels as +> a provisioned stack. This is the SigNoz equivalent for teams who already run it. + +## Point RiskKernel at SigNoz + +RiskKernel exports OTLP traces; SigNoz ingests OTLP. So this is the usual one env +var — point the exporter at your SigNoz OTLP endpoint and start the daemon (your +keys, as always): + +```bash +# Self-hosted SigNoz (OTLP collector listens on 4317 gRPC / 4318 HTTP): +export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +export OTEL_EXPORTER_OTLP_PROTOCOL=grpc +export ANTHROPIC_API_KEY=sk-ant-... +riskkernel serve + +# Then drive some governed traffic — point your app's base URL at the proxy: +export OPENAI_BASE_URL=http://localhost:7070/v1 +# (or run any example under ../../ — the loop-killer, the MCP demo, etc.) +``` + +**SigNoz Cloud** (or any endpoint that needs a key) uses the standard OTLP env +vars — set the ingestion key as a header: + +```bash +export OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest..signoz.cloud:443 +export OTEL_EXPORTER_OTLP_PROTOCOL=grpc +export OTEL_EXPORTER_OTLP_HEADERS="signoz-ingestion-key=$SIGNOZ_INGESTION_KEY" +``` + +Header values carry secrets and are never logged. See the OTLP export notes in +[`../README.md`](../README.md) for HTTP-vs-gRPC and other backends. + +> **No telemetry by default.** RiskKernel exports *nothing* unless +> `OTEL_EXPORTER_OTLP_ENDPOINT` is set; spans go only to the SigNoz you point it +> at. See [`SECURITY.md`](../../../SECURITY.md). + +## Import the dashboard + +In the SigNoz UI: **Dashboards → New dashboard → Import JSON**, and upload +[`dashboards/riskkernel-agent-runs.json`](dashboards/riskkernel-agent-runs.json). +Spend, halts, and tool outcomes fill in as runs execute (default range: last 1h, +adjust with the time picker). + +## What each panel is + +| Panel | Trace query (Query Builder) | +|---|---| +| Total spend / Output tokens / Model calls | `sum(riskkernel.cost.usd)`, `sum(gen_ai.usage.output_tokens)`, `count()` | +| **Tool calls refused** | `count()` over `execute_tool` spans where `riskkernel.tool.status != 'approved'` | +| Spend over time / Spend by run | `sum(riskkernel.cost.usd)`, grouped by `riskkernel.run.id` | +| p95 latency by model | `p95(durationNano)` group by `gen_ai.request.model` | +| Model-call rate by model | `rate()` group by `gen_ai.request.model` | +| Output tokens by model | `sum(gen_ai.usage.output_tokens)` group by `gen_ai.request.model` | +| Tool calls by outcome | `count()` group by `riskkernel.tool.status` (approved / blocked / denied / timeout) | +| Budget halts by reason | `count()` over spans where `riskkernel.halt.reason EXISTS`, group by reason | + +Every attribute these queries touch is pinned in +[`api/v1/otel-genai.md`](../../../api/v1/otel-genai.md) — the names are a stable +public contract, so the panels keep working across RiskKernel upgrades. + +## Notes & assumptions + +This dashboard targets **SigNoz Query Builder v5** (the dot-preserving attribute +schema), which references OpenTelemetry attributes by their original dotted names — +e.g. `riskkernel.cost.usd`, `gen_ai.request.model` — exactly as RiskKernel emits +them. A few things to know: + +- **Service name.** The summary panels filter on + `resource.service.name = 'riskkernel'`, the service name RiskKernel sets on the + spans it exports. If you run multiple services through one SigNoz, this scopes the + totals to RiskKernel; if you've overridden the service name, adjust the filter (or + drop it) in those panels. +- **`durationNano`.** Latency panels use SigNoz's built-in span-duration column + (`durationNano`, nanoseconds) — span duration is intrinsic, not a RiskKernel + attribute. +- **Older SigNoz (pre-v5).** If your SigNoz predates the v5 query builder / + dot-preserving schema, attribute keys may have been normalized to underscores + (e.g. `gen_ai_request_model`). Upgrade SigNoz, or edit the attribute keys in the + query builder after import. The pinned RiskKernel attribute names don't change; + only SigNoz's storage convention does. +- **Empty panels?** That almost always means no spans match the filter yet — drive + some governed traffic, widen the time range, or confirm the service-name filter + matches your deployment. The tool/halt panels stay empty until a tool call or a + budget halt actually happens. diff --git a/examples/otel/signoz/dashboards/riskkernel-agent-runs.json b/examples/otel/signoz/dashboards/riskkernel-agent-runs.json new file mode 100644 index 0000000..72972c3 --- /dev/null +++ b/examples/otel/signoz/dashboards/riskkernel-agent-runs.json @@ -0,0 +1,934 @@ +{ + "title": "RiskKernel — agent runs", + "description": "Spend, token burn, budget halts, tool-call outcomes, and latency by model for your governed agent runs, built from the OpenTelemetry GenAI spans RiskKernel exports. See api/v1/otel-genai.md for the pinned attribute set.", + "tags": [ + "riskkernel", + "agents", + "genai", + "llm" + ], + "version": "v5", + "uploadedGrafana": false, + "panelMap": {}, + "variables": {}, + "layout": [ + { + "i": "6b910a4b-e51d-59a7-9614-e7066b2435eb", + "x": 0, + "y": 0, + "w": 6, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "fe7534c6-cf29-516a-a4e0-982f2731d4d5", + "x": 6, + "y": 0, + "w": 6, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "cb33b8f3-7b32-5f20-9c34-4af83b74cec9", + "x": 12, + "y": 0, + "w": 6, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "fce208d9-765d-5930-b56a-e140c779d62d", + "x": 18, + "y": 0, + "w": 6, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "e58a7a9e-7061-5992-8a21-a035a1cc1e4e", + "x": 0, + "y": 6, + "w": 12, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "494c3d45-b8d3-5a95-be83-835a647acf09", + "x": 12, + "y": 6, + "w": 12, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "62d38e90-b6ad-5e63-aec1-b9f3fa10d3fe", + "x": 0, + "y": 12, + "w": 12, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "ae1cde96-640b-5941-a5df-64fa9b1ade20", + "x": 12, + "y": 12, + "w": 12, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "0dc68b95-21f3-5164-9506-ff2e5fe3d360", + "x": 0, + "y": 18, + "w": 12, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "4c6f14ff-3baa-58df-a40d-1d0a92d7150d", + "x": 12, + "y": 18, + "w": 12, + "h": 6, + "moved": false, + "static": false + }, + { + "i": "7f41705e-9810-513d-9877-2f53c6563113", + "x": 0, + "y": 24, + "w": 12, + "h": 6, + "moved": false, + "static": false + } + ], + "widgets": [ + { + "id": "6b910a4b-e51d-59a7-9614-e7066b2435eb", + "title": "Total spend", + "description": "Total cost charged to the ledger across governed runs in range.", + "panelTypes": "value", + "yAxisUnit": "usd", + "query": { + "queryType": "builder", + "id": "e54d8705-19bb-5a53-ba76-9ec9bf59bfd3", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "Spend (USD)", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "sum(riskkernel.cost.usd)", + "reduceTo": "sum" + } + ], + "groupBy": [], + "filter": { + "expression": "resource.service.name = 'riskkernel'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "fe7534c6-cf29-516a-a4e0-982f2731d4d5", + "title": "Output tokens", + "description": "Sum of completion tokens across model calls.", + "panelTypes": "value", + "yAxisUnit": "short", + "query": { + "queryType": "builder", + "id": "d4789456-bdc2-5462-8719-97f6dbed22b0", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "Output tokens", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "sum(gen_ai.usage.output_tokens)", + "reduceTo": "sum" + } + ], + "groupBy": [], + "filter": { + "expression": "resource.service.name = 'riskkernel'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "cb33b8f3-7b32-5f20-9c34-4af83b74cec9", + "title": "Model calls", + "description": "Count of governed model calls (chat spans).", + "panelTypes": "value", + "yAxisUnit": "short", + "query": { + "queryType": "builder", + "id": "f71b889c-d68d-5557-a47a-37674e5dbc06", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "Model calls", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "count()", + "reduceTo": "sum" + } + ], + "groupBy": [], + "filter": { + "expression": "gen_ai.operation.name = 'chat'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "fce208d9-765d-5930-b56a-e140c779d62d", + "title": "Tool calls refused", + "description": "Tool calls the governor did not approve (blocked / denied / timeout).", + "panelTypes": "value", + "yAxisUnit": "short", + "query": { + "queryType": "builder", + "id": "c8ca9d78-ba38-526f-8789-8dab55b39c7a", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "Refused", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "count()", + "reduceTo": "sum" + } + ], + "groupBy": [], + "filter": { + "expression": "gen_ai.operation.name = 'execute_tool' AND riskkernel.tool.status != 'approved'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "e58a7a9e-7061-5992-8a21-a035a1cc1e4e", + "title": "Spend over time (USD)", + "description": "Spend per time bucket.", + "panelTypes": "graph", + "yAxisUnit": "usd", + "query": { + "queryType": "builder", + "id": "64c108a5-e702-534b-a162-44a31f41c6c8", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "Spend", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "sum(riskkernel.cost.usd)" + } + ], + "groupBy": [], + "filter": { + "expression": "resource.service.name = 'riskkernel'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "494c3d45-b8d3-5a95-be83-835a647acf09", + "title": "Spend by run (USD)", + "description": "Spend grouped by governed run id.", + "panelTypes": "graph", + "yAxisUnit": "usd", + "query": { + "queryType": "builder", + "id": "459d84dd-b172-5f6d-9c8b-eecbd6e40a3e", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "{{riskkernel.run.id}}", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "sum(riskkernel.cost.usd)" + } + ], + "groupBy": [ + { + "key": "riskkernel.run.id", + "dataType": "string", + "type": "tag", + "isColumn": false, + "isJSON": false, + "id": "riskkernel.run.id--string--tag--false" + } + ], + "filter": { + "expression": "resource.service.name = 'riskkernel'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "62d38e90-b6ad-5e63-aec1-b9f3fa10d3fe", + "title": "p95 latency by model", + "description": "95th percentile model-call duration, per requested model.", + "panelTypes": "graph", + "yAxisUnit": "ns", + "query": { + "queryType": "builder", + "id": "4573d5b7-33d8-55f7-87ae-56428b920f0f", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "{{gen_ai.request.model}}", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "p95(durationNano)" + } + ], + "groupBy": [ + { + "key": "gen_ai.request.model", + "dataType": "string", + "type": "tag", + "isColumn": false, + "isJSON": false, + "id": "gen_ai.request.model--string--tag--false" + } + ], + "filter": { + "expression": "gen_ai.operation.name = 'chat'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "ae1cde96-640b-5941-a5df-64fa9b1ade20", + "title": "Model-call rate by model", + "description": "Model-call rate, per requested model.", + "panelTypes": "graph", + "yAxisUnit": "reqps", + "query": { + "queryType": "builder", + "id": "4573d5b7-33d8-55f7-87ae-56428b920f0f", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "{{gen_ai.request.model}}", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "rate()" + } + ], + "groupBy": [ + { + "key": "gen_ai.request.model", + "dataType": "string", + "type": "tag", + "isColumn": false, + "isJSON": false, + "id": "gen_ai.request.model--string--tag--false" + } + ], + "filter": { + "expression": "gen_ai.operation.name = 'chat'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "0dc68b95-21f3-5164-9506-ff2e5fe3d360", + "title": "Output tokens by model", + "description": "Completion-token burn, per requested model.", + "panelTypes": "graph", + "yAxisUnit": "short", + "query": { + "queryType": "builder", + "id": "4573d5b7-33d8-55f7-87ae-56428b920f0f", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "{{gen_ai.request.model}}", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "sum(gen_ai.usage.output_tokens)" + } + ], + "groupBy": [ + { + "key": "gen_ai.request.model", + "dataType": "string", + "type": "tag", + "isColumn": false, + "isJSON": false, + "id": "gen_ai.request.model--string--tag--false" + } + ], + "filter": { + "expression": "gen_ai.operation.name = 'chat'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "4c6f14ff-3baa-58df-a40d-1d0a92d7150d", + "title": "Tool calls by outcome", + "description": "Governed tool calls grouped by outcome (approved / blocked / denied / timeout).", + "panelTypes": "graph", + "yAxisUnit": "short", + "query": { + "queryType": "builder", + "id": "8f6c880a-74fe-5aca-8635-12be62e4c7a9", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "{{riskkernel.tool.status}}", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "count()" + } + ], + "groupBy": [ + { + "key": "riskkernel.tool.status", + "dataType": "string", + "type": "tag", + "isColumn": false, + "isJSON": false, + "id": "riskkernel.tool.status--string--tag--false" + } + ], + "filter": { + "expression": "gen_ai.operation.name = 'execute_tool'" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + }, + { + "id": "7f41705e-9810-513d-9877-2f53c6563113", + "title": "Budget halts by reason", + "description": "Spans where the governor halted a run, grouped by halt reason.", + "panelTypes": "graph", + "yAxisUnit": "short", + "query": { + "queryType": "builder", + "id": "26948412-b898-5e38-a04a-6090a0dc99b4", + "builder": { + "queryData": [ + { + "dataSource": "traces", + "queryName": "A", + "expression": "A", + "disabled": false, + "stepInterval": 60, + "limit": null, + "offset": 0, + "pageSize": 10, + "orderBy": [], + "reduceTo": "sum", + "legend": "{{riskkernel.halt.reason}}", + "functions": [], + "having": { + "expression": "" + }, + "aggregations": [ + { + "expression": "count()" + } + ], + "groupBy": [ + { + "key": "riskkernel.halt.reason", + "dataType": "string", + "type": "tag", + "isColumn": false, + "isJSON": false, + "id": "riskkernel.halt.reason--string--tag--false" + } + ], + "filter": { + "expression": "riskkernel.halt.reason EXISTS" + } + } + ], + "queryFormulas": [] + }, + "clickhouse_sql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ], + "promql": [ + { + "name": "A", + "legend": "", + "disabled": false, + "query": "" + } + ] + }, + "softMin": 0, + "softMax": 0, + "fillSpans": false, + "nullZeroValues": "zero", + "opacity": "1", + "isStacked": false, + "thresholds": [], + "columnUnits": {}, + "timePreferance": "GLOBAL_TIME", + "selectedLogFields": [], + "selectedTracesFields": [] + } + ] +}