Persist state across restarts and avoid readability js crash loop
Browse files- README.md +13 -0
- backend/checkpointer.py +58 -0
- backend/docs/CONFIGURATION.md +4 -0
- backend/langgraph.json +2 -1
- backend/pyproject.toml +2 -0
- backend/src/utils/readability.py +33 -1
- backend/tests/test_readability.py +22 -0
- backend/uv.lock +79 -0
- start-hf.sh +28 -1
README.md
CHANGED
|
@@ -192,6 +192,19 @@ For dynamic websites, DeerFlow also supports the `agent_browser` tool (based on
|
|
| 192 |
# agent-browser install --with-deps
|
| 193 |
```
|
| 194 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
## From Deep Research to Super Agent Harness
|
| 196 |
|
| 197 |
DeerFlow started as a Deep Research framework — and the community ran with it. Since launch, developers have pushed it far beyond research: building data pipelines, generating slide decks, spinning up dashboards, automating content workflows. Things we never anticipated.
|
|
|
|
| 192 |
# agent-browser install --with-deps
|
| 193 |
```
|
| 194 |
|
| 195 |
+
#### Persistent Storage (Space / External DB)
|
| 196 |
+
|
| 197 |
+
By default, local `langgraph dev` state is in-memory and will be lost after restart.
|
| 198 |
+
|
| 199 |
+
This Space startup now supports persistent state with:
|
| 200 |
+
- `DEER_FLOW_HOME` (defaults to `/data/deer-flow`): stores thread files, artifacts, and memory
|
| 201 |
+
- `DEER_FLOW_EXTENSIONS_CONFIG_PATH`: stores MCP/skills enable-state JSON
|
| 202 |
+
- LangGraph checkpointer:
|
| 203 |
+
- `LANGGRAPH_CHECKPOINT_POSTGRES_URI` (recommended external DB, PostgreSQL)
|
| 204 |
+
- fallback: `LANGGRAPH_CHECKPOINT_SQLITE_PATH` (SQLite file, defaults to `${DEER_FLOW_HOME}/checkpoints.sqlite`)
|
| 205 |
+
|
| 206 |
+
If you enable HF persistent storage, `/data` survives restarts, so memory/skills/thread artifacts and SQLite checkpoints persist.
|
| 207 |
+
|
| 208 |
## From Deep Research to Super Agent Harness
|
| 209 |
|
| 210 |
DeerFlow started as a Deep Research framework — and the community ran with it. Since launch, developers have pushed it far beyond research: building data pipelines, generating slide decks, spinning up dashboards, automating content workflows. Things we never anticipated.
|
backend/checkpointer.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""LangGraph checkpointer bootstrap for persistent thread state.
|
| 2 |
+
|
| 3 |
+
Resolution order:
|
| 4 |
+
1. `LANGGRAPH_CHECKPOINT_POSTGRES_URI` -> Postgres checkpointer
|
| 5 |
+
2. `LANGGRAPH_CHECKPOINT_SQLITE_PATH` -> SQLite checkpointer
|
| 6 |
+
3. Fallback to `${DEER_FLOW_HOME}/checkpoints.sqlite`
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import atexit
|
| 10 |
+
import os
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
from langgraph.checkpoint.postgres import PostgresSaver
|
| 14 |
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
| 15 |
+
|
| 16 |
+
_ctx = None
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def _default_sqlite_path() -> str:
|
| 20 |
+
deer_flow_home = os.getenv("DEER_FLOW_HOME")
|
| 21 |
+
if deer_flow_home:
|
| 22 |
+
base = Path(deer_flow_home)
|
| 23 |
+
else:
|
| 24 |
+
base = Path.cwd() / ".deer-flow"
|
| 25 |
+
base.mkdir(parents=True, exist_ok=True)
|
| 26 |
+
return str((base / "checkpoints.sqlite").resolve())
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _init_checkpointer():
|
| 30 |
+
global _ctx
|
| 31 |
+
|
| 32 |
+
postgres_uri = os.getenv("LANGGRAPH_CHECKPOINT_POSTGRES_URI", "").strip()
|
| 33 |
+
if postgres_uri:
|
| 34 |
+
_ctx = PostgresSaver.from_conn_string(postgres_uri)
|
| 35 |
+
saver = _ctx.__enter__()
|
| 36 |
+
saver.setup()
|
| 37 |
+
return saver
|
| 38 |
+
|
| 39 |
+
sqlite_path = os.getenv("LANGGRAPH_CHECKPOINT_SQLITE_PATH", "").strip() or _default_sqlite_path()
|
| 40 |
+
sqlite_file = Path(sqlite_path).expanduser().resolve()
|
| 41 |
+
sqlite_file.parent.mkdir(parents=True, exist_ok=True)
|
| 42 |
+
_ctx = SqliteSaver.from_conn_string(str(sqlite_file))
|
| 43 |
+
saver = _ctx.__enter__()
|
| 44 |
+
saver.setup()
|
| 45 |
+
return saver
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _close_context() -> None:
|
| 49 |
+
global _ctx
|
| 50 |
+
if _ctx is not None:
|
| 51 |
+
try:
|
| 52 |
+
_ctx.__exit__(None, None, None)
|
| 53 |
+
finally:
|
| 54 |
+
_ctx = None
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
checkpointer = _init_checkpointer()
|
| 58 |
+
atexit.register(_close_context)
|
backend/docs/CONFIGURATION.md
CHANGED
|
@@ -198,6 +198,10 @@ models:
|
|
| 198 |
- `NOVITA_API_KEY` - Novita API key (OpenAI-compatible endpoint)
|
| 199 |
- `TAVILY_API_KEY` - Tavily search API key
|
| 200 |
- `DEER_FLOW_CONFIG_PATH` - Custom config file path
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
## Configuration Location
|
| 203 |
|
|
|
|
| 198 |
- `NOVITA_API_KEY` - Novita API key (OpenAI-compatible endpoint)
|
| 199 |
- `TAVILY_API_KEY` - Tavily search API key
|
| 200 |
- `DEER_FLOW_CONFIG_PATH` - Custom config file path
|
| 201 |
+
- `DEER_FLOW_HOME` - Base directory for memory/thread files (default path root for persistence)
|
| 202 |
+
- `DEER_FLOW_EXTENSIONS_CONFIG_PATH` - Path to `extensions_config.json` (MCP + skills state)
|
| 203 |
+
- `LANGGRAPH_CHECKPOINT_POSTGRES_URI` - PostgreSQL URI for LangGraph checkpointer
|
| 204 |
+
- `LANGGRAPH_CHECKPOINT_SQLITE_PATH` - SQLite file path for LangGraph checkpointer
|
| 205 |
|
| 206 |
## Configuration Location
|
| 207 |
|
backend/langgraph.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
| 4 |
"."
|
| 5 |
],
|
| 6 |
"env": ".env",
|
|
|
|
| 7 |
"graphs": {
|
| 8 |
"lead_agent": "src.agents:make_lead_agent"
|
| 9 |
}
|
| 10 |
-
}
|
|
|
|
| 4 |
"."
|
| 5 |
],
|
| 6 |
"env": ".env",
|
| 7 |
+
"checkpointer": "checkpointer:checkpointer",
|
| 8 |
"graphs": {
|
| 9 |
"lead_agent": "src.agents:make_lead_agent"
|
| 10 |
}
|
| 11 |
+
}
|
backend/pyproject.toml
CHANGED
|
@@ -15,6 +15,8 @@ dependencies = [
|
|
| 15 |
"langchain-mcp-adapters>=0.1.0",
|
| 16 |
"langchain-openai>=1.1.7",
|
| 17 |
"langgraph>=1.0.6",
|
|
|
|
|
|
|
| 18 |
"langgraph-cli[inmem]>=0.4.11",
|
| 19 |
"markdownify>=1.2.2",
|
| 20 |
"markitdown[all,xlsx]>=0.0.1a2",
|
|
|
|
| 15 |
"langchain-mcp-adapters>=0.1.0",
|
| 16 |
"langchain-openai>=1.1.7",
|
| 17 |
"langgraph>=1.0.6",
|
| 18 |
+
"langgraph-checkpoint-postgres>=3.0.4",
|
| 19 |
+
"langgraph-checkpoint-sqlite>=3.0.3",
|
| 20 |
"langgraph-cli[inmem]>=0.4.11",
|
| 21 |
"markdownify>=1.2.2",
|
| 22 |
"markitdown[all,xlsx]>=0.0.1a2",
|
backend/src/utils/readability.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import logging
|
| 2 |
import re
|
| 3 |
import subprocess
|
|
|
|
| 4 |
from urllib.parse import urljoin
|
| 5 |
|
| 6 |
from markdownify import markdownify as md
|
|
@@ -9,6 +10,36 @@ from readabilipy import simple_json_from_html_string
|
|
| 9 |
logger = logging.getLogger(__name__)
|
| 10 |
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
class Article:
|
| 13 |
url: str
|
| 14 |
|
|
@@ -57,8 +88,9 @@ class Article:
|
|
| 57 |
|
| 58 |
class ReadabilityExtractor:
|
| 59 |
def extract_article(self, html: str) -> Article:
|
|
|
|
| 60 |
try:
|
| 61 |
-
article = simple_json_from_html_string(html, use_readability=
|
| 62 |
except (subprocess.CalledProcessError, FileNotFoundError) as exc:
|
| 63 |
stderr = getattr(exc, "stderr", None)
|
| 64 |
if isinstance(stderr, bytes):
|
|
|
|
| 1 |
import logging
|
| 2 |
import re
|
| 3 |
import subprocess
|
| 4 |
+
from functools import lru_cache
|
| 5 |
from urllib.parse import urljoin
|
| 6 |
|
| 7 |
from markdownify import markdownify as md
|
|
|
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
| 12 |
|
| 13 |
+
@lru_cache(maxsize=1)
|
| 14 |
+
def _is_readability_js_available() -> bool:
|
| 15 |
+
"""Check whether Node + @mozilla/readability is available in runtime."""
|
| 16 |
+
try:
|
| 17 |
+
subprocess.run(
|
| 18 |
+
["node", "-e", "require.resolve('@mozilla/readability')"],
|
| 19 |
+
capture_output=True,
|
| 20 |
+
text=True,
|
| 21 |
+
check=True,
|
| 22 |
+
timeout=5,
|
| 23 |
+
)
|
| 24 |
+
return True
|
| 25 |
+
except FileNotFoundError:
|
| 26 |
+
logger.warning("Node.js is not available; readability extraction will use pure-Python mode")
|
| 27 |
+
return False
|
| 28 |
+
except subprocess.TimeoutExpired:
|
| 29 |
+
logger.warning("Node.js readability check timed out; readability extraction will use pure-Python mode")
|
| 30 |
+
return False
|
| 31 |
+
except subprocess.CalledProcessError as exc:
|
| 32 |
+
stderr_info = (exc.stderr or "").strip()
|
| 33 |
+
if stderr_info:
|
| 34 |
+
logger.warning(
|
| 35 |
+
"Node readability module is unavailable (%s); readability extraction will use pure-Python mode",
|
| 36 |
+
stderr_info,
|
| 37 |
+
)
|
| 38 |
+
else:
|
| 39 |
+
logger.warning("Node readability module is unavailable; readability extraction will use pure-Python mode")
|
| 40 |
+
return False
|
| 41 |
+
|
| 42 |
+
|
| 43 |
class Article:
|
| 44 |
url: str
|
| 45 |
|
|
|
|
| 88 |
|
| 89 |
class ReadabilityExtractor:
|
| 90 |
def extract_article(self, html: str) -> Article:
|
| 91 |
+
use_readability_js = _is_readability_js_available()
|
| 92 |
try:
|
| 93 |
+
article = simple_json_from_html_string(html, use_readability=use_readability_js)
|
| 94 |
except (subprocess.CalledProcessError, FileNotFoundError) as exc:
|
| 95 |
stderr = getattr(exc, "stderr", None)
|
| 96 |
if isinstance(stderr, bytes):
|
backend/tests/test_readability.py
CHANGED
|
@@ -11,6 +11,7 @@ def test_extract_article_falls_back_when_readability_js_fails(monkeypatch):
|
|
| 11 |
"""When Node-based readability fails, extraction should fall back to Python mode."""
|
| 12 |
|
| 13 |
calls: list[bool] = []
|
|
|
|
| 14 |
|
| 15 |
def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
|
| 16 |
calls.append(use_readability)
|
|
@@ -38,6 +39,7 @@ def test_extract_article_re_raises_unexpected_exception(monkeypatch):
|
|
| 38 |
"""Unexpected errors should be surfaced instead of silently falling back."""
|
| 39 |
|
| 40 |
calls: list[bool] = []
|
|
|
|
| 41 |
|
| 42 |
def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
|
| 43 |
calls.append(use_readability)
|
|
@@ -53,3 +55,23 @@ def test_extract_article_re_raises_unexpected_exception(monkeypatch):
|
|
| 53 |
with pytest.raises(RuntimeError, match="unexpected parser failure"):
|
| 54 |
ReadabilityExtractor().extract_article("<html><body>test</body></html>")
|
| 55 |
assert calls == [True]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"""When Node-based readability fails, extraction should fall back to Python mode."""
|
| 12 |
|
| 13 |
calls: list[bool] = []
|
| 14 |
+
monkeypatch.setattr("src.utils.readability._is_readability_js_available", lambda: True)
|
| 15 |
|
| 16 |
def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
|
| 17 |
calls.append(use_readability)
|
|
|
|
| 39 |
"""Unexpected errors should be surfaced instead of silently falling back."""
|
| 40 |
|
| 41 |
calls: list[bool] = []
|
| 42 |
+
monkeypatch.setattr("src.utils.readability._is_readability_js_available", lambda: True)
|
| 43 |
|
| 44 |
def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
|
| 45 |
calls.append(use_readability)
|
|
|
|
| 55 |
with pytest.raises(RuntimeError, match="unexpected parser failure"):
|
| 56 |
ReadabilityExtractor().extract_article("<html><body>test</body></html>")
|
| 57 |
assert calls == [True]
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def test_extract_article_uses_python_path_when_js_unavailable(monkeypatch):
|
| 61 |
+
"""If JS readability is unavailable, extractor should directly use Python mode."""
|
| 62 |
+
|
| 63 |
+
calls: list[bool] = []
|
| 64 |
+
monkeypatch.setattr("src.utils.readability._is_readability_js_available", lambda: False)
|
| 65 |
+
|
| 66 |
+
def _fake_simple_json_from_html_string(html: str, use_readability: bool = False):
|
| 67 |
+
calls.append(use_readability)
|
| 68 |
+
return {"title": "Python Mode", "content": "<p>OK</p>"}
|
| 69 |
+
|
| 70 |
+
monkeypatch.setattr(
|
| 71 |
+
"src.utils.readability.simple_json_from_html_string",
|
| 72 |
+
_fake_simple_json_from_html_string,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
article = ReadabilityExtractor().extract_article("<html><body>test</body></html>")
|
| 76 |
+
assert calls == [False]
|
| 77 |
+
assert article.title == "Python Mode"
|
backend/uv.lock
CHANGED
|
@@ -131,6 +131,15 @@ wheels = [
|
|
| 131 |
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
| 132 |
]
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
[[package]]
|
| 135 |
name = "annotated-doc"
|
| 136 |
version = "0.0.4"
|
|
@@ -610,6 +619,8 @@ dependencies = [
|
|
| 610 |
{ name = "langchain-mcp-adapters" },
|
| 611 |
{ name = "langchain-openai" },
|
| 612 |
{ name = "langgraph" },
|
|
|
|
|
|
|
| 613 |
{ name = "langgraph-cli", extra = ["inmem"] },
|
| 614 |
{ name = "markdownify" },
|
| 615 |
{ name = "markitdown", extra = ["all", "xlsx"] },
|
|
@@ -644,6 +655,8 @@ requires-dist = [
|
|
| 644 |
{ name = "langchain-mcp-adapters", specifier = ">=0.1.0" },
|
| 645 |
{ name = "langchain-openai", specifier = ">=1.1.7" },
|
| 646 |
{ name = "langgraph", specifier = ">=1.0.6" },
|
|
|
|
|
|
|
| 647 |
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.11" },
|
| 648 |
{ name = "markdownify", specifier = ">=1.2.2" },
|
| 649 |
{ name = "markitdown", extras = ["all", "xlsx"], specifier = ">=0.0.1a2" },
|
|
@@ -1475,6 +1488,35 @@ wheels = [
|
|
| 1475 |
{ url = "https://files.pythonhosted.org/packages/4a/de/ddd53b7032e623f3c7bcdab2b44e8bf635e468f62e10e5ff1946f62c9356/langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784", size = 46329, upload-time = "2026-01-12T20:30:25.2Z" },
|
| 1476 |
]
|
| 1477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1478 |
[[package]]
|
| 1479 |
name = "langgraph-cli"
|
| 1480 |
version = "0.4.11"
|
|
@@ -2485,6 +2527,31 @@ wheels = [
|
|
| 2485 |
{ url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" },
|
| 2486 |
]
|
| 2487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2488 |
[[package]]
|
| 2489 |
name = "pycparser"
|
| 2490 |
version = "2.23"
|
|
@@ -3100,6 +3167,18 @@ wheels = [
|
|
| 3100 |
{ url = "https://files.pythonhosted.org/packages/b8/a7/903429719d39ac2c42aa37086c90e816d883560f13c87d51f09a2962e021/speechrecognition-3.14.5-py3-none-any.whl", hash = "sha256:0c496d74e9f29b1daadb0d96f5660f47563e42bf09316dacdd57094c5095977e", size = 32856308, upload-time = "2025-12-31T11:25:41.161Z" },
|
| 3101 |
]
|
| 3102 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3103 |
[[package]]
|
| 3104 |
name = "sse-starlette"
|
| 3105 |
version = "2.1.3"
|
|
|
|
| 131 |
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
|
| 132 |
]
|
| 133 |
|
| 134 |
+
[[package]]
|
| 135 |
+
name = "aiosqlite"
|
| 136 |
+
version = "0.22.1"
|
| 137 |
+
source = { registry = "https://pypi.org/simple" }
|
| 138 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" }
|
| 139 |
+
wheels = [
|
| 140 |
+
{ url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" },
|
| 141 |
+
]
|
| 142 |
+
|
| 143 |
[[package]]
|
| 144 |
name = "annotated-doc"
|
| 145 |
version = "0.0.4"
|
|
|
|
| 619 |
{ name = "langchain-mcp-adapters" },
|
| 620 |
{ name = "langchain-openai" },
|
| 621 |
{ name = "langgraph" },
|
| 622 |
+
{ name = "langgraph-checkpoint-postgres" },
|
| 623 |
+
{ name = "langgraph-checkpoint-sqlite" },
|
| 624 |
{ name = "langgraph-cli", extra = ["inmem"] },
|
| 625 |
{ name = "markdownify" },
|
| 626 |
{ name = "markitdown", extra = ["all", "xlsx"] },
|
|
|
|
| 655 |
{ name = "langchain-mcp-adapters", specifier = ">=0.1.0" },
|
| 656 |
{ name = "langchain-openai", specifier = ">=1.1.7" },
|
| 657 |
{ name = "langgraph", specifier = ">=1.0.6" },
|
| 658 |
+
{ name = "langgraph-checkpoint-postgres", specifier = ">=3.0.4" },
|
| 659 |
+
{ name = "langgraph-checkpoint-sqlite", specifier = ">=3.0.3" },
|
| 660 |
{ name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.11" },
|
| 661 |
{ name = "markdownify", specifier = ">=1.2.2" },
|
| 662 |
{ name = "markitdown", extras = ["all", "xlsx"], specifier = ">=0.0.1a2" },
|
|
|
|
| 1488 |
{ url = "https://files.pythonhosted.org/packages/4a/de/ddd53b7032e623f3c7bcdab2b44e8bf635e468f62e10e5ff1946f62c9356/langgraph_checkpoint-4.0.0-py3-none-any.whl", hash = "sha256:3fa9b2635a7c5ac28b338f631abf6a030c3b508b7b9ce17c22611513b589c784", size = 46329, upload-time = "2026-01-12T20:30:25.2Z" },
|
| 1489 |
]
|
| 1490 |
|
| 1491 |
+
[[package]]
|
| 1492 |
+
name = "langgraph-checkpoint-postgres"
|
| 1493 |
+
version = "3.0.4"
|
| 1494 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1495 |
+
dependencies = [
|
| 1496 |
+
{ name = "langgraph-checkpoint" },
|
| 1497 |
+
{ name = "orjson" },
|
| 1498 |
+
{ name = "psycopg" },
|
| 1499 |
+
{ name = "psycopg-pool" },
|
| 1500 |
+
]
|
| 1501 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f5/39/6a409958bd1e4e0804bbe4f9351e620f6087d5346e452c59824298a2a330/langgraph_checkpoint_postgres-3.0.4.tar.gz", hash = "sha256:83e6a1097563369173442de2a66e6d712d60a1a6de07c98c5130d476bb2b76ae", size = 127627, upload-time = "2026-01-31T00:44:16.478Z" }
|
| 1502 |
+
wheels = [
|
| 1503 |
+
{ url = "https://files.pythonhosted.org/packages/14/56/7466f596add278798ab42697a56e992adde6866664afff6a5e4432540f29/langgraph_checkpoint_postgres-3.0.4-py3-none-any.whl", hash = "sha256:12cd5661da2a374882770deb9008a4eb16641c3fd38d7595e312030080390c6e", size = 42834, upload-time = "2026-01-31T00:44:15.118Z" },
|
| 1504 |
+
]
|
| 1505 |
+
|
| 1506 |
+
[[package]]
|
| 1507 |
+
name = "langgraph-checkpoint-sqlite"
|
| 1508 |
+
version = "3.0.3"
|
| 1509 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1510 |
+
dependencies = [
|
| 1511 |
+
{ name = "aiosqlite" },
|
| 1512 |
+
{ name = "langgraph-checkpoint" },
|
| 1513 |
+
{ name = "sqlite-vec" },
|
| 1514 |
+
]
|
| 1515 |
+
sdist = { url = "https://files.pythonhosted.org/packages/04/61/40b7f8f29d6de92406e668c35265f409f57064907e31eae84ab3f2a3e3e1/langgraph_checkpoint_sqlite-3.0.3.tar.gz", hash = "sha256:438c234d37dabda979218954c9c6eb1db73bee6492c2f1d3a00552fe23fa34ed", size = 123876, upload-time = "2026-01-19T00:38:44.473Z" }
|
| 1516 |
+
wheels = [
|
| 1517 |
+
{ url = "https://files.pythonhosted.org/packages/a3/d8/84ef22ee1cc485c4910df450108fd5e246497379522b3c6cfba896f71bf6/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl", hash = "sha256:02eb683a79aa6fcda7cd4de43861062a5d160dbbb990ef8a9fd76c979998a952", size = 33593, upload-time = "2026-01-19T00:38:43.288Z" },
|
| 1518 |
+
]
|
| 1519 |
+
|
| 1520 |
[[package]]
|
| 1521 |
name = "langgraph-cli"
|
| 1522 |
version = "0.4.11"
|
|
|
|
| 2527 |
{ url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" },
|
| 2528 |
]
|
| 2529 |
|
| 2530 |
+
[[package]]
|
| 2531 |
+
name = "psycopg"
|
| 2532 |
+
version = "3.3.3"
|
| 2533 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2534 |
+
dependencies = [
|
| 2535 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 2536 |
+
{ name = "tzdata", marker = "sys_platform == 'win32'" },
|
| 2537 |
+
]
|
| 2538 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" }
|
| 2539 |
+
wheels = [
|
| 2540 |
+
{ url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" },
|
| 2541 |
+
]
|
| 2542 |
+
|
| 2543 |
+
[[package]]
|
| 2544 |
+
name = "psycopg-pool"
|
| 2545 |
+
version = "3.3.0"
|
| 2546 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2547 |
+
dependencies = [
|
| 2548 |
+
{ name = "typing-extensions" },
|
| 2549 |
+
]
|
| 2550 |
+
sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606, upload-time = "2025-12-01T11:34:33.11Z" }
|
| 2551 |
+
wheels = [
|
| 2552 |
+
{ url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995, upload-time = "2025-12-01T11:34:29.761Z" },
|
| 2553 |
+
]
|
| 2554 |
+
|
| 2555 |
[[package]]
|
| 2556 |
name = "pycparser"
|
| 2557 |
version = "2.23"
|
|
|
|
| 3167 |
{ url = "https://files.pythonhosted.org/packages/b8/a7/903429719d39ac2c42aa37086c90e816d883560f13c87d51f09a2962e021/speechrecognition-3.14.5-py3-none-any.whl", hash = "sha256:0c496d74e9f29b1daadb0d96f5660f47563e42bf09316dacdd57094c5095977e", size = 32856308, upload-time = "2025-12-31T11:25:41.161Z" },
|
| 3168 |
]
|
| 3169 |
|
| 3170 |
+
[[package]]
|
| 3171 |
+
name = "sqlite-vec"
|
| 3172 |
+
version = "0.1.6"
|
| 3173 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3174 |
+
wheels = [
|
| 3175 |
+
{ url = "https://files.pythonhosted.org/packages/88/ed/aabc328f29ee6814033d008ec43e44f2c595447d9cccd5f2aabe60df2933/sqlite_vec-0.1.6-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:77491bcaa6d496f2acb5cc0d0ff0b8964434f141523c121e313f9a7d8088dee3", size = 164075, upload-time = "2024-11-20T16:40:29.847Z" },
|
| 3176 |
+
{ url = "https://files.pythonhosted.org/packages/a7/57/05604e509a129b22e303758bfa062c19afb020557d5e19b008c64016704e/sqlite_vec-0.1.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fdca35f7ee3243668a055255d4dee4dea7eed5a06da8cad409f89facf4595361", size = 165242, upload-time = "2024-11-20T16:40:31.206Z" },
|
| 3177 |
+
{ url = "https://files.pythonhosted.org/packages/f2/48/dbb2cc4e5bad88c89c7bb296e2d0a8df58aab9edc75853728c361eefc24f/sqlite_vec-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b0519d9cd96164cd2e08e8eed225197f9cd2f0be82cb04567692a0a4be02da3", size = 103704, upload-time = "2024-11-20T16:40:33.729Z" },
|
| 3178 |
+
{ url = "https://files.pythonhosted.org/packages/80/76/97f33b1a2446f6ae55e59b33869bed4eafaf59b7f4c662c8d9491b6a714a/sqlite_vec-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:823b0493add80d7fe82ab0fe25df7c0703f4752941aee1c7b2b02cec9656cb24", size = 151556, upload-time = "2024-11-20T16:40:35.387Z" },
|
| 3179 |
+
{ url = "https://files.pythonhosted.org/packages/6a/98/e8bc58b178266eae2fcf4c9c7a8303a8d41164d781b32d71097924a6bebe/sqlite_vec-0.1.6-py3-none-win_amd64.whl", hash = "sha256:c65bcfd90fa2f41f9000052bcb8bb75d38240b2dae49225389eca6c3136d3f0c", size = 281540, upload-time = "2024-11-20T16:40:37.296Z" },
|
| 3180 |
+
]
|
| 3181 |
+
|
| 3182 |
[[package]]
|
| 3183 |
name = "sse-starlette"
|
| 3184 |
version = "2.1.3"
|
start-hf.sh
CHANGED
|
@@ -14,7 +14,26 @@ fi
|
|
| 14 |
|
| 15 |
export OPENAI_API_KEY
|
| 16 |
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
touch /app/.env /app/backend/.env /app/frontend/.env
|
| 19 |
|
| 20 |
if [ -n "${SPACE_PASSWORD}" ]; then
|
|
@@ -86,6 +105,14 @@ tools:
|
|
| 86 |
group: bash
|
| 87 |
use: src.sandbox.tools:bash_tool
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
sandbox:
|
| 90 |
use: src.sandbox.local:LocalSandboxProvider
|
| 91 |
EOF
|
|
|
|
| 14 |
|
| 15 |
export OPENAI_API_KEY
|
| 16 |
|
| 17 |
+
PERSIST_ROOT="${DEER_FLOW_HOME:-/data/deer-flow}"
|
| 18 |
+
if ! mkdir -p "${PERSIST_ROOT}" 2>/dev/null; then
|
| 19 |
+
PERSIST_ROOT="/app/backend/.deer-flow"
|
| 20 |
+
mkdir -p "${PERSIST_ROOT}"
|
| 21 |
+
fi
|
| 22 |
+
|
| 23 |
+
export DEER_FLOW_HOME="${PERSIST_ROOT}"
|
| 24 |
+
export DEER_FLOW_EXTENSIONS_CONFIG_PATH="${DEER_FLOW_EXTENSIONS_CONFIG_PATH:-${DEER_FLOW_HOME}/extensions_config.json}"
|
| 25 |
+
export LANGGRAPH_CHECKPOINT_SQLITE_PATH="${LANGGRAPH_CHECKPOINT_SQLITE_PATH:-${DEER_FLOW_HOME}/checkpoints.sqlite}"
|
| 26 |
+
export DEER_FLOW_MEMORY_PATH="${DEER_FLOW_MEMORY_PATH:-${DEER_FLOW_HOME}/memory.json}"
|
| 27 |
+
export DEER_FLOW_SKILLS_PATH="${DEER_FLOW_SKILLS_PATH:-${DEER_FLOW_HOME}/skills}"
|
| 28 |
+
|
| 29 |
+
mkdir -p /app/logs "${DEER_FLOW_HOME}"
|
| 30 |
+
if [ ! -f "${DEER_FLOW_EXTENSIONS_CONFIG_PATH}" ]; then
|
| 31 |
+
cp /app/extensions_config.example.json "${DEER_FLOW_EXTENSIONS_CONFIG_PATH}"
|
| 32 |
+
fi
|
| 33 |
+
if [ ! -d "${DEER_FLOW_SKILLS_PATH}" ]; then
|
| 34 |
+
cp -R /app/skills "${DEER_FLOW_SKILLS_PATH}"
|
| 35 |
+
fi
|
| 36 |
+
|
| 37 |
touch /app/.env /app/backend/.env /app/frontend/.env
|
| 38 |
|
| 39 |
if [ -n "${SPACE_PASSWORD}" ]; then
|
|
|
|
| 105 |
group: bash
|
| 106 |
use: src.sandbox.tools:bash_tool
|
| 107 |
|
| 108 |
+
skills:
|
| 109 |
+
path: ${DEER_FLOW_SKILLS_PATH}
|
| 110 |
+
container_path: /mnt/skills
|
| 111 |
+
|
| 112 |
+
memory:
|
| 113 |
+
enabled: true
|
| 114 |
+
storage_path: ${DEER_FLOW_MEMORY_PATH}
|
| 115 |
+
|
| 116 |
sandbox:
|
| 117 |
use: src.sandbox.local:LocalSandboxProvider
|
| 118 |
EOF
|