Files
EvoScientist-Multi/EvoScientist/langgraph_dev/sdk.py
T
m4 470cf75722 merge: bring upstream v0.3.0 (72 commits) into Ai4Sci fork
Merged upstream/main (418abca, release v0.3.0) into our fork on a
dedicated branch. 21 conflicting files resolved; main worktree untouched.

Resolution policy and key decisions:
- Keep Ai4Sci runtime endpoints, durable dispatch, workspace scopes and
  the HITL/DynamicReview approval chain (approval path is product-critical).
- Adopt upstream model registry (llm/registry.py): our 136 model entries
  are a strict subset of upstream's 180, so dropping our inline table
  loses nothing and gains 44 new models.
- Adopt upstream native EvoChatDeepSeek; drop our obsolete
  _patch_deepseek_reasoning_passback monkey patch.
- Keep our six patches.py additions, ported onto upstream's new
  _OpenAICompatContent class: stable tool-call ids, tool-history
  sanitization, drop_reasoning_metadata, empty-SSE keepalive,
  extracted-document-text patch, _has_assistant_tool_protocol.
- Keep our skill-budget middleware path (skills=None) instead of passing
  skills through, to avoid double loading.
- Keep sanitized error labels (_safe_error_label) while adopting
  upstream's injected MiddlewareEventSink for fallback narration.
- Keep port 3076 and the LANGGRAPH_SERVER_URL override; adopt upstream's
  host/probe-host handling and CONFIG_DRIFT_SINCE_LAUNCH.
- Adopt upstream dependency stack: deepagents 0.7.6, langchain-quickjs
  0.3.7, langgraph-api 0.14; keep our extra deps (rfc8785, pillow,
  firecrawl-anydoc, nest-asyncio).
- Align call sites with upstream APIs: create_tool_selector_middleware
  now takes events= instead of track_stream_selection=.
2026-09-13 16:07:27 +08:00

98 lines
3.5 KiB
Python

"""Shared LangGraph SDK plumbing for the local langgraph-dev server."""
from __future__ import annotations
import os
from collections.abc import Mapping
DEFAULT_LANGGRAPH_DEV_PORT = 3076
# Mirrors ``config.langgraph_dev_host`` / ``manager._DEFAULT_HOST``. The value
# only matters as a stand-in for the *bind* host — ``_format_hostport`` runs it
# through ``_probe_host``, so both this and "0.0.0.0" yield the same client URL.
DEFAULT_LANGGRAPH_DEV_HOST = "127.0.0.1"
LANGGRAPH_DEV_AUTH_HEADERS = {"x-auth-scheme": "langsmith"}
def langgraph_dev_url(
config: object | None = None,
*,
port: int | None = None,
host: str | None = None,
) -> str:
"""Return the local langgraph-dev base URL for a config or explicit port/host.
An explicit ``LANGGRAPH_SERVER_URL`` (container / prod deploy) wins when no
port or host override is supplied. Otherwise the configured bind interface is
mapped through ``manager._probe_host``: a wildcard bind (``0.0.0.0``) still
resolves to loopback here, while a specific interface is honored so
self-dispatch keeps working when the server is pinned to one address.
"""
runtime_url = os.environ.get("LANGGRAPH_SERVER_URL", "").strip().rstrip("/")
if port is None and host is None and runtime_url:
return runtime_url
from .manager import _format_hostport
selected_port = (
int(port)
if port is not None
else int(getattr(config, "langgraph_dev_port", DEFAULT_LANGGRAPH_DEV_PORT))
)
selected_host = (
host
if host is not None
else str(
getattr(config, "langgraph_dev_host", DEFAULT_LANGGRAPH_DEV_HOST)
or DEFAULT_LANGGRAPH_DEV_HOST
)
)
return f"http://{_format_hostport(selected_host, selected_port)}"
def configured_langgraph_dev_url() -> str:
"""Return the local langgraph-dev URL from the effective application config."""
from ..EvoScientist import _ensure_config
return langgraph_dev_url(_ensure_config())
def langgraph_dev_headers(headers: Mapping[str, str] | None = None) -> dict[str, str]:
"""Return SDK headers, defaulting to the local langgraph-dev auth scheme."""
return dict(LANGGRAPH_DEV_AUTH_HEADERS if headers is None else headers)
def get_langgraph_sync_client(*, url: str, headers: Mapping[str, str] | None = None):
"""Build a sync LangGraph SDK client with EvoScientist's default headers."""
from langgraph_sdk import get_sync_client
return get_sync_client(url=url, headers=langgraph_dev_headers(headers))
def get_langgraph_async_client(*, url: str, headers: Mapping[str, str] | None = None):
"""Build an async LangGraph SDK client with EvoScientist's default headers."""
from langgraph_sdk import get_client
return get_client(url=url, headers=langgraph_dev_headers(headers))
def default_scheduler_timezone(config: object | None = None) -> str | None:
"""Return configured scheduler timezone, falling back to the host timezone."""
if config is None:
from ..EvoScientist import _ensure_config
config = _ensure_config()
timezone = str(getattr(config, "scheduler_default_timezone", "") or "")
if timezone:
return timezone
try:
from tzlocal import get_localzone_name
return get_localzone_name()
except Exception:
return None
def messages_input(content: str) -> dict[str, list[dict[str, str]]]:
"""Return the standard LangGraph chat input shape for one user message."""
return {"messages": [{"role": "user", "content": content}]}