mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-07 16:19:09 +00:00
Merge remote-tracking branch 'origin/dev' into update-154
This commit is contained in:
commit
71240c4007
8 changed files with 332 additions and 96 deletions
|
|
@ -1,22 +1,40 @@
|
|||
"""Middleware that falls back to a secondary model when the primary fails transiently.
|
||||
"""Middleware that retries model calls across a primary and fallback provider.
|
||||
|
||||
Wraps the model call. When the primary model raises a transient provider error
|
||||
(5xx, 429, connection/timeout), the same request is retried once against the
|
||||
configured fallback model. The fallback is bound to tools by the agent factory
|
||||
on the second call, so swapping ``request.model`` is sufficient.
|
||||
Wraps the model call. When a model raises a transient provider error (5xx,
|
||||
429, connection/timeout), the request is retried, alternating between the
|
||||
primary and the configured fallback model with exponential backoff between
|
||||
attempts. The fallback is bound to tools by the agent factory on each call,
|
||||
so swapping ``request.model`` is sufficient.
|
||||
|
||||
Why alternate with backoff instead of failing over once: both providers can
|
||||
be routed through the same LLM Gateway, so a gateway outage takes out the
|
||||
"cross-provider" fallback too. A single immediate failover cannot ride out
|
||||
even a short shared outage (the gateway's 502 page literally says "try again
|
||||
in 30 seconds"), and an unprotected fallback call crashes the whole run.
|
||||
Alternating with a backoff schedule that reaches past 30s lets a long-running
|
||||
agent run survive multi-minute provider or gateway blips.
|
||||
|
||||
Bidirectional: if the primary is Anthropic the fallback is typically OpenAI,
|
||||
and vice versa. The middleware itself is provider-agnostic — it inspects the
|
||||
exception type/status code to decide whether to fall over.
|
||||
exception type/status code to decide whether an attempt is retryable.
|
||||
|
||||
If every attempt fails, the middleware either raises the last error or (by
|
||||
default) returns a terminal ``AIMessage`` explaining the outage, so the run
|
||||
ends with a visible message in Slack/GitHub instead of an abrupt crash. The
|
||||
turn's progress is checkpointed, so the user can retrigger to continue.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from collections.abc import Awaitable, Callable
|
||||
import random
|
||||
import time
|
||||
from collections.abc import Awaitable, Callable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import anthropic
|
||||
import httpx
|
||||
import openai
|
||||
from botocore.exceptions import ClientError
|
||||
from langchain.agents.middleware import AgentMiddleware
|
||||
|
|
@ -37,6 +55,7 @@ _TRANSIENT_EXCEPTIONS: tuple[type[BaseException], ...] = (
|
|||
openai.APITimeoutError,
|
||||
openai.RateLimitError,
|
||||
openai.InternalServerError,
|
||||
httpx.TransportError,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -48,6 +67,23 @@ _RETRYABLE_BEDROCK_ERROR_CODES = {
|
|||
}
|
||||
|
||||
|
||||
# Seconds slept before each retry attempt (attempt 0 is the initial call).
|
||||
# The first failover is immediate: a provider-specific outage should not delay
|
||||
# the cross-provider retry. Later delays grow past the ~30s the gateway's 502
|
||||
# page asks for. Each attempt additionally benefits from the SDK's own
|
||||
# ``max_retries`` backoff, so worst-case wall time before giving up is a few
|
||||
# minutes — acceptable for a long-running agent, far better than crashing.
|
||||
DEFAULT_BACKOFF_SCHEDULE: tuple[float, ...] = (0.0, 5.0, 15.0, 30.0, 45.0)
|
||||
|
||||
MODEL_OUTAGE_MESSAGE = (
|
||||
"I wasn't able to reach the language model providers after several retries "
|
||||
"(both the primary and fallback models returned transient errors, e.g. "
|
||||
"502/503/overloaded). This is a temporary provider or gateway outage, not a "
|
||||
"problem with your task. My progress so far has been saved — please retrigger "
|
||||
"the run in a few minutes to continue."
|
||||
)
|
||||
|
||||
|
||||
def _should_fallback(exc: BaseException) -> bool:
|
||||
if isinstance(exc, _TRANSIENT_EXCEPTIONS):
|
||||
return True
|
||||
|
|
@ -117,52 +153,132 @@ def _provider_access_error_message(exc: BaseException) -> str | None:
|
|||
|
||||
|
||||
class ModelFallbackMiddleware(AgentMiddleware):
|
||||
"""Retry the model call against a fallback provider on transient errors."""
|
||||
"""Retry the model call across primary and fallback providers on transient errors.
|
||||
|
||||
def __init__(self, fallback_model: BaseChatModel) -> None:
|
||||
Args:
|
||||
fallback_model: Cross-provider model used on odd-numbered attempts.
|
||||
backoff_schedule: Seconds slept before each retry. ``len(schedule) + 1``
|
||||
is the total number of attempts. Delays get ±25% jitter.
|
||||
surface_outage_message: When all attempts fail, return a terminal
|
||||
``AIMessage`` describing the outage instead of raising, so the run
|
||||
ends gracefully with a user-visible message rather than a crash.
|
||||
Set to ``False`` to re-raise the last error (e.g. if platform-level
|
||||
alerting keys off failed runs).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fallback_model: BaseChatModel,
|
||||
*,
|
||||
backoff_schedule: Sequence[float] = DEFAULT_BACKOFF_SCHEDULE,
|
||||
surface_outage_message: bool = True,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._fallback_model = fallback_model
|
||||
self._backoff_schedule = tuple(backoff_schedule)
|
||||
self._surface_outage_message = surface_outage_message
|
||||
|
||||
def _fallback_name(self) -> str:
|
||||
return (
|
||||
getattr(self._fallback_model, "model_name", None)
|
||||
or getattr(self._fallback_model, "model", None)
|
||||
or "fallback"
|
||||
)
|
||||
|
||||
def _jittered_delay(self, delay: float) -> float:
|
||||
return delay + random.uniform(-delay * 0.25, delay * 0.25) if delay > 0 else 0.0
|
||||
|
||||
def _log_retry(
|
||||
self, exc_type: str, use_fallback: bool, attempt: int, total: int, delay: float
|
||||
) -> None:
|
||||
logger.warning(
|
||||
"Model call failed transiently (%s) on %s model "
|
||||
"(attempt %d/%d); retrying %s model in %.1fs",
|
||||
exc_type,
|
||||
"fallback" if use_fallback else "primary",
|
||||
attempt + 1,
|
||||
total,
|
||||
"primary" if use_fallback else f"fallback ({self._fallback_name()})",
|
||||
delay,
|
||||
)
|
||||
|
||||
def _resolve_request(self, request: ModelRequest, use_fallback: bool) -> ModelRequest:
|
||||
return request.override(model=self._fallback_model) if use_fallback else request
|
||||
|
||||
def wrap_model_call(
|
||||
self,
|
||||
request: ModelRequest,
|
||||
handler: Callable[[ModelRequest], ModelResponse],
|
||||
) -> ModelCallResult:
|
||||
try:
|
||||
return handler(request)
|
||||
except Exception as exc:
|
||||
access_error_message = _provider_access_error_message(exc)
|
||||
if access_error_message is not None:
|
||||
logger.warning("Model access error surfaced to user: %s", type(exc).__name__)
|
||||
return AIMessage(content=access_error_message)
|
||||
if not _should_fallback(exc):
|
||||
raise
|
||||
logger.warning(
|
||||
"Primary model failed (%s); falling back to %s",
|
||||
type(exc).__name__,
|
||||
getattr(self._fallback_model, "model_name", None)
|
||||
or getattr(self._fallback_model, "model", "fallback"),
|
||||
)
|
||||
return handler(request.override(model=self._fallback_model))
|
||||
total_attempts = len(self._backoff_schedule) + 1
|
||||
last_exc: BaseException | None = None
|
||||
|
||||
for attempt in range(total_attempts):
|
||||
use_fallback = attempt % 2 == 1
|
||||
attempt_request = self._resolve_request(request, use_fallback)
|
||||
try:
|
||||
return handler(attempt_request)
|
||||
except Exception as exc:
|
||||
access_error_message = _provider_access_error_message(exc)
|
||||
if access_error_message is not None:
|
||||
logger.warning("Model access error surfaced to user: %s", type(exc).__name__)
|
||||
return AIMessage(content=access_error_message)
|
||||
if not _should_fallback(exc):
|
||||
raise
|
||||
last_exc = exc
|
||||
if attempt + 1 >= total_attempts:
|
||||
break
|
||||
delay = self._jittered_delay(self._backoff_schedule[attempt])
|
||||
self._log_retry(type(exc).__name__, use_fallback, attempt, total_attempts, delay)
|
||||
if delay > 0:
|
||||
time.sleep(delay)
|
||||
|
||||
assert last_exc is not None
|
||||
logger.error(
|
||||
"Model call failed after %d attempts across primary and fallback (%s): %s",
|
||||
total_attempts,
|
||||
self._fallback_name(),
|
||||
last_exc,
|
||||
)
|
||||
if self._surface_outage_message:
|
||||
return AIMessage(content=MODEL_OUTAGE_MESSAGE)
|
||||
raise last_exc
|
||||
|
||||
async def awrap_model_call(
|
||||
self,
|
||||
request: ModelRequest,
|
||||
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
||||
) -> Any:
|
||||
try:
|
||||
return await handler(request)
|
||||
except Exception as exc:
|
||||
access_error_message = _provider_access_error_message(exc)
|
||||
if access_error_message is not None:
|
||||
logger.warning("Model access error surfaced to user: %s", type(exc).__name__)
|
||||
return AIMessage(content=access_error_message)
|
||||
if not _should_fallback(exc):
|
||||
raise
|
||||
logger.warning(
|
||||
"Primary model failed (%s); falling back to %s",
|
||||
type(exc).__name__,
|
||||
getattr(self._fallback_model, "model_name", None)
|
||||
or getattr(self._fallback_model, "model", "fallback"),
|
||||
)
|
||||
return await handler(request.override(model=self._fallback_model))
|
||||
total_attempts = len(self._backoff_schedule) + 1
|
||||
last_exc: BaseException | None = None
|
||||
|
||||
for attempt in range(total_attempts):
|
||||
use_fallback = attempt % 2 == 1
|
||||
attempt_request = self._resolve_request(request, use_fallback)
|
||||
try:
|
||||
return await handler(attempt_request)
|
||||
except Exception as exc:
|
||||
access_error_message = _provider_access_error_message(exc)
|
||||
if access_error_message is not None:
|
||||
logger.warning("Model access error surfaced to user: %s", type(exc).__name__)
|
||||
return AIMessage(content=access_error_message)
|
||||
if not _should_fallback(exc):
|
||||
raise
|
||||
last_exc = exc
|
||||
if attempt + 1 >= total_attempts:
|
||||
break
|
||||
delay = self._jittered_delay(self._backoff_schedule[attempt])
|
||||
self._log_retry(type(exc).__name__, use_fallback, attempt, total_attempts, delay)
|
||||
if delay > 0:
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
assert last_exc is not None
|
||||
logger.error(
|
||||
"Model call failed after %d attempts across primary and fallback (%s): %s",
|
||||
total_attempts,
|
||||
self._fallback_name(),
|
||||
last_exc,
|
||||
)
|
||||
if self._surface_outage_message:
|
||||
return AIMessage(content=MODEL_OUTAGE_MESSAGE)
|
||||
raise last_exc
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{"_meta": {"last_synced": "fd2541ce", "last_synced_date": "2026-07-09"}}
|
||||
{"_meta": {"last_synced": "71e3b818", "last_synced_date": "2026-07-09"}}
|
||||
{"sha": "0b76afdc", "pr": 1653, "subject": "reviews block agenda, sticky headers, diff scroll", "disposition": "landed", "reason": "", "branch": "cherry-pick-upstream", "local_sha": null, "updated": "2026-07-02T00:00:00Z"}
|
||||
{"sha": "7530653b", "pr": 1655, "subject": "ResizeObserver settle for review scroll-to", "disposition": "landed", "reason": "", "branch": "cherry-pick-upstream", "local_sha": null, "updated": "2026-07-02T00:00:00Z"}
|
||||
{"sha": "23bd4a63", "pr": 1660, "subject": "top padding to sticky review block header", "disposition": "landed", "reason": "", "branch": "cherry-pick-upstream", "local_sha": null, "updated": "2026-07-02T00:00:00Z"}
|
||||
|
|
@ -62,8 +62,8 @@
|
|||
{"sha": "13b40113", "pr": 1666, "subject": "chore(deps): bump cryptography from 48.0.1 to 49.0.0 in the major group (#1666)", "disposition": "wont-merge", "reason": "already in dev — same 48->49 cryptography bump dev did via #105", "branch": "", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "fbc6de85", "pr": 1667, "subject": "chore(deps): bump fireworks-ai from 1.2.0a75 to 1.2.0a86 (#1667)", "disposition": "deferred", "reason": "conflicts on pick — dev diverged to fireworks-ai a85; a86 still wanted, needs manual bump + uv lock", "branch": "deps", "local_sha": null, "updated": "2026-07-08T23:17:35Z"}
|
||||
{"sha": "290d0fee", "pr": 1669, "subject": "chore(deps): update langgraph-cli[inmem] requirement (#1669)", "disposition": "wont-merge", "reason": "already in dev — langgraph-cli[inmem] at 0.4.30", "branch": "", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "c9f6dd86", "pr": 1694, "subject": "fix: fall back on model stream transport errors (#1694)", "disposition": "deferred", "reason": "adds httpx.TransportError to transient set; small conflict w/ dev's diverged Bedrock fallback", "branch": "model-fallback", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "c9a9a7cd", "pr": 1695, "subject": "fix: retry model fallback exhaustion (#1695)", "disposition": "deferred", "reason": "alternating retry+backoff rewrite; reconcile by hand w/ dev's Bedrock + sync wrap path", "branch": "model-fallback", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "c9f6dd86", "pr": 1694, "subject": "fix: fall back on model stream transport errors (#1694)", "disposition": "landed", "reason": "adds httpx.TransportError to transient set; small conflict w/ dev's diverged Bedrock fallback", "branch": "model-fallback", "local_sha": null, "updated": "2026-07-09T18:59:20Z"}
|
||||
{"sha": "c9a9a7cd", "pr": 1695, "subject": "fix: retry model fallback exhaustion (#1695)", "disposition": "landed", "reason": "alternating retry+backoff rewrite; reconcile by hand w/ dev's Bedrock + sync wrap path", "branch": "model-fallback", "local_sha": null, "updated": "2026-07-09T18:59:20Z"}
|
||||
{"sha": "e5dbc788", "pr": 1696, "subject": "fix: Harden durable agent runs (#1696)", "disposition": "landed", "reason": "large durable-run hardening; 3 new modules dev lacks; rewrites fork dispatch/completion", "branch": "durable-dispatch", "local_sha": null, "updated": "2026-07-09T17:22:53Z"}
|
||||
{"sha": "52fe2916", "pr": 1698, "subject": "feat: add PR review link route (#1698)", "disposition": "landed", "reason": "cherry-picked (-x) in #127 (PR review link route)", "branch": "reviewer-misc", "local_sha": null, "updated": "2026-07-08T22:58:21Z"}
|
||||
{"sha": "5f7f5fbd", "pr": 1697, "subject": "fix: Reduce graph import and loader startup latency (#1697)", "disposition": "landed", "reason": "import-hygiene refactor; cross-cutting, references many deferred upstream-only modules", "branch": "durable-dispatch", "local_sha": null, "updated": "2026-07-09T17:22:53Z"}
|
||||
|
|
@ -83,3 +83,7 @@
|
|||
{"sha": "9cd7e464", "pr": 1700, "subject": "Fix workflow approval visibility (#1700)", "disposition": "wont-merge", "reason": "superseded — dev's list_workflow_approvals_for_thread already enforces owner-only 403", "branch": "", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "22e024cb", "pr": 1704, "subject": "fix: link issue PRs and prompt repo conventions (#1704)", "disposition": "deferred", "reason": "issue/PR linking + repo-convention prompt; clean but prompt-conflict risk vs #113", "branch": "webhook-issue-linking", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "fd2541ce", "pr": 1705, "subject": "fix: drop orphaned function_call items with stale OpenAI reasoning (#1705)", "disposition": "wont-merge", "reason": "N/A — edits sanitize_openai_responses.py which dev deleted in the Bedrock/Fireworks migration (#62)", "branch": "", "local_sha": null, "updated": "2026-07-08T20:14:42Z"}
|
||||
{"sha": "27b0ddeb", "pr": 1708, "subject": "feat: add GPT-5.6 OpenAI models (#1708)", "disposition": "deferred", "reason": "FLAG-HUMAN: adds OpenAI GPT-5.6 to the model picker; fork's picker is Bedrock/Fireworks-only — needs a product decision before adopting OpenAI models. Gateway (#155) can route OpenAI if adopted.", "branch": "model-picker", "local_sha": null, "updated": "2026-07-09T22:20:06Z"}
|
||||
{"sha": "62e0ca2d", "pr": 1709, "subject": "fix: stale admin model defaults after model upgrades (#1709)", "disposition": "deferred", "reason": "stale admin model-default cleanup in team_settings after model upgrades; applies to fork's default-model resolution.", "branch": "model-picker", "local_sha": null, "updated": "2026-07-09T22:20:06Z"}
|
||||
{"sha": "138ab9ec", "pr": 1710, "subject": "fix: bump langchain-fireworks to 1.4.4 (#1710)", "disposition": "deferred", "reason": "langchain-fireworks 1.4.4 bump; fork uses Fireworks as a primary provider — adopt with a lockfile refresh.", "branch": "deps", "local_sha": null, "updated": "2026-07-09T22:20:07Z"}
|
||||
{"sha": "71e3b818", "pr": 1713, "subject": "fix: align reviewer eval with published findings (#1713)", "disposition": "deferred", "reason": "reviewer-eval/judge alignment; touches reviewer.py + add_finding/publish_review which are fork-diverged — reconcile against fork's reviewer before porting.", "branch": "reviewer-eval", "local_sha": null, "updated": "2026-07-09T22:20:07Z"}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Commits on `upstream/main` (langchain-ai/open-swe) not yet in `dev`, and the dec
|
|||
Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred rows are provisional
|
||||
— re-inspect before picking. See the fork-maintenance runbook in `CLAUDE.md`.
|
||||
|
||||
**Last synced `upstream/main`:** `fd2541ce` (2026-07-09)
|
||||
**Last synced `upstream/main`:** `71e3b818` (2026-07-09)
|
||||
|
||||
| sha | pr | subject | decision | why | branch |
|
||||
|---|---|---|---|---|---|
|
||||
|
|
@ -46,6 +46,8 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
|||
| `4f913198` | #1647 | widen split review diffs | Landed | already present in dev; empty pick confirmed in #127 | reviewer-misc |
|
||||
| `20f63e8c` | #1646 | install missing deps before verification | Landed | already in dev via #81 (upstream-sync); ledger was stale | prompt-tweaks |
|
||||
| `2f237b53` | #1626 | fall back to vision model for image threads | Landed | ported (adapted to Bedrock/Fireworks vision) in #128 | gateway-routing |
|
||||
| `c9f6dd86` | #1694 | fix: fall back on model stream transport errors (#1694) | Landed | adds httpx.TransportError to transient set; small conflict w/ dev's diverged Bedrock fallback | model-fallback |
|
||||
| `c9a9a7cd` | #1695 | fix: retry model fallback exhaustion (#1695) | Landed | alternating retry+backoff rewrite; reconcile by hand w/ dev's Bedrock + sync wrap path | model-fallback |
|
||||
| `e5dbc788` | #1696 | fix: Harden durable agent runs (#1696) | Landed | large durable-run hardening; 3 new modules dev lacks; rewrites fork dispatch/completion | durable-dispatch |
|
||||
| `52fe2916` | #1698 | feat: add PR review link route (#1698) | Landed | cherry-picked (-x) in #127 (PR review link route) | reviewer-misc |
|
||||
| `5f7f5fbd` | #1697 | fix: Reduce graph import and loader startup latency (#1697) | Landed | import-hygiene refactor; cross-cutting, references many deferred upstream-only modules | durable-dispatch |
|
||||
|
|
@ -85,8 +87,6 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
|||
| `4f8bc2dd` | #1692 | refactor: simplify open-swe agent sandbox lifecycle (#1692) | Deferred | FLAG-HUMAN: structural rewrite of ensure_sandbox_for_thread (drops __creating__ 4-case sentinel) | sandbox-refactor |
|
||||
| `48217b68` | #1489 | feat(open-swe): add E2B sandbox provider (#1489) | Deferred | additive E2B provider; separable but ships on the async sandbox.py base | sandbox-refactor |
|
||||
| `fbc6de85` | #1667 | chore(deps): bump fireworks-ai from 1.2.0a75 to 1.2.0a86 (#1667) | Deferred | conflicts on pick — dev diverged to fireworks-ai a85; a86 still wanted, needs manual bump + uv lock | deps |
|
||||
| `c9f6dd86` | #1694 | fix: fall back on model stream transport errors (#1694) | Deferred | adds httpx.TransportError to transient set; small conflict w/ dev's diverged Bedrock fallback | model-fallback |
|
||||
| `c9a9a7cd` | #1695 | fix: retry model fallback exhaustion (#1695) | Deferred | alternating retry+backoff rewrite; reconcile by hand w/ dev's Bedrock + sync wrap path | model-fallback |
|
||||
| `67abf5b0` | #1659 | fix: surface attributed PR creation failures (#1659) | Deferred | PR-attribution-failure guard (new mw, safe imports); heavy conflict on diverged open_pull_request.py | pr-attribution |
|
||||
| `c75cbb1f` | #1677 | feat: re-add Fable 5 with an admin toggle to disable it (#1677) | Deferred | FLAG-HUMAN: re-adds Fable 5 via anthropic: — contradicts dev's deliberate hide (#1483) + Bedrock migration (#62); wont-merge candidate | fable-admin-toggle |
|
||||
| `304032fa` | #1680 | chore: clarify question answering prompt (#1680) | Deferred | reword Slack info-only answer guidance; conflicts w/ fork's customized Slack prompt | prompt-tweaks |
|
||||
|
|
@ -94,5 +94,9 @@ Rows key on the **upstream SHA** (stable across local cherry-picks). Deferred ro
|
|||
| `feb7ac98` | #1689 | feat(web): surface thread sandbox ID with touch-friendly menu (#1689) | Deferred | applies clean but frontend<->backend contract (thread_api->queries->types->sidebar); needs UI build + e2e validation — separate PR | dashboard-ui |
|
||||
| `f53caff1` | #1701 | fix: fall back to core GitHub App scope when optional grants missing (#1701) | Deferred | FLAG-HUMAN: GitHub-App permission-ladder degrade (auth surface); heavy conflict on diverged github_app.py/_resolve_proxy_token | github-app-scope |
|
||||
| `22e024cb` | #1704 | fix: link issue PRs and prompt repo conventions (#1704) | Deferred | issue/PR linking + repo-convention prompt; clean but prompt-conflict risk vs #113 | webhook-issue-linking |
|
||||
| `27b0ddeb` | #1708 | feat: add GPT-5.6 OpenAI models (#1708) | Deferred | FLAG-HUMAN: adds OpenAI GPT-5.6 to the model picker; fork's picker is Bedrock/Fireworks-only — needs a product decision before adopting OpenAI models. Gateway (#155) can route OpenAI if adopted. | model-picker |
|
||||
| `62e0ca2d` | #1709 | fix: stale admin model defaults after model upgrades (#1709) | Deferred | stale admin model-default cleanup in team_settings after model upgrades; applies to fork's default-model resolution. | model-picker |
|
||||
| `138ab9ec` | #1710 | fix: bump langchain-fireworks to 1.4.4 (#1710) | Deferred | langchain-fireworks 1.4.4 bump; fork uses Fireworks as a primary provider — adopt with a lockfile refresh. | deps |
|
||||
| `71e3b818` | #1713 | fix: align reviewer eval with published findings (#1713) | Deferred | reviewer-eval/judge alignment; touches reviewer.py + add_finding/publish_review which are fork-diverged — reconcile against fork's reviewer before porting. | reviewer-eval |
|
||||
|
||||
_Maintenance: after a `git sync`, add new `dev..upstream/main` SHAs as **Untriaged** (edit `triage.jsonl`) and bump "Last synced". A successful `git cherry-pick -x` auto-moves the row to **Landed** via the `post-commit` journal + `make triage-reconcile`._
|
||||
|
|
|
|||
|
|
@ -8,27 +8,27 @@ license = { text = "MIT" }
|
|||
dependencies = [
|
||||
"deepagents==0.6.12",
|
||||
"fastapi>=0.139.0",
|
||||
"uvicorn>=0.49.0",
|
||||
"uvicorn>=0.51.0",
|
||||
"httpx>=0.28.1",
|
||||
"PyJWT>=2.13.0",
|
||||
"cryptography>=49.0.0",
|
||||
"langgraph-sdk>=0.4.2",
|
||||
"langchain>=1.3.9",
|
||||
"langgraph>=1.1.10",
|
||||
"langchain>=1.3.12",
|
||||
"langgraph>=1.2.8",
|
||||
"markdownify>=1.2.3",
|
||||
"langchain-anthropic>=1.4.6",
|
||||
"langchain-aws>=0.2.0",
|
||||
"langchain-aws>=1.6.2",
|
||||
"langgraph-cli[inmem]>=0.4.30",
|
||||
"langsmith==0.9.7",
|
||||
"langchain-openai>=1.3.3",
|
||||
"langsmith==0.10.0",
|
||||
"langchain-openai>=1.3.4",
|
||||
"langchain-fireworks>=1.4.3",
|
||||
# langchain-fireworks 1.4.2 pins a pre-release fireworks-ai; opt in explicitly so uv resolves it.
|
||||
"fireworks-ai>=1.2.0a85",
|
||||
"fireworks-ai>=1.2.0a88",
|
||||
"langchain-daytona>=0.0.7",
|
||||
"langchain-modal>=0.0.5",
|
||||
"langchain-runloop>=0.0.6",
|
||||
"exa-py>=2.16.0",
|
||||
"langchain-google-genai>=4.2.4",
|
||||
"langchain-google-genai>=4.2.7",
|
||||
"langchain-mcp-adapters>=0.3.0",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,12 @@ class TestShouldFallback:
|
|||
exc = anthropic.BadRequestError("bad", response=response, body={})
|
||||
assert _should_fallback(exc) is False
|
||||
|
||||
def test_httpx_remote_protocol_error_falls_back(self) -> None:
|
||||
exc = httpx.RemoteProtocolError(
|
||||
"peer closed connection without sending complete message body (incomplete chunked read)"
|
||||
)
|
||||
assert _should_fallback(exc) is True
|
||||
|
||||
def test_value_error_does_not_fall_back(self) -> None:
|
||||
assert _should_fallback(ValueError("nope")) is False
|
||||
|
||||
|
|
@ -100,6 +106,31 @@ class TestModelFallbackMiddleware:
|
|||
request.override.assert_called_once_with(model=fallback_model)
|
||||
assert calls[1] is request.override.return_value
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_falls_over_on_stream_transport_error(self) -> None:
|
||||
fallback_model = MagicMock(name="fallback_model")
|
||||
middleware = ModelFallbackMiddleware(fallback_model)
|
||||
|
||||
calls: list[object] = []
|
||||
good_response = MagicMock(result=[AIMessage(content="ok from fallback")])
|
||||
|
||||
async def handler(req: object) -> object:
|
||||
calls.append(req)
|
||||
if len(calls) == 1:
|
||||
raise httpx.RemoteProtocolError(
|
||||
"peer closed connection without sending complete message body "
|
||||
"(incomplete chunked read)"
|
||||
)
|
||||
return good_response
|
||||
|
||||
request = _make_request()
|
||||
result = await middleware.awrap_model_call(request, handler)
|
||||
|
||||
assert result is good_response
|
||||
assert len(calls) == 2
|
||||
request.override.assert_called_once_with(model=fallback_model)
|
||||
assert calls[1] is request.override.return_value
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_propagates_non_transient_error(self) -> None:
|
||||
middleware = ModelFallbackMiddleware(MagicMock())
|
||||
|
|
@ -128,9 +159,50 @@ class TestModelFallbackMiddleware:
|
|||
assert "data retention enabled" in result.text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_does_not_double_fall_back(self) -> None:
|
||||
"""If the fallback also fails transiently, the error propagates."""
|
||||
middleware = ModelFallbackMiddleware(MagicMock())
|
||||
async def test_async_retries_primary_after_fallback_failure(self) -> None:
|
||||
"""If the fallback also fails transiently, retry the primary instead of crashing."""
|
||||
fallback_model = MagicMock(name="fallback_model")
|
||||
middleware = ModelFallbackMiddleware(fallback_model, backoff_schedule=(0.0, 0.0, 0.0))
|
||||
calls: list[object] = []
|
||||
good_response = MagicMock(result=[AIMessage(content="ok from primary retry")])
|
||||
|
||||
async def handler(req: object) -> object:
|
||||
calls.append(req)
|
||||
if len(calls) <= 2: # primary fails, then fallback fails
|
||||
raise _openai_5xx()
|
||||
return good_response
|
||||
|
||||
request = _make_request()
|
||||
result = await middleware.awrap_model_call(request, handler)
|
||||
|
||||
assert result is good_response
|
||||
assert len(calls) == 3
|
||||
# Attempts alternate primary -> fallback -> primary.
|
||||
assert calls[0] is request
|
||||
assert calls[1] is request.override.return_value
|
||||
assert calls[2] is request
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_exhaustion_returns_outage_message(self) -> None:
|
||||
"""After exhausting all attempts, the run ends with a visible message, not a crash."""
|
||||
middleware = ModelFallbackMiddleware(MagicMock(), backoff_schedule=(0.0, 0.0))
|
||||
calls: list[object] = []
|
||||
|
||||
async def handler(req: object) -> object:
|
||||
calls.append(req)
|
||||
raise _openai_5xx()
|
||||
|
||||
result = await middleware.awrap_model_call(_make_request(), handler)
|
||||
|
||||
assert len(calls) == 3
|
||||
assert isinstance(result, AIMessage)
|
||||
assert "retrigger" in result.text
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_exhaustion_raises_when_message_disabled(self) -> None:
|
||||
middleware = ModelFallbackMiddleware(
|
||||
MagicMock(), backoff_schedule=(0.0,), surface_outage_message=False
|
||||
)
|
||||
calls: list[object] = []
|
||||
|
||||
async def handler(req: object) -> object:
|
||||
|
|
|
|||
44
ui/bun.lock
44
ui/bun.lock
|
|
@ -51,7 +51,7 @@
|
|||
"jsdom": "^29.1.1",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "^8.1.2",
|
||||
"vite-plugin-pwa": "^1.3.0",
|
||||
"vitest": "^4.1.0",
|
||||
|
|
@ -866,6 +866,46 @@
|
|||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.0", "", { "dependencies": { "@typescript-eslint/types": "8.61.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ=="],
|
||||
|
||||
"@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="],
|
||||
|
||||
"@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="],
|
||||
|
||||
"@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="],
|
||||
|
||||
"@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="],
|
||||
|
||||
"@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="],
|
||||
|
||||
"@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="],
|
||||
|
||||
"@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="],
|
||||
|
||||
"@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="],
|
||||
|
||||
"@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="],
|
||||
|
||||
"@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="],
|
||||
|
||||
"@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="],
|
||||
|
||||
"@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="],
|
||||
|
||||
"@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="],
|
||||
|
||||
"@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="],
|
||||
|
||||
"@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="],
|
||||
|
||||
"@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="],
|
||||
|
||||
"@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="],
|
||||
|
||||
"@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="],
|
||||
|
||||
"@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="],
|
||||
|
||||
"@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.12.2", "", { "os": "android", "cpu": "arm" }, "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w=="],
|
||||
|
|
@ -2218,7 +2258,7 @@
|
|||
|
||||
"typed-array-length": ["typed-array-length@1.0.8", "", { "dependencies": { "call-bind": "^1.0.9", "for-each": "^0.3.5", "gopd": "^1.2.0", "is-typed-array": "^1.1.15", "possible-typed-array-names": "^1.1.0", "reflect.getprototypeof": "^1.0.10" } }, "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g=="],
|
||||
|
||||
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||
"typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="],
|
||||
|
||||
"typescript-eslint": ["typescript-eslint@8.61.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.61.0", "@typescript-eslint/parser": "8.61.0", "@typescript-eslint/typescript-estree": "8.61.0", "@typescript-eslint/utils": "8.61.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw=="],
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
"jsdom": "^29.1.1",
|
||||
"prettier": "^3.8.1",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript": "^7.0.2",
|
||||
"vite": "^8.1.2",
|
||||
"vite-plugin-pwa": "^1.3.0",
|
||||
"vitest": "^4.1.0",
|
||||
|
|
|
|||
70
uv.lock
generated
70
uv.lock
generated
|
|
@ -781,7 +781,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "fireworks-ai"
|
||||
version = "1.2.0a85"
|
||||
version = "1.2.0a88"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
|
@ -793,9 +793,9 @@ dependencies = [
|
|||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/94/fb75cf9c769a09de31e550ba99a279e5f83a05cfcf49cd0cbb8ad586ab35/fireworks_ai-1.2.0a85.tar.gz", hash = "sha256:33abbeddd9a6bd9993bca6cb9e16afe9799b2cd674d6b7c04a601cd4e412f2a7", size = 448217, upload-time = "2026-06-26T08:06:57.286Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e9/17/f48738655a14d93c98ac34d966caeec7de69843b1e4492e748cc78622169/fireworks_ai-1.2.0a88.tar.gz", hash = "sha256:565db96c0804d9a7901eedad06ae0b656bbbc7514bdf94a1408afba46b40737c", size = 456358, upload-time = "2026-07-09T05:52:58.742Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3f/5b/0b10d0afb633b45f234f6728cc680851f136b9f75903b4387049537a6cd6/fireworks_ai-1.2.0a85-py3-none-any.whl", hash = "sha256:80a4b13bda8e28ca9064c8339d0afe47cbbdb7c61a88eeebc173ccb58129f19a", size = 528844, upload-time = "2026-06-26T08:06:55.927Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/8d/ecbdaf71f79d5a29075837300609fc6d0a802025acd4c648e2b6e8f19fc8/fireworks_ai-1.2.0a88-py3-none-any.whl", hash = "sha256:b532ac0a04e9f5c112297732c3f1238d61641b34a6f5843c87f24c879e842d24", size = 537817, upload-time = "2026-07-09T05:52:57.357Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1431,16 +1431,16 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langchain"
|
||||
version = "1.3.11"
|
||||
version = "1.3.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
{ name = "langgraph" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/a2/91a7197c604a3ce1b774b3c10dd114c3c745c6186a304fc2573b3f94d400/langchain-1.3.11.tar.gz", hash = "sha256:f3cf9cd4d2329b1a03eb8fd92b9d73e4e58a4d52570d67725fc77fbe0f104b32", size = 633374, upload-time = "2026-06-22T23:00:33.44Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/68/c0/8481c93a3899d7e05ac27f21a4e984dab613b72631f39fe01fe0100e3be0/langchain-1.3.12.tar.gz", hash = "sha256:3321f86824a0c0720004d28797d729a58b9bb8a0ac73c6ea2b856cac38117879", size = 642308, upload-time = "2026-07-08T22:38:12.466Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/df/a4/3a181967294f8876362cc4ba36840d50b8286fa23bb3f5e602b69eb3cb1e/langchain-1.3.11-py3-none-any.whl", hash = "sha256:7ae011f95a09b22feea1e8ae4e43f0b6164aebf4c61b8ad845b45f72ff3a90a2", size = 133639, upload-time = "2026-06-22T23:00:31.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/31/33ba60f3aaa88dc7b97f9c7680f431237466804bf6826efb75400dfd2e6c/langchain-1.3.12-py3-none-any.whl", hash = "sha256:19685eb9dce01ebd71b11281d5489453e7ab3d2caf61eacb2a76d15574246535", size = 136863, upload-time = "2026-07-08T22:38:11.003Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1459,7 +1459,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langchain-aws"
|
||||
version = "1.6.1"
|
||||
version = "1.6.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "boto3" },
|
||||
|
|
@ -1468,14 +1468,14 @@ dependencies = [
|
|||
{ name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/c6/4065908dc2f113324d1ac31ecf5c907a23863f7aeaab7b9361efd7109563/langchain_aws-1.6.1.tar.gz", hash = "sha256:b5b054f48e2697fa1b96733e9de2accfdd1a4948d9b4e3712e8180c4585cfd2f", size = 538265, upload-time = "2026-06-25T19:02:23.364Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/59/04f8572e65581d032b2df2516c0059fa064e680f5caa5cabcd03d525092a/langchain_aws-1.6.2.tar.gz", hash = "sha256:df2c15b463c068031af40460dbea6efb7999832263ff6eaec6b99c5f6b291acd", size = 537522, upload-time = "2026-07-07T01:24:58.11Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/ab/d85b915c12394385459bb536202227d0fd81377929d71b16f177559ba074/langchain_aws-1.6.1-py3-none-any.whl", hash = "sha256:a121f687b36678239dd96ee9d0503a0b7d5fc4570b3af6d19983ef6ebfaf115e", size = 206315, upload-time = "2026-06-25T19:02:21.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/ed/bb5211637276616e7b176f3ff08e9d0bf1ce61d1a00c99d26348d6236a3d/langchain_aws-1.6.2-py3-none-any.whl", hash = "sha256:4755c60ce88fff2b829d96027068b7930f335cd85da41fda061769439c288251", size = 206507, upload-time = "2026-07-07T01:24:56.557Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "1.4.8"
|
||||
version = "1.4.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonpatch" },
|
||||
|
|
@ -1488,9 +1488,9 @@ dependencies = [
|
|||
{ name = "typing-extensions" },
|
||||
{ name = "uuid-utils" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/12/e3/bea6d0080acf183332f24dcd74c208aee5857cf8f783c3fb0bd86027d8fb/langchain_core-1.4.8.tar.gz", hash = "sha256:5bf1f8411077c904182ad8f975943d36adcbf579c4e017b3a118b719229ebf9a", size = 957974, upload-time = "2026-06-18T19:39:23.636Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/b9/e937d0a90b26540bff07e7a7c64349f3b29c2dcc36257cd1cd3fdce17f2a/langchain_core-1.4.9.tar.gz", hash = "sha256:f8078901145bed0466755277500a5a22822a7b628808c4c0a28d4fc88895fcf2", size = 967294, upload-time = "2026-07-08T20:06:54.191Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/d6/bdf6f0481cc57ef300d6b1eb48cf1400c0409be715d6eb3cabadd1142a09/langchain_core-1.4.8-py3-none-any.whl", hash = "sha256:d84c28b05e3ba8d4271d0827aad5b592ccdaaf986e76768c23503f0a2045e8aa", size = 557416, upload-time = "2026-06-18T19:39:21.902Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/70/ade2fada52772798ef815b6352b59e71b116aa0c32c3aef5be3dc2cbed12/langchain_core-1.4.9-py3-none-any.whl", hash = "sha256:28e3909e2a10cc81504952d795ac0a9e014c0018121ef89d48dd396fa09ec624", size = 558293, upload-time = "2026-07-08T20:06:52.382Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1524,7 +1524,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langchain-google-genai"
|
||||
version = "4.2.6"
|
||||
version = "4.2.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "filetype" },
|
||||
|
|
@ -1532,9 +1532,9 @@ dependencies = [
|
|||
{ name = "langchain-core" },
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4c/08/68e4f90b273d1d8dc3a7a948e6535e60baac726adbb2a8341dcf17662f54/langchain_google_genai-4.2.6.tar.gz", hash = "sha256:653dc331e691ddd79784d9ff6a4082749e0f873394c6dc782c414eb4409850eb", size = 280074, upload-time = "2026-06-26T06:18:58.807Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1d/0c/bc60dabc362ca7c6ffe8c4bcc2f724c7e566b43eb230cee51419f88f784c/langchain_google_genai-4.2.7.tar.gz", hash = "sha256:03b1463ffe4d42435f43c7870467f2215f684bb46400d2543435d10157c80ac7", size = 281605, upload-time = "2026-07-06T13:51:58.724Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/3c/9a6b43bced0238f95640555e14423fe4a8268e3f4f536f67e26e79e633d4/langchain_google_genai-4.2.6-py3-none-any.whl", hash = "sha256:c40db0c2d033a5fb6db8e2cc3fb6d49c5678b89b337a64da095fb73ec9f72021", size = 70457, upload-time = "2026-06-26T06:18:57.781Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/f9/d73d1e712591723aaddb7a7b1e94978cd2320c29acfe0d26b6169a2f26f0/langchain_google_genai-4.2.7-py3-none-any.whl", hash = "sha256:0d9c388d0e6c629718fca6abb19c6fdca728a9a7873d0324c1ec821288b5b571", size = 70702, upload-time = "2026-07-06T13:51:57.499Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1566,16 +1566,16 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langchain-openai"
|
||||
version = "1.3.3"
|
||||
version = "1.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
{ name = "openai" },
|
||||
{ name = "tiktoken" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/f3/b38e052f943a75ba0e6762c0a50b6f1d6bcd52a9ce63386d8803c2ff506e/langchain_openai-1.3.3.tar.gz", hash = "sha256:143769bf943820b80db769e47ca8fd0aac08ed18714519333b044c4431e9aa67", size = 3256559, upload-time = "2026-06-22T22:54:05.445Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2a/fe/cfd11b9ebc54f7667e3c8f847f64246fad169ad998b8a08d7c3c9d9bccaf/langchain_openai-1.3.4.tar.gz", hash = "sha256:d888d5f39c2a8c3d0d8aa88f5cf50e58a8e7d242f3f15e39422add520eec8e31", size = 3258207, upload-time = "2026-07-08T22:59:50.651Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/4b/7520114de1ce36bb17cbc98d0fcda048a9f755bedaeb73b92137aeaaf1db/langchain_openai-1.3.3-py3-none-any.whl", hash = "sha256:e469659862c8aabba4f6653df973206e7be54f98cf2275c86be7f06b7abe20d7", size = 120437, upload-time = "2026-06-22T22:54:03.8Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/6e/f8f47f2c6976a4520b5416eaeeaee5e6aa7dd906b39685dc1ad4ef6d1ba2/langchain_openai-1.3.4-py3-none-any.whl", hash = "sha256:3241b8392b29c1af233b902b7d9a84bfc5fe26ccb210a7febdfc3972af7e5771", size = 120529, upload-time = "2026-07-08T22:59:49.451Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1605,7 +1605,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "1.2.7"
|
||||
version = "1.2.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core" },
|
||||
|
|
@ -1615,9 +1615,9 @@ dependencies = [
|
|||
{ name = "pydantic" },
|
||||
{ name = "xxhash" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/11/c3961537ac7577125aabba68effc33ecc3dc0962e2d287d734dbd61caad7/langgraph-1.2.7.tar.gz", hash = "sha256:dcdf5b441bf8c7c7c154e603b302c9dbfbfd2d11e1b7ae7d93a5aba979dc87bd", size = 721385, upload-time = "2026-06-30T01:24:12.528Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b6/ad/583fda4c69501390b989770a465ccd0bdab1c1612eba582c012002ddf9b6/langgraph-1.2.8.tar.gz", hash = "sha256:f79d3575f45b404899358976e4fac0294eb75f8df1bfe8cd11286be7539c4548", size = 722464, upload-time = "2026-07-06T20:40:19.487Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/5d/19de0c674cf2d8bc95b25cdb4c13ba3f4fbe0f35bf2224ef19cc46bbb4cd/langgraph-1.2.7-py3-none-any.whl", hash = "sha256:249349a5d6a32cb0aa2304a8fb6529bd0cf56db8780d21db9e086e5383668f89", size = 246930, upload-time = "2026-06-30T01:24:11.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/49/b958a9963606807e5a20cc75fced14aa77c5cbcc470d5bf8ae13277cd298/langgraph-1.2.8-py3-none-any.whl", hash = "sha256:aa8de1d4df44162353d117589ae0bf6930ca009b62d2d6e26cc32580794c5be6", size = 246983, upload-time = "2026-07-06T20:40:18.242Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1747,7 +1747,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "langsmith"
|
||||
version = "0.9.7"
|
||||
version = "0.10.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
|
|
@ -1765,9 +1765,9 @@ dependencies = [
|
|||
{ name = "xxhash" },
|
||||
{ name = "zstandard" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d4/8b/a56b97a3b8a94f850fe2ec42351d4c508cc6bd7ca96644906c311e32ec5b/langsmith-0.9.7.tar.gz", hash = "sha256:40f8a66a466a7abd991a9df1b50de0a9d30c48ee0d3da46ce00516b68e41c9d7", size = 4711142, upload-time = "2026-07-02T20:11:09.619Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/53/18bc9169517c274681402f3714d1975ff687e8e175a9fea2b8087db99747/langsmith-0.10.0.tar.gz", hash = "sha256:1bc97a28e4b7a0f2e2f5419668592b3bf460ba5d886991ad725fb2778aa2e236", size = 4708392, upload-time = "2026-07-08T13:28:28.081Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/95/0abb8647123c9ebbb31162bcdba3183f5f3d3bb2f752ef3d777ab715e88c/langsmith-0.9.7-py3-none-any.whl", hash = "sha256:a5ff9179b77eb0c3a0129294d30924eab13e51e2720f374372dbbc014c892911", size = 671052, upload-time = "2026-07-02T20:11:07.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/cf/41d4077d82dce88fb8f6c02938dd5937b5a74cd67bfd7bdb5206277752ca/langsmith-0.10.0-py3-none-any.whl", hash = "sha256:63aec1105b776b8c65a32b002b29ada02469cd26c91beeb2fc2831b247f4da27", size = 652557, upload-time = "2026-07-08T13:28:26.366Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
|
|
@ -2221,29 +2221,29 @@ requires-dist = [
|
|||
{ name = "deepagents", specifier = "==0.6.12" },
|
||||
{ name = "exa-py", specifier = ">=2.16.0" },
|
||||
{ name = "fastapi", specifier = ">=0.139.0" },
|
||||
{ name = "fireworks-ai", specifier = ">=1.2.0a85" },
|
||||
{ name = "fireworks-ai", specifier = ">=1.2.0a88" },
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "langchain", specifier = ">=1.3.9" },
|
||||
{ name = "langchain", specifier = ">=1.3.12" },
|
||||
{ name = "langchain-anthropic", specifier = ">=1.4.6" },
|
||||
{ name = "langchain-aws", specifier = ">=0.2.0" },
|
||||
{ name = "langchain-aws", specifier = ">=1.6.2" },
|
||||
{ name = "langchain-daytona", specifier = ">=0.0.7" },
|
||||
{ name = "langchain-fireworks", specifier = ">=1.4.3" },
|
||||
{ name = "langchain-google-genai", specifier = ">=4.2.4" },
|
||||
{ name = "langchain-google-genai", specifier = ">=4.2.7" },
|
||||
{ name = "langchain-mcp-adapters", specifier = ">=0.3.0" },
|
||||
{ name = "langchain-modal", specifier = ">=0.0.5" },
|
||||
{ name = "langchain-openai", specifier = ">=1.3.3" },
|
||||
{ name = "langchain-openai", specifier = ">=1.3.4" },
|
||||
{ name = "langchain-runloop", specifier = ">=0.0.6" },
|
||||
{ name = "langgraph", specifier = ">=1.1.10" },
|
||||
{ name = "langgraph", specifier = ">=1.2.8" },
|
||||
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.30" },
|
||||
{ name = "langgraph-sdk", specifier = ">=0.4.2" },
|
||||
{ name = "langsmith", specifier = "==0.9.7" },
|
||||
{ name = "langsmith", specifier = "==0.10.0" },
|
||||
{ name = "markdownify", specifier = ">=1.2.3" },
|
||||
{ name = "pygments", marker = "extra == 'dev'", specifier = ">=2.20.0" },
|
||||
{ name = "pyjwt", specifier = ">=2.13.0" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.1.1" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.4.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15.20" },
|
||||
{ name = "uvicorn", specifier = ">=0.49.0" },
|
||||
{ name = "uvicorn", specifier = ">=0.51.0" },
|
||||
]
|
||||
provides-extras = ["dev"]
|
||||
|
||||
|
|
@ -3589,15 +3589,15 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.49.0"
|
||||
version = "0.51.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "h11" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue