mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 20:53:15 +00:00
Some checks are pending
CI / Lint (push) Waiting to run
CI / Format check (push) Waiting to run
CI / Typecheck (push) Waiting to run
CI / Unit tests (push) Waiting to run
CI / Playwright E2E (push) Waiting to run
CI / Docker build smoke (push) Waiting to run
CI / Triage ledger up to date (push) Waiting to run
CI / ui bun.lock in sync (push) Waiting to run
* feat(models): re-add Fable 5 with admin disable toggle (port of upstream #1677) * refactor(models): convert re-added Fable 5 to Bedrock model IDs * fix(open-swe): correct Fable copy to describe provider data sharing, not ZDR The ported admin toggle description and code comments described Fable 5 as incompatible with Zero Data Retention. That is backwards: Fable 5 requires the account to opt into Bedrock provider_data_share — prompts/completions are retained and shared with Anthropic (up to 30 days, incl. human review). The old UI copy would lead an admin to believe the opposite of what enabling the toggle does. Reword the toggle description and the gate_fable_model / team_settings comments accordingly. Still off by default. Refs #171.
175 lines
6.7 KiB
Python
175 lines
6.7 KiB
Python
"""Chat graph.
|
|
|
|
A read-only "chat with this PR" agent for the review UI. Unlike the main agent
|
|
and reviewer, it has **no sandbox**: it answers questions about a single pull
|
|
request using the diff, the published review findings, and read-only access to
|
|
the repository over the GitHub API.
|
|
|
|
PR context (diff, findings, overview) is seeded as virtual files under ``/pr/``
|
|
into the ``files`` state channel by the dashboard chat proxy
|
|
(``agent/dashboard/review_chat_api.py``); the built-in ``read_file``/``grep``
|
|
tools operate over those. Repo coordinates and the reviewer thread id arrive in
|
|
``configurable``; a repo-scoped GitHub App token is resolved here so the
|
|
GitHub-backed tools never receive a user credential.
|
|
"""
|
|
# ruff: noqa: E402
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import warnings
|
|
|
|
from langgraph.graph.state import RunnableConfig
|
|
from langgraph.pregel import Pregel
|
|
|
|
warnings.filterwarnings("ignore", module="langchain_core._api.deprecation")
|
|
warnings.filterwarnings("ignore", message=".*Pydantic V1.*", category=UserWarning)
|
|
|
|
from deepagents import create_deep_agent
|
|
from langchain.agents.middleware import ModelCallLimitMiddleware
|
|
|
|
from .dashboard.options import (
|
|
SUPPORTED_MODEL_IDS,
|
|
gate_fable_model,
|
|
model_supports_effort,
|
|
)
|
|
from .dashboard.team_settings import (
|
|
get_effective_gateway_enabled,
|
|
get_team_default_model,
|
|
get_team_fable_enabled,
|
|
)
|
|
from .middleware import (
|
|
ExcludeToolsMiddleware,
|
|
SanitizeFireworksMessagesMiddleware,
|
|
SanitizeOpenAIResponsesMiddleware,
|
|
SanitizeThinkingBlocksMiddleware,
|
|
SanitizeToolInputsMiddleware,
|
|
ToolErrorMiddleware,
|
|
)
|
|
from .server import (
|
|
DEFAULT_LLM_MAX_TOKENS,
|
|
DEFAULT_RECURSION_LIMIT,
|
|
graph_loaded_for_execution,
|
|
)
|
|
from .tools import (
|
|
fetch_url,
|
|
list_review_findings,
|
|
read_repo_file,
|
|
search_repo_code,
|
|
web_search,
|
|
)
|
|
from .utils.github_app import get_github_app_installation_token
|
|
from .utils.model import DEFAULT_LLM_REASONING, make_model, provider_model_kwargs
|
|
from .utils.tracing import AGENT_TRACING_PROJECT, traced_graph_factory
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
CHAT_MODEL_CALL_LIMIT = 100
|
|
|
|
# Read-only: the chat agent never mutates files or runs shell commands. These are
|
|
# injected by deepagents' FilesystemMiddleware and stripped before the model sees
|
|
# them (there is no sandbox, so ``execute`` would error anyway).
|
|
_EXCLUDED_TOOLS = frozenset({"execute", "write_file", "edit_file"})
|
|
|
|
CHAT_PROMPT = """You are a code-review chat assistant. You help the author and reviewers \
|
|
understand one GitHub pull request: `{repo_owner}/{repo_name}` #{pr_number}.
|
|
|
|
You have NO sandbox and cannot run code, execute tests, commit, or open PRs. You \
|
|
reason from the PR's diff, the published review findings, and read-only access to \
|
|
the repository.
|
|
|
|
Context already loaded as virtual files (use `read_file`, `ls`, `grep`):
|
|
- `/pr/overview.md` — title, description, author, branches, head commit, change stats.
|
|
- `/pr/diff.patch` — the unified diff under review.
|
|
- `/pr/findings.md` — the reviewer's published findings, rendered for reading.
|
|
|
|
Tools:
|
|
- `read_repo_file(path, ref)` — read any repo file/dir at a commit (defaults to the \
|
|
PR head). Use it to inspect callers, definitions, and neighboring code beyond the diff.
|
|
- `search_repo_code(query)` — find a symbol or phrase across the repository.
|
|
- `list_review_findings(status_filter)` — the live findings (open/resolved/dismissed) \
|
|
with severity, confidence, and resolution notes.
|
|
- `web_search`, `fetch_url` — for external docs or standards.
|
|
|
|
Guidance:
|
|
- Be concrete and cite specific files and line numbers from the diff.
|
|
- Ground claims about the review in the actual findings; don't invent issues.
|
|
- When you propose a change, describe it precisely — you cannot apply it yourself.
|
|
- Keep answers focused and skimmable. Match the depth of the question.
|
|
"""
|
|
|
|
|
|
async def _resolve_chat_model(configurable: dict) -> tuple[str, str]:
|
|
model_id = configurable.get("chat_model_id")
|
|
effort = configurable.get("chat_effort")
|
|
if (
|
|
isinstance(model_id, str)
|
|
and model_id in SUPPORTED_MODEL_IDS
|
|
and isinstance(effort, str)
|
|
and model_supports_effort(model_id, effort)
|
|
):
|
|
return model_id, effort
|
|
# Team review-chat default, which itself inherits the Agent default if unset.
|
|
return await get_team_default_model("chat")
|
|
|
|
|
|
async def get_chat_agent(config: RunnableConfig) -> Pregel:
|
|
"""Get a read-only PR chat agent. No sandbox; PR context comes via config."""
|
|
thread_id = config["configurable"].get("thread_id")
|
|
config["recursion_limit"] = DEFAULT_RECURSION_LIMIT
|
|
|
|
if thread_id is None or not graph_loaded_for_execution(config):
|
|
return create_deep_agent(system_prompt="", tools=[]).with_config(config)
|
|
|
|
configurable = config["configurable"]
|
|
repo_owner = str(configurable.get("chat_repo_owner") or "")
|
|
repo_name = str(configurable.get("chat_repo_name") or "")
|
|
pr_number = configurable.get("chat_pr_number")
|
|
|
|
# Resolve a repo-scoped, read-only App token in-graph so a user credential is
|
|
# never passed through the run config. Tools read it from configurable.
|
|
token = await get_github_app_installation_token(repositories=[repo_name] if repo_name else None)
|
|
if isinstance(token, str) and token:
|
|
configurable["chat_github_token"] = token
|
|
|
|
model_id, effort = await _resolve_chat_model(configurable)
|
|
model_id, effort = gate_fable_model(
|
|
model_id, effort, fable_enabled=await get_team_fable_enabled()
|
|
)
|
|
use_gateway = await get_effective_gateway_enabled()
|
|
model_kwargs = provider_model_kwargs(
|
|
model_id,
|
|
effort,
|
|
max_tokens=DEFAULT_LLM_MAX_TOKENS,
|
|
openai_reasoning_default=DEFAULT_LLM_REASONING,
|
|
)
|
|
|
|
system_prompt = CHAT_PROMPT.format(
|
|
repo_owner=repo_owner or "<owner>",
|
|
repo_name=repo_name or "<repo>",
|
|
pr_number=pr_number if isinstance(pr_number, int) else "?",
|
|
)
|
|
|
|
return create_deep_agent(
|
|
model=make_model(model_id, use_gateway=use_gateway, **model_kwargs),
|
|
system_prompt=system_prompt,
|
|
tools=[
|
|
read_repo_file,
|
|
search_repo_code,
|
|
list_review_findings,
|
|
web_search,
|
|
fetch_url,
|
|
],
|
|
middleware=[
|
|
SanitizeToolInputsMiddleware(),
|
|
ModelCallLimitMiddleware(run_limit=CHAT_MODEL_CALL_LIMIT, exit_behavior="end"),
|
|
ToolErrorMiddleware(),
|
|
ExcludeToolsMiddleware(excluded=_EXCLUDED_TOOLS),
|
|
SanitizeOpenAIResponsesMiddleware(),
|
|
SanitizeFireworksMessagesMiddleware(),
|
|
SanitizeThinkingBlocksMiddleware(),
|
|
],
|
|
).with_config(config)
|
|
|
|
|
|
traced_chat_agent = traced_graph_factory(get_chat_agent, AGENT_TRACING_PROJECT)
|