open-swe/agent/utils/model.py
Johannes du Plessis 82852f9eda
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 18:35:00 +00:00

141 lines
4.7 KiB
Python

from typing import Literal, TypedDict, Unpack
from langchain.chat_models import init_chat_model
OPENAI_RESPONSES_WS_BASE_URL = "wss://api.openai.com/v1"
# Anthropic SDK default is 2; a 529 burst can outlive that. Bump to give the
# primary provider a fair chance before the fallback middleware kicks in.
DEFAULT_MAX_RETRIES = 6
DEFAULT_LLM_REASONING: "OpenAIReasoning" = {"effort": "medium"}
OpenAIReasoningEffort = Literal["none", "low", "medium", "high", "xhigh"]
AnthropicThinkingType = Literal["adaptive"]
AnthropicEffort = Literal["low", "medium", "high", "xhigh", "max"]
GoogleThinkingLevel = Literal["minimal", "low", "medium", "high"]
class OpenAIReasoning(TypedDict, total=False):
effort: OpenAIReasoningEffort
class AnthropicThinking(TypedDict, total=False):
type: AnthropicThinkingType
class ModelKwargs(TypedDict, total=False):
max_tokens: int | None
reasoning: OpenAIReasoning | None
thinking: AnthropicThinking | None
effort: AnthropicEffort | None
thinking_level: GoogleThinkingLevel | None
temperature: float | None
max_retries: int | None
_ANTHROPIC_EFFORTS: set[AnthropicEffort] = {"low", "medium", "high", "xhigh", "max"}
def make_model(model_id: str, **kwargs: Unpack[ModelKwargs]):
model_kwargs: dict[str, object] = kwargs.copy()
model_kwargs.setdefault("max_retries", DEFAULT_MAX_RETRIES)
if model_id.startswith("openai:"):
model_kwargs["base_url"] = OPENAI_RESPONSES_WS_BASE_URL
model_kwargs["use_responses_api"] = True
return init_chat_model(model=model_id, **model_kwargs)
def fallback_model_id_for(primary_model_id: str) -> str | None:
"""Return the cross-provider fallback model id for a given primary, if any.
Anthropic primaries fall back to OpenAI and vice versa. Returns ``None``
when the provider has no configured cross-provider fallback (e.g. Google,
local, or self-hosted providers we don't want to silently route off-host).
"""
if primary_model_id.startswith("anthropic:"):
return "openai:gpt-5.5"
if primary_model_id.startswith("openai:"):
return "anthropic:claude-opus-4-5"
return None
def is_gemini_3_family(model_id: str) -> bool:
model_name = model_id.split(":", 1)[-1]
return model_name.startswith("gemini-3")
def openai_reasoning_for(
profile_effort: str | None,
*,
default_effort: OpenAIReasoningEffort | None = None,
) -> OpenAIReasoning | None:
"""Return an OpenAI reasoning kwarg from a profile effort string."""
effort = profile_effort or default_effort or DEFAULT_LLM_REASONING.get("effort")
if effort == "none":
return {"effort": "none"}
if effort == "low":
return {"effort": "low"}
if effort == "medium":
return {"effort": "medium"}
if effort == "high":
return {"effort": "high"}
if effort == "xhigh":
return {"effort": "xhigh"}
return None
def anthropic_thinking_for(profile_effort: str | None) -> AnthropicThinking | None:
if profile_effort in _ANTHROPIC_EFFORTS:
return {"type": "adaptive"}
return None
def anthropic_effort_for(profile_effort: str | None) -> AnthropicEffort | None:
if profile_effort in _ANTHROPIC_EFFORTS:
return profile_effort
return None
def google_thinking_level_for(profile_effort: str | None) -> GoogleThinkingLevel | None:
"""Map profile effort to Gemini 3+ ``thinking_level``."""
if profile_effort == "none":
return "minimal"
if profile_effort == "low":
return "low"
if profile_effort == "medium":
return "medium"
if profile_effort in ("high", "xhigh", "max"):
return "high"
return None
def provider_model_kwargs(
model_id: str,
profile_effort: str | None,
*,
max_tokens: int,
openai_reasoning_default: OpenAIReasoning | None = None,
) -> ModelKwargs:
"""Build provider-specific kwargs for ``make_model`` from a model id and effort."""
kwargs: ModelKwargs = {"max_tokens": max_tokens}
if model_id.startswith("openai:"):
reasoning = openai_reasoning_for(profile_effort)
if reasoning is not None:
kwargs["reasoning"] = reasoning
elif openai_reasoning_default is not None:
kwargs["reasoning"] = openai_reasoning_default
elif model_id.startswith("anthropic:"):
thinking = anthropic_thinking_for(profile_effort)
if thinking is not None:
kwargs["thinking"] = thinking
effort = anthropic_effort_for(profile_effort)
if effort is not None:
kwargs["effort"] = effort
elif model_id.startswith("google_genai:") and is_gemini_3_family(model_id):
thinking_level = google_thinking_level_for(profile_effort)
if thinking_level is not None:
kwargs["thinking_level"] = thinking_level
return kwargs