open-swe/agent/utils/deferred_model.py

58 lines
1.9 KiB
Python
Raw Permalink Normal View History

feat: port durable dispatch hardening and startup latency improvements (#160) * feat: port plan-review & workflow-approval UX (#135) Port six upstream commits onto dev: - c03a6be7 (already ported): keep plan guidance high-level - 546042a4: add workflow approval UI with diff preview, approval URLs, web review links, and polling for approval status during active runs - 216cf181: remove workflow token elevation; approved pushes pass through directly without proxy token rewriting - 3dbc0282: preserve plan redirects after login by accepting relative same-origin redirect_to values and rejecting blocked paths - bb104d93: submit plan comments with cmd+enter - 90cb6caa: terse Slack replies, shared content via save_plan outside plan mode (PLAN_STATUS_SHARED), reject shared-content mutations Refs: #135 * feat: port durable dispatch hardening and startup latency improvements Port five upstream PRs onto dev: - #1621 / #1658: durable dispatch with loopback webhook defense, create_durable_run helper, _config_with_prepare_run_id, degradation to None for relative/loopback completion webhook URLs - #1696: run-level completion webhook deduplication (replace claim-then-post with post-then-flag per run_id), DeferredErrorModel for graph-factory resilience, ToolRetryMiddleware for task subagents, TimeoutWrapupMiddleware for all three graphs - #1697: lazy-load __init__.py for agent.middleware, agent.tools, agent.dashboard (PEP 562); defer heavy imports (exa_py in web_search, agent.webapp in request_pr_review, deepagents in sandbox.py); add ttl_cache.py with stale-while-revalidate for tool loaders Refs: #137 * fix: restore login page render and clear CI lint/format The plan-review port removed the authRedirectUrl import from login.tsx but left its call site, crashing the login page at runtime (blank page, no 'Sign in to open-swe'). Pass the relative path straight to loginUrl, matching the plan route and the backend relative-redirect handling. Also drop an unused os import in the guard test and reformat workflow_push_guard.py to satisfy ruff. * fix: restore RepairOrphaned middleware export and repoint model fake to deferred_model boundary * fix: restore RepairOrphanedToolCallsMiddleware, fix E2E model-fake patch, drop dead ttl_cache - Re-add RepairOrphanedToolCallsMiddleware to the lazy middleware __init__ (_MIDDLEWARE_MODULES, __all__, TYPE_CHECKING) so agent.reviewer can import it. - Reroute E2E model patching to deferred_model.make_model so make_model_or_defer (used by all three graph factories) returns the scripted fake instead of building a real model with fake credentials. - Drop unused agent/utils/ttl_cache.py — no agent module imports it. - Fix import ordering in agent/reviewer.py and agent/analyzer.py (ruff I001). - Format tests/test_dispatch.py. * fix: claim-then-post run-level failure dedup; stop permanent suppression --------- Co-authored-by: amoussa1229 <166072409+amoussa1229@users.noreply.github.com> Co-authored-by: Adam Moussa <adam@seahavenind.com>
2026-07-09 17:11:25 -04:00
from __future__ import annotations
import logging
from typing import Any
from langchain_core.language_models import BaseChatModel
from .model import make_model
logger = logging.getLogger(__name__)
class DeferredErrorModel(BaseChatModel):
"""Model placeholder that raises a stored setup error on first invocation."""
error_message: str
model_id: str | None = None
@property
def _llm_type(self) -> str:
return "deferred-error"
def _get_ls_params(self, stop: Any = None, **kwargs: Any) -> dict[str, Any]:
params = super()._get_ls_params(stop=stop, **kwargs)
if self.model_id:
params["ls_model_name"] = self.model_id
if ":" in self.model_id:
params["ls_provider"] = self.model_id.split(":", 1)[0]
return params
def bind_tools(self, tools: Any, **kwargs: Any) -> DeferredErrorModel:
return self
def _generate(self, messages: Any, stop: Any = None, run_manager: Any = None, **kwargs: Any):
raise ValueError(self.error_message)
def make_deferred_error_model(
error: BaseException, *, model_id: str | None = None
) -> BaseChatModel:
return DeferredErrorModel(error_message=f"{type(error).__name__}: {error}", model_id=model_id)
def make_model_or_defer(model_id: str, **kwargs: Any) -> BaseChatModel:
"""Call ``make_model`` and wrap any setup error in a ``DeferredErrorModel``.
This lets graph factories pass a model placeholder into the agent graph on
startup without crashing the process. The error is raised later inside the
agent run when the model is first invoked, so the caller can recover
gracefully (e.g. via fallback middleware or by surfacing the error to the
user).
"""
try:
return make_model(model_id, **kwargs)
except Exception as e: # noqa: BLE001
logger.warning("Deferring model setup failure for %s", model_id, exc_info=True)
return make_deferred_error_model(e, model_id=model_id)