nooa-cli 0.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nooa_cli/AGENTS.md +162 -0
- nooa_cli/__init__.py +70 -0
- nooa_cli/__main__.py +8 -0
- nooa_cli/_common.py +45 -0
- nooa_cli/commands/__init__.py +106 -0
- nooa_cli/commands/_otlp_helpers.py +122 -0
- nooa_cli/commands/_template.py +89 -0
- nooa_cli/commands/config.py +356 -0
- nooa_cli/commands/delete_traces.py +63 -0
- nooa_cli/commands/eval.py +68 -0
- nooa_cli/commands/import_harbor.py +512 -0
- nooa_cli/commands/import_traces.py +189 -0
- nooa_cli/commands/start_dev.py +145 -0
- nooa_cli/commands/traces.py +316 -0
- nooa_cli/completion.py +253 -0
- nooa_cli/tools/__init__.py +0 -0
- nooa_cli/tools/_tree_sitter_backend.py +351 -0
- nooa_cli/tools/pyp/__init__.py +71 -0
- nooa_cli/tools/pyp/errors.py +92 -0
- nooa_cli/tools/pyp/sources.py +458 -0
- nooa_cli/tools/pyp/stream.py +527 -0
- nooa_cli/tools/pyp/tests/__init__.py +0 -0
- nooa_cli/tools/pyp/tests/test_pyp.py +394 -0
- nooa_cli/tools/repo_tools.py +902 -0
- nooa_cli-0.0.7.dist-info/METADATA +52 -0
- nooa_cli-0.0.7.dist-info/RECORD +28 -0
- nooa_cli-0.0.7.dist-info/WHEEL +4 -0
- nooa_cli-0.0.7.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""``nooa config`` — inspect and customize the project's config files.
|
|
4
|
+
|
|
5
|
+
Subcommands:
|
|
6
|
+
|
|
7
|
+
- ``show`` — print the resolved layers for ``llm_config.yaml``,
|
|
8
|
+
``settings.yaml``, and ``secrets.yaml`` (secret values
|
|
9
|
+
redacted — only key names shown).
|
|
10
|
+
- ``path`` — print the user-level YAML path (where ``eject`` writes).
|
|
11
|
+
- ``eject`` — copy a bundled-defaults YAML to the user-level path so
|
|
12
|
+
you can edit it locally.
|
|
13
|
+
|
|
14
|
+
All three files share the same directories and precedence (user →
|
|
15
|
+
project → env-var override); see :mod:`nooa.layered_config`.
|
|
16
|
+
|
|
17
|
+
The config chain (last wins):
|
|
18
|
+
|
|
19
|
+
1. Bundled defaults — every package that registers under the
|
|
20
|
+
``nooa.bundled_configs`` entry-point group. Install
|
|
21
|
+
``nemo-oo-agents-nvidia`` for the NVIDIA-gateway aliases.
|
|
22
|
+
2. User config at ``~/.config/nooa/llm_config.yaml``
|
|
23
|
+
(overridable via ``NEMO_OO_USER_DIR``).
|
|
24
|
+
3. Project-local ``.nooa/llm_config.yaml``.
|
|
25
|
+
4. ``NEMO_OO_LLM_CONFIG`` env var — comma-separated YAML paths;
|
|
26
|
+
the global override, highest priority.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import os
|
|
30
|
+
import shutil
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
import click
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _user_config_path() -> Path:
|
|
37
|
+
"""Where ``eject`` writes and the registry's user layer reads from."""
|
|
38
|
+
from nooa.paths import get_user_dir
|
|
39
|
+
|
|
40
|
+
return get_user_dir("llm_config.yaml")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _project_config_path() -> Path:
|
|
44
|
+
"""Where the registry's project-local layer reads from."""
|
|
45
|
+
from nooa.paths import get_project_dir
|
|
46
|
+
|
|
47
|
+
return get_project_dir("llm_config.yaml")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _count_aliases(path: Path) -> int | None:
|
|
51
|
+
"""Return the count of ``models:`` entries in a YAML file, or None on error."""
|
|
52
|
+
try:
|
|
53
|
+
import yaml
|
|
54
|
+
|
|
55
|
+
data = yaml.safe_load(path.read_text())
|
|
56
|
+
except Exception:
|
|
57
|
+
return None
|
|
58
|
+
if not isinstance(data, dict):
|
|
59
|
+
return 0
|
|
60
|
+
models = data.get("models")
|
|
61
|
+
if not isinstance(models, dict):
|
|
62
|
+
return 0
|
|
63
|
+
# Count non-None entries (null entries are removal directives, not aliases).
|
|
64
|
+
return sum(1 for v in models.values() if v is not None)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.group()
|
|
68
|
+
def command():
|
|
69
|
+
"""Inspect and customize the LLM-registry config chain."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@command.command("show")
|
|
73
|
+
def show_cmd():
|
|
74
|
+
"""Print the resolved config-file chain.
|
|
75
|
+
|
|
76
|
+
Useful for debugging "why is my model alias not found" — shows each
|
|
77
|
+
layer, whether it's present, and how many aliases it contributes.
|
|
78
|
+
|
|
79
|
+
For each candidate layer, output includes a "(deduplicated …)" note
|
|
80
|
+
if the file's resolved path is *also* reachable through a
|
|
81
|
+
higher-priority layer — :func:`llm_config_chain` keeps only the
|
|
82
|
+
higher slot, so the lower one isn't actually loaded.
|
|
83
|
+
"""
|
|
84
|
+
from nooa.llm_config import (
|
|
85
|
+
bundled_config_paths,
|
|
86
|
+
llm_config_chain,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Resolve the chain once up-front so each candidate line below can
|
|
90
|
+
# accurately note whether it survived deduplication.
|
|
91
|
+
resolved_chain = llm_config_chain()
|
|
92
|
+
resolved_set = {p.resolve() for p in resolved_chain}
|
|
93
|
+
|
|
94
|
+
def _dedup_suffix(candidate_path: Path) -> str:
|
|
95
|
+
"""Return ``""`` if loaded, else a "(deduplicated …)" annotation."""
|
|
96
|
+
try:
|
|
97
|
+
return (
|
|
98
|
+
""
|
|
99
|
+
if candidate_path.resolve() in resolved_set
|
|
100
|
+
else " (deduplicated — same resolved path as a higher-priority layer)"
|
|
101
|
+
)
|
|
102
|
+
except OSError:
|
|
103
|
+
return ""
|
|
104
|
+
|
|
105
|
+
# Layer 1 — bundled defaults (entry-point providers)
|
|
106
|
+
click.echo("Bundled defaults:")
|
|
107
|
+
bundled = bundled_config_paths()
|
|
108
|
+
if not bundled:
|
|
109
|
+
click.echo(" (none registered — install nemo-oo-agents-nvidia or another provider)")
|
|
110
|
+
else:
|
|
111
|
+
for b in bundled:
|
|
112
|
+
n = _count_aliases(b)
|
|
113
|
+
alias_suffix = f"({n} aliases)" if n is not None else "(unreadable)"
|
|
114
|
+
click.echo(f" {b} {alias_suffix}{_dedup_suffix(b)}")
|
|
115
|
+
|
|
116
|
+
# Layer 2 — user
|
|
117
|
+
user_path = _user_config_path()
|
|
118
|
+
click.echo(f"User config ({user_path}):")
|
|
119
|
+
if user_path.exists():
|
|
120
|
+
n = _count_aliases(user_path)
|
|
121
|
+
suffix = f" ({n} aliases)" if n is not None else " (unreadable)"
|
|
122
|
+
click.echo(f" present{suffix}{_dedup_suffix(user_path)}")
|
|
123
|
+
else:
|
|
124
|
+
click.echo(" not present")
|
|
125
|
+
|
|
126
|
+
# Layer 3 — project-local
|
|
127
|
+
project_path = _project_config_path()
|
|
128
|
+
click.echo(f"Project config ({project_path}):")
|
|
129
|
+
if project_path.exists():
|
|
130
|
+
n = _count_aliases(project_path)
|
|
131
|
+
suffix = f" ({n} aliases)" if n is not None else " (unreadable)"
|
|
132
|
+
click.echo(f" present{suffix}{_dedup_suffix(project_path)}")
|
|
133
|
+
else:
|
|
134
|
+
click.echo(" not present")
|
|
135
|
+
|
|
136
|
+
# Layer 4 — env var (global override, highest priority)
|
|
137
|
+
env_val = os.environ.get("NEMO_OO_LLM_CONFIG", "").strip()
|
|
138
|
+
click.echo("NEMO_OO_LLM_CONFIG (global override):")
|
|
139
|
+
if not env_val:
|
|
140
|
+
click.echo(" not set")
|
|
141
|
+
else:
|
|
142
|
+
for raw in env_val.split(","):
|
|
143
|
+
raw = raw.strip()
|
|
144
|
+
if not raw:
|
|
145
|
+
continue
|
|
146
|
+
p = Path(raw).expanduser().resolve()
|
|
147
|
+
if p.exists():
|
|
148
|
+
n = _count_aliases(p)
|
|
149
|
+
suffix = f" ({n} aliases)" if n is not None else " (unreadable)"
|
|
150
|
+
click.echo(f" {p}{suffix}{_dedup_suffix(p)}")
|
|
151
|
+
else:
|
|
152
|
+
click.echo(f" {p} (does not exist)")
|
|
153
|
+
|
|
154
|
+
# Total — populate MODELS via the same resolved chain so the count
|
|
155
|
+
# is consistent with the dedup-annotated layers above.
|
|
156
|
+
from nooa.unifiedllm import MODELS, reload_registry
|
|
157
|
+
|
|
158
|
+
reload_registry(*resolved_chain)
|
|
159
|
+
click.echo("")
|
|
160
|
+
click.echo(f"Total: {len(MODELS)} model aliases registered")
|
|
161
|
+
|
|
162
|
+
# Surface which API key env vars the loaded aliases reference, and
|
|
163
|
+
# whether each is set. This is the "why isn't my key working"
|
|
164
|
+
# diagnostic.
|
|
165
|
+
referenced = {
|
|
166
|
+
cfg["api_key_env"]
|
|
167
|
+
for cfg in MODELS.values()
|
|
168
|
+
if isinstance(cfg, dict) and cfg.get("api_key_env")
|
|
169
|
+
}
|
|
170
|
+
if referenced:
|
|
171
|
+
click.echo("")
|
|
172
|
+
click.echo("API key env vars referenced:")
|
|
173
|
+
for var in sorted(referenced):
|
|
174
|
+
present = "set" if os.environ.get(var) else "NOT SET"
|
|
175
|
+
click.echo(f" {var:30s} ({present})")
|
|
176
|
+
|
|
177
|
+
# ── Settings & secrets (same dirs, same precedence) ───────────────
|
|
178
|
+
click.echo("")
|
|
179
|
+
_show_layered_file("Settings", "settings.yaml", "NEMO_OO_SETTINGS", _summarize_settings)
|
|
180
|
+
click.echo("")
|
|
181
|
+
_show_layered_file("Secrets", "secrets.yaml", "NEMO_OO_SECRETS", _summarize_secrets)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _summarize_settings(path: Path) -> str:
|
|
185
|
+
"""One-line summary of a settings.yaml: top-level sections + key count."""
|
|
186
|
+
try:
|
|
187
|
+
import yaml
|
|
188
|
+
|
|
189
|
+
data = yaml.safe_load(path.read_text())
|
|
190
|
+
except Exception:
|
|
191
|
+
return "(unreadable)"
|
|
192
|
+
if not isinstance(data, dict) or not data:
|
|
193
|
+
return "(empty)"
|
|
194
|
+
parts = []
|
|
195
|
+
for section in ("tui", "agent"):
|
|
196
|
+
sect = data.get(section)
|
|
197
|
+
if isinstance(sect, dict):
|
|
198
|
+
parts.append(f"{section}: {len(sect)} keys")
|
|
199
|
+
return ", ".join(parts) if parts else f"{len(data)} keys"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _summarize_secrets(path: Path) -> str:
|
|
203
|
+
"""One-line summary of a secrets.yaml: env var NAMES only (values redacted)."""
|
|
204
|
+
try:
|
|
205
|
+
import yaml
|
|
206
|
+
|
|
207
|
+
data = yaml.safe_load(path.read_text())
|
|
208
|
+
except Exception:
|
|
209
|
+
return "(unreadable)"
|
|
210
|
+
if not isinstance(data, dict):
|
|
211
|
+
return "(empty)"
|
|
212
|
+
env_map = data.get("env")
|
|
213
|
+
if not isinstance(env_map, dict) or not env_map:
|
|
214
|
+
return "(no env: keys)"
|
|
215
|
+
names = sorted(str(k) for k in env_map)
|
|
216
|
+
return "env: " + ", ".join(names) + " (values redacted)"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _show_layered_file(label: str, filename: str, env_var: str, summarize) -> None:
|
|
220
|
+
"""Print the user → project → env-var layers for *filename*.
|
|
221
|
+
|
|
222
|
+
*summarize* is called per existing file to produce a one-line,
|
|
223
|
+
value-safe description. The highest-priority occurrence of a resolved
|
|
224
|
+
path wins; lower occurrences are flagged as shadowed.
|
|
225
|
+
"""
|
|
226
|
+
from nooa.layered_config import layered_paths
|
|
227
|
+
from nooa.paths import get_project_dir, get_user_dir
|
|
228
|
+
|
|
229
|
+
resolved_set = set(layered_paths(filename, env_var))
|
|
230
|
+
|
|
231
|
+
def _summary(path: Path) -> str:
|
|
232
|
+
try:
|
|
233
|
+
note = "" if path.resolve() in resolved_set else " (shadowed by a higher layer)"
|
|
234
|
+
except OSError:
|
|
235
|
+
note = ""
|
|
236
|
+
return f"present — {summarize(path)}{note}"
|
|
237
|
+
|
|
238
|
+
click.echo(f"{label} ({filename}):")
|
|
239
|
+
|
|
240
|
+
user_path = get_user_dir(filename)
|
|
241
|
+
project_path = get_project_dir(filename)
|
|
242
|
+
for layer, path in (("User", user_path), ("Project", project_path)):
|
|
243
|
+
click.echo(f" {layer} ({path}):")
|
|
244
|
+
click.echo(f" {_summary(path)}" if path.exists() else " not present")
|
|
245
|
+
|
|
246
|
+
env_val = os.environ.get(env_var, "").strip()
|
|
247
|
+
click.echo(f" {env_var} (override):")
|
|
248
|
+
if not env_val:
|
|
249
|
+
click.echo(" not set")
|
|
250
|
+
return
|
|
251
|
+
for raw in env_val.split(","):
|
|
252
|
+
raw = raw.strip()
|
|
253
|
+
if not raw:
|
|
254
|
+
continue
|
|
255
|
+
p = Path(raw).expanduser()
|
|
256
|
+
click.echo(f" {p}: {_summary(p)}" if p.exists() else f" {p} (does not exist)")
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@command.command("path")
|
|
260
|
+
def path_cmd():
|
|
261
|
+
"""Print the user-level config path (where ``eject`` writes)."""
|
|
262
|
+
click.echo(str(_user_config_path()))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@command.command("eject")
|
|
266
|
+
@click.option(
|
|
267
|
+
"--force",
|
|
268
|
+
is_flag=True,
|
|
269
|
+
help="Overwrite an existing user-level config file.",
|
|
270
|
+
)
|
|
271
|
+
def eject_cmd(force: bool):
|
|
272
|
+
"""Copy a bundled-defaults YAML to the user-level config path.
|
|
273
|
+
|
|
274
|
+
The user-level path is ``~/.config/nooa/llm_config.yaml`` (XDG;
|
|
275
|
+
override with ``NEMO_OO_USER_DIR``). Bundled defaults still load
|
|
276
|
+
as a lower-priority layer, so the ejected copy *adds* to /
|
|
277
|
+
overrides on top of them — you can prune the ejected file to just
|
|
278
|
+
the aliases you want to customize.
|
|
279
|
+
|
|
280
|
+
When multiple bundled-config providers are registered, ``eject``
|
|
281
|
+
refuses (rather than silently picking one). Install only the one
|
|
282
|
+
you want to use as the editing baseline, or write a hand-built
|
|
283
|
+
``llm_config.yaml`` directly.
|
|
284
|
+
"""
|
|
285
|
+
from nooa.llm_config import bundled_config_paths
|
|
286
|
+
|
|
287
|
+
target = _user_config_path()
|
|
288
|
+
if target.exists() and not force:
|
|
289
|
+
click.echo(
|
|
290
|
+
f"Refusing to overwrite existing file: {target}\nRe-run with --force to overwrite.",
|
|
291
|
+
err=True,
|
|
292
|
+
)
|
|
293
|
+
raise SystemExit(1)
|
|
294
|
+
|
|
295
|
+
bundled = bundled_config_paths()
|
|
296
|
+
if not bundled:
|
|
297
|
+
click.echo(
|
|
298
|
+
"No bundled-defaults provider is installed. Nothing to eject.\n"
|
|
299
|
+
"Install `nemo-oo-agents-nvidia` (or another provider) first.",
|
|
300
|
+
err=True,
|
|
301
|
+
)
|
|
302
|
+
raise SystemExit(1)
|
|
303
|
+
if len(bundled) > 1:
|
|
304
|
+
formatted = "\n ".join(str(b) for b in bundled)
|
|
305
|
+
click.echo(
|
|
306
|
+
"Multiple bundled-defaults providers are registered; refusing to "
|
|
307
|
+
"pick one to eject. Providers:\n " + formatted + "\n"
|
|
308
|
+
"Uninstall all but the one you want as the eject baseline.",
|
|
309
|
+
err=True,
|
|
310
|
+
)
|
|
311
|
+
raise SystemExit(1)
|
|
312
|
+
source = bundled[0]
|
|
313
|
+
|
|
314
|
+
# Refuse to clobber a symlink target — managed-dotfiles users
|
|
315
|
+
# routinely symlink ~/.config files into a versioned dir;
|
|
316
|
+
# ``shutil.copyfile`` would silently write through the symlink to
|
|
317
|
+
# the upstream target.
|
|
318
|
+
if target.is_symlink():
|
|
319
|
+
click.echo(
|
|
320
|
+
f"Target is a symlink ({target} → {os.readlink(target)}); refusing to overwrite.\n"
|
|
321
|
+
"Remove the symlink first, or write to a different path via NEMO_OO_USER_DIR.",
|
|
322
|
+
err=True,
|
|
323
|
+
)
|
|
324
|
+
raise SystemExit(1)
|
|
325
|
+
|
|
326
|
+
# Refuse to clobber a directory at the target path.
|
|
327
|
+
if target.is_dir():
|
|
328
|
+
click.echo(
|
|
329
|
+
f"Target path is a directory: {target}\nRemove or rename it first.",
|
|
330
|
+
err=True,
|
|
331
|
+
)
|
|
332
|
+
raise SystemExit(1)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
336
|
+
except OSError as exc:
|
|
337
|
+
click.echo(f"Cannot create parent dir {target.parent}: {exc}", err=True)
|
|
338
|
+
raise SystemExit(1) from exc
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
shutil.copyfile(source, target)
|
|
342
|
+
except OSError as exc:
|
|
343
|
+
click.echo(f"Cannot write to {target}: {exc}", err=True)
|
|
344
|
+
raise SystemExit(1) from exc
|
|
345
|
+
|
|
346
|
+
click.echo(f"Wrote {target}")
|
|
347
|
+
click.echo(
|
|
348
|
+
"Edit it to customize. `nooa` will load it as a layer on top of the bundled defaults."
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if os.environ.get("NEMO_OO_LLM_CONFIG", "").strip():
|
|
352
|
+
click.echo("")
|
|
353
|
+
click.echo(
|
|
354
|
+
"Note: $NEMO_OO_LLM_CONFIG is set; paths in that variable load *after* the file you "
|
|
355
|
+
"just wrote and may override it. Run `nooa config show` to inspect resolution."
|
|
356
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Delete imported trace batches from the viewer.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
nooa delete-traces --batch-id my-experiment-v2
|
|
7
|
+
nooa delete-traces --batch-id my-experiment-v2 --endpoint http://host:5001
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
NAME = "delete-traces"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _validate_endpoint(endpoint: str) -> None:
|
|
20
|
+
"""Validate that the endpoint uses an HTTP(S) scheme."""
|
|
21
|
+
parsed = urllib.parse.urlparse(endpoint)
|
|
22
|
+
if parsed.scheme not in ("http", "https"):
|
|
23
|
+
raise click.BadParameter(
|
|
24
|
+
f"Endpoint must use http:// or https:// scheme, got: {endpoint}",
|
|
25
|
+
param_hint="'--endpoint'",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@click.command()
|
|
30
|
+
@click.option(
|
|
31
|
+
"--batch-id",
|
|
32
|
+
required=True,
|
|
33
|
+
help="Batch ID to delete.",
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
"--endpoint",
|
|
37
|
+
default="http://localhost:5001",
|
|
38
|
+
show_default=True,
|
|
39
|
+
help="Viewer API endpoint.",
|
|
40
|
+
)
|
|
41
|
+
def command(batch_id: str, endpoint: str):
|
|
42
|
+
"""Delete all traces belonging to a batch."""
|
|
43
|
+
_validate_endpoint(endpoint)
|
|
44
|
+
|
|
45
|
+
# Verify endpoint is reachable
|
|
46
|
+
try:
|
|
47
|
+
with urllib.request.urlopen(f"{endpoint.rstrip('/')}/api/version", timeout=5):
|
|
48
|
+
pass
|
|
49
|
+
except Exception:
|
|
50
|
+
click.echo(f"Cannot reach viewer at {endpoint}. Is it running?")
|
|
51
|
+
raise SystemExit(1) from None
|
|
52
|
+
|
|
53
|
+
url = f"{endpoint.rstrip('/')}/api/traces?batch_id={urllib.parse.quote(batch_id, safe='')}"
|
|
54
|
+
req = urllib.request.Request(url, method="DELETE")
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
58
|
+
result = json.loads(resp.read())
|
|
59
|
+
count = result.get("deleted", 0)
|
|
60
|
+
click.echo(f"Deleted {count} trace(s) from batch '{batch_id}'")
|
|
61
|
+
except Exception as e:
|
|
62
|
+
click.echo(f"Failed to delete batch '{batch_id}': {e}")
|
|
63
|
+
raise SystemExit(1) from None
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Run an evaluation pipeline job.
|
|
4
|
+
|
|
5
|
+
Thin passthrough to `python -m eval_pipeline`. All arguments are forwarded
|
|
6
|
+
as-is, so this command always stays in sync with eval_pipeline's own CLI.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
nooa eval --config config.yaml
|
|
10
|
+
nooa eval --config config.yaml --runs 3 --parallel 10
|
|
11
|
+
nooa eval --config config.yaml --test sentiment --limit 5
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command(
|
|
18
|
+
add_help_option=False,
|
|
19
|
+
context_settings={
|
|
20
|
+
"ignore_unknown_options": True,
|
|
21
|
+
"allow_extra_args": True,
|
|
22
|
+
},
|
|
23
|
+
)
|
|
24
|
+
@click.argument("args", nargs=-1, type=click.UNPROCESSED)
|
|
25
|
+
@click.pass_context
|
|
26
|
+
def command(ctx: click.Context, args: tuple[str, ...]):
|
|
27
|
+
"""Run an evaluation pipeline (passthrough to eval_pipeline).
|
|
28
|
+
|
|
29
|
+
All arguments are forwarded directly to eval_pipeline's CLI.
|
|
30
|
+
|
|
31
|
+
\b
|
|
32
|
+
Examples:
|
|
33
|
+
nooa eval --config config.yaml
|
|
34
|
+
nooa eval --config config.yaml --runs 3 --parallel 10
|
|
35
|
+
nooa eval --config config.yaml --test sentiment --limit 5
|
|
36
|
+
nooa eval --config config.yaml --models gpt-4,claude-3 -q
|
|
37
|
+
nooa eval --config config.yaml --default_strategy codeact
|
|
38
|
+
nooa eval --help
|
|
39
|
+
"""
|
|
40
|
+
import asyncio
|
|
41
|
+
import faulthandler
|
|
42
|
+
import signal
|
|
43
|
+
import sys
|
|
44
|
+
|
|
45
|
+
# Enable faulthandler (same as eval_pipeline.__main__)
|
|
46
|
+
faulthandler.enable()
|
|
47
|
+
if hasattr(signal, "SIGUSR1"):
|
|
48
|
+
faulthandler.register(signal.SIGUSR1, file=sys.stderr, all_threads=True)
|
|
49
|
+
|
|
50
|
+
# Forward all arguments directly to eval_pipeline
|
|
51
|
+
old_argv = sys.argv
|
|
52
|
+
sys.argv = ["eval_pipeline", *args]
|
|
53
|
+
try:
|
|
54
|
+
try:
|
|
55
|
+
from eval_pipeline.cli import main_async
|
|
56
|
+
except ImportError:
|
|
57
|
+
click.echo(
|
|
58
|
+
"The eval command requires the 'eval_pipeline' package, which is "
|
|
59
|
+
"not installed.\nIt ships with the nemo-oo-agents monorepo workspace "
|
|
60
|
+
"but is not a dependency of the standalone CLI wheel.\n"
|
|
61
|
+
"Install it from a monorepo checkout with: uv sync",
|
|
62
|
+
err=True,
|
|
63
|
+
)
|
|
64
|
+
sys.exit(1)
|
|
65
|
+
|
|
66
|
+
asyncio.run(main_async())
|
|
67
|
+
finally:
|
|
68
|
+
sys.argv = old_argv
|