2026-05-05 13:04:23 -07:00
|
|
|
"""Run the reviewer eval against the LangSmith dataset.
|
|
|
|
|
|
|
|
|
|
Usage:
|
2026-05-18 15:47:13 -07:00
|
|
|
uv run python -m evals.reviewer.run_eval
|
2026-05-05 13:04:23 -07:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import argparse
|
2026-06-16 19:38:36 -07:00
|
|
|
import asyncio
|
|
|
|
|
import contextlib
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
import logging
|
2026-05-18 15:47:13 -07:00
|
|
|
import os
|
2026-06-16 19:38:36 -07:00
|
|
|
import sys
|
|
|
|
|
import threading
|
2026-05-18 15:47:13 -07:00
|
|
|
import tomllib
|
2026-06-16 10:12:35 -07:00
|
|
|
from collections.abc import Iterable, Mapping
|
2026-05-18 15:47:13 -07:00
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Any, Literal, TypedDict
|
2026-05-05 13:04:23 -07:00
|
|
|
|
|
|
|
|
from dotenv import load_dotenv
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
from langgraph_sdk import get_client
|
|
|
|
|
from langsmith import Client, aevaluate
|
|
|
|
|
from langsmith.schemas import Example
|
2026-05-05 13:04:23 -07:00
|
|
|
|
2026-06-16 19:38:36 -07:00
|
|
|
from agent.reviewer_eval_store import _EXPERIMENT_URL_RE, _LOG_TAIL_CHARS
|
2026-07-17 12:10:54 -04:00
|
|
|
from agent.reviewer_findings import REVIEW_FINDING_CAP
|
2026-05-05 13:04:23 -07:00
|
|
|
from evals.reviewer.judge import aggregate_pr, judge_match
|
2026-06-16 19:38:36 -07:00
|
|
|
from evals.reviewer.store_reporter import StoreReporter, is_enabled
|
|
|
|
|
from evals.reviewer.target import (
|
|
|
|
|
drain_thread_ids,
|
|
|
|
|
get_completed_count,
|
|
|
|
|
get_langgraph_url,
|
|
|
|
|
review_pr,
|
|
|
|
|
)
|
2026-05-05 13:04:23 -07:00
|
|
|
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2026-05-18 15:47:13 -07:00
|
|
|
CONFIG_PATH = Path(__file__).with_name("config.toml")
|
2026-06-15 09:48:12 -07:00
|
|
|
DEFAULT_LANGSMITH_PROJECT = "open-swe-evals"
|
2026-05-18 15:47:13 -07:00
|
|
|
ScoreMode = Literal["all_findings", "surfaced_findings"]
|
feat: tune reviewer for precision — web/wiki tools + recalibrated prompt (#1312)
* feat: tune reviewer for precision — web/wiki tools + recalibrated prompt
Reviewer agent now has web_search, fetch_url, and http_request alongside the
finding tools, so it can verify library semantics and consult the DeepWiki
auto-generated wiki for public repos (https://deepwiki.com/<owner>/<repo>)
before flagging cross-file or architectural concerns.
Prompt rewritten to push precision over recall:
- explicit severity ladder pushing reviews toward bimodal high/low instead of
defaulting to medium
- ≤200-char description target (gold set averages ~186 chars; we were at ~436)
- mandatory docs / wiki / code lookup before flagging concurrency, security,
or perf — the three categories that dominated false positives
- "do not flag" list covering compiler/linter-catchable nits, speculative
claims without a concrete attacker/interleaving/scale, style preferences
the codebase doesn't share, and test-quality nits on non-test diffs
- smart file-selection guidance for large PRs (deprioritize generated /
vendored / pure-rename hunks)
Eval config switched to openai:gpt-5.5 + high reasoning effort for the next
benchmark run.
* trim prompt
* subagent prompting
* confidence ratings
* added medium
* enforce confidence threshold
* .
* reviewer: precision-tuned prompt + drop confidence gate
Rewrites the reviewer system prompt around a defensibility bar (anchor +
failure mode + maintainer wouldn't say "not a bug"), an explicit do-not-file
list (style nits, speculation, scope-policing, same-bug fan-out), and a
checklist of 10 bug archetypes drawn from a per-PR audit of the eval golden
set. The audit showed 145 FPs in the last eval split ~28% speculative, ~26%
style-nit, ~31% real-but-unscored (mostly same-archetype fan-out); the new
prompt targets each class directly.
Confidence is still recorded on every finding for post-hoc calibration but
no longer gates publication — the audit showed the gate was a no-op (agent
self-rated 65% of findings "high" regardless), and the prompt's defensibility
bar is the actual discipline. Drops CONFIDENCE_ORDER, CONFIDENCE_THRESHOLD,
the confidence_threshold kwarg on filter_findings_for_publish, the
confidence_filtered score_mode, and the min_confidence kwarg on the eval
target's _extract_comments — all dead once the gate is gone.
Also removes the "informational" severity tier from the Severity enum,
SEVERITY_ORDER, and all validators / tests / docstrings. It was reserved for
FYI observations the dataset never rewards.
* benchmax
* adding google provider
* slight steering
* tuning
* more tuning
* fix
* cleanup
* reducing overfitting
* Add per-repo review style profiles and inject them into the reviewer.
Dashboard users can analyze historical PR review feedback per repository,
edit the resulting style guide, and have it loaded from LangGraph Store at
reviewer runtime (including Martian eval runs) keyed by owner/name.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Fix review style job errors leaking exception details to clients.
Return generic dashboard messages while logging full stack traces server-side.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 11:35:00 -07:00
|
|
|
Severity = Literal["low", "medium", "high", "critical"]
|
2026-06-16 10:12:35 -07:00
|
|
|
_VALID_SCORE_MODES: set[str] = {"all_findings", "surfaced_findings"}
|
|
|
|
|
_VALID_SEVERITIES: set[str] = {"low", "medium", "high", "critical"}
|
|
|
|
|
|
|
|
|
|
_ENV_MAPPING: dict[str, str] = {
|
|
|
|
|
"dataset_name": "REVIEWER_EVAL_DATASET_NAME",
|
|
|
|
|
"experiment_prefix": "REVIEWER_EVAL_EXPERIMENT_PREFIX",
|
|
|
|
|
"max_concurrency": "REVIEWER_EVAL_MAX_CONCURRENCY",
|
|
|
|
|
"langgraph_url": "LANGGRAPH_URL",
|
|
|
|
|
"langsmith_project": "LANGSMITH_PROJECT",
|
|
|
|
|
"assistant_id": "REVIEWER_ASSISTANT_ID",
|
|
|
|
|
"model_id": "REVIEWER_EVAL_MODEL_ID",
|
|
|
|
|
"reasoning_effort": "REVIEWER_EVAL_REASONING_EFFORT",
|
|
|
|
|
"score_mode": "REVIEWER_EVAL_SCORE_MODE",
|
|
|
|
|
"severity_threshold": "REVIEWER_EVAL_SEVERITY_THRESHOLD",
|
|
|
|
|
"cap": "REVIEWER_EVAL_CAP",
|
|
|
|
|
}
|
2026-05-18 15:47:13 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ReviewerEvalConfig(TypedDict, total=False):
|
|
|
|
|
dataset_name: str
|
|
|
|
|
experiment_prefix: str
|
|
|
|
|
max_concurrency: int
|
|
|
|
|
langgraph_url: str
|
2026-06-15 09:48:12 -07:00
|
|
|
langsmith_project: str
|
2026-05-18 15:47:13 -07:00
|
|
|
assistant_id: str
|
|
|
|
|
model_id: str
|
|
|
|
|
reasoning_effort: str
|
|
|
|
|
score_mode: ScoreMode
|
|
|
|
|
severity_threshold: Severity
|
|
|
|
|
cap: int
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 10:12:35 -07:00
|
|
|
DEFAULT_CONFIG: ReviewerEvalConfig = {
|
|
|
|
|
"dataset_name": "openswe-reviewer-v1",
|
|
|
|
|
"experiment_prefix": "openswe-reviewer-baseline",
|
|
|
|
|
"max_concurrency": 5,
|
|
|
|
|
"langgraph_url": "",
|
|
|
|
|
"langsmith_project": DEFAULT_LANGSMITH_PROJECT,
|
|
|
|
|
"assistant_id": "reviewer",
|
|
|
|
|
"model_id": "google_genai:gemini-3.5-flash",
|
|
|
|
|
"reasoning_effort": "medium",
|
2026-07-17 12:10:54 -04:00
|
|
|
"score_mode": "surfaced_findings",
|
|
|
|
|
"severity_threshold": "low",
|
|
|
|
|
"cap": REVIEW_FINDING_CAP,
|
2026-06-16 10:12:35 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_int(value: str) -> int | None:
|
|
|
|
|
try:
|
|
|
|
|
return int(value)
|
|
|
|
|
except ValueError:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
2026-05-18 15:47:13 -07:00
|
|
|
def _load_config() -> ReviewerEvalConfig:
|
|
|
|
|
if not CONFIG_PATH.exists():
|
|
|
|
|
return {}
|
|
|
|
|
with CONFIG_PATH.open("rb") as f:
|
|
|
|
|
raw = tomllib.load(f)
|
|
|
|
|
return _coerce_config(raw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _coerce_config(raw: dict[str, Any]) -> ReviewerEvalConfig:
|
|
|
|
|
config: ReviewerEvalConfig = {}
|
|
|
|
|
dataset_name = raw.get("dataset_name")
|
|
|
|
|
if isinstance(dataset_name, str) and dataset_name:
|
|
|
|
|
config["dataset_name"] = dataset_name
|
|
|
|
|
|
|
|
|
|
experiment_prefix = raw.get("experiment_prefix")
|
|
|
|
|
if isinstance(experiment_prefix, str) and experiment_prefix:
|
|
|
|
|
config["experiment_prefix"] = experiment_prefix
|
|
|
|
|
|
|
|
|
|
langgraph_url = raw.get("langgraph_url")
|
|
|
|
|
if isinstance(langgraph_url, str) and langgraph_url:
|
|
|
|
|
config["langgraph_url"] = langgraph_url
|
|
|
|
|
|
2026-06-15 09:48:12 -07:00
|
|
|
langsmith_project = raw.get("langsmith_project")
|
|
|
|
|
if isinstance(langsmith_project, str) and langsmith_project:
|
|
|
|
|
config["langsmith_project"] = langsmith_project
|
|
|
|
|
|
2026-05-18 15:47:13 -07:00
|
|
|
assistant_id = raw.get("assistant_id")
|
|
|
|
|
if isinstance(assistant_id, str) and assistant_id:
|
|
|
|
|
config["assistant_id"] = assistant_id
|
|
|
|
|
|
|
|
|
|
model_id = raw.get("model_id")
|
|
|
|
|
if isinstance(model_id, str) and model_id:
|
|
|
|
|
config["model_id"] = model_id
|
|
|
|
|
|
|
|
|
|
reasoning_effort = raw.get("reasoning_effort")
|
|
|
|
|
if isinstance(reasoning_effort, str) and reasoning_effort:
|
|
|
|
|
config["reasoning_effort"] = reasoning_effort
|
|
|
|
|
|
|
|
|
|
max_concurrency = raw.get("max_concurrency")
|
|
|
|
|
if isinstance(max_concurrency, int) and max_concurrency > 0:
|
|
|
|
|
config["max_concurrency"] = max_concurrency
|
|
|
|
|
|
|
|
|
|
score_mode = raw.get("score_mode")
|
2026-06-16 10:12:35 -07:00
|
|
|
if score_mode in _VALID_SCORE_MODES:
|
2026-05-18 15:47:13 -07:00
|
|
|
config["score_mode"] = score_mode
|
|
|
|
|
|
|
|
|
|
severity_threshold = raw.get("severity_threshold")
|
2026-06-16 10:12:35 -07:00
|
|
|
if severity_threshold in _VALID_SEVERITIES:
|
2026-05-18 15:47:13 -07:00
|
|
|
config["severity_threshold"] = severity_threshold
|
|
|
|
|
|
|
|
|
|
cap = raw.get("cap")
|
|
|
|
|
if isinstance(cap, int) and cap >= 0:
|
|
|
|
|
config["cap"] = cap
|
|
|
|
|
return config
|
|
|
|
|
|
|
|
|
|
|
2026-06-16 10:12:35 -07:00
|
|
|
def _load_env_config(env: Mapping[str, str] = os.environ) -> ReviewerEvalConfig:
|
|
|
|
|
raw: dict[str, Any] = {}
|
|
|
|
|
for config_key, env_key in _ENV_MAPPING.items():
|
|
|
|
|
value = env.get(env_key)
|
|
|
|
|
if value is None or value == "":
|
|
|
|
|
continue
|
|
|
|
|
if config_key in {"max_concurrency", "cap"}:
|
|
|
|
|
parsed = _parse_int(value)
|
|
|
|
|
if parsed is not None:
|
|
|
|
|
raw[config_key] = parsed
|
|
|
|
|
else:
|
|
|
|
|
raw[config_key] = value
|
|
|
|
|
return _coerce_config(raw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _config_from_args(args: argparse.Namespace) -> ReviewerEvalConfig:
|
|
|
|
|
raw: dict[str, Any] = {}
|
|
|
|
|
for key in _ENV_MAPPING:
|
|
|
|
|
value = getattr(args, key, None)
|
|
|
|
|
if value is not None:
|
|
|
|
|
raw[key] = value
|
|
|
|
|
return _coerce_config(raw)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_config(cli_config: ReviewerEvalConfig | None = None) -> ReviewerEvalConfig:
|
|
|
|
|
resolved: ReviewerEvalConfig = {
|
|
|
|
|
**DEFAULT_CONFIG,
|
|
|
|
|
**_load_config(),
|
|
|
|
|
**_load_env_config(),
|
2026-05-18 15:47:13 -07:00
|
|
|
}
|
2026-06-16 10:12:35 -07:00
|
|
|
if cli_config is not None:
|
|
|
|
|
resolved.update(cli_config)
|
|
|
|
|
return resolved
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_config_to_env(config: ReviewerEvalConfig) -> None:
|
|
|
|
|
for config_key, env_key in _ENV_MAPPING.items():
|
|
|
|
|
if config_key == "langsmith_project":
|
|
|
|
|
continue
|
2026-05-18 15:47:13 -07:00
|
|
|
value = config.get(config_key)
|
|
|
|
|
if value is not None:
|
|
|
|
|
os.environ[env_key] = str(value)
|
2026-06-15 09:48:12 -07:00
|
|
|
_apply_langsmith_project(config.get("langsmith_project"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_langsmith_project(project: str | None) -> None:
|
|
|
|
|
"""Route eval traces to a dedicated LangSmith project.
|
|
|
|
|
|
2026-06-16 10:12:35 -07:00
|
|
|
``project`` is expected to be the resolved value after CLI/env/config
|
|
|
|
|
precedence has already been applied.
|
2026-06-15 09:48:12 -07:00
|
|
|
"""
|
2026-06-16 10:12:35 -07:00
|
|
|
resolved = project or os.environ.get("LANGSMITH_PROJECT") or DEFAULT_LANGSMITH_PROJECT
|
2026-06-15 09:48:12 -07:00
|
|
|
os.environ["LANGSMITH_PROJECT"] = resolved
|
|
|
|
|
os.environ["LANGCHAIN_PROJECT"] = resolved
|
|
|
|
|
os.environ.setdefault("LANGSMITH_TRACING", "true")
|
2026-05-18 15:47:13 -07:00
|
|
|
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
|
2026-06-16 19:38:36 -07:00
|
|
|
class _TailCapture:
|
|
|
|
|
"""Thread-safe rolling tail of eval output + the last LangSmith experiment URL."""
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
self._buf = ""
|
|
|
|
|
self._url: str | None = None
|
|
|
|
|
self._lock = threading.Lock()
|
|
|
|
|
|
|
|
|
|
def append(self, text: str) -> None:
|
|
|
|
|
if not text:
|
|
|
|
|
return
|
|
|
|
|
with self._lock:
|
|
|
|
|
self._buf = (self._buf + text)[-_LOG_TAIL_CHARS:]
|
|
|
|
|
found = _EXPERIMENT_URL_RE.findall(self._buf)
|
|
|
|
|
if found:
|
|
|
|
|
self._url = found[-1]
|
|
|
|
|
|
|
|
|
|
def tail(self) -> str | None:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return self._buf or None
|
|
|
|
|
|
|
|
|
|
def url(self) -> str | None:
|
|
|
|
|
with self._lock:
|
|
|
|
|
return self._url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _BufferingHandler(logging.Handler):
|
|
|
|
|
"""Mirror log records into a ``_TailCapture`` so the reporter can publish them."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, capture: _TailCapture) -> None:
|
|
|
|
|
super().__init__()
|
|
|
|
|
self._capture = capture
|
|
|
|
|
|
|
|
|
|
def emit(self, record: logging.LogRecord) -> None:
|
|
|
|
|
try:
|
|
|
|
|
self._capture.append(self.format(record) + "\n")
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _TeeStream:
|
|
|
|
|
"""Write to the original stream and mirror into the capture (for ``print``ed output)."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, original: Any, capture: _TailCapture) -> None:
|
|
|
|
|
self._original = original
|
|
|
|
|
self._capture = capture
|
|
|
|
|
|
|
|
|
|
def write(self, text: str) -> int:
|
|
|
|
|
self._capture.append(text)
|
|
|
|
|
return self._original.write(text)
|
|
|
|
|
|
|
|
|
|
def flush(self) -> None:
|
|
|
|
|
self._original.flush()
|
|
|
|
|
|
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
|
|
|
return getattr(self._original, name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _resolve_total(dataset_name: str, data: str | list[Example]) -> int | None:
|
|
|
|
|
if isinstance(data, list):
|
|
|
|
|
return len(data)
|
|
|
|
|
try:
|
|
|
|
|
return Client().read_dataset(dataset_name=dataset_name).example_count
|
|
|
|
|
except Exception:
|
|
|
|
|
logger.warning("Could not resolve dataset example count for progress", exc_info=True)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
async def _cleanup_threads(thread_ids: Iterable[str]) -> None:
|
|
|
|
|
"""Delete LangGraph threads created during the eval.
|
|
|
|
|
|
|
|
|
|
Underlying sandboxes are reclaimed by the provider's TTL — this only
|
|
|
|
|
drops the LangGraph checkpoint/metadata records.
|
|
|
|
|
"""
|
2026-05-18 15:47:13 -07:00
|
|
|
sdk = get_client(url=get_langgraph_url())
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
for tid in thread_ids:
|
|
|
|
|
try:
|
|
|
|
|
await sdk.threads.delete(tid)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
logger.warning("Failed to delete thread %s: %s", tid, exc)
|
|
|
|
|
|
2026-05-05 13:04:23 -07:00
|
|
|
|
|
|
|
|
async def main() -> None:
|
2026-06-16 10:12:35 -07:00
|
|
|
logging.basicConfig(
|
|
|
|
|
level=os.environ.get("REVIEWER_EVAL_LOG_LEVEL", "INFO"),
|
|
|
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
|
|
|
)
|
2026-06-10 11:08:01 -07:00
|
|
|
load_dotenv()
|
2026-05-18 15:47:13 -07:00
|
|
|
|
2026-05-05 13:04:23 -07:00
|
|
|
ap = argparse.ArgumentParser()
|
|
|
|
|
ap.add_argument("--limit", type=int, default=None, help="Run only the first N examples.")
|
2026-06-16 10:12:35 -07:00
|
|
|
ap.add_argument("--dataset-name", dest="dataset_name")
|
|
|
|
|
ap.add_argument("--experiment-prefix", dest="experiment_prefix")
|
|
|
|
|
ap.add_argument("--max-concurrency", dest="max_concurrency", type=int)
|
|
|
|
|
ap.add_argument("--langgraph-url", dest="langgraph_url")
|
|
|
|
|
ap.add_argument("--langsmith-project", dest="langsmith_project")
|
|
|
|
|
ap.add_argument("--assistant-id", dest="assistant_id")
|
|
|
|
|
ap.add_argument("--model-id", dest="model_id")
|
|
|
|
|
ap.add_argument("--reasoning-effort", dest="reasoning_effort")
|
|
|
|
|
ap.add_argument("--score-mode", dest="score_mode", choices=sorted(_VALID_SCORE_MODES))
|
|
|
|
|
ap.add_argument(
|
|
|
|
|
"--severity-threshold",
|
|
|
|
|
dest="severity_threshold",
|
|
|
|
|
choices=sorted(_VALID_SEVERITIES),
|
|
|
|
|
)
|
|
|
|
|
ap.add_argument("--cap", type=int)
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
ap.add_argument(
|
|
|
|
|
"--no-cleanup",
|
|
|
|
|
action="store_true",
|
|
|
|
|
help="Skip deleting LangGraph threads after the experiment finishes.",
|
|
|
|
|
)
|
2026-05-05 13:04:23 -07:00
|
|
|
args = ap.parse_args()
|
2026-06-16 10:12:35 -07:00
|
|
|
config = _resolve_config(_config_from_args(args))
|
|
|
|
|
_apply_config_to_env(config)
|
2026-05-05 13:04:23 -07:00
|
|
|
|
2026-06-16 10:12:35 -07:00
|
|
|
dataset_name = config["dataset_name"]
|
|
|
|
|
experiment_prefix = config["experiment_prefix"]
|
|
|
|
|
max_concurrency = config["max_concurrency"]
|
|
|
|
|
logger.info(
|
|
|
|
|
"Starting reviewer eval: dataset=%s experiment_prefix=%s max_concurrency=%s "
|
|
|
|
|
"model=%s effort=%s score_mode=%s severity_threshold=%s cap=%s project=%s "
|
|
|
|
|
"assistant_id=%s langgraph_url=%s limit=%s",
|
|
|
|
|
dataset_name,
|
|
|
|
|
experiment_prefix,
|
|
|
|
|
max_concurrency,
|
|
|
|
|
config["model_id"],
|
|
|
|
|
config["reasoning_effort"],
|
|
|
|
|
config["score_mode"],
|
|
|
|
|
config["severity_threshold"],
|
|
|
|
|
config["cap"],
|
|
|
|
|
config["langsmith_project"],
|
|
|
|
|
config["assistant_id"],
|
|
|
|
|
config["langgraph_url"] or "(default)",
|
|
|
|
|
args.limit,
|
|
|
|
|
)
|
2026-05-18 15:47:13 -07:00
|
|
|
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
data: str | list[Example]
|
|
|
|
|
if args.limit:
|
|
|
|
|
client = Client()
|
2026-05-18 15:47:13 -07:00
|
|
|
data = list(client.list_examples(dataset_name=dataset_name, limit=args.limit))
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
else:
|
2026-05-18 15:47:13 -07:00
|
|
|
data = dataset_name
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
|
2026-06-16 19:38:36 -07:00
|
|
|
reporter: StoreReporter | None = None
|
|
|
|
|
heartbeat: asyncio.Task[None] | None = None
|
|
|
|
|
log_handler: logging.Handler | None = None
|
|
|
|
|
original_stdout = sys.stdout
|
|
|
|
|
if is_enabled():
|
|
|
|
|
capture = _TailCapture()
|
|
|
|
|
log_handler = _BufferingHandler(capture)
|
|
|
|
|
log_handler.setFormatter(
|
|
|
|
|
logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s")
|
|
|
|
|
)
|
|
|
|
|
logging.getLogger().addHandler(log_handler)
|
|
|
|
|
sys.stdout = _TeeStream(original_stdout, capture)
|
|
|
|
|
reporter = StoreReporter(
|
|
|
|
|
config=dict(config),
|
|
|
|
|
limit=args.limit,
|
|
|
|
|
total=_resolve_total(dataset_name, data),
|
|
|
|
|
created_by=None,
|
|
|
|
|
completed_getter=get_completed_count,
|
|
|
|
|
tail_getter=capture.tail,
|
|
|
|
|
experiment_url_getter=capture.url,
|
|
|
|
|
)
|
|
|
|
|
await reporter.start()
|
|
|
|
|
heartbeat = reporter.run_heartbeat()
|
|
|
|
|
|
|
|
|
|
eval_error: BaseException | None = None
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
try:
|
|
|
|
|
await aevaluate(
|
|
|
|
|
review_pr,
|
|
|
|
|
data=data,
|
|
|
|
|
evaluators=[judge_match],
|
|
|
|
|
summary_evaluators=[aggregate_pr],
|
2026-05-18 15:47:13 -07:00
|
|
|
experiment_prefix=experiment_prefix,
|
|
|
|
|
max_concurrency=max_concurrency,
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
num_repetitions=1,
|
|
|
|
|
)
|
2026-06-16 19:38:36 -07:00
|
|
|
except BaseException as exc:
|
|
|
|
|
eval_error = exc
|
|
|
|
|
raise
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
finally:
|
2026-06-16 19:38:36 -07:00
|
|
|
if reporter is not None:
|
|
|
|
|
if heartbeat is not None:
|
|
|
|
|
heartbeat.cancel()
|
|
|
|
|
with contextlib.suppress(asyncio.CancelledError):
|
|
|
|
|
await heartbeat
|
|
|
|
|
status = "failed" if eval_error is not None else "completed"
|
|
|
|
|
error = None if eval_error is None else f"{type(eval_error).__name__}: {eval_error}"
|
|
|
|
|
await reporter.finish(status=status, error=error)
|
|
|
|
|
if log_handler is not None:
|
|
|
|
|
logging.getLogger().removeHandler(log_handler)
|
|
|
|
|
sys.stdout = original_stdout
|
feat: add reviewer graph + eval target wiring (#1241)
* feat: add reviewer graph + eval target wiring
- New `reviewer` graph (`agent/reviewer.py`) registered in langgraph.json
alongside the main `agent` graph. Reuses the same sandbox lifecycle,
GH proxy auth, and middleware primitives from `agent.server`, but with
a narrower tool set, a reviewer-specific system prompt, no
commit/push, and the `task` (subagent) tool stripped via
`_ToolExclusionMiddleware` so review stays in one context.
- New `github_comment` tool: agents call it once per issue with
`(file, line, body, severity)` and the eval scores those calls
against golden comments.
- `ensure_no_empty_msg` middleware (the no_op nudge) is intentionally
*not* on the reviewer's stack — that middleware exists to enforce the
main agent's "always finalize via Slack/Linear/PR" contract, which
the reviewer doesn't have. The main agent's behavior is unchanged.
- `evals/reviewer/target.py`: send PR info as a user message, extract
every `github_comment` tool call (multiple expected per review) into
the run output.
- `evals/reviewer/judge.py`: per-example evaluator now returns a list
of metrics under `{"results": [...]}` so LangSmith averages each
numeric key (f1/precision/recall/tp/fp/fn) across the experiment in
the UI. Dropped the broken `aggregate_pr` summary evaluator that
reached for an attribute that doesn't exist on `RunTree`.
- `evals/reviewer/run_eval.py`: `--limit` now slices the dataset via
`client.list_examples(limit=N)` since `aevaluate` doesn't accept
`max_examples`.
- Makefile: `dev` and `run` targets now use `uv run` so they work
without an activated venv.
* resolve comments
---------
Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
2026-05-06 10:15:58 -07:00
|
|
|
if not args.no_cleanup:
|
|
|
|
|
thread_ids = drain_thread_ids()
|
|
|
|
|
if thread_ids:
|
|
|
|
|
logger.info("Cleaning up %d LangGraph threads", len(thread_ids))
|
|
|
|
|
await _cleanup_threads(thread_ids)
|
2026-05-05 13:04:23 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(main())
|