-
Notifications
You must be signed in to change notification settings - Fork 17
refactor(experiments): Rename evaluation span-attribute bag key #1238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,7 +43,7 @@ def _positive_int(value: str) -> int: | |
| return parsed | ||
|
|
||
|
|
||
| def _experiment_id() -> str: | ||
| def _evaluation_name() -> str: | ||
| stamp = datetime.now(UTC).strftime("%Y%m%d-%H%M%S") | ||
| return f"tau3-airline-{stamp}-{secrets.token_hex(2)}" | ||
|
|
||
|
|
@@ -96,7 +96,7 @@ def _resolve_harbor_output_dir(path: Path) -> tuple[Path, Path]: | |
| def _write_upload_summary( | ||
| run_dir: Path, | ||
| *, | ||
| experiment_id: str, | ||
| evaluation_name: str, | ||
| workspace: str, | ||
| agent_name: str, | ||
| agent_version: str, | ||
|
|
@@ -108,7 +108,7 @@ def _write_upload_summary( | |
| summary_path.write_text( | ||
| json.dumps( | ||
| { | ||
| "experiment_id": experiment_id, | ||
| "evaluation_name": evaluation_name, | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Compatibility note: this changes the |
||
| "workspace": workspace, | ||
| "agent_name": agent_name, | ||
| "agent_version": agent_version, | ||
|
|
@@ -133,7 +133,7 @@ async def _upload_trials( | |
| trials: list[TrialResult], | ||
| *, | ||
| workspace: str, | ||
| experiment_id: str, | ||
| evaluation_name: str, | ||
| agent_name: str, | ||
| agent_version: str, | ||
| model: str, | ||
|
|
@@ -149,7 +149,7 @@ async def _upload_trials( | |
| raise RuntimeError(f"Trace {trace_id} was produced by more than one trial") | ||
|
|
||
| attrs = { | ||
| "nemo.experiment.id": experiment_id, | ||
| "nemo.evaluation.name": evaluation_name, | ||
| "nemo.test_case.id": trial.task_id, | ||
| "nemo.trial.id": trial.id, | ||
| "gen_ai.agent.name": agent_name, | ||
|
|
@@ -220,7 +220,7 @@ async def run(args: argparse.Namespace) -> Path: | |
| if not uploadable_trials: | ||
| raise RuntimeError(f"No completed Harbor trials with trace artifacts found in {job_dir}") | ||
|
|
||
| experiment_id = args.experiment_id or run_dir.name | ||
| evaluation_name = args.evaluation_name or run_dir.name | ||
| client = make_client(args.base_url) | ||
| try: | ||
| await client.workspaces.create( | ||
|
|
@@ -232,7 +232,7 @@ async def run(args: argparse.Namespace) -> Path: | |
| client, | ||
| uploadable_trials, | ||
| workspace=args.workspace, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| agent_name=args.agent_name, | ||
| agent_version=args.agent_version, | ||
| model=args.model, | ||
|
|
@@ -243,7 +243,7 @@ async def run(args: argparse.Namespace) -> Path: | |
|
|
||
| summary_path = _write_upload_summary( | ||
| run_dir, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| workspace=args.workspace, | ||
| agent_name=args.agent_name, | ||
| agent_version=args.agent_version, | ||
|
|
@@ -255,8 +255,8 @@ async def run(args: argparse.Namespace) -> Path: | |
| print(summary_path) | ||
| return summary_path | ||
|
|
||
| experiment_id = args.experiment_id or _experiment_id() | ||
| run_dir = args.output.expanduser().resolve() / experiment_id | ||
| evaluation_name = args.evaluation_name or _evaluation_name() | ||
| run_dir = args.output.expanduser().resolve() / evaluation_name | ||
| if run_dir.exists(): | ||
| raise FileExistsError(f"Output directory already exists: {run_dir}") | ||
|
|
||
|
|
@@ -294,7 +294,7 @@ async def run(args: argparse.Namespace) -> Path: | |
| client, | ||
| trials, | ||
| workspace=args.workspace, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| agent_name=args.agent_name, | ||
| agent_version=args.agent_version, | ||
| model=args.model, | ||
|
|
@@ -305,7 +305,7 @@ async def run(args: argparse.Namespace) -> Path: | |
|
|
||
| summary_path = _write_upload_summary( | ||
| run_dir, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| workspace=args.workspace, | ||
| agent_name=args.agent_name, | ||
| agent_version=args.agent_version, | ||
|
|
@@ -338,7 +338,7 @@ def parse_args() -> argparse.Namespace: | |
| parser.add_argument("--user-model", default=os.environ.get("TAU2_USER_MODEL", DEFAULT_MODEL)) | ||
| parser.add_argument("--agent-name", default=DEFAULT_AGENT_NAME) | ||
| parser.add_argument("--agent-version", default=DEFAULT_AGENT_VERSION) | ||
| parser.add_argument("--experiment-id") | ||
| parser.add_argument("--evaluation-name") | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Compatibility note: replacing |
||
| parser.add_argument("--expected-task-count", type=_positive_int, default=20) | ||
| parser.add_argument( | ||
| "--task-id", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,7 +58,7 @@ async def _upload_trace_otlp( | |
| workspace: str, | ||
| ref: ResourceRef, | ||
| *, | ||
| experiment_id: str, | ||
| evaluation_name: str, | ||
| trial_id: str, | ||
| task_id: str, | ||
| extra_attrs: dict[str, str] | None = None, | ||
|
|
@@ -67,7 +67,7 @@ async def _upload_trace_otlp( | |
|
|
||
| path = Path(urlparse(ref.uri).path) | ||
| attrs: dict[str, str] = { | ||
| "nemo.experiment.id": experiment_id, | ||
| "nemo.evaluation.name": evaluation_name, | ||
| "nemo.test_case.id": task_id, | ||
| "nemo.trial.id": trial_id, | ||
| **(extra_attrs or {}), | ||
|
|
@@ -87,7 +87,7 @@ async def _upload_trace_atif( | |
| workspace: str, | ||
| ref: ResourceRef, | ||
| *, | ||
| experiment_id: str, | ||
| evaluation_name: str, | ||
| task_id: str, | ||
| extra_attrs: dict[str, str] | None = None, | ||
| ) -> None: | ||
|
|
@@ -100,7 +100,7 @@ async def _upload_trace_atif( | |
|
|
||
| payload = build_ingest_payload( | ||
| ref, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| task_id=task_id, | ||
| agent_attrs=extra_attrs or {}, | ||
| ) | ||
|
|
@@ -206,16 +206,16 @@ async def persist_evaluation( | |
| ) -> None: | ||
| """Persist traces and metrics for a completed evaluation. | ||
|
|
||
| Derives experiment_id internally from candidate × split, ensuring the | ||
| Experiment entity exists before stamping traces. | ||
| Derives the Evaluation name internally from candidate × split, ensuring | ||
| the Evaluation exists before stamping traces. | ||
| """ | ||
| ... | ||
|
|
||
| @abstractmethod | ||
| async def get_experiment_id(self, *, workspace: str, candidate: Candidate, split: str) -> str: | ||
| """Best-effort native Experiment id for *candidate* × *split* in *workspace*. | ||
| async def get_evaluation_name(self, *, workspace: str, candidate: Candidate, split: str) -> str: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Compatibility note: renaming this abstract method requires every downstream |
||
| """Best-effort Evaluation name for *candidate* × *split* in *workspace*. | ||
|
|
||
| Returns "" when there is no projection (offline) or it fails — the id only | ||
| Returns "" when there is no projection (offline) or it fails — the name only | ||
| tags Intake resource attributes, so a run must not break on it. | ||
| """ | ||
| ... | ||
|
|
@@ -630,9 +630,9 @@ async def persist_evaluation( | |
| if self.client is None: | ||
| return # pure-offline run: traces stay on local disk | ||
|
|
||
| # Derive experiment_id internally (ensures entity exists, returns deterministic name) | ||
| experiment_id = await self.get_experiment_id(workspace=workspace, candidate=candidate, split=split) | ||
| if not experiment_id: | ||
| # Derive the Evaluation name internally (ensures the entity exists and returns a deterministic name). | ||
| evaluation_name = await self.get_evaluation_name(workspace=workspace, candidate=candidate, split=split) | ||
| if not evaluation_name: | ||
| return # projection failed (shouldn't happen, but defensive) | ||
|
|
||
| agent_attrs = { | ||
|
|
@@ -649,13 +649,13 @@ async def persist_evaluation( | |
| continue | ||
| try: | ||
| await self._persist_trial( | ||
| trial, workspace=workspace, experiment_id=experiment_id, agent_attrs=agent_attrs | ||
| trial, workspace=workspace, evaluation_name=evaluation_name, agent_attrs=agent_attrs | ||
| ) | ||
| except Exception as exc: # noqa: BLE001 - Intake persistence is best-effort | ||
| logger.warning(f"[INTAKE] persist_evaluation failed for trial {trial.id}: {exc}") | ||
|
|
||
| async def _persist_trial( | ||
| self, trial: TrialResult, *, workspace: str, experiment_id: str, agent_attrs: dict[str, str] | ||
| self, trial: TrialResult, *, workspace: str, evaluation_name: str, agent_attrs: dict[str, str] | ||
| ) -> None: | ||
| assert trial.trace is not None | ||
| assert self.client is not None | ||
|
|
@@ -664,7 +664,7 @@ async def _persist_trial( | |
| trace_id = uri.removeprefix("intake://traces/") | ||
| trace = await self._retrieve_trace_with_retry(trace_id, workspace=workspace) | ||
| ctx = getattr(trace, "evaluation_context", None) | ||
| if ctx is None or getattr(ctx, "evaluation_id", None) != experiment_id: | ||
| if ctx is None or getattr(ctx, "evaluation_id", None) != evaluation_name: | ||
| rows: list[dict] = [] | ||
| async for span in self.client.intake.spans.list( | ||
| workspace=workspace, | ||
|
|
@@ -674,7 +674,7 @@ async def _persist_trial( | |
| ): | ||
| rows.append(span.model_dump(mode="json", exclude_none=True)) | ||
| attrs = { | ||
| "nemo.experiment.id": experiment_id, | ||
| "nemo.evaluation.name": evaluation_name, | ||
| "nemo.test_case.id": trial.task_id, | ||
| "nemo.trial.id": trial.id, | ||
| **agent_attrs, | ||
|
|
@@ -702,7 +702,7 @@ async def _persist_trial( | |
| self.client, | ||
| workspace, | ||
| trial.trace, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| task_id=trial.task_id, | ||
| extra_attrs=agent_attrs, | ||
| ) | ||
|
|
@@ -711,7 +711,7 @@ async def _persist_trial( | |
| self.client, | ||
| workspace, | ||
| trial.trace, | ||
| experiment_id=experiment_id, | ||
| evaluation_name=evaluation_name, | ||
| trial_id=trial.id, | ||
| task_id=trial.task_id, | ||
| extra_attrs=agent_attrs, | ||
|
|
@@ -755,7 +755,7 @@ async def _retrieve_trace_with_retry( | |
| delay *= 2 # Exponential backoff | ||
| raise last_exc | ||
|
|
||
| async def get_experiment_id(self, *, workspace: str, candidate: Candidate, split: str) -> str: | ||
| async def get_evaluation_name(self, *, workspace: str, candidate: Candidate, split: str) -> str: | ||
| if self.client is None: | ||
| return "" | ||
| mirror = self._mirrors.get(workspace) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Document legacy OTLP-key compatibility.
Ingest still accepts and normalizes
nemo.experiment.id. State this here so existing producers are not told to migrate unnecessarily when diagnosing missing evaluation associations.Proposed clarification
📝 Committable suggestion
🧰 Tools
🪛 SkillSpector (2.5.1)
[warning] 69: [E1] External Transmission: Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
Remediation: Verify the destination URL is trusted and necessary. Remove or replace with documented APIs. Ensure no secrets, tokens, or PII are transmitted.
(Data Exfiltration (E1))
[error] 99: [SC2] External Script Fetching: Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
Remediation: Avoid downloading and executing remote scripts. Use trusted packages from PyPI/npm. If remote fetch is required, verify checksums and use HTTPS.
(Supply Chain (SC2))
🤖 Prompt for AI Agents