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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ User-facing skills in `packages/nemo_platform_ext/src/nemo_platform_ext/skills/`
- `nemo-spec`: writes an agent spec from explore output.
- `nemo-build-agent`: scaffolds NAT workflow YAML from the spec and deploys.
- `nemo-try-agent`: test a deployed agent or chat with a model.
- `nemo-intake`: instrument agents, choose an ingest format, upload/query telemetry, and attach evaluator results.
- `nemo-experiments-upload`: publish named evaluation runs and scores to the Experiments leaderboard.
- `nemo-status`: read-only health dashboard.
- `nemo-teardown`: guided shutdown with confirmation.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
---
name: nemo-experiments-upload
description: End-to-end guide for getting evaluation data into NeMo Platform Intake so it shows up as Experiments. Create an Experiment, create an Evaluation, then log traces and evaluator results via the ATIF (Harbor), chat-completions, or OTLP ingest endpoint — and view the rollups in Studio. Use when a user wants to upload, log, ingest, publish, or send evaluation runs, agent traces, or scores to NeMo Experiments / Intake.
description: End-to-end guide for getting evaluation data into NeMo Platform Intake so it shows up in the Experiments leaderboard. Create an Experiment, create an Evaluation, then log traces and evaluator results via ATIF (Harbor), chat-completions, or OTLP and view the rollups in Studio. Use when a user wants to create named evaluation runs, publish evaluation results, or compare runs in NeMo Experiments.
triggers:
- log traces to intake
- log evaluation traces to experiments
- upload experiment results
- ingest evaluation data
- how do I log to experiments
- send traces to nemo intake
- send evaluation traces to nemo experiments
- publish evaluation results
- log to experiments
- upload harbor / atif results
- get my eval data into nemo
not-for:
- nemo-intake (use for general instrumentation, telemetry ingestion, trace queries, or evaluator results outside an Experiments leaderboard)
- nemo-evaluator (use to AUTHOR and RUN evaluations/metrics; this skill UPLOADS results)
- nemo-status (use for a read-only platform health dashboard)
- nemo-skill-selection (use for dispatch when intent is unclear)
compatibility: nemo-platform >= 0.1.0; needs the intake service running (auth, entities, intake) and ClickHouse for rollups/results; talks HTTP to /apis/intake/v2 (curl only, no Docker); Experiments viewing in Studio is behind the VITE_FF_EXPERIMENT feature flag.
compatibility: nemo-platform >= 0.1.0; needs a reachable local or remote intake service (with auth and entities) backed by ClickHouse for rollups/results; talks HTTP to /apis/intake/v2 (curl only, no Docker); Experiments viewing in Studio is behind the VITE_FF_EXPERIMENT feature flag.
maturity: beta
license: Apache-2.0
user-invocable: true
Expand All @@ -26,20 +27,45 @@ allowed-tools: [Bash, Read, Write]

Get evaluation runs into the platform end-to-end: **create an Experiment → create an Evaluation → log traces + scores to an ingest endpoint → see the rollups.** The API calls the parent (the leaderboard) an **Experiment** and each row an **Evaluation**; the whole feature is called **Experiments**.

Everything below uses `${NMP_BASE_URL}` (default `http://localhost:8080`) and a `${WORKSPACE}` (default `default`). All routes are under `/apis/intake/v2/workspaces/${WORKSPACE}`.
Everything below uses `${NMP_BASE_URL}` (default `http://localhost:8080`) and a `${WORKSPACE}`
(default `default`). Point `NMP_BASE_URL` at the local platform or a remote HTTPS origin. Reject
non-loopback `http://` targets, and never send authentication across an HTTP redirect. All routes
are under `/apis/intake/v2/workspaces/${WORKSPACE}`.

## Pre-flight

Confirm intake is up before doing anything. If this fails, the platform isn't running — route to `setup`/`nemo-status` and stop.
Confirm the target platform is reachable before doing anything. If this fails, report the target as
unreachable and stop; route to `setup`/`nemo-status` only for a local platform.

```bash
set -euo pipefail
: "${NMP_BASE_URL:=http://localhost:8080}"
: "${WORKSPACE:=default}"
nmp_authority=${NMP_BASE_URL#*://}
nmp_authority=${nmp_authority%%/*}
case "${nmp_authority}" in
*@*) echo "NMP_BASE_URL must not contain userinfo" >&2; exit 1 ;;
esac
case "${NMP_BASE_URL}" in
https://*) ;;
http://*)
case "${nmp_authority}" in
localhost|127.0.0.1) ;;
localhost:*|127.0.0.1:*)
nmp_port=${nmp_authority#*:}
case "${nmp_port}" in
""|*[!0-9]*) echo "loopback NMP_BASE_URL has an invalid port" >&2; exit 1 ;;
esac
;;
*) echo "HTTP NMP_BASE_URL must use exactly localhost or 127.0.0.1" >&2; exit 1 ;;
esac
;;
*) echo "remote NMP_BASE_URL must use https://" >&2; exit 1 ;;
esac
if curl -sf "${NMP_BASE_URL}/health/ready" >/dev/null; then
echo "platform ready"
else
echo "NOT READY — platform isn't running; route to setup/nemo-status and stop" >&2
echo "NOT READY — target platform is unreachable or not ready" >&2
exit 1
fi
```
Expand Down Expand Up @@ -97,20 +123,22 @@ curl -sf -X POST \

### 3. Log traces + evaluator results

Pick the ingest endpoint that matches your producer. **Read `references/ingest-formats.md` for the full schema and a copy-pasteable example for each.** How you attach evaluation identity depends on the endpoint:
Pick the ingest endpoint that matches your producer. **Read `../nemo-intake/references/ingest-formats.md` for the full schema and a copy-pasteable example for each.** How you attach evaluation identity depends on the endpoint:

- **ATIF and chat-completions** (JSON body) — add an `evaluation_context = {evaluation_id: "<the Evaluation name>", test_case_id: "<task id>"}` object to the payload.
- **OTLP** — there is no body field; set identity as **root-span resource attributes** `nemo.experiment.id` (the Evaluation **name**) and `nemo.test_case.id`. Spans missing these still ingest but won't associate to an Evaluation.
- **OTLP** — there is no body field; set `nemo.experiment.id` (the Evaluation **name**) and
`nemo.test_case.id` (the task ID) as **attributes on the root span**. Spans missing these still
ingest but won't associate to an Evaluation.

| Producer | Endpoint | Read |
|---|---|---|
| **Harbor / agent trajectories** (most common) | `POST .../ingest/atif` | `references/harbor-quickstart.md` |
| A single captured model call | `POST .../ingest/chat-completions` | `references/ingest-formats.md` |
| OpenTelemetry spans | `POST .../ingest/otlp/v1/traces` | `references/ingest-formats.md` |
| A single captured model call | `POST .../ingest/chat-completions` | `../nemo-intake/references/ingest-formats.md` |
| OpenTelemetry spans | `POST .../ingest/otlp/v1/traces` | `../nemo-intake/references/ingest-formats.md` |

Evaluator **scores** arrive one of two ways (both covered in the references):
- **Automatically** with ATIF — put rewards under `extra.verifier_result.rewards` (one key per criterion).
- **Explicitly** — `POST .../evaluator-results` with `{span_id, session_id, name, data_type, value}` — use `string_value` instead of `value` for `CATEGORICAL`/`TEXT` results (see `references/ingest-formats.md`).
- **Explicitly** — `POST .../evaluator-results` with `{span_id, session_id, name, data_type, value}` — use `string_value` instead of `value` for `CATEGORICAL`/`TEXT` results (see `../nemo-intake/references/ingest-formats.md`).

### 4. Verify the data landed

Expand All @@ -134,7 +162,7 @@ Open Studio → the **Experiments** area (behind the `VITE_FF_EXPERIMENT` flag)

Read these before hand-writing a payload:

- **`references/ingest-formats.md`** — the three ingest endpoints in full: request schemas, how `evaluation_context` and evaluator results attach, and a working example for each (ATIF, chat-completions, OTLP).
- **`../nemo-intake/references/ingest-formats.md`** — the shared Intake request schemas, evaluation context, evaluator results, and examples for ATIF, chat-completions, and OTLP.
- **`references/harbor-quickstart.md`** — the Harbor path specifically: mapping a Harbor trial result → an ATIF payload, including verifier rewards → evaluator scores.
- **`references/troubleshooting.md`** — every common `400`/`422`/`503` from the ingest and CRUD endpoints, with the fix.

Expand All @@ -151,7 +179,7 @@ If `run_count` is 0 after ingesting, the traces didn't associate — almost alwa
| Symptom | Cause | Recovery |
|---|---|---|
| `400 "…must be created before it can be logged."` | Ingested before the Evaluation existed, or `evaluation_id` doesn't match | Create the Evaluation (step 2); ensure `evaluation_context.evaluation_id` equals its **name** |
| `422 Unprocessable` on ingest | Unknown/typo'd top-level key (ATIF/chat-completions are `extra="forbid"`) or bad `schema_version` | Check the exact schema in `references/ingest-formats.md`; remove stray keys |
| `422 Unprocessable` on ingest | Unknown/typo'd top-level key (ATIF/chat-completions are `extra="forbid"`) or bad `schema_version` | Check the exact schema in `../nemo-intake/references/ingest-formats.md`; remove stray keys |
| Ingest 2xx but `run_count` stays 0 | Evaluation identity missing/wrong — `evaluation_context.evaluation_id` (ATIF/chat-completions) or the `nemo.experiment.id` root-span attribute (OTLP) ≠ the Evaluation's name | Attach the identity for your endpoint; use the Evaluation **name**, not its id |
| `503` on GET evaluation / sessions | ClickHouse (telemetry store) not running | Start ClickHouse; rollups and sessions require it |
| Scores don't show up | Rewards not under `extra.verifier_result.rewards`, or wrong `data_type` on `/evaluator-results` | See `references/troubleshooting.md` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ string — read it first.
|---|---|---|
| `400 "Evaluation '…' must be created before it can be logged."` | Ingested before the Evaluation existed, or `evaluation_id` typo | Create the Evaluation first; set `evaluation_context.evaluation_id` to its **name** |
| `400 "Evaluation '…' has been deleted…"` | The referenced Evaluation is soft-deleted | Recreate it or target a live one |
| `422` on ATIF/chat-completions | Unknown top-level key (both are `extra="forbid"`) | Remove stray keys; check the schema in `ingest-formats.md` |
| `422` on ATIF/chat-completions | Unknown top-level key (both are `extra="forbid"`) | Remove stray keys; check the schema in `../../nemo-intake/references/ingest-formats.md` |
| `422` bad `schema_version` (ATIF) | Not one of `ATIF-v1.0` … `ATIF-v1.7` | Use a supported literal |
| `422` non-sequential `step_id` / duplicate `tool_call_id` (ATIF) | Step/tool-call invariants violated | 1-based sequential `step_id`; unique `tool_call_id`; observation `source_call_id` must resolve |
| `422 cost_total_usd` unexpected (chat-completions) | Used the wrong cost key | Use top-level `cost_usd` (not `cost_total_usd`, not nested in `response`) |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"skill": "nemo-experiments-upload",
"tests": [
{ "type": "explicit", "prompt": "How do I log traces to NeMo intake?", "expected_skill": "nemo-experiments-upload" },
{ "type": "explicit", "prompt": "How do I log evaluation traces to NeMo Experiments?", "expected_skill": "nemo-experiments-upload" },
{ "type": "explicit", "prompt": "Upload my Harbor eval results to NeMo Experiments", "expected_skill": "nemo-experiments-upload" },
{ "type": "explicit", "prompt": "log to experiments", "expected_skill": "nemo-experiments-upload" },
{ "type": "implicit", "prompt": "I ran an agent eval and want to see it in the platform leaderboard", "expected_skill": "nemo-experiments-upload" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
name: nemo-intake
description: Instrument agents, ingest telemetry into NeMo Intake, and query spans, traces, sessions, and evaluator results. Use when connecting agent code or existing telemetry to Intake, choosing among OTLP, chat-completions, or ATIF, checking Intake and ClickHouse readiness, inspecting agent runs, or attaching evaluation scores outside the Experiments leaderboard workflow.
license: Apache-2.0
allowed-tools: [Bash, Read]
---

# Use NeMo Intake

Use Intake to normalize agent telemetry into queryable spans and traces. The Intake endpoint may be
local or remote. An agent does not need to run on NeMo Platform; it only needs network access and
credentials, when required, to send a supported format to that endpoint.

## Requirements

Set the target to the local or remote NeMo Platform origin:

```bash
export NMP_BASE_URL=http://127.0.0.1:8080
export WORKSPACE=default

nmp_authority=${NMP_BASE_URL#*://}
nmp_authority=${nmp_authority%%/*}
case "${nmp_authority}" in
*@*) echo "NMP_BASE_URL must not contain userinfo" >&2; exit 1 ;;
esac
case "${NMP_BASE_URL}" in
https://*) ;;
http://*)
case "${nmp_authority}" in
localhost|127.0.0.1) ;;
localhost:*|127.0.0.1:*)
nmp_port=${nmp_authority#*:}
case "${nmp_port}" in
""|*[!0-9]*) echo "loopback NMP_BASE_URL has an invalid port" >&2; exit 1 ;;
esac
;;
*) echo "HTTP NMP_BASE_URL must use exactly localhost or 127.0.0.1" >&2; exit 1 ;;
esac
;;
*) echo "remote NMP_BASE_URL must use https://" >&2; exit 1 ;;
esac
Comment thread
coderabbitai[bot] marked this conversation as resolved.
```

Require:

- Reachable ClickHouse storage.
- A reachable local or remote NeMo Platform `intake` service with its `auth` and `entities`
dependencies. ClickHouse must be reachable from Intake; it does not need to be reachable from the
telemetry producer.
- One supported telemetry source. NeMo Studio is optional.

For a remote deployment, set `NMP_BASE_URL` to its HTTPS origin and skip local startup. Use the
deployment's authentication mechanism. Do not send authentication across an HTTP redirect: validate
the final HTTPS origin and do not add `curl -L` to authenticated requests. For a local source
checkout, follow `SETUP.md`, then start ClickHouse before the backend:

```bash
services/intake/scripts/spans/run_clickhouse.sh
uv run nemo services run --services auth,entities,intake --host 127.0.0.1 --port 8080
```

Verify the Intake read path before ingesting:

```bash
curl -i "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?page=1&page_size=1"
```

Continue only on `200`; an empty list is healthy. `503` means Intake cannot reach ClickHouse. For
other failures, report the response and route platform startup problems to `setup` or `nemo-status`.

## Choose an ingest path

| Path | Use it for | Endpoint |
|---|---|---|
| **OTLP/HTTP protobuf** | Live, granular telemetry when instrumentation emits **OpenInference** or **OTel GenAI semantic conventions**. Prefer this for ongoing observability and complete agent hierarchies. | `POST .../ingest/otlp/v1/traces` |
| **Chat completions** | Captured OpenAI-compatible request/response logs, proxy instrumentation, or runtimes without OpenInference or OTel GenAI instrumentation. It represents one model interaction, not a full agent trajectory. | `POST .../ingest/chat-completions` |
| **ATIF** | Complete agent trajectories with ordered steps and metadata, especially Harbor evaluation trials. | `POST .../ingest/atif` |

All endpoint prefixes are:
`$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE`.

Read `references/ingest-formats.md` before hand-writing an ATIF or chat-completions payload, or when
you need the semantic attributes and response behavior for OTLP.

Do not translate a native source format without need. Preserve the source's IDs, timestamps,
hierarchy, inputs, outputs, statuses, errors, and semantic attributes.

## Instrument code for OTLP

Only choose OTLP when the instrumentation emits **OpenInference** or **OTel GenAI semantic
conventions**. Generic OpenTelemetry spans may ingest, but they do not provide the semantic model,
tool, token, cost, input/output, and session fields needed for useful Intake telemetry.

1. Recommend **NeMo Relay** when it supports the agent runtime and emits a supported semantic
format; it avoids hand-written span capture and records agent, model, and tool activity
consistently. Use the format Relay actually exports: send semantic OTLP to the OTLP endpoint, or
Relay ATIF output to the ATIF endpoint.
2. Otherwise use the runtime's **OpenInference instrumentor**, or its native **OTel GenAI**
instrumentation, with an OpenTelemetry SDK OTLP exporter. Choose the framework-specific
instrumentor from its current documentation; do not invent package names or APIs.
3. If neither semantic convention is available, capture each OpenAI-compatible request and response
and send it directly to `.../ingest/chat-completions`. Do not recommend generic OTLP as a
substitute.
4. For supported OTLP instrumentation, point the exporter at Intake:

```bash
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT="$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/ingest/otlp/v1/traces"
export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
```

5. Emit a root agent/chain span plus granular model, tool, retrieval, guardrail, and error spans.
Preserve parent-child IDs and set a stable `session.id` for related traces.
6. Run one representative interaction, then verify it through the spans query below.

Intake maps OpenInference and OTel GenAI semantic attributes into queryable model, provider, tool,
status, token, cost, and error fields while retaining unhandled attributes.

## Customer-facing data model

The hierarchy is `session -> trace -> span`; evaluator results attach to a span and session.

| Record | Meaning |
|---|---|
| **Span** | One timed operation, such as an agent step, model call, tool call, retrieval, guardrail, evaluator, or chain step. It carries IDs, timing, status/error, input/output, semantic fields, and source attributes. |
| **Trace** | One end-to-end agent run. Its spans share a trace ID and form a parent-child tree. |
| **Session** | Related traces, such as a multi-turn conversation or the traces for one evaluation case. A stable session ID is the main grouping key across ingest paths. |
| **Evaluator result** | A score attached to an exact span and session. `NUMERIC` and `BOOLEAN` use `value`; `CATEGORICAL` and `TEXT` use `string_value`. |

For ATIF, top-level `extra.verifier_result.rewards = {criterion: score}` automatically creates a
`harbor.verifier` evaluator span and one evaluator result per criterion. Stock Harbor keeps rewards
in a separate `reward.json`; enrich the trajectory before ingesting or post results explicitly:

```bash
curl -X POST "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/evaluator-results" \
-H 'Content-Type: application/json' \
-d '{"span_id":"<span-id>","session_id":"<session-id>","name":"faithfulness/v1","data_type":"NUMERIC","value":0.82}'
```

## Verify ingestion

Always query the interaction back; a successful POST alone is insufficient:

```bash
curl -g "$NMP_BASE_URL/apis/intake/v2/workspaces/$WORKSPACE/spans?filter[session_id]=<session-id>&page=1&page_size=100"
```

Confirm the response contains the expected session, trace/span hierarchy, inputs and outputs,
status/errors, and any evaluator results. If the goal is to create named evaluation runs and compare
them in a leaderboard, hand off to `nemo-experiments-upload`.
Loading