Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

816 lines
29 KiB
Python

"""Adapter-facing NL2Repo smoke harness for the code-agent matrix.
The upstream NL2Repo runner is OpenHands-specific. This module keeps the
dataset/task contract reusable for Eliza/OpenCode matrix plumbing while live
agent execution is still being wired.
"""
from __future__ import annotations
import argparse
import json
import logging
import os
import shutil
import shlex
import subprocess
import sys
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any, Callable
@dataclass(frozen=True)
class NL2RepoTask:
name: str
prompt_md_path: str
test_commands: list[str]
strip_paths: list[str]
test_case_count: int
eval_image: str
source_name: str = ""
edge_condition: str = ""
PostProcessFn = Callable[[str, str, Any, Any], dict[str, Any]]
EDGE_VARIANTS: tuple[str, ...] = (
"preserve public API names while adding complete implementations",
"handle empty, missing, and malformed input files gracefully",
"support unicode text, path separators, and whitespace-heavy inputs",
"avoid network access and keep all behavior deterministic offline",
"maintain compatibility with the package's documented CLI entrypoints",
"cover nested package imports and relative import behavior",
"keep generated tests and benchmark test files separate from source",
"handle large collections without quadratic slowdowns",
"provide clear exceptions without swallowing assertion failures",
"maintain packaging metadata for editable installation",
)
def nl2repo_root() -> Path:
return Path(__file__).resolve().parent
def canonical_task_names(root: Path | None = None) -> list[str]:
root = nl2repo_root() if root is None else root
config_path = root / "config.json"
config = json.loads(config_path.read_text(encoding="utf-8"))
start_pro = config.get("startPro")
if not isinstance(start_pro, list) or not start_pro:
raise ValueError("NL2Repo config.json is missing startPro")
names = start_pro[0].get("proNameList")
if not isinstance(names, list):
raise ValueError("NL2Repo config.json is missing startPro[0].proNameList")
return [str(name) for name in names]
def expand_tasks(tasks: list[NL2RepoTask], *, expand_scenarios: bool = False) -> list[NL2RepoTask]:
if not expand_scenarios:
return list(tasks)
expanded = list(tasks)
for task in tasks:
source_name = task.source_name or task.name
for index, edge_condition in enumerate(EDGE_VARIANTS, start=1):
expanded.append(
NL2RepoTask(
name=f"{source_name}__edge_{index:02d}",
prompt_md_path=task.prompt_md_path,
test_commands=list(task.test_commands),
strip_paths=list(task.strip_paths),
test_case_count=task.test_case_count,
eval_image=task.eval_image,
source_name=source_name,
edge_condition=edge_condition,
)
)
return expanded
def count_tasks(tasks: list[NL2RepoTask], *, expand_scenarios: bool = False) -> dict[str, int]:
base = len(tasks)
edge = base * len(EDGE_VARIANTS) if expand_scenarios else 0
return {"base": base, "edge": edge, "total": base + edge}
def validate_tasks(tasks: list[NL2RepoTask], *, expand_scenarios: bool = False) -> dict[str, Any]:
expanded = expand_tasks(tasks, expand_scenarios=expand_scenarios)
names = [task.name for task in expanded]
duplicate_count = len(names) - len(set(names))
missing_prompts = [
task.name for task in expanded if not Path(task.prompt_md_path).exists()
]
return {
"valid": duplicate_count == 0 and not missing_prompts,
"duplicate_count": duplicate_count,
"missing_prompts": missing_prompts,
"total": len(expanded),
}
def load_tasks(root: Path | None = None, *, max_tasks: int | None = None) -> list[NL2RepoTask]:
root = nl2repo_root() if root is None else root
tasks: list[NL2RepoTask] = []
for name in canonical_task_names(root):
task_dir = root / "test_files" / name
prompt = task_dir / "start.md"
commands_path = task_dir / "test_commands.json"
strip_paths_path = task_dir / "test_files.json"
count_path = task_dir / "test_case_count.txt"
if not (prompt.exists() and commands_path.exists() and strip_paths_path.exists() and count_path.exists()):
raise FileNotFoundError(f"NL2Repo task is incomplete: {name}")
commands = json.loads(commands_path.read_text(encoding="utf-8"))
strip_paths = json.loads(strip_paths_path.read_text(encoding="utf-8"))
if not isinstance(commands, list) or not isinstance(strip_paths, list):
raise ValueError(f"NL2Repo task metadata must be JSON arrays: {name}")
tasks.append(
NL2RepoTask(
name=name,
prompt_md_path=str(prompt),
test_commands=[str(command) for command in commands],
strip_paths=[str(path) for path in strip_paths],
test_case_count=int(count_path.read_text(encoding="utf-8").strip()),
eval_image=f"ghcr.io/multimodal-art-projection/nl2repobench/{name}:1.0",
)
)
if max_tasks is not None and len(tasks) >= max_tasks:
break
return tasks
def build_mock_result(
*,
tasks: list[NL2RepoTask],
task_agent: str,
model_provider: str,
model: str,
) -> dict[str, Any]:
results = [
{
"task": task.name,
"status": "mock",
"success": True,
"score": 1.0,
"passed": task.test_case_count,
"failed": 0,
"errors": 0,
"total": task.test_case_count,
"eval_image": task.eval_image,
}
for task in tasks
]
total = len(results)
return {
"benchmark": "nl2repo",
"adapter": task_agent,
"model_provider": model_provider,
"model": model,
"mode": "mock",
"summary": {
"total_instances": total,
"resolved": total,
"unresolved": 0,
"resolve_rate": 1.0 if total else 0.0,
"score": 1.0 if total else 0.0,
},
"results": results,
}
def _adapter_command_env_name(task_agent: str) -> str:
normalized = "".join(char if char.isalnum() else "_" for char in task_agent).upper()
return f"NL2REPO_AGENT_COMMAND_TEMPLATE_{normalized}"
def agent_command_template(task_agent: str, explicit: str = "") -> str:
return (
explicit
or os.environ.get(_adapter_command_env_name(task_agent), "")
or os.environ.get("NL2REPO_AGENT_COMMAND_TEMPLATE", "")
).strip()
def _format_command(template: str, values: dict[str, str]) -> list[str]:
return shlex.split(template.format(**values))
def _read_agent_result(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return {}
return payload if isinstance(payload, dict) else {}
def _number(value: Any) -> int:
if isinstance(value, bool):
return 0
if isinstance(value, (int, float)):
return int(value)
return 0
def _first_present(payload: dict[str, Any], *keys: str) -> Any:
for key in keys:
if key in payload:
return payload.get(key)
return None
def _token_detail_value(usage: dict[str, Any], *keys: str) -> Any:
for container_key in ("prompt_tokens_details", "input_token_details", "token_details"):
details = usage.get(container_key)
if not isinstance(details, dict):
continue
value = _first_present(details, *keys)
if value is not None:
return value
return None
def _single_usage_token_metrics(usage: dict[str, Any]) -> dict[str, int]:
tokens = usage.get("tokens")
token_payload = tokens if isinstance(tokens, dict) else usage
cache_payload = token_payload.get("cache") if isinstance(token_payload, dict) else None
cache_payload = cache_payload if isinstance(cache_payload, dict) else {}
input_tokens = _number(
_first_present(
token_payload,
"promptTokens",
"prompt_tokens",
"input_tokens",
"input",
)
)
output_tokens = _number(
_first_present(
token_payload,
"completionTokens",
"completion_tokens",
"output_tokens",
"output",
)
)
total_tokens = _number(
_first_present(token_payload, "totalTokens", "total_tokens", "total")
)
cached_raw = _first_present(
token_payload,
"cachedTokens",
"cached_tokens",
"cacheReadInputTokens",
"cache_read_input_tokens",
)
if cached_raw is None:
cached_raw = _token_detail_value(
usage,
"cached_tokens",
"cache_read_input_tokens",
"prompt_cache_hit_tokens",
)
if cached_raw is None:
cached_raw = _first_present(cache_payload, "read", "cached", "hit")
cache_creation_raw = _first_present(
token_payload,
"cacheCreationInputTokens",
"cache_creation_input_tokens",
)
if cache_creation_raw is None:
cache_creation_raw = _token_detail_value(
usage,
"cache_creation_input_tokens",
"cache_write_tokens",
"prompt_cache_miss_tokens",
)
if cache_creation_raw is None:
cache_creation_raw = _first_present(cache_payload, "write", "created", "miss")
if not total_tokens and (input_tokens or output_tokens):
total_tokens = input_tokens + output_tokens
return {
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"total_tokens": total_tokens,
"cached_tokens": _number(cached_raw),
"cache_creation_tokens": _number(cache_creation_raw),
}
def token_metrics_from_usage(usage: dict[str, Any]) -> dict[str, int | float | None]:
calls = usage.get("calls")
call_items = [call for call in calls if isinstance(call, dict)] if isinstance(calls, list) else []
if call_items:
call_metrics = [_single_usage_token_metrics(call) for call in call_items]
input_tokens = sum(metric["input_tokens"] for metric in call_metrics)
output_tokens = sum(metric["output_tokens"] for metric in call_metrics)
total_tokens = sum(metric["total_tokens"] for metric in call_metrics)
cached_tokens = sum(metric["cached_tokens"] for metric in call_metrics)
cache_creation_tokens = sum(metric["cache_creation_tokens"] for metric in call_metrics)
if not total_tokens and (input_tokens or output_tokens):
total_tokens = input_tokens + output_tokens
else:
metrics = _single_usage_token_metrics(usage)
input_tokens = metrics["input_tokens"]
output_tokens = metrics["output_tokens"]
total_tokens = metrics["total_tokens"]
cached_tokens = metrics["cached_tokens"]
cache_creation_tokens = metrics["cache_creation_tokens"]
cached_token_percent = (
(cached_tokens / input_tokens) * 100.0 if input_tokens else None
)
explicit_calls = _number(_first_present(usage, "llm_call_count", "llmCallCount"))
llm_call_count = (
explicit_calls
if explicit_calls
else len(call_items)
if call_items
else 1
if input_tokens or output_tokens
else 0
)
return {
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"total_tokens": total_tokens,
"cached_tokens": cached_tokens,
"cache_creation_tokens": cache_creation_tokens,
"cached_token_percent": cached_token_percent,
"llm_call_count": llm_call_count,
}
def _write_agent_trajectory(
*,
trajectory_dir: Path | None,
task: NL2RepoTask,
agent_result: dict[str, Any],
) -> str:
if trajectory_dir is None:
return ""
usage = agent_result.get("usage")
if not isinstance(usage, dict) or not usage:
return ""
trajectory_dir.mkdir(parents=True, exist_ok=True)
path = trajectory_dir / f"trajectory-{task.name}.jsonl"
path.write_text(
json.dumps(
{
"task": task.name,
"prompt": agent_result.get("prompt") or "",
"usage": usage,
"agent_status": agent_result.get("status"),
},
sort_keys=True,
)
+ "\n",
encoding="utf-8",
)
return str(path)
def build_agent_instruction(task: NL2RepoTask, workspace: Path) -> str:
lines = [
"You are running NL2Repo-Bench.",
"",
f"Task: {task.name}",
f"Source task: {task.source_name or task.name}",
f"Workspace: {workspace}",
f"Requirements document: {workspace / 'start.md'}",
]
if task.edge_condition:
lines.extend(["", f"Edge condition: {task.edge_condition}."])
lines.extend(
[
"",
"Implement the complete Python project described in start.md inside this workspace.",
"Create or edit files only under the workspace unless your agent runtime needs temporary files.",
"Do not rely on hidden tests being present; the evaluator will remove any generated test files listed by the benchmark and run the canonical tests from the Docker image.",
"Make the project installable and runnable from the workspace root.",
"When finished, leave the generated repository contents in the workspace and exit successfully.",
]
)
return "\n".join(lines)
def _prepare_workspace(output_dir: Path, task: NL2RepoTask) -> Path:
workspace = output_dir / "workspaces" / task.name / "workspace"
workspace.mkdir(parents=True, exist_ok=True)
shutil.copy2(task.prompt_md_path, workspace / "start.md")
if task.edge_condition:
start_path = workspace / "start.md"
start_path.write_text(
start_path.read_text(encoding="utf-8")
+ "\n\n"
+ f"Additional benchmark edge condition: {task.edge_condition}.\n",
encoding="utf-8",
)
(workspace / "NL2REPO_TASK.md").write_text(
build_agent_instruction(task, workspace),
encoding="utf-8",
)
return workspace
def run_agent_generation(
*,
output_dir: Path,
trajectory_dir: Path | None,
tasks: list[NL2RepoTask],
task_agent: str,
model_provider: str,
model: str,
command_template: str,
timeout_seconds: int,
) -> list[dict[str, Any]]:
logs_dir = output_dir / "logs"
logs_dir.mkdir(parents=True, exist_ok=True)
if trajectory_dir is not None:
trajectory_dir.mkdir(parents=True, exist_ok=True)
results: list[dict[str, Any]] = []
for task in tasks:
workspace = _prepare_workspace(output_dir, task)
agent_result_path = workspace / ".nl2repo-agent-result.json"
command = _format_command(
command_template,
{
"task": task.name,
"workspace": str(workspace),
"prompt": str(workspace / "start.md"),
"instruction": str(workspace / "NL2REPO_TASK.md"),
"result_json": str(agent_result_path),
"task_dir": str(Path(task.prompt_md_path).parent),
"output": str(output_dir),
"adapter": task_agent,
"model_provider": model_provider,
"model": model,
},
)
completed = subprocess.run(
command,
cwd=workspace,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=timeout_seconds,
check=False,
)
stdout_path = logs_dir / f"{task.name}.stdout.log"
stderr_path = logs_dir / f"{task.name}.stderr.log"
stdout_path.write_text(completed.stdout or "", encoding="utf-8")
stderr_path.write_text(completed.stderr or "", encoding="utf-8")
agent_result = _read_agent_result(agent_result_path)
usage = agent_result.get("usage") if isinstance(agent_result, dict) else None
token_metrics = token_metrics_from_usage(usage) if isinstance(usage, dict) else {}
trajectory_path = _write_agent_trajectory(
trajectory_dir=trajectory_dir,
task=task,
agent_result=agent_result,
)
results.append(
{
"task": task.name,
"status": "generated" if completed.returncode == 0 else "generation_failed",
"success": completed.returncode == 0,
"score": 0.0,
"passed": 0,
"failed": task.test_case_count,
"errors": 0 if completed.returncode == 0 else 1,
"total": task.test_case_count,
"workspace": str(workspace),
"eval_image": task.eval_image,
"agent_command": command,
"exit_code": completed.returncode,
"stdout_path": str(stdout_path),
"stderr_path": str(stderr_path),
"agent_result_path": str(agent_result_path),
"agent_result_status": agent_result.get("status"),
"token_metrics": token_metrics,
"trajectory_path": trajectory_path,
}
)
return results
def build_generation_only_result(
*,
tasks: list[NL2RepoTask],
task_agent: str,
model_provider: str,
model: str,
results: list[dict[str, Any]],
) -> dict[str, Any]:
generated = sum(1 for item in results if item.get("success") is True)
total = len(tasks)
return {
"benchmark": "nl2repo",
"adapter": task_agent,
"model_provider": model_provider,
"model": model,
"mode": "generation_only",
"summary": {
"total_instances": total,
"resolved": 0,
"unresolved": total,
"resolve_rate": 0.0,
"score": 0.0,
"generated": generated,
},
"results": results,
"error": "NL2Repo Docker post-processing was skipped; scores are not release-comparable",
"required_next_step": "run without --no-docker after the adapter command can generate workspaces",
}
def _docker_safe_id(value: str) -> str:
normalized = "".join(char.lower() if char.isalnum() else "-" for char in value)
return "-".join(part for part in normalized.split("-") if part) or "task"
def _make_test_data(task: NL2RepoTask) -> Any:
root = nl2repo_root()
if str(root) not in sys.path:
sys.path.insert(0, str(root))
from test_data_service import TestData # type: ignore
return TestData(
pro_name=task.source_name or task.name,
test_case_count=task.test_case_count,
test_shell=task.test_commands,
py_test_file_list=task.strip_paths,
image_tar="",
md=task.prompt_md_path,
)
def _load_post_processor() -> PostProcessFn:
root = nl2repo_root()
if str(root) not in sys.path:
sys.path.insert(0, str(root))
from openhands.post_processor import post_process_task # type: ignore
return post_process_task
def _result_from_postprocess(
*,
task: NL2RepoTask,
generation: dict[str, Any],
post_process_result: dict[str, Any],
) -> dict[str, Any]:
pytest_results = post_process_result.get("pytest_results") or {}
passed = int(pytest_results.get("passed") or 0)
failed = int(pytest_results.get("failed") or 0)
errors = int(pytest_results.get("errors") or 0)
total = int(pytest_results.get("total") or task.test_case_count)
score = min(passed / total, 1.0) if total else 0.0
status = "completed" if post_process_result.get("status") == "success" else "scoring_failed"
return {
**generation,
"status": status,
"success": status == "completed",
"score": score,
"passed": passed,
"failed": failed,
"errors": errors,
"total": total,
"post_process_result": post_process_result,
}
def score_generated_workspaces(
*,
tasks: list[NL2RepoTask],
task_agent: str,
generation_results: list[dict[str, Any]],
post_process: PostProcessFn | None = None,
) -> list[dict[str, Any]]:
post_process = _load_post_processor() if post_process is None else post_process
logger = logging.getLogger("nl2repo.adapter_matrix")
task_by_name = {task.name: task for task in tasks}
scored: list[dict[str, Any]] = []
for generation in generation_results:
task_name = str(generation.get("task") or "")
task = task_by_name[task_name]
if generation.get("success") is not True:
scored.append(generation)
continue
task_uuid = f"{_docker_safe_id(task.name)}-{_docker_safe_id(task_agent)}-{os.getpid()}"
post_process_result = post_process(
task_uuid,
str(generation["workspace"]),
_make_test_data(task),
logger,
)
scored.append(
_result_from_postprocess(
task=task,
generation=generation,
post_process_result=post_process_result,
)
)
return scored
def build_scored_result(
*,
tasks: list[NL2RepoTask],
task_agent: str,
model_provider: str,
model: str,
results: list[dict[str, Any]],
) -> dict[str, Any]:
total = len(tasks)
resolved = sum(1 for item in results if item.get("status") == "completed")
total_score = sum(float(item.get("score") or 0.0) for item in results)
return {
"benchmark": "nl2repo",
"adapter": task_agent,
"model_provider": model_provider,
"model": model,
"mode": "live",
"summary": {
"total_instances": total,
"resolved": resolved,
"unresolved": total - resolved,
"resolve_rate": resolved / total if total else 0.0,
"score": total_score / total if total else 0.0,
},
"results": results,
}
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Run NL2Repo through a code-agent adapter.")
parser.add_argument("--agent-harness", default="eliza")
parser.add_argument("--task-agent", default="elizaos")
parser.add_argument("--model-provider", default="cerebras")
parser.add_argument("--model", default="gemma-4-31b")
parser.add_argument("--output", required=True)
parser.add_argument("--trajectory-dir", default="")
parser.add_argument("--max-tasks", type=int, default=1)
parser.add_argument("--agent-command-template", default="")
parser.add_argument("--timeout-seconds", type=int, default=3600)
parser.add_argument("--mock", action="store_true")
parser.add_argument("--no-docker", action="store_true")
parser.add_argument("--expand-scenarios", action="store_true")
parser.add_argument("--count-scenarios", action="store_true")
parser.add_argument("--validate-scenarios", action="store_true")
parser.add_argument("--json", action="store_true")
return parser.parse_args(argv)
def _truthy_env(name: str) -> bool:
return os.getenv(name, "").strip().lower() in {"1", "true", "yes", "on"}
def _with_scenario_metadata(
result: dict[str, Any],
*,
counts: dict[str, int],
expand_scenarios: bool,
) -> dict[str, Any]:
result["include_edge_scenarios"] = expand_scenarios
result["scenario_counts"] = counts
return result
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
output_dir = Path(args.output)
trajectory_dir = Path(args.trajectory_dir) if args.trajectory_dir else None
output_dir.mkdir(parents=True, exist_ok=True)
base_tasks = load_tasks(max_tasks=args.max_tasks)
expand_scenarios = (
args.expand_scenarios
or _truthy_env("EXPAND_SCENARIOS")
or _truthy_env("INCLUDE_EDGE_SCENARIOS")
)
counts = count_tasks(base_tasks, expand_scenarios=expand_scenarios)
if args.count_scenarios or _truthy_env("COUNT_SCENARIOS"):
print(
"NL2Repo scenario counts: "
f"base={counts['base']} edge={counts['edge']} total={counts['total']}"
)
if args.validate_scenarios or _truthy_env("VALIDATE_SCENARIOS"):
validation = validate_tasks(base_tasks, expand_scenarios=expand_scenarios)
if not validation["valid"]:
raise ValueError(f"Invalid NL2Repo task expansion: {validation}")
print(f"NL2Repo scenario validation passed: {counts['total']} task(s)")
tasks = expand_tasks(base_tasks, expand_scenarios=expand_scenarios)
if args.mock:
result = _with_scenario_metadata(
build_mock_result(
tasks=tasks,
task_agent=args.task_agent,
model_provider=args.model_provider,
model=args.model,
),
counts=counts,
expand_scenarios=expand_scenarios,
)
result_path = output_dir / "result.json"
result_path.write_text(json.dumps(result, indent=2, sort_keys=True), encoding="utf-8")
if args.json:
print(json.dumps(result, indent=2, sort_keys=True))
return 0
command_template = agent_command_template(args.task_agent, args.agent_command_template)
if not command_template:
result = _with_scenario_metadata({
"benchmark": "nl2repo",
"adapter": args.task_agent,
"model_provider": args.model_provider,
"model": args.model,
"mode": "live",
"summary": {
"total_instances": len(tasks),
"resolved": 0,
"unresolved": len(tasks),
"resolve_rate": 0.0,
"score": 0.0,
},
"error": "NL2Repo live adapter command template is not configured",
"required_next_step": (
"set NL2REPO_AGENT_COMMAND_TEMPLATE or "
f"{_adapter_command_env_name(args.task_agent)}"
),
}, counts=counts, expand_scenarios=expand_scenarios)
(output_dir / "result.json").write_text(json.dumps(result, indent=2, sort_keys=True), encoding="utf-8")
if args.json:
print(json.dumps(result, indent=2, sort_keys=True), file=sys.stderr)
return 2
generated_results = run_agent_generation(
output_dir=output_dir,
trajectory_dir=trajectory_dir,
tasks=tasks,
task_agent=args.task_agent,
model_provider=args.model_provider,
model=args.model,
command_template=command_template,
timeout_seconds=args.timeout_seconds,
)
if args.no_docker:
result = _with_scenario_metadata(
build_generation_only_result(
tasks=tasks,
task_agent=args.task_agent,
model_provider=args.model_provider,
model=args.model,
results=generated_results,
),
counts=counts,
expand_scenarios=expand_scenarios,
)
(output_dir / "result.json").write_text(json.dumps(result, indent=2, sort_keys=True), encoding="utf-8")
if args.json:
print(json.dumps(result, indent=2, sort_keys=True), file=sys.stderr)
return 2
try:
scored_results = score_generated_workspaces(
tasks=tasks,
task_agent=args.task_agent,
generation_results=generated_results,
)
result = _with_scenario_metadata(
build_scored_result(
tasks=tasks,
task_agent=args.task_agent,
model_provider=args.model_provider,
model=args.model,
results=scored_results,
),
counts=counts,
expand_scenarios=expand_scenarios,
)
except Exception as exc:
result = _with_scenario_metadata(
build_generation_only_result(
tasks=tasks,
task_agent=args.task_agent,
model_provider=args.model_provider,
model=args.model,
results=generated_results,
),
counts=counts,
expand_scenarios=expand_scenarios,
)
result["mode"] = "postprocess_failed"
result["error"] = f"NL2Repo Docker post-processing failed: {exc}"
result["required_next_step"] = (
"install NL2Repo Docker dependencies and verify Docker can pull/run the eval image"
)
(output_dir / "result.json").write_text(json.dumps(result, indent=2, sort_keys=True), encoding="utf-8")
if args.json:
stream = sys.stdout if result.get("mode") == "live" else sys.stderr
print(json.dumps(result, indent=2, sort_keys=True), file=stream)
return 0 if result.get("mode") == "live" else 2
if __name__ == "__main__":
raise SystemExit(main())