Spaces:
Running
Running
SilverElixir commited on
Commit ·
a0f5ec7
1
Parent(s): ecde1bf
Fix harness-eval: agent edits vs setup artifacts, weak arm, atomic cache
Browse files- eval/harness_eval.py +114 -52
- eval/harness_weak.md +3 -0
eval/harness_eval.py
CHANGED
|
@@ -35,7 +35,9 @@ OUT_ROOT = Path(
|
|
| 35 |
OPENCODE_DB = Path(os.environ.get("USERPROFILE", ".")) / ".local" / "share" / "opencode" / "opencode.db"
|
| 36 |
|
| 37 |
AGENT_MODEL = os.environ.get("EVAL_AGENT_MODEL", "opencode/glm-5.3-flash-free")
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
AGENT_TIMEOUT = int(os.environ.get("EVAL_AGENT_TIMEOUT", "1200"))
|
| 40 |
|
| 41 |
# Команды, которые агент в песочнице запускать не должен: часть из них ломает
|
|
@@ -226,7 +228,10 @@ def compute_targets(task: Task) -> dict:
|
|
| 226 |
}
|
| 227 |
cache[key] = entry
|
| 228 |
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
| 230 |
return entry
|
| 231 |
|
| 232 |
|
|
@@ -256,10 +261,13 @@ def pytest_state(sandbox: Path) -> tuple[set[str], str]:
|
|
| 256 |
|
| 257 |
|
| 258 |
def pyflakes_state(sandbox: Path) -> tuple[bool, str]:
|
| 259 |
-
#
|
| 260 |
modules = sorted(p.name for p in sandbox.glob("lumen_*.py"))
|
|
|
|
| 261 |
proc = run_cmd(
|
| 262 |
-
[sys.executable, "-m", "pyflakes", "bot.py", "system_prompt.py", *modules
|
|
|
|
|
|
|
| 263 |
)
|
| 264 |
return proc.returncode == 0, (proc.stdout + proc.stderr)[-2000:]
|
| 265 |
|
|
@@ -347,15 +355,21 @@ def session_from_db(sandbox: Path, retries: int = 10) -> dict | None:
|
|
| 347 |
return None
|
| 348 |
|
| 349 |
|
| 350 |
-
def keep_sandbox(sandbox: Path, kept: Path) -> dict:
|
| 351 |
-
"""Снимает слепок песочницы: после прогона содержимое могут снести.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
info = {"changed_files": [], "diff": ""}
|
| 353 |
if not sandbox.exists():
|
| 354 |
return info
|
| 355 |
try:
|
| 356 |
-
|
| 357 |
except RuntimeError:
|
| 358 |
-
|
|
|
|
|
|
|
| 359 |
if kept.exists():
|
| 360 |
shutil.rmtree(kept, ignore_errors=True)
|
| 361 |
shutil.copytree(sandbox, kept, ignore=shutil.ignore_patterns(".pip-target"), dirs_exist_ok=True)
|
|
@@ -432,40 +446,39 @@ def score(task: Task, result: RunResult, targets: list[str]) -> None:
|
|
| 432 |
result.solved = not result.target_failed and not result.new_failures
|
| 433 |
|
| 434 |
|
| 435 |
-
def
|
|
|
|
| 436 |
tasks = {t.id: t for t in load_tasks()}
|
| 437 |
-
task = tasks[
|
| 438 |
-
out_dir = Path(args.out) if args.out else OUT_ROOT / time.strftime("%Y%m%d-%H%M%S")
|
| 439 |
out_dir.mkdir(parents=True, exist_ok=True)
|
| 440 |
-
sandbox = out_dir / f"sandbox-{task.id}-{
|
| 441 |
-
kept = out_dir / f"kept-{task.id}-{
|
| 442 |
-
agents_md = Path(args.agents_md) if args.agents_md else None
|
| 443 |
-
model = args.model or AGENT_MODEL
|
| 444 |
|
| 445 |
-
log(f"песочница {task.id} ({
|
| 446 |
prepare(task, sandbox, agents_md)
|
| 447 |
|
| 448 |
pre_fail, _ = pytest_state(sandbox)
|
| 449 |
targets = compute_targets(task)["fail_to_pass"]
|
|
|
|
| 450 |
log(f"до агента падают {len(pre_fail)} тестов, целевых (FAIL_TO_PASS): {len(targets)}")
|
| 451 |
|
| 452 |
-
transcript = out_dir / f"{task.id}.{
|
| 453 |
log(f"агент: {model}, таймаут {AGENT_TIMEOUT}с")
|
| 454 |
started = time.time()
|
| 455 |
final, _ = run_agent(sandbox, task.prompt(), model, transcript, AGENT_TIMEOUT)
|
| 456 |
duration = round(time.time() - started, 1)
|
| 457 |
|
| 458 |
# Слепок снимаем сразу: содержимое песочницы могут снести (чистка temp, OpenCode).
|
| 459 |
-
snapshot = keep_sandbox(sandbox, kept)
|
| 460 |
session = session_from_db(sandbox)
|
| 461 |
if session:
|
| 462 |
-
(out_dir / f"{task.id}.{
|
| 463 |
json.dumps(session, ensure_ascii=False, indent=2), encoding="utf-8"
|
| 464 |
)
|
| 465 |
|
| 466 |
result = RunResult(
|
| 467 |
task=task.id,
|
| 468 |
-
variant=
|
| 469 |
model=model,
|
| 470 |
duration_s=duration,
|
| 471 |
final_message=final,
|
|
@@ -480,7 +493,7 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
| 480 |
|
| 481 |
payload = {
|
| 482 |
"task": task.id,
|
| 483 |
-
"variant":
|
| 484 |
"model": model,
|
| 485 |
"solved": result.solved,
|
| 486 |
"pre_fail": result.pre_fail,
|
|
@@ -496,10 +509,42 @@ def cmd_run(args: argparse.Namespace) -> int:
|
|
| 496 |
"diff": snapshot["diff"][-8000:],
|
| 497 |
"post_pytest_tail": post_out[-1500:],
|
| 498 |
}
|
| 499 |
-
result_file = out_dir / f"{task.id}.{
|
| 500 |
result_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
| 501 |
log(f"итог: {'решено' if result.solved else 'не решено'} ({duration}с) -> {result_file}")
|
| 502 |
check_repo_untouched()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 503 |
return 0
|
| 504 |
|
| 505 |
|
|
@@ -510,7 +555,9 @@ def check_repo_untouched() -> None:
|
|
| 510 |
except RuntimeError as exc:
|
| 511 |
log(f"ВНИМАНИЕ: не смог проверить состояние репозитория: {exc}")
|
| 512 |
return
|
| 513 |
-
unexpected = [
|
|
|
|
|
|
|
| 514 |
if unexpected:
|
| 515 |
log(f"ВНИМАНИЕ: репозиторий изменён во время прогона: {unexpected[:5]}")
|
| 516 |
|
|
@@ -548,14 +595,14 @@ def cmd_propose(args: argparse.Namespace) -> int:
|
|
| 548 |
reports.mkdir()
|
| 549 |
for run_file in runs:
|
| 550 |
data = json.loads(run_file.read_text(encoding="utf-8"))
|
| 551 |
-
|
| 552 |
-
continue
|
| 553 |
body = [
|
| 554 |
f"Задача: {data['task']}",
|
|
|
|
| 555 |
f"Целевые тесты: {', '.join(data.get('target_tests') or [])}",
|
| 556 |
f"Целевые тесты после прогона упали: {', '.join(data.get('target_failed') or [])}",
|
| 557 |
f"Новые падения: {', '.join(data.get('new_failures') or [])}",
|
| 558 |
-
f"Нарушения правил: {json.dumps(
|
| 559 |
f"pyflakes: {'ok' if data.get('pyflakes_ok') else 'ошибка'}",
|
| 560 |
"",
|
| 561 |
"Последнее сообщение агента:",
|
|
@@ -566,13 +613,15 @@ def cmd_propose(args: argparse.Namespace) -> int:
|
|
| 566 |
]
|
| 567 |
(reports / f"{data['task']}.md").write_text("\n".join(body), encoding="utf-8")
|
| 568 |
if not any(reports.iterdir()):
|
| 569 |
-
log("базовы
|
| 570 |
return 0
|
| 571 |
|
| 572 |
prompt = (
|
| 573 |
-
"Ты редактор правил проекта. Посмотри AGENTS.md и отчёты о про
|
| 574 |
-
"Составь новый AGENTS.md: добавь правила, которые предотвратили бы
|
| 575 |
-
"
|
|
|
|
|
|
|
| 576 |
"Существующие правила не удаляй и не переписывай, только дополняй.\n"
|
| 577 |
"Сохрани результат в файл AGENTS.candidate.md в этой же папке. Больше ничего не меняй."
|
| 578 |
)
|
|
@@ -602,10 +651,8 @@ def cmd_propose(args: argparse.Namespace) -> int:
|
|
| 602 |
|
| 603 |
def cmd_report(args: argparse.Namespace) -> int:
|
| 604 |
out_dir = Path(args.out) if args.out else OUT_ROOT
|
| 605 |
-
files = sorted(out_dir.glob("*.json"))
|
| 606 |
-
files = [f for f in files if f.name not in {"propose.json"}]
|
| 607 |
rows = []
|
| 608 |
-
for file in
|
| 609 |
try:
|
| 610 |
data = json.loads(file.read_text(encoding="utf-8"))
|
| 611 |
except json.JSONDecodeError:
|
|
@@ -616,31 +663,37 @@ def cmd_report(args: argparse.Namespace) -> int:
|
|
| 616 |
by_task: dict[str, dict[str, dict]] = {}
|
| 617 |
for row in rows:
|
| 618 |
by_task.setdefault(row["task"], {})[row["variant"]] = row
|
|
|
|
| 619 |
|
|
|
|
|
|
|
| 620 |
lines = [
|
| 621 |
-
"# Harness-eval:
|
| 622 |
"",
|
| 623 |
f"Модель агента: {AGENT_MODEL} · Модель-редактор: {PROPOSER_MODEL}",
|
| 624 |
"",
|
| 625 |
-
|
| 626 |
-
|
| 627 |
]
|
| 628 |
-
|
| 629 |
-
for task_id,
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
|
|
|
|
|
|
|
|
|
| 644 |
text = "\n".join(lines)
|
| 645 |
print(text)
|
| 646 |
if args.out:
|
|
@@ -677,6 +730,15 @@ def main() -> int:
|
|
| 677 |
run.add_argument("--out", default=None)
|
| 678 |
run.set_defaults(func=cmd_run)
|
| 679 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
check = sub.add_parser("check", help="проверить, что задачи валидны на базе")
|
| 681 |
check.add_argument("--out", default=None)
|
| 682 |
check.set_defaults(func=cmd_check)
|
|
|
|
| 35 |
OPENCODE_DB = Path(os.environ.get("USERPROFILE", ".")) / ".local" / "share" / "opencode" / "opencode.db"
|
| 36 |
|
| 37 |
AGENT_MODEL = os.environ.get("EVAL_AGENT_MODEL", "opencode/glm-5.3-flash-free")
|
| 38 |
+
# Google-модели из этого аккаунта недоступны ("User location is not supported"),
|
| 39 |
+
# поэтому и агент, и редактор берутся из бесплатной линейки opencode.
|
| 40 |
+
PROPOSER_MODEL = os.environ.get("EVAL_PROPOSER_MODEL", "opencode/nemotron-3-ultra-free")
|
| 41 |
AGENT_TIMEOUT = int(os.environ.get("EVAL_AGENT_TIMEOUT", "1200"))
|
| 42 |
|
| 43 |
# Команды, которые агент в песочнице запускать не должен: часть из них ломает
|
|
|
|
| 228 |
}
|
| 229 |
cache[key] = entry
|
| 230 |
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
| 231 |
+
# запись через временный файл: батчи считают цели параллельно
|
| 232 |
+
tmp = cache_file.with_suffix(".json.tmp")
|
| 233 |
+
tmp.write_text(json.dumps(cache, ensure_ascii=False, indent=2), encoding="utf-8")
|
| 234 |
+
tmp.replace(cache_file)
|
| 235 |
return entry
|
| 236 |
|
| 237 |
|
|
|
|
| 261 |
|
| 262 |
|
| 263 |
def pyflakes_state(sandbox: Path) -> tuple[bool, str]:
|
| 264 |
+
# Гейт как в CI: bot.py, system_prompt.py, все lumen_*.py и все tests/*.py.
|
| 265 |
modules = sorted(p.name for p in sandbox.glob("lumen_*.py"))
|
| 266 |
+
tests = sorted(f"tests/{p.name}" for p in (sandbox / "tests").glob("*.py"))
|
| 267 |
proc = run_cmd(
|
| 268 |
+
[sys.executable, "-m", "pyflakes", "bot.py", "system_prompt.py", *modules, *tests],
|
| 269 |
+
cwd=sandbox,
|
| 270 |
+
timeout=300,
|
| 271 |
)
|
| 272 |
return proc.returncode == 0, (proc.stdout + proc.stderr)[-2000:]
|
| 273 |
|
|
|
|
| 355 |
return None
|
| 356 |
|
| 357 |
|
| 358 |
+
def keep_sandbox(sandbox: Path, kept: Path, base_status: list[str]) -> dict:
|
| 359 |
+
"""Снимает слепок песочницы: после прогона содержимое могут снести.
|
| 360 |
+
|
| 361 |
+
base_status — состояние git сразу после постановки задачи: staged-тесты и
|
| 362 |
+
записанные нами AGENTS.md/opencode.json не считаются правками агента.
|
| 363 |
+
"""
|
| 364 |
info = {"changed_files": [], "diff": ""}
|
| 365 |
if not sandbox.exists():
|
| 366 |
return info
|
| 367 |
try:
|
| 368 |
+
status = [line for line in git(sandbox, "status", "--porcelain").splitlines() if line]
|
| 369 |
except RuntimeError:
|
| 370 |
+
status = []
|
| 371 |
+
base_keys = {line[3:] for line in base_status}
|
| 372 |
+
info["changed_files"] = [line for line in status if line[3:] not in base_keys]
|
| 373 |
if kept.exists():
|
| 374 |
shutil.rmtree(kept, ignore_errors=True)
|
| 375 |
shutil.copytree(sandbox, kept, ignore=shutil.ignore_patterns(".pip-target"), dirs_exist_ok=True)
|
|
|
|
| 446 |
result.solved = not result.target_failed and not result.new_failures
|
| 447 |
|
| 448 |
|
| 449 |
+
def run_one(task_id: str, variant: str, out_dir: Path, agents_md: Path | None, model: str) -> dict:
|
| 450 |
+
"""Один прогон: песочница, агент, слепок, судья. Возвращает payload результата."""
|
| 451 |
tasks = {t.id: t for t in load_tasks()}
|
| 452 |
+
task = tasks[task_id]
|
|
|
|
| 453 |
out_dir.mkdir(parents=True, exist_ok=True)
|
| 454 |
+
sandbox = out_dir / f"sandbox-{task.id}-{variant}"
|
| 455 |
+
kept = out_dir / f"kept-{task.id}-{variant}"
|
|
|
|
|
|
|
| 456 |
|
| 457 |
+
log(f"песочница {task.id} ({variant}) на {task.base[:8]}")
|
| 458 |
prepare(task, sandbox, agents_md)
|
| 459 |
|
| 460 |
pre_fail, _ = pytest_state(sandbox)
|
| 461 |
targets = compute_targets(task)["fail_to_pass"]
|
| 462 |
+
base_status = [line for line in git(sandbox, "status", "--porcelain").splitlines() if line]
|
| 463 |
log(f"до агента падают {len(pre_fail)} тестов, целевых (FAIL_TO_PASS): {len(targets)}")
|
| 464 |
|
| 465 |
+
transcript = out_dir / f"{task.id}.{variant}.jsonl"
|
| 466 |
log(f"агент: {model}, таймаут {AGENT_TIMEOUT}с")
|
| 467 |
started = time.time()
|
| 468 |
final, _ = run_agent(sandbox, task.prompt(), model, transcript, AGENT_TIMEOUT)
|
| 469 |
duration = round(time.time() - started, 1)
|
| 470 |
|
| 471 |
# Слепок снимаем сразу: содержимое песочницы могут снести (чистка temp, OpenCode).
|
| 472 |
+
snapshot = keep_sandbox(sandbox, kept, base_status)
|
| 473 |
session = session_from_db(sandbox)
|
| 474 |
if session:
|
| 475 |
+
(out_dir / f"{task.id}.{variant}.session.json").write_text(
|
| 476 |
json.dumps(session, ensure_ascii=False, indent=2), encoding="utf-8"
|
| 477 |
)
|
| 478 |
|
| 479 |
result = RunResult(
|
| 480 |
task=task.id,
|
| 481 |
+
variant=variant,
|
| 482 |
model=model,
|
| 483 |
duration_s=duration,
|
| 484 |
final_message=final,
|
|
|
|
| 493 |
|
| 494 |
payload = {
|
| 495 |
"task": task.id,
|
| 496 |
+
"variant": variant,
|
| 497 |
"model": model,
|
| 498 |
"solved": result.solved,
|
| 499 |
"pre_fail": result.pre_fail,
|
|
|
|
| 509 |
"diff": snapshot["diff"][-8000:],
|
| 510 |
"post_pytest_tail": post_out[-1500:],
|
| 511 |
}
|
| 512 |
+
result_file = out_dir / f"{task.id}.{variant}.json"
|
| 513 |
result_file.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
| 514 |
log(f"итог: {'решено' if result.solved else 'не решено'} ({duration}с) -> {result_file}")
|
| 515 |
check_repo_untouched()
|
| 516 |
+
return payload
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
def cmd_run(args: argparse.Namespace) -> int:
|
| 520 |
+
out_dir = Path(args.out) if args.out else OUT_ROOT / time.strftime("%Y%m%d-%H%M%S")
|
| 521 |
+
agents_md = Path(args.agents_md) if args.agents_md else None
|
| 522 |
+
run_one(args.task, args.variant, out_dir, agents_md, args.model or AGENT_MODEL)
|
| 523 |
+
return 0
|
| 524 |
+
|
| 525 |
+
|
| 526 |
+
def cmd_batch(args: argparse.Namespace) -> int:
|
| 527 |
+
"""Прогоняет набор задач одним вариантом AGENTS.md."""
|
| 528 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 529 |
+
|
| 530 |
+
tasks = [t.id for t in load_tasks()]
|
| 531 |
+
if args.tasks:
|
| 532 |
+
wanted = set(args.tasks.split(","))
|
| 533 |
+
tasks = [t for t in tasks if t in wanted]
|
| 534 |
+
out_dir = Path(args.out) if args.out else OUT_ROOT / time.strftime("%Y%m%d-%H%M%S")
|
| 535 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 536 |
+
agents_md = Path(args.agents_md) if args.agents_md else None
|
| 537 |
+
model = args.model or AGENT_MODEL
|
| 538 |
+
log(f"batch: {len(tasks)} задач, вариант {args.variant}, потоков {args.jobs}")
|
| 539 |
+
|
| 540 |
+
def work(task_id: str) -> None:
|
| 541 |
+
try:
|
| 542 |
+
run_one(task_id, args.variant, out_dir, agents_md, model)
|
| 543 |
+
except Exception as exc: # noqa: BLE001 — прогон не должен валить весь батч
|
| 544 |
+
log(f"ошибка на {task_id}: {exc}")
|
| 545 |
+
|
| 546 |
+
with ThreadPoolExecutor(max_workers=args.jobs) as pool:
|
| 547 |
+
list(pool.map(work, tasks))
|
| 548 |
return 0
|
| 549 |
|
| 550 |
|
|
|
|
| 555 |
except RuntimeError as exc:
|
| 556 |
log(f"ВНИМАНИЕ: не смог проверить состояние репозитория: {exc}")
|
| 557 |
return
|
| 558 |
+
unexpected = [
|
| 559 |
+
line for line in status.splitlines() if line and not line[3:].startswith("eval/")
|
| 560 |
+
]
|
| 561 |
if unexpected:
|
| 562 |
log(f"ВНИМАНИЕ: репозиторий изменён во время прогона: {unexpected[:5]}")
|
| 563 |
|
|
|
|
| 595 |
reports.mkdir()
|
| 596 |
for run_file in runs:
|
| 597 |
data = json.loads(run_file.read_text(encoding="utf-8"))
|
| 598 |
+
violations = data.get("violations") or {}
|
|
|
|
| 599 |
body = [
|
| 600 |
f"Задача: {data['task']}",
|
| 601 |
+
f"Итог: {'решено' if data.get('solved') else 'не решено'}, {data.get('duration_s')}с",
|
| 602 |
f"Целевые тесты: {', '.join(data.get('target_tests') or [])}",
|
| 603 |
f"Целевые тесты после прогона упали: {', '.join(data.get('target_failed') or [])}",
|
| 604 |
f"Новые падения: {', '.join(data.get('new_failures') or [])}",
|
| 605 |
+
f"Нарушения правил: {json.dumps(violations, ensure_ascii=False)}",
|
| 606 |
f"pyflakes: {'ok' if data.get('pyflakes_ok') else 'ошибка'}",
|
| 607 |
"",
|
| 608 |
"Последнее сообщение агента:",
|
|
|
|
| 613 |
]
|
| 614 |
(reports / f"{data['task']}.md").write_text("\n".join(body), encoding="utf-8")
|
| 615 |
if not any(reports.iterdir()):
|
| 616 |
+
log("базовых прогонов нет — править нечего")
|
| 617 |
return 0
|
| 618 |
|
| 619 |
prompt = (
|
| 620 |
+
"Ты редактор правил проекта. Посмотри AGENTS.md и отчёты о прогонах в папке failures.\n"
|
| 621 |
+
"Составь новый AGENTS.md: добавь правила, которые предотвратили бы провалы "
|
| 622 |
+
"и убрали бы замеченную в отчётах неэффективность (лишние команды, unix-команды "
|
| 623 |
+
"в PowerShell, гейт не запускался). Правила конкретные, в стиле существующих разделов, "
|
| 624 |
+
"только по фактам из отчётов.\n"
|
| 625 |
"Существующие правила не удаляй и не переписывай, только дополняй.\n"
|
| 626 |
"Сохрани результат в файл AGENTS.candidate.md в этой же папке. Больше ничего не меняй."
|
| 627 |
)
|
|
|
|
| 651 |
|
| 652 |
def cmd_report(args: argparse.Namespace) -> int:
|
| 653 |
out_dir = Path(args.out) if args.out else OUT_ROOT
|
|
|
|
|
|
|
| 654 |
rows = []
|
| 655 |
+
for file in sorted(out_dir.glob("*.json")):
|
| 656 |
try:
|
| 657 |
data = json.loads(file.read_text(encoding="utf-8"))
|
| 658 |
except json.JSONDecodeError:
|
|
|
|
| 663 |
by_task: dict[str, dict[str, dict]] = {}
|
| 664 |
for row in rows:
|
| 665 |
by_task.setdefault(row["task"], {})[row["variant"]] = row
|
| 666 |
+
variants = sorted({v for task in by_task.values() for v in task})
|
| 667 |
|
| 668 |
+
header = "| Задача | " + " | ".join(variants) + " |"
|
| 669 |
+
sep = "|---|" + "---|" * len(variants)
|
| 670 |
lines = [
|
| 671 |
+
"# Harness-eval: варианты AGENTS.md на задачах из истории",
|
| 672 |
"",
|
| 673 |
f"Модель агента: {AGENT_MODEL} · Модель-редактор: {PROPOSER_MODEL}",
|
| 674 |
"",
|
| 675 |
+
header,
|
| 676 |
+
sep,
|
| 677 |
]
|
| 678 |
+
totals = {v: 0 for v in variants}
|
| 679 |
+
for task_id, task_variants in sorted(by_task.items()):
|
| 680 |
+
cells = []
|
| 681 |
+
for variant in variants:
|
| 682 |
+
data = task_variants.get(variant)
|
| 683 |
+
if not data:
|
| 684 |
+
cells.append("-")
|
| 685 |
+
continue
|
| 686 |
+
ok = bool(data.get("solved"))
|
| 687 |
+
totals[variant] += ok
|
| 688 |
+
viol = data.get("violations") or {}
|
| 689 |
+
mark = "решено" if ok else "нет"
|
| 690 |
+
if viol.get("tests_touched"):
|
| 691 |
+
mark += " (тесты правил)"
|
| 692 |
+
if viol.get("unix_shell_commands"):
|
| 693 |
+
mark += " (unix)"
|
| 694 |
+
cells.append(mark)
|
| 695 |
+
lines.append(f"| {task_id} | " + " | ".join(cells) + " |")
|
| 696 |
+
lines += ["", "Итог: " + " · ".join(f"{v}: {totals[v]}/{len(by_task)}" for v in variants), ""]
|
| 697 |
text = "\n".join(lines)
|
| 698 |
print(text)
|
| 699 |
if args.out:
|
|
|
|
| 730 |
run.add_argument("--out", default=None)
|
| 731 |
run.set_defaults(func=cmd_run)
|
| 732 |
|
| 733 |
+
batch = sub.add_parser("batch", help="прогнать несколько задач одним вариантом")
|
| 734 |
+
batch.add_argument("--variant", default="base", choices=["base", "candidate", "weak"])
|
| 735 |
+
batch.add_argument("--tasks", default=None, help="список id через запятую")
|
| 736 |
+
batch.add_argument("--agents-md", default=None)
|
| 737 |
+
batch.add_argument("--model", default=None)
|
| 738 |
+
batch.add_argument("--jobs", type=int, default=1)
|
| 739 |
+
batch.add_argument("--out", default=None)
|
| 740 |
+
batch.set_defaults(func=cmd_batch)
|
| 741 |
+
|
| 742 |
check = sub.add_parser("check", help="проверить, что задачи валидны на базе")
|
| 743 |
check.add_argument("--out", default=None)
|
| 744 |
check.set_defaults(func=cmd_check)
|
eval/harness_weak.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Lumen
|
| 2 |
+
|
| 3 |
+
Telegram-бот на Python. Отвечай на сообщение пользователя.
|