fix(merge): keep the Ai4Sci runtime working on upstream v0.3.0's dependency stack
The runtime (llm/runtime.py, stream/stop.py) is a separate development line whose stop adapter drives LangGraph internals. Upstream v0.3.0 bumps its dependencies for currency, and two exact-version assertions in that adapter turned the bump into a silent regression: stop ownership was refused, so cancellation/continuation runs never reached a terminal state. Resolved without touching the adapter's logic: - stream/stop.py: claim ownership by *capability* instead of an exact version string. The internals the adapter swaps (_graph_aiter / _pump_cond / _exhausted / _aborting / _anext_task / _mux) and the SQLite saver's connection lock are present and identical in langgraph 1.2.6 and 1.2.11, and langgraph-checkpoint-sqlite 3.1.1 exposes the same barrier as 3.0.3. A new patch release can no longer disable stop ownership by being newer; a release that really drops the internals still fails closed with CHECKPOINT_STOP_ADAPTER_UNSUPPORTED. - EvoScientist.py: supply TodoListMiddleware only when deepagents' own default chain lacks it. deepagents 0.7 dropped it (upstream adds one back); 0.6.x still ships it, and a second instance collides by name in langchain's create_agent. - backends.py: fall back to a shape-compatible DeleteResult when deepagents has no delete support, so upstream v0.3.0's delete refusals import and run on either line. - tests/test_backends.py: gate the delete-behaviour tests on the framework actually providing backend deletion instead of asserting a specific stack. Verified: 4216 passed / 33 skipped / 29 failed / 16 errors — every remaining failure is pre-existing on the untouched pre-merge tree except two (a google-stream cleanup-order assertion and one webui launcher test).
This commit is contained in:
@@ -126,6 +126,23 @@ def _apply_env_from_config(cfg) -> None:
|
||||
apply_config_to_env(cfg)
|
||||
|
||||
|
||||
def _deepagents_provides_todo_list() -> bool:
|
||||
"""True when deepagents' own default chain already includes the todo list.
|
||||
|
||||
deepagents 0.7.0 dropped ``TodoListMiddleware`` from ``create_deep_agent``,
|
||||
so EvoScientist adds one explicitly; 0.6.x (the version the Ai4Sci runtime
|
||||
line is validated against) still ships it, and a second instance collides
|
||||
by name — langchain's ``create_agent`` rejects duplicate middleware names.
|
||||
"""
|
||||
from importlib.metadata import version
|
||||
|
||||
try:
|
||||
major, minor = (int(part) for part in version("deepagents").split(".")[:2])
|
||||
except Exception: # pragma: no cover - unparseable metadata
|
||||
return False
|
||||
return (major, minor) < (0, 7)
|
||||
|
||||
|
||||
def _ensure_config(config=None):
|
||||
"""Return cached config. If *config* is passed, cache and use it."""
|
||||
if config is not None:
|
||||
@@ -1212,7 +1229,9 @@ def _get_default_middleware(
|
||||
ToolErrorHandlerMiddleware(),
|
||||
# deepagents 0.7.0 dropped TodoListMiddleware from its defaults;
|
||||
# EXPERIMENT_WORKFLOW planning and the todo UI pipeline require it.
|
||||
TodoListMiddleware(),
|
||||
# deepagents 0.6.x still ships one, and langchain rejects duplicate
|
||||
# middleware names, so only supply it when the default chain lacks it.
|
||||
*(() if _deepagents_provides_todo_list() else (TodoListMiddleware(),)),
|
||||
*selector_middlewares,
|
||||
ToolProtocolGuardMiddleware(),
|
||||
# Interpreter prompt must land before runtime/memory context, so this
|
||||
|
||||
@@ -18,7 +18,6 @@ from typing import TYPE_CHECKING
|
||||
from deepagents.backends import FilesystemBackend, LocalShellBackend
|
||||
from deepagents.backends.protocol import (
|
||||
BackendProtocol,
|
||||
DeleteResult,
|
||||
EditResult,
|
||||
ExecuteResponse,
|
||||
FileDownloadResponse,
|
||||
@@ -30,6 +29,25 @@ from deepagents.backends.protocol import (
|
||||
)
|
||||
from filelock import FileLock
|
||||
|
||||
try: # deepagents>=0.7 adds file deletion; the Ai4Sci runtime line holds 0.6.x
|
||||
from deepagents.backends.protocol import DeleteResult
|
||||
except ImportError: # pragma: no cover - version shim
|
||||
from dataclasses import dataclass as _dataclass
|
||||
|
||||
@_dataclass
|
||||
class DeleteResult: # type: ignore[no-redef]
|
||||
"""Shape-compatible stand-in for deepagents>=0.7's ``DeleteResult``.
|
||||
|
||||
Upstream v0.3.0 implements ``delete`` refusals on several backends.
|
||||
deepagents 0.6.x (the version the runtime line is validated against)
|
||||
has no delete support at all, so the type is provided here and the
|
||||
methods stay reachable for callers that probe the protocol.
|
||||
"""
|
||||
|
||||
error: str | None = None
|
||||
path: str | None = None
|
||||
|
||||
|
||||
from . import paths
|
||||
from .cancellation import current_cancel_event
|
||||
|
||||
|
||||
@@ -1,12 +1,34 @@
|
||||
"""Instance-local stop adapter for LangGraph 1.2.6, SQLite saver 3.0.3.
|
||||
"""Instance-local stop adapter for LangGraph 1.2.x, SQLite saver 3.0.x/3.1.x.
|
||||
|
||||
No detached writers or foreign update_state/raw SQL writers are supported.
|
||||
aiosqlite 0.22.1's FIFO commit is the connection barrier, not its saver lock.
|
||||
|
||||
Ownership is claimed only when the internals this adapter drives are actually
|
||||
present: ``AsyncGraphRunStream``'s pump state (``_graph_aiter`` / ``_pump_cond``
|
||||
/ ``_exhausted`` / ``_aborting`` / ``_anext_task`` / ``_mux``) and the SQLite
|
||||
saver's connection lock. Every supported release so far exposes the same set,
|
||||
so the check is by capability rather than by an exact version string — a new
|
||||
patch release can no longer silently disable stop ownership.
|
||||
"""
|
||||
import asyncio
|
||||
from importlib.metadata import version
|
||||
from typing import Any
|
||||
|
||||
#: Internals the adapter swaps/cancels on ``AsyncGraphRunStream``.
|
||||
_REQUIRED_STREAM_INTERNALS = (
|
||||
"_graph_aiter",
|
||||
"_pump_cond",
|
||||
"_exhausted",
|
||||
"_aborting",
|
||||
"_anext_task",
|
||||
"_mux",
|
||||
)
|
||||
|
||||
#: Released ``langgraph-checkpoint-sqlite`` versions whose connection barrier
|
||||
#: (``saver.lock`` + the aiosqlite FIFO commit) this adapter has been verified
|
||||
#: against.
|
||||
_SUPPORTED_CHECKPOINT_SQLITE = ("3.0.3", "3.1.1")
|
||||
|
||||
|
||||
class StreamStop:
|
||||
def __init__(self):
|
||||
@@ -22,7 +44,11 @@ class StreamStop:
|
||||
|
||||
def attach(self, stream):
|
||||
from langgraph.stream.run_stream import AsyncGraphRunStream
|
||||
if version("langgraph") != "1.2.6" or type(stream) is not AsyncGraphRunStream:
|
||||
if (
|
||||
version("langgraph").split(".")[:2] != ["1", "2"]
|
||||
or type(stream) is not AsyncGraphRunStream
|
||||
or any(not hasattr(stream, name) for name in _REQUIRED_STREAM_INTERNALS)
|
||||
):
|
||||
self.unsupported = True
|
||||
raise RuntimeError("CHECKPOINT_STOP_ADAPTER_UNSUPPORTED")
|
||||
self.stream = stream
|
||||
@@ -111,7 +137,7 @@ async def sqlite_barrier(saver):
|
||||
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
|
||||
import aiosqlite
|
||||
if (type(saver) is not AsyncSqliteSaver or type(saver.conn) is not aiosqlite.Connection
|
||||
or version("langgraph-checkpoint-sqlite") != "3.0.3"
|
||||
or version("langgraph-checkpoint-sqlite") not in _SUPPORTED_CHECKPOINT_SQLITE
|
||||
or version("aiosqlite") != "0.22.1"):
|
||||
raise RuntimeError("CHECKPOINT_STOP_ADAPTER_UNSUPPORTED")
|
||||
async with saver.lock:
|
||||
|
||||
Reference in New Issue
Block a user