mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-10 14:08:52 +00:00
fix(dev): exclude backend runtime state from reload (#4759)
This commit is contained in:
parent
9ba04bf80c
commit
f78730ab86
@ -98,7 +98,7 @@ make stop # Stop all services
|
|||||||
**Backend directory** (for backend development only):
|
**Backend directory** (for backend development only):
|
||||||
```bash
|
```bash
|
||||||
make install # Install backend dependencies
|
make install # Install backend dependencies
|
||||||
make dev # Run Gateway API with reload (port 8001)
|
make dev # Run Gateway API with runtime-safe reload (port 8001)
|
||||||
make gateway # Run Gateway API only (port 8001)
|
make gateway # Run Gateway API only (port 8001)
|
||||||
make test # Run offline backend tests (excludes live external-API tests)
|
make test # Run offline backend tests (excludes live external-API tests)
|
||||||
make test-live # Explicitly run live DeerFlowClient tests with real APIs
|
make test-live # Explicitly run live DeerFlowClient tests with real APIs
|
||||||
@ -108,6 +108,12 @@ make format # Format code with ruff
|
|||||||
make migrate-rev MSG="..." # Autogenerate a new alembic revision (see Schema Migrations section)
|
make migrate-rev MSG="..." # Autogenerate a new alembic revision (see Schema Migrations section)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The backend `make dev` target pre-creates and excludes `DEER_FLOW_HOME`
|
||||||
|
(default: `backend/.deer-flow`) and `backend/sandbox` from Uvicorn's reload
|
||||||
|
watcher. Do not replace it with a bare `uvicorn --reload`: agent tasks write
|
||||||
|
Python and other runtime files below `DEER_FLOW_HOME`, which would otherwise
|
||||||
|
restart the Gateway during an active run.
|
||||||
|
|
||||||
The root `detect-thread-boundaries` target statically inventories execution
|
The root `detect-thread-boundaries` target statically inventories execution
|
||||||
boundaries under `backend/app/` and `backend/packages/harness/deerflow/`. It
|
boundaries under `backend/app/` and `backend/packages/harness/deerflow/`. It
|
||||||
prints a concise count by execution domain and writes the complete, versioned
|
prints a concise count by execution domain and writes the complete, versioned
|
||||||
|
|||||||
@ -1,8 +1,20 @@
|
|||||||
|
DEER_FLOW_HOME ?= $(CURDIR)/.deer-flow
|
||||||
|
DEER_FLOW_HOME := $(abspath $(DEER_FLOW_HOME))
|
||||||
|
BACKEND_SANDBOX_HOME := $(abspath $(CURDIR)/sandbox)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
uv sync
|
uv sync
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
PYTHONPATH=. PYTHONIOENCODING=utf-8 PYTHONUTF8=1 uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 --reload
|
mkdir -p "$(DEER_FLOW_HOME)" "$(BACKEND_SANDBOX_HOME)"
|
||||||
|
PYTHONPATH=. PYTHONIOENCODING=utf-8 PYTHONUTF8=1 DEER_FLOW_HOME="$(DEER_FLOW_HOME)" uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001 \
|
||||||
|
--reload \
|
||||||
|
--reload-include='*.yaml' \
|
||||||
|
--reload-include='.env' \
|
||||||
|
--reload-exclude='*.pyc' \
|
||||||
|
--reload-exclude='__pycache__' \
|
||||||
|
--reload-exclude="$(BACKEND_SANDBOX_HOME)" \
|
||||||
|
--reload-exclude="$(DEER_FLOW_HOME)"
|
||||||
|
|
||||||
gateway:
|
gateway:
|
||||||
PYTHONPATH=. PYTHONIOENCODING=utf-8 PYTHONUTF8=1 uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001
|
PYTHONPATH=. PYTHONIOENCODING=utf-8 PYTHONUTF8=1 uv run uvicorn app.gateway.app:app --host 0.0.0.0 --port 8001
|
||||||
|
|||||||
@ -414,7 +414,7 @@ If a provider is explicitly enabled but required credentials are missing, or the
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
make install # Install dependencies
|
make install # Install dependencies
|
||||||
make dev # Run Gateway API + embedded agent runtime (port 8001)
|
make dev # Run Gateway API + embedded agent runtime with safe reload (port 8001)
|
||||||
make gateway # Run Gateway API without reload (port 8001)
|
make gateway # Run Gateway API without reload (port 8001)
|
||||||
make lint # Run linter (ruff)
|
make lint # Run linter (ruff)
|
||||||
make format # Format code (ruff)
|
make format # Format code (ruff)
|
||||||
@ -422,6 +422,12 @@ make detect-blocking-io # Inventory blocking IO that may block the backend even
|
|||||||
make migrate-rev MSG="..." # Autogenerate a new alembic revision against the live ORM models
|
make migrate-rev MSG="..." # Autogenerate a new alembic revision against the live ORM models
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`make dev` pre-creates and excludes `DEER_FLOW_HOME` (by default
|
||||||
|
`backend/.deer-flow`) and `backend/sandbox` from Uvicorn's reload watcher. Use
|
||||||
|
this target instead of a bare `uvicorn --reload`: agent tasks write Python and
|
||||||
|
other runtime files under `DEER_FLOW_HOME`, and watching that directory can
|
||||||
|
restart the Gateway during an active run.
|
||||||
|
|
||||||
### Schema Migrations
|
### Schema Migrations
|
||||||
|
|
||||||
DeerFlow's application tables (`runs`, `threads_meta`, `feedback`, `users`,
|
DeerFlow's application tables (`runs`, `threads_meta`, `feedback`, `users`,
|
||||||
|
|||||||
@ -98,6 +98,18 @@ def test_local_dev_gateway_reload_excludes_runtime_state_with_absolute_dirs():
|
|||||||
assert "--reload-exclude='.deer-flow/'" not in serve_sh
|
assert "--reload-exclude='.deer-flow/'" not in serve_sh
|
||||||
|
|
||||||
|
|
||||||
|
def test_backend_make_dev_gateway_reload_excludes_runtime_state_with_absolute_dirs():
|
||||||
|
makefile = _read("backend/Makefile")
|
||||||
|
|
||||||
|
assert "DEER_FLOW_HOME ?= $(CURDIR)/.deer-flow" in makefile
|
||||||
|
assert "DEER_FLOW_HOME := $(abspath $(DEER_FLOW_HOME))" in makefile
|
||||||
|
assert "BACKEND_SANDBOX_HOME := $(abspath $(CURDIR)/sandbox)" in makefile
|
||||||
|
assert 'mkdir -p "$(DEER_FLOW_HOME)" "$(BACKEND_SANDBOX_HOME)"' in makefile
|
||||||
|
assert 'DEER_FLOW_HOME="$(DEER_FLOW_HOME)" uv run uvicorn' in makefile
|
||||||
|
assert '--reload-exclude="$(DEER_FLOW_HOME)"' in makefile
|
||||||
|
assert '--reload-exclude="$(BACKEND_SANDBOX_HOME)"' in makefile
|
||||||
|
|
||||||
|
|
||||||
def test_backend_container_only_exposes_gateway_port():
|
def test_backend_container_only_exposes_gateway_port():
|
||||||
dockerfile = _read("backend/Dockerfile")
|
dockerfile = _read("backend/Dockerfile")
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ Two layers of coverage:
|
|||||||
* ``test_*_resolve_*`` exercises uvicorn's real ``resolve_reload_patterns`` to
|
* ``test_*_resolve_*`` exercises uvicorn's real ``resolve_reload_patterns`` to
|
||||||
pin the failure mode and the fix's mechanism.
|
pin the failure mode and the fix's mechanism.
|
||||||
* ``test_launcher_precreates_every_absolute_reload_exclude`` enforces the actual
|
* ``test_launcher_precreates_every_absolute_reload_exclude`` enforces the actual
|
||||||
invariant on both launchers: every absolute exclude dir is ``mkdir -p``'d
|
invariant on every dev launcher: every absolute exclude dir is ``mkdir -p``'d
|
||||||
before uvicorn starts. This encodes the root cause, so any future absolute
|
before uvicorn starts. This encodes the root cause, so any future absolute
|
||||||
exclude that forgets its ``mkdir`` fails here.
|
exclude that forgets its ``mkdir`` fails here.
|
||||||
"""
|
"""
|
||||||
@ -35,6 +35,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2]
|
|||||||
LAUNCHERS = {
|
LAUNCHERS = {
|
||||||
"scripts/serve.sh": REPO_ROOT / "scripts" / "serve.sh",
|
"scripts/serve.sh": REPO_ROOT / "scripts" / "serve.sh",
|
||||||
"docker/dev-entrypoint.sh": REPO_ROOT / "docker" / "dev-entrypoint.sh",
|
"docker/dev-entrypoint.sh": REPO_ROOT / "docker" / "dev-entrypoint.sh",
|
||||||
|
"backend/Makefile": REPO_ROOT / "backend" / "Makefile",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Shell terminators / redirects that end a simple command's argument list.
|
# Shell terminators / redirects that end a simple command's argument list.
|
||||||
@ -161,7 +162,7 @@ def test_sandbox_mkdir_precedes_uvicorn_launch(name):
|
|||||||
"""
|
"""
|
||||||
lines = LAUNCHERS[name].read_text(encoding="utf-8").splitlines()
|
lines = LAUNCHERS[name].read_text(encoding="utf-8").splitlines()
|
||||||
launch_idx = next((i for i, ln in enumerate(lines) if "uv run uvicorn" in ln), None)
|
launch_idx = next((i for i, ln in enumerate(lines) if "uv run uvicorn" in ln), None)
|
||||||
mkdir_idx = next((i for i, ln in enumerate(lines) if re.search(r"\bmkdir\b", ln) and "sandbox" in ln), None)
|
mkdir_idx = next((i for i, ln in enumerate(lines) if re.search(r"\bmkdir\b", ln) and "sandbox" in ln.lower()), None)
|
||||||
|
|
||||||
assert launch_idx is not None, f"{name}: could not locate the 'uv run uvicorn' launch line"
|
assert launch_idx is not None, f"{name}: could not locate the 'uv run uvicorn' launch line"
|
||||||
assert mkdir_idx is not None, f"{name}: could not locate the sandbox mkdir line"
|
assert mkdir_idx is not None, f"{name}: could not locate the sandbox mkdir line"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user