Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ attributes retain native JSON types. Known semantic attributes populate queryabl
attributes appear in detailed reads under `raw_attributes`. Reposting the same
`(source, trace_id, span_id)` updates the existing logical span.

String inputs and outputs are stored verbatim; objects and arrays are serialized as JSON.

The default ClickHouse TTL is 90 days from `started_at`. If any span is outside that window, Intake
returns `422` before writing the batch and instructs the operator to increase the `spans` and
`trace_index` TTLs. Provider timestamps are never rewritten by the endpoint.
Expand Down
9 changes: 7 additions & 2 deletions plugins/nemo-insights/evaluation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ wheel.

- Run every command from `plugins/nemo-insights`; the `evaluation` package is
not installed in the plugin wheel.
- Install the AWS CLI and configure the CSS S3 credentials described in
- Install AWS CLI 2.33.0 or newer and configure the CSS S3 credentials described in
[State bundles](#state-bundles).
- Start a local NeMo Platform at `http://localhost:8080` before using pinned
analysis, restore, roundtrip, or guarded publish commands.
Expand Down Expand Up @@ -158,7 +158,10 @@ uv run python -m evaluation publish evaluation/tmp/glamr.tar.zst --base http://l
```

`snapshot` drains the subject's workspaces (benchmark subjects: realistic +
`-oracle` twin) into JSONL + manifest — no ClickHouse, no Docker. `publish`
`-oracle` twin) into JSONL + manifest — no ClickHouse, no Docker. An intake
subject can set `experiment = "<name>"` to capture only that Experiment's
complete traces; membership comes from the Experiment's evaluations and their
traces, then every span in each trace is exported by trace ID. `publish`
refuses to mint unverified: `--base` runs the round-trip fidelity guard there
first (re-ingest into scratch workspaces → re-export → doc diff), or pass
`--no-verify` only after running `roundtrip` separately and confirming it
Expand Down Expand Up @@ -190,6 +193,8 @@ What restore touches:
- Accepted losses: annotation/evaluator-result `created_at`/`created_by` are
server-stamped at restore (the write APIs reject client values); a running
platform is required to analyze.
- Non-OTLP spans, including ATIF, restore through the provider-neutral direct
ingest API so their source names and arbitrary span IDs remain unchanged.
- Legacy `state-v1..v5` tars are not present in CSS (the v4 corpus lives on as
`state-v6`); a stray local copy restores only from a checkout
predating the v6 migration.
Expand Down
19 changes: 17 additions & 2 deletions plugins/nemo-insights/evaluation/artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ def build_export_manifest(
"platform_revision": (platform_info or {}).get("revision"),
"records": [r.name for r in records],
}
if stats.get("selections"):
manifest["selections"] = stats["selections"]
env = env or {}
manifest |= {key: env[var] for key, var in _LINEAGE_ENV.items() if env.get(var)}
return manifest
Expand Down Expand Up @@ -222,12 +224,19 @@ def snapshot_export(
CLI's ``--base`` override rewrites every stanza before this is called).
"""
subject_workspaces: list[tuple[Subject, list[str]]] = []
claimed_workspaces: set[str] = set()
claimed_workspaces: dict[str, Subject] = {}
for subject in subjects:
for workspace in workspaces_for_subject(subject):
owner = claimed_workspaces.get(workspace)
if owner is not None and (owner.config.get("experiment") or subject.config.get("experiment")):
sys.exit(
f"snapshot: workspace '{workspace}' is shared by experiment-scoped subject "
f"'{owner.name if owner.config.get('experiment') else subject.name}' and another subject"
)
workspaces = [workspace for workspace in workspaces_for_subject(subject) if workspace not in claimed_workspaces]
if workspaces:
subject_workspaces.append((subject, workspaces))
claimed_workspaces.update(workspaces)
claimed_workspaces.update({workspace: subject for workspace in workspaces})
# Every selected subject must carry a base_url: a partial miss would silently
# let the agreement check pass on the configured subset and export the
# unconfigured subject from the others' platform.
Expand Down Expand Up @@ -256,6 +265,7 @@ def snapshot_export(
state,
since=since,
client=_basic_auth_intake_client_for(subject, source_url),
experiment=str(subject.config["experiment"]) if subject.config.get("experiment") else None,
)
for subject, workspaces in subject_workspaces
]
Expand All @@ -275,6 +285,11 @@ def snapshot_export(
},
"min_start_time": min(min_bounds).isoformat() if min_bounds else None,
"max_start_time": max(max_bounds).isoformat() if max_bounds else None,
"selections": {
workspace: selection
for result in exported
for workspace, selection in result.get("selections", {}).items()
},
}
for rec in records:
shutil.copy2(rec, state / "tmp" / rec.name)
Expand Down
17 changes: 15 additions & 2 deletions plugins/nemo-insights/evaluation/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,17 +95,30 @@ def _load_dotenv(path: Path = ENV_PATH) -> None:
os.environ.setdefault(key, value)


def _aws_cli_version(executable: str) -> tuple[int, int, int] | None:
result = subprocess.run([executable, "--version"], capture_output=True, text=True)
match = re.search(r"aws-cli/(\d+)\.(\d+)\.(\d+)", result.stdout + result.stderr)
if not match:
return None
major, minor, patch = match.groups()
return int(major), int(minor), int(patch)

Comment thread
coderabbitai[bot] marked this conversation as resolved.

def _doctor(subjects: dict[str, Subject], name: str | None) -> None:
"""Print a readiness checklist for one subject (or all): what's set up, what's not."""
names = [name] if name else sorted(subjects)
aws = shutil.which("aws")
aws_version = _aws_cli_version(aws) if aws else None
for subject_name in names:
subject = subjects.get(subject_name)
if subject is None:
print(f"✗ {subject_name}: unknown subject")
continue
unmet: list[str] = []
if shutil.which("aws") is None:
unmet.append("AWS CLI (needed for pinned/--state analyze; install with `brew install awscli`)")
if aws is None:
unmet.append("AWS CLI 2.33.0+ (needed for pinned/--state analyze; install with `brew install awscli`)")
elif (aws_version or (0, 0, 0)) < (2, 33, 0):
unmet.append("AWS CLI 2.33.0+ (required for immutable multipart state uploads)")
if not os.environ.get(release.ACCESS_KEY_ENV) or not os.environ.get(release.SECRET_KEY_ENV):
unmet.append("CSS S3 credentials in evaluation/.env (CSS Portal → Auth Info)")
unmet += build_adapter(subject).check()
Expand Down
7 changes: 7 additions & 0 deletions plugins/nemo-insights/evaluation/evaluations.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ agent = "content-dedup"
workspace = "nvq"
state = "state-v7"

[kernel-factory]
type = "intake"
agent = "solswarm-campaign"
workspace = "kf-prod-evals"
experiment = "prod-latest-completed"
state = "state-v11"

[glamr]
type = "intake"
agent = "glamr" # the main GLAMR agent (ux-agent); the only agent_name-tagged one
Expand Down
Loading