fix: support Studio file-based app loading (#4838)

* fix: support Studio file-based app loading

* docs: clarify Studio loader invariant
This commit is contained in:
Mason Zhou 2026-08-16 23:54:06 +08:00 committed by GitHub
parent 16ecf7b006
commit a181c3398b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 36 additions and 9 deletions

View File

@ -400,6 +400,8 @@ cannot restore server privileges or be discarded by the runtime's startup
cleanup. Keep the backend dependencies synchronized with `uv sync`; this
compatibility path requires the declared LangGraph runtime versions and logs a
warning if the persisted-store contract no longer matches its expectations.
The documented command uses LangGraph's file-based custom-app loader, which is
also covered directly by DeerFlow's regression tests.
For workflows that invoke `backend/langgraph.json` through LangGraph Studio or
a direct LangGraph Server, DeerFlow consumes the authenticated identity

View File

@ -298,6 +298,8 @@ reactivate server-only privileges or be discarded by runtime startup cleanup.
Run `uv sync` after dependency changes; this compatibility path requires the
declared LangGraph runtime versions and warns when the persisted-store contract
does not match its expectations.
The same file-based custom-app loading path used by this command is covered by
the backend regression suite.
---

View File

@ -23,12 +23,16 @@ genuine system assistant IDs from the CLI-provided graph registry, removes
their persisted active/version rows so graph registration recreates them, and
demotes every other legacy `created_by=system` marker in both active assistants
and version history. This must happen before runtime 0.30.0 loads and purges
system-marked rows; a user application lifespan is too late. An empty graph
registry or absent persistence file is a no-op, while persistence parse/write
errors fail startup closed. The harness requires in-memory runtime 0.30.0 or
newer, and a persisted store containing no expected registered assistant row
emits a drift warning so changes to LangGraph's internal persistence contract
are observable. Because current create/update writes and all legacy
system-marked rows; a user application lifespan is too late. LangGraph executes
this file-backed custom app without first registering its module in
`sys.modules`; keep its annotations eager so dataclass processing remains
compatible with that loader, and preserve the direct file-loader regression
test. An empty graph registry or absent persistence file is a no-op, while
persistence parse/write errors fail startup closed. The harness requires
in-memory runtime 0.30.0 or newer, and a persisted store containing no
expected registered assistant row emits a drift warning so changes to
LangGraph's internal persistence contract are observable. Because current
create/update writes and all legacy
versions are sanitized, ordinary owner-scoped assistant version selection
remains enabled. Ordinary authenticated users retain owner-scoped assistant
reads/searches.

View File

@ -3,11 +3,11 @@
``langgraph dev`` imports this custom application before entering the locked
in-memory runtime lifespan. That ordering is intentional: runtime 0.30.0 loads
and purges persisted ``created_by=system`` assistants before graph registration
and before a user application lifespan can run.
and before a user application lifespan can run. Keep annotations eager in this
module: LangGraph's file loader executes it without first registering the module
in ``sys.modules``, which breaks dataclasses with postponed annotations.
"""
from __future__ import annotations
import json
import logging
import os

View File

@ -2,8 +2,10 @@
from __future__ import annotations
import importlib.util
import json
import logging
import sys
from pathlib import Path
from uuid import NAMESPACE_DNS, uuid4, uuid5
@ -198,3 +200,20 @@ def test_langgraph_config_loads_the_pre_runtime_studio_app():
config = json.loads((Path(__file__).resolve().parents[1] / "langgraph.json").read_text(encoding="utf-8"))
assert config["http"]["app"].endswith("app/gateway/langgraph_studio.py:langgraph_app")
def test_studio_app_supports_langgraph_file_loader():
"""The CLI executes the custom app without first registering its module."""
module_path = Path(__file__).resolve().parents[1] / "app" / "gateway" / "langgraph_studio.py"
module_name = f"langgraph_studio_file_loader_{uuid4().hex}"
spec = importlib.util.spec_from_file_location(module_name, module_path)
assert spec is not None
assert spec.loader is not None
assert module_name not in sys.modules
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
assert module_name not in sys.modules
assert module.langgraph_app is not None