Deploy DeerFlow to Hugging Face Space
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +69 -0
- .env.example +13 -0
- .gitattributes +61 -35
- .github/workflows/backend-unit-tests.yml +39 -0
- .gitignore +49 -0
- CONTRIBUTING.md +270 -0
- Dockerfile +34 -0
- LICENSE +22 -0
- Makefile +267 -0
- README.md +316 -8
- SECURITY.md +12 -0
- backend/.gitignore +28 -0
- backend/.python-version +1 -0
- backend/.vscode/extensions.json +3 -0
- backend/.vscode/settings.json +11 -0
- backend/AGENTS.md +2 -0
- backend/CLAUDE.md +441 -0
- backend/CONTRIBUTING.md +426 -0
- backend/Dockerfile +28 -0
- backend/Makefile +17 -0
- backend/README.md +355 -0
- backend/debug.py +92 -0
- backend/docs/API.md +607 -0
- backend/docs/APPLE_CONTAINER.md +238 -0
- backend/docs/ARCHITECTURE.md +464 -0
- backend/docs/AUTO_TITLE_GENERATION.md +256 -0
- backend/docs/CONFIGURATION.md +238 -0
- backend/docs/FILE_UPLOAD.md +293 -0
- backend/docs/MCP_SERVER.md +65 -0
- backend/docs/MEMORY_IMPROVEMENTS.md +281 -0
- backend/docs/MEMORY_IMPROVEMENTS_SUMMARY.md +260 -0
- backend/docs/PATH_EXAMPLES.md +289 -0
- backend/docs/README.md +53 -0
- backend/docs/SETUP.md +92 -0
- backend/docs/TITLE_GENERATION_IMPLEMENTATION.md +222 -0
- backend/docs/TODO.md +27 -0
- backend/docs/plan_mode_usage.md +204 -0
- backend/docs/summarization.md +353 -0
- backend/docs/task_tool_improvements.md +174 -0
- backend/langgraph.json +10 -0
- backend/pyproject.toml +35 -0
- backend/ruff.toml +10 -0
- backend/src/__init__.py +0 -0
- backend/src/agents/__init__.py +4 -0
- backend/src/agents/lead_agent/__init__.py +3 -0
- backend/src/agents/lead_agent/agent.py +303 -0
- backend/src/agents/lead_agent/prompt.py +391 -0
- backend/src/agents/memory/__init__.py +44 -0
- backend/src/agents/memory/prompt.py +261 -0
- backend/src/agents/memory/queue.py +191 -0
.dockerignore
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.dockerignore
|
| 3 |
+
.git
|
| 4 |
+
.gitignore
|
| 5 |
+
docker/
|
| 6 |
+
|
| 7 |
+
# Python
|
| 8 |
+
__pycache__/
|
| 9 |
+
*.py[cod]
|
| 10 |
+
*$py.class
|
| 11 |
+
*.so
|
| 12 |
+
.Python
|
| 13 |
+
env/
|
| 14 |
+
build/
|
| 15 |
+
develop-eggs/
|
| 16 |
+
dist/
|
| 17 |
+
downloads/
|
| 18 |
+
eggs/
|
| 19 |
+
.eggs/
|
| 20 |
+
lib/
|
| 21 |
+
lib64/
|
| 22 |
+
parts/
|
| 23 |
+
sdist/
|
| 24 |
+
var/
|
| 25 |
+
wheels/
|
| 26 |
+
*.egg-info/
|
| 27 |
+
.installed.cfg
|
| 28 |
+
*.egg
|
| 29 |
+
.venv/
|
| 30 |
+
|
| 31 |
+
# Web
|
| 32 |
+
node_modules
|
| 33 |
+
npm-debug.log
|
| 34 |
+
.next
|
| 35 |
+
|
| 36 |
+
# IDE
|
| 37 |
+
.idea/
|
| 38 |
+
.vscode/
|
| 39 |
+
*.swp
|
| 40 |
+
*.swo
|
| 41 |
+
|
| 42 |
+
# OS
|
| 43 |
+
.DS_Store
|
| 44 |
+
Thumbs.db
|
| 45 |
+
|
| 46 |
+
# Project specific
|
| 47 |
+
conf.yaml
|
| 48 |
+
web/
|
| 49 |
+
docs/
|
| 50 |
+
examples/
|
| 51 |
+
assets/
|
| 52 |
+
tests/
|
| 53 |
+
*.log
|
| 54 |
+
|
| 55 |
+
# Exclude directories not needed in Docker context
|
| 56 |
+
# Frontend build only needs frontend/
|
| 57 |
+
# Backend build only needs backend/
|
| 58 |
+
scripts/
|
| 59 |
+
logs/
|
| 60 |
+
docker/
|
| 61 |
+
frontend/.next
|
| 62 |
+
frontend/node_modules
|
| 63 |
+
backend/.venv
|
| 64 |
+
backend/htmlcov
|
| 65 |
+
backend/.coverage
|
| 66 |
+
*.md
|
| 67 |
+
!README.md
|
| 68 |
+
!frontend/README.md
|
| 69 |
+
!backend/README.md
|
.env.example
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TAVILY API Key
|
| 2 |
+
TAVILY_API_KEY=your-tavily-api-key
|
| 3 |
+
|
| 4 |
+
# Jina API Key
|
| 5 |
+
JINA_API_KEY=your-jina-api-key
|
| 6 |
+
|
| 7 |
+
# Optional:
|
| 8 |
+
# FIRECRAWL_API_KEY=your-firecrawl-api-key
|
| 9 |
+
# VOLCENGINE_API_KEY=your-volcengine-api-key
|
| 10 |
+
# OPENAI_API_KEY=your-openai-api-key
|
| 11 |
+
# GEMINI_API_KEY=your-gemini-api-key
|
| 12 |
+
# DEEPSEEK_API_KEY=your-deepseek-api-key
|
| 13 |
+
# NOVITA_API_KEY=your-novita-api-key # OpenAI-compatible, see https://novita.ai
|
.gitattributes
CHANGED
|
@@ -1,35 +1,61 @@
|
|
| 1 |
-
|
| 2 |
-
*
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
*.
|
| 6 |
-
|
| 7 |
-
*
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
*.
|
| 11 |
-
*.
|
| 12 |
-
*.
|
| 13 |
-
*.
|
| 14 |
-
*.
|
| 15 |
-
*.
|
| 16 |
-
*.
|
| 17 |
-
*.
|
| 18 |
-
*.
|
| 19 |
-
*.
|
| 20 |
-
*.
|
| 21 |
-
*.
|
| 22 |
-
*.
|
| 23 |
-
*.
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
*.
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
*.
|
| 31 |
-
*.
|
| 32 |
-
*.
|
| 33 |
-
*.
|
| 34 |
-
*.
|
| 35 |
-
*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Normalize line endings to LF for all text files
|
| 2 |
+
* text=auto eol=lf
|
| 3 |
+
|
| 4 |
+
# Shell scripts and makefiles must always use LF
|
| 5 |
+
*.sh text eol=lf
|
| 6 |
+
Makefile text eol=lf
|
| 7 |
+
**/Makefile text eol=lf
|
| 8 |
+
|
| 9 |
+
# Common config/source files
|
| 10 |
+
*.yml text eol=lf
|
| 11 |
+
*.yaml text eol=lf
|
| 12 |
+
*.toml text eol=lf
|
| 13 |
+
*.json text eol=lf
|
| 14 |
+
*.md text eol=lf
|
| 15 |
+
*.py text eol=lf
|
| 16 |
+
*.ts text eol=lf
|
| 17 |
+
*.tsx text eol=lf
|
| 18 |
+
*.js text eol=lf
|
| 19 |
+
*.jsx text eol=lf
|
| 20 |
+
*.css text eol=lf
|
| 21 |
+
*.scss text eol=lf
|
| 22 |
+
*.html text eol=lf
|
| 23 |
+
*.env text eol=lf
|
| 24 |
+
|
| 25 |
+
# Windows scripts
|
| 26 |
+
*.bat text eol=crlf
|
| 27 |
+
*.cmd text eol=crlf
|
| 28 |
+
|
| 29 |
+
# Binary assets
|
| 30 |
+
*.png binary
|
| 31 |
+
*.jpg binary
|
| 32 |
+
*.jpeg binary
|
| 33 |
+
*.gif binary
|
| 34 |
+
*.webp binary
|
| 35 |
+
*.ico binary
|
| 36 |
+
*.pdf binary
|
| 37 |
+
*.zip binary
|
| 38 |
+
*.tar binary
|
| 39 |
+
*.gz binary
|
| 40 |
+
*.mp4 binary
|
| 41 |
+
*.mov binary
|
| 42 |
+
*.woff binary
|
| 43 |
+
*.woff2 binary
|
| 44 |
+
frontend/public/demo/threads/21cfea46-34bd-4aa6-9e1f-3009452fbeb9/user-data/outputs/doraemon-moe-comic.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
frontend/public/demo/threads/4f3e55ee-f853-43db-bfb3-7d1a411f03cb/user-data/outputs/darcy-proposal-reference.jpg filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
frontend/public/demo/threads/4f3e55ee-f853-43db-bfb3-7d1a411f03cb/user-data/outputs/darcy-proposal-video.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-nyc-candid.jpg filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-paris-decisive-moment.jpg filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
frontend/public/demo/threads/7f9dc56c-e49c-4671-a3d2-c492ff4dce0c/user-data/outputs/leica-tokyo-night.jpg filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
frontend/public/demo/threads/90040b36-7eba-4b97-ba89-02c3ad47a8b9/user-data/outputs/american-woman-newyork.jpg filter=lfs diff=lfs merge=lfs -text
|
| 51 |
+
frontend/public/demo/threads/90040b36-7eba-4b97-ba89-02c3ad47a8b9/user-data/outputs/american-woman-shanghai.jpg filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
frontend/public/demo/threads/b83fbb2a-4e36-4d82-9de0-7b2a02c2092a/user-data/outputs/caren-hero.jpg filter=lfs diff=lfs merge=lfs -text
|
| 53 |
+
frontend/public/demo/threads/b83fbb2a-4e36-4d82-9de0-7b2a02c2092a/user-data/outputs/caren-ingredients.jpg filter=lfs diff=lfs merge=lfs -text
|
| 54 |
+
frontend/public/demo/threads/b83fbb2a-4e36-4d82-9de0-7b2a02c2092a/user-data/outputs/caren-lifestyle.jpg filter=lfs diff=lfs merge=lfs -text
|
| 55 |
+
frontend/public/demo/threads/b83fbb2a-4e36-4d82-9de0-7b2a02c2092a/user-data/outputs/caren-products.jpg filter=lfs diff=lfs merge=lfs -text
|
| 56 |
+
frontend/public/images/21cfea46-34bd-4aa6-9e1f-3009452fbeb9.jpg filter=lfs diff=lfs merge=lfs -text
|
| 57 |
+
frontend/public/images/3823e443-4e2b-4679-b496-a9506eae462b.jpg filter=lfs diff=lfs merge=lfs -text
|
| 58 |
+
frontend/public/images/4f3e55ee-f853-43db-bfb3-7d1a411f03cb.jpg filter=lfs diff=lfs merge=lfs -text
|
| 59 |
+
frontend/public/images/7cfa5f8f-a2f8-47ad-acbd-da7137baf990.jpg filter=lfs diff=lfs merge=lfs -text
|
| 60 |
+
frontend/public/images/ad76c455-5bf9-4335-8517-fc03834ab828.jpg filter=lfs diff=lfs merge=lfs -text
|
| 61 |
+
frontend/public/images/d3e5adaf-084c-4dd5-9d29-94f1d6bccd98.jpg filter=lfs diff=lfs merge=lfs -text
|
.github/workflows/backend-unit-tests.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Unit Tests
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
pull_request:
|
| 5 |
+
types: [opened, synchronize, reopened, ready_for_review]
|
| 6 |
+
|
| 7 |
+
concurrency:
|
| 8 |
+
group: unit-tests-${{ github.event.pull_request.number || github.ref }}
|
| 9 |
+
cancel-in-progress: true
|
| 10 |
+
|
| 11 |
+
jobs:
|
| 12 |
+
backend-unit-tests:
|
| 13 |
+
if: github.event.pull_request.draft == false
|
| 14 |
+
runs-on: ubuntu-latest
|
| 15 |
+
timeout-minutes: 15
|
| 16 |
+
|
| 17 |
+
steps:
|
| 18 |
+
- name: Checkout
|
| 19 |
+
uses: actions/checkout@v6
|
| 20 |
+
|
| 21 |
+
- name: Set up Python
|
| 22 |
+
uses: actions/setup-python@v6
|
| 23 |
+
with:
|
| 24 |
+
python-version: '3.12'
|
| 25 |
+
|
| 26 |
+
- name: Install uv
|
| 27 |
+
uses: astral-sh/setup-uv@v7
|
| 28 |
+
|
| 29 |
+
- name: Install backend dependencies
|
| 30 |
+
working-directory: backend
|
| 31 |
+
run: uv sync --group dev
|
| 32 |
+
|
| 33 |
+
- name: Lint backend
|
| 34 |
+
working-directory: backend
|
| 35 |
+
run: make lint
|
| 36 |
+
|
| 37 |
+
- name: Run unit tests of backend
|
| 38 |
+
working-directory: backend
|
| 39 |
+
run: make test
|
.gitignore
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DeerFlow docker image cache
|
| 2 |
+
docker/.cache/
|
| 3 |
+
# OS generated files
|
| 4 |
+
.DS_Store
|
| 5 |
+
*.local
|
| 6 |
+
._*
|
| 7 |
+
.Spotlight-V100
|
| 8 |
+
.Trashes
|
| 9 |
+
ehthumbs.db
|
| 10 |
+
Thumbs.db
|
| 11 |
+
|
| 12 |
+
# Python cache
|
| 13 |
+
__pycache__/
|
| 14 |
+
*.pyc
|
| 15 |
+
*.pyo
|
| 16 |
+
|
| 17 |
+
# Virtual environments
|
| 18 |
+
.venv
|
| 19 |
+
venv/
|
| 20 |
+
|
| 21 |
+
# Environment variables
|
| 22 |
+
.env
|
| 23 |
+
|
| 24 |
+
# Configuration files
|
| 25 |
+
config.yaml
|
| 26 |
+
mcp_config.json
|
| 27 |
+
extensions_config.json
|
| 28 |
+
|
| 29 |
+
# IDE
|
| 30 |
+
.idea/
|
| 31 |
+
|
| 32 |
+
# Coverage report
|
| 33 |
+
coverage.xml
|
| 34 |
+
coverage/
|
| 35 |
+
.deer-flow/
|
| 36 |
+
.claude/
|
| 37 |
+
skills/custom/*
|
| 38 |
+
logs/
|
| 39 |
+
log/
|
| 40 |
+
|
| 41 |
+
# Local git hooks (keep only on this machine, do not push)
|
| 42 |
+
.githooks/
|
| 43 |
+
|
| 44 |
+
# pnpm
|
| 45 |
+
.pnpm-store
|
| 46 |
+
sandbox_image_cache.tar
|
| 47 |
+
|
| 48 |
+
# ignore the legacy `web` folder
|
| 49 |
+
web/
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to DeerFlow
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing to DeerFlow! This guide will help you set up your development environment and understand our development workflow.
|
| 4 |
+
|
| 5 |
+
## Development Environment Setup
|
| 6 |
+
|
| 7 |
+
We offer two development environments. **Docker is recommended** for the most consistent and hassle-free experience.
|
| 8 |
+
|
| 9 |
+
### Option 1: Docker Development (Recommended)
|
| 10 |
+
|
| 11 |
+
Docker provides a consistent, isolated environment with all dependencies pre-configured. No need to install Node.js, Python, or nginx on your local machine.
|
| 12 |
+
|
| 13 |
+
#### Prerequisites
|
| 14 |
+
|
| 15 |
+
- Docker Desktop or Docker Engine
|
| 16 |
+
- pnpm (for caching optimization)
|
| 17 |
+
|
| 18 |
+
#### Setup Steps
|
| 19 |
+
|
| 20 |
+
1. **Configure the application**:
|
| 21 |
+
```bash
|
| 22 |
+
# Copy example configuration
|
| 23 |
+
cp config.example.yaml config.yaml
|
| 24 |
+
|
| 25 |
+
# Set your API keys
|
| 26 |
+
export OPENAI_API_KEY="your-key-here"
|
| 27 |
+
# or edit config.yaml directly
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
2. **Initialize Docker environment** (first time only):
|
| 31 |
+
```bash
|
| 32 |
+
make docker-init
|
| 33 |
+
```
|
| 34 |
+
This will:
|
| 35 |
+
- Build Docker images
|
| 36 |
+
- Install frontend dependencies (pnpm)
|
| 37 |
+
- Install backend dependencies (uv)
|
| 38 |
+
- Share pnpm cache with host for faster builds
|
| 39 |
+
|
| 40 |
+
3. **Start development services**:
|
| 41 |
+
```bash
|
| 42 |
+
make docker-start
|
| 43 |
+
```
|
| 44 |
+
`make docker-start` reads `config.yaml` and starts `provisioner` only for provisioner/Kubernetes sandbox mode.
|
| 45 |
+
|
| 46 |
+
All services will start with hot-reload enabled:
|
| 47 |
+
- Frontend changes are automatically reloaded
|
| 48 |
+
- Backend changes trigger automatic restart
|
| 49 |
+
- LangGraph server supports hot-reload
|
| 50 |
+
|
| 51 |
+
4. **Access the application**:
|
| 52 |
+
- Web Interface: http://localhost:2026
|
| 53 |
+
- API Gateway: http://localhost:2026/api/*
|
| 54 |
+
- LangGraph: http://localhost:2026/api/langgraph/*
|
| 55 |
+
|
| 56 |
+
#### Docker Commands
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
# Build the custom k3s image (with pre-cached sandbox image)
|
| 60 |
+
make docker-init
|
| 61 |
+
# Start Docker services (mode-aware, localhost:2026)
|
| 62 |
+
make docker-start
|
| 63 |
+
# Stop Docker development services
|
| 64 |
+
make docker-stop
|
| 65 |
+
# View Docker development logs
|
| 66 |
+
make docker-logs
|
| 67 |
+
# View Docker frontend logs
|
| 68 |
+
make docker-logs-frontend
|
| 69 |
+
# View Docker gateway logs
|
| 70 |
+
make docker-logs-gateway
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
#### Docker Architecture
|
| 74 |
+
|
| 75 |
+
```
|
| 76 |
+
Host Machine
|
| 77 |
+
↓
|
| 78 |
+
Docker Compose (deer-flow-dev)
|
| 79 |
+
├→ nginx (port 2026) ← Reverse proxy
|
| 80 |
+
├→ web (port 3000) ← Frontend with hot-reload
|
| 81 |
+
├→ api (port 8001) ← Gateway API with hot-reload
|
| 82 |
+
├→ langgraph (port 2024) ← LangGraph server with hot-reload
|
| 83 |
+
└→ provisioner (optional, port 8002) ← Started only in provisioner/K8s sandbox mode
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
**Benefits of Docker Development**:
|
| 87 |
+
- ✅ Consistent environment across different machines
|
| 88 |
+
- ✅ No need to install Node.js, Python, or nginx locally
|
| 89 |
+
- ✅ Isolated dependencies and services
|
| 90 |
+
- ✅ Easy cleanup and reset
|
| 91 |
+
- ✅ Hot-reload for all services
|
| 92 |
+
- ✅ Production-like environment
|
| 93 |
+
|
| 94 |
+
### Option 2: Local Development
|
| 95 |
+
|
| 96 |
+
If you prefer to run services directly on your machine:
|
| 97 |
+
|
| 98 |
+
#### Prerequisites
|
| 99 |
+
|
| 100 |
+
Check that you have all required tools installed:
|
| 101 |
+
|
| 102 |
+
```bash
|
| 103 |
+
make check
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
Required tools:
|
| 107 |
+
- Node.js 22+
|
| 108 |
+
- pnpm
|
| 109 |
+
- uv (Python package manager)
|
| 110 |
+
- nginx
|
| 111 |
+
|
| 112 |
+
#### Setup Steps
|
| 113 |
+
|
| 114 |
+
1. **Configure the application** (same as Docker setup above)
|
| 115 |
+
|
| 116 |
+
2. **Install dependencies**:
|
| 117 |
+
```bash
|
| 118 |
+
make install
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
3. **Run development server** (starts all services with nginx):
|
| 122 |
+
```bash
|
| 123 |
+
make dev
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
4. **Access the application**:
|
| 127 |
+
- Web Interface: http://localhost:2026
|
| 128 |
+
- All API requests are automatically proxied through nginx
|
| 129 |
+
|
| 130 |
+
#### Manual Service Control
|
| 131 |
+
|
| 132 |
+
If you need to start services individually:
|
| 133 |
+
|
| 134 |
+
1. **Start backend services**:
|
| 135 |
+
```bash
|
| 136 |
+
# Terminal 1: Start LangGraph Server (port 2024)
|
| 137 |
+
cd backend
|
| 138 |
+
make dev
|
| 139 |
+
|
| 140 |
+
# Terminal 2: Start Gateway API (port 8001)
|
| 141 |
+
cd backend
|
| 142 |
+
make gateway
|
| 143 |
+
|
| 144 |
+
# Terminal 3: Start Frontend (port 3000)
|
| 145 |
+
cd frontend
|
| 146 |
+
pnpm dev
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
2. **Start nginx**:
|
| 150 |
+
```bash
|
| 151 |
+
make nginx
|
| 152 |
+
# or directly: nginx -c $(pwd)/docker/nginx/nginx.local.conf -g 'daemon off;'
|
| 153 |
+
```
|
| 154 |
+
|
| 155 |
+
3. **Access the application**:
|
| 156 |
+
- Web Interface: http://localhost:2026
|
| 157 |
+
|
| 158 |
+
#### Nginx Configuration
|
| 159 |
+
|
| 160 |
+
The nginx configuration provides:
|
| 161 |
+
- Unified entry point on port 2026
|
| 162 |
+
- Routes `/api/langgraph/*` to LangGraph Server (2024)
|
| 163 |
+
- Routes other `/api/*` endpoints to Gateway API (8001)
|
| 164 |
+
- Routes non-API requests to Frontend (3000)
|
| 165 |
+
- Centralized CORS handling
|
| 166 |
+
- SSE/streaming support for real-time agent responses
|
| 167 |
+
- Optimized timeouts for long-running operations
|
| 168 |
+
|
| 169 |
+
## Project Structure
|
| 170 |
+
|
| 171 |
+
```
|
| 172 |
+
deer-flow/
|
| 173 |
+
├── config.example.yaml # Configuration template
|
| 174 |
+
├── extensions_config.example.json # MCP and Skills configuration template
|
| 175 |
+
├── Makefile # Build and development commands
|
| 176 |
+
├── scripts/
|
| 177 |
+
│ └── docker.sh # Docker management script
|
| 178 |
+
├── docker/
|
| 179 |
+
│ ├── docker-compose-dev.yaml # Docker Compose configuration
|
| 180 |
+
│ └── nginx/
|
| 181 |
+
│ ├── nginx.conf # Nginx config for Docker
|
| 182 |
+
│ └── nginx.local.conf # Nginx config for local dev
|
| 183 |
+
├── backend/ # Backend application
|
| 184 |
+
│ ├── src/
|
| 185 |
+
│ │ ├── gateway/ # Gateway API (port 8001)
|
| 186 |
+
│ │ ├── agents/ # LangGraph agents (port 2024)
|
| 187 |
+
│ │ ├── mcp/ # Model Context Protocol integration
|
| 188 |
+
│ │ ├── skills/ # Skills system
|
| 189 |
+
│ │ └── sandbox/ # Sandbox execution
|
| 190 |
+
│ ├── docs/ # Backend documentation
|
| 191 |
+
│ └── Makefile # Backend commands
|
| 192 |
+
├── frontend/ # Frontend application
|
| 193 |
+
│ └── Makefile # Frontend commands
|
| 194 |
+
└── skills/ # Agent skills
|
| 195 |
+
├── public/ # Public skills
|
| 196 |
+
└── custom/ # Custom skills
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
## Architecture
|
| 200 |
+
|
| 201 |
+
```
|
| 202 |
+
Browser
|
| 203 |
+
↓
|
| 204 |
+
Nginx (port 2026) ← Unified entry point
|
| 205 |
+
├→ Frontend (port 3000) ← / (non-API requests)
|
| 206 |
+
├→ Gateway API (port 8001) ← /api/models, /api/mcp, /api/skills, /api/threads/*/artifacts
|
| 207 |
+
└→ LangGraph Server (port 2024) ← /api/langgraph/* (agent interactions)
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
## Development Workflow
|
| 211 |
+
|
| 212 |
+
1. **Create a feature branch**:
|
| 213 |
+
```bash
|
| 214 |
+
git checkout -b feature/your-feature-name
|
| 215 |
+
```
|
| 216 |
+
|
| 217 |
+
2. **Make your changes** with hot-reload enabled
|
| 218 |
+
|
| 219 |
+
3. **Test your changes** thoroughly
|
| 220 |
+
|
| 221 |
+
4. **Commit your changes**:
|
| 222 |
+
```bash
|
| 223 |
+
git add .
|
| 224 |
+
git commit -m "feat: description of your changes"
|
| 225 |
+
```
|
| 226 |
+
|
| 227 |
+
5. **Push and create a Pull Request**:
|
| 228 |
+
```bash
|
| 229 |
+
git push origin feature/your-feature-name
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
## Testing
|
| 233 |
+
|
| 234 |
+
```bash
|
| 235 |
+
# Backend tests
|
| 236 |
+
cd backend
|
| 237 |
+
uv run pytest
|
| 238 |
+
|
| 239 |
+
# Frontend tests
|
| 240 |
+
cd frontend
|
| 241 |
+
pnpm test
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
### PR Regression Checks
|
| 245 |
+
|
| 246 |
+
Every pull request runs the backend regression workflow at [.github/workflows/backend-unit-tests.yml](.github/workflows/backend-unit-tests.yml), including:
|
| 247 |
+
|
| 248 |
+
- `tests/test_provisioner_kubeconfig.py`
|
| 249 |
+
- `tests/test_docker_sandbox_mode_detection.py`
|
| 250 |
+
|
| 251 |
+
## Code Style
|
| 252 |
+
|
| 253 |
+
- **Backend (Python)**: We use `ruff` for linting and formatting
|
| 254 |
+
- **Frontend (TypeScript)**: We use ESLint and Prettier
|
| 255 |
+
|
| 256 |
+
## Documentation
|
| 257 |
+
|
| 258 |
+
- [Configuration Guide](backend/docs/CONFIGURATION.md) - Setup and configuration
|
| 259 |
+
- [Architecture Overview](backend/CLAUDE.md) - Technical architecture
|
| 260 |
+
- [MCP Setup Guide](MCP_SETUP.md) - Model Context Protocol configuration
|
| 261 |
+
|
| 262 |
+
## Need Help?
|
| 263 |
+
|
| 264 |
+
- Check existing [Issues](https://github.com/bytedance/deer-flow/issues)
|
| 265 |
+
- Read the [Documentation](backend/docs/)
|
| 266 |
+
- Ask questions in [Discussions](https://github.com/bytedance/deer-flow/discussions)
|
| 267 |
+
|
| 268 |
+
## License
|
| 269 |
+
|
| 270 |
+
By contributing to DeerFlow, you agree that your contributions will be licensed under the [MIT License](./LICENSE).
|
Dockerfile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
|
| 3 |
+
ENV DEBIAN_FRONTEND=noninteractive \
|
| 4 |
+
PATH="/root/.local/bin:${PATH}" \
|
| 5 |
+
NODE_ENV=production \
|
| 6 |
+
SKIP_ENV_VALIDATION=1 \
|
| 7 |
+
NEXT_TELEMETRY_DISABLED=1 \
|
| 8 |
+
PORT=7860
|
| 9 |
+
|
| 10 |
+
RUN apt-get update && \
|
| 11 |
+
apt-get install -y --no-install-recommends curl ca-certificates gnupg nginx build-essential git && \
|
| 12 |
+
mkdir -p /etc/apt/keyrings && \
|
| 13 |
+
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg && \
|
| 14 |
+
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" > /etc/apt/sources.list.d/nodesource.list && \
|
| 15 |
+
apt-get update && \
|
| 16 |
+
apt-get install -y --no-install-recommends nodejs && \
|
| 17 |
+
npm install -g corepack && \
|
| 18 |
+
corepack enable && \
|
| 19 |
+
corepack prepare pnpm@10.26.2 --activate && \
|
| 20 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
| 21 |
+
apt-get clean && \
|
| 22 |
+
rm -rf /var/lib/apt/lists/*
|
| 23 |
+
|
| 24 |
+
WORKDIR /app
|
| 25 |
+
COPY . /app
|
| 26 |
+
|
| 27 |
+
RUN cd /app/backend && uv sync --frozen && \
|
| 28 |
+
cd /app/frontend && pnpm install --frozen-lockfile && pnpm build && \
|
| 29 |
+
chmod +x /app/start-hf.sh && \
|
| 30 |
+
mkdir -p /app/logs /app/backend/.deer-flow/threads /app/backend/.deer-flow/artifacts
|
| 31 |
+
|
| 32 |
+
EXPOSE 7860
|
| 33 |
+
|
| 34 |
+
CMD ["/app/start-hf.sh"]
|
LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
|
| 4 |
+
Copyright (c) 2025-2026 DeerFlow Authors
|
| 5 |
+
|
| 6 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 7 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 8 |
+
in the Software without restriction, including without limitation the rights
|
| 9 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 10 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 11 |
+
furnished to do so, subject to the following conditions:
|
| 12 |
+
|
| 13 |
+
The above copyright notice and this permission notice shall be included in all
|
| 14 |
+
copies or substantial portions of the Software.
|
| 15 |
+
|
| 16 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 17 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 18 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 19 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 20 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 21 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 22 |
+
SOFTWARE.
|
Makefile
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DeerFlow - Unified Development Environment
|
| 2 |
+
|
| 3 |
+
.PHONY: help config check install dev stop clean docker-init docker-start docker-stop docker-logs docker-logs-frontend docker-logs-gateway
|
| 4 |
+
|
| 5 |
+
help:
|
| 6 |
+
@echo "DeerFlow Development Commands:"
|
| 7 |
+
@echo " make check - Check if all required tools are installed"
|
| 8 |
+
@echo " make install - Install all dependencies (frontend + backend)"
|
| 9 |
+
@echo " make setup-sandbox - Pre-pull sandbox container image (recommended)"
|
| 10 |
+
@echo " make dev - Start all services (frontend + backend + nginx on localhost:2026)"
|
| 11 |
+
@echo " make stop - Stop all running services"
|
| 12 |
+
@echo " make clean - Clean up processes and temporary files"
|
| 13 |
+
@echo ""
|
| 14 |
+
@echo "Docker Development Commands:"
|
| 15 |
+
@echo " make docker-init - Build the custom k3s image (with pre-cached sandbox image)"
|
| 16 |
+
@echo " make docker-start - Start Docker services (mode-aware from config.yaml, localhost:2026)"
|
| 17 |
+
@echo " make docker-stop - Stop Docker development services"
|
| 18 |
+
@echo " make docker-logs - View Docker development logs"
|
| 19 |
+
@echo " make docker-logs-frontend - View Docker frontend logs"
|
| 20 |
+
@echo " make docker-logs-gateway - View Docker gateway logs"
|
| 21 |
+
|
| 22 |
+
config:
|
| 23 |
+
@test -f config.yaml || cp config.example.yaml config.yaml
|
| 24 |
+
@test -f .env || cp .env.example .env
|
| 25 |
+
@test -f frontend/.env || cp frontend/.env.example frontend/.env
|
| 26 |
+
|
| 27 |
+
# Check required tools
|
| 28 |
+
check:
|
| 29 |
+
@echo "=========================================="
|
| 30 |
+
@echo " Checking Required Dependencies"
|
| 31 |
+
@echo "=========================================="
|
| 32 |
+
@echo ""
|
| 33 |
+
@FAILED=0; \
|
| 34 |
+
echo "Checking Node.js..."; \
|
| 35 |
+
if command -v node >/dev/null 2>&1; then \
|
| 36 |
+
NODE_VERSION=$$(node -v | sed 's/v//'); \
|
| 37 |
+
NODE_MAJOR=$$(echo $$NODE_VERSION | cut -d. -f1); \
|
| 38 |
+
if [ $$NODE_MAJOR -ge 22 ]; then \
|
| 39 |
+
echo " ✓ Node.js $$NODE_VERSION (>= 22 required)"; \
|
| 40 |
+
else \
|
| 41 |
+
echo " ✗ Node.js $$NODE_VERSION found, but version 22+ is required"; \
|
| 42 |
+
echo " Install from: https://nodejs.org/"; \
|
| 43 |
+
FAILED=1; \
|
| 44 |
+
fi; \
|
| 45 |
+
else \
|
| 46 |
+
echo " ✗ Node.js not found (version 22+ required)"; \
|
| 47 |
+
echo " Install from: https://nodejs.org/"; \
|
| 48 |
+
FAILED=1; \
|
| 49 |
+
fi; \
|
| 50 |
+
echo ""; \
|
| 51 |
+
echo "Checking pnpm..."; \
|
| 52 |
+
if command -v pnpm >/dev/null 2>&1; then \
|
| 53 |
+
PNPM_VERSION=$$(pnpm -v); \
|
| 54 |
+
echo " ✓ pnpm $$PNPM_VERSION"; \
|
| 55 |
+
else \
|
| 56 |
+
echo " ✗ pnpm not found"; \
|
| 57 |
+
echo " Install: npm install -g pnpm"; \
|
| 58 |
+
echo " Or visit: https://pnpm.io/installation"; \
|
| 59 |
+
FAILED=1; \
|
| 60 |
+
fi; \
|
| 61 |
+
echo ""; \
|
| 62 |
+
echo "Checking uv..."; \
|
| 63 |
+
if command -v uv >/dev/null 2>&1; then \
|
| 64 |
+
UV_VERSION=$$(uv --version | awk '{print $$2}'); \
|
| 65 |
+
echo " ✓ uv $$UV_VERSION"; \
|
| 66 |
+
else \
|
| 67 |
+
echo " ✗ uv not found"; \
|
| 68 |
+
echo " Install: curl -LsSf https://astral.sh/uv/install.sh | sh"; \
|
| 69 |
+
echo " Or visit: https://docs.astral.sh/uv/getting-started/installation/"; \
|
| 70 |
+
FAILED=1; \
|
| 71 |
+
fi; \
|
| 72 |
+
echo ""; \
|
| 73 |
+
echo "Checking nginx..."; \
|
| 74 |
+
if command -v nginx >/dev/null 2>&1; then \
|
| 75 |
+
NGINX_VERSION=$$(nginx -v 2>&1 | awk -F'/' '{print $$2}'); \
|
| 76 |
+
echo " ✓ nginx $$NGINX_VERSION"; \
|
| 77 |
+
else \
|
| 78 |
+
echo " ✗ nginx not found"; \
|
| 79 |
+
echo " macOS: brew install nginx"; \
|
| 80 |
+
echo " Ubuntu: sudo apt install nginx"; \
|
| 81 |
+
echo " Or visit: https://nginx.org/en/download.html"; \
|
| 82 |
+
FAILED=1; \
|
| 83 |
+
fi; \
|
| 84 |
+
echo ""; \
|
| 85 |
+
if [ $$FAILED -eq 0 ]; then \
|
| 86 |
+
echo "=========================================="; \
|
| 87 |
+
echo " ✓ All dependencies are installed!"; \
|
| 88 |
+
echo "=========================================="; \
|
| 89 |
+
echo ""; \
|
| 90 |
+
echo "You can now run:"; \
|
| 91 |
+
echo " make install - Install project dependencies"; \
|
| 92 |
+
echo " make dev - Start development server"; \
|
| 93 |
+
else \
|
| 94 |
+
echo "=========================================="; \
|
| 95 |
+
echo " ✗ Some dependencies are missing"; \
|
| 96 |
+
echo "=========================================="; \
|
| 97 |
+
echo ""; \
|
| 98 |
+
echo "Please install the missing tools and run 'make check' again."; \
|
| 99 |
+
exit 1; \
|
| 100 |
+
fi
|
| 101 |
+
|
| 102 |
+
# Install all dependencies
|
| 103 |
+
install:
|
| 104 |
+
@echo "Installing backend dependencies..."
|
| 105 |
+
@cd backend && uv sync
|
| 106 |
+
@echo "Installing frontend dependencies..."
|
| 107 |
+
@cd frontend && pnpm install
|
| 108 |
+
@echo "✓ All dependencies installed"
|
| 109 |
+
@echo ""
|
| 110 |
+
@echo "=========================================="
|
| 111 |
+
@echo " Optional: Pre-pull Sandbox Image"
|
| 112 |
+
@echo "=========================================="
|
| 113 |
+
@echo ""
|
| 114 |
+
@echo "If you plan to use Docker/Container-based sandbox, you can pre-pull the image:"
|
| 115 |
+
@echo " make setup-sandbox"
|
| 116 |
+
@echo ""
|
| 117 |
+
|
| 118 |
+
# Pre-pull sandbox Docker image (optional but recommended)
|
| 119 |
+
setup-sandbox:
|
| 120 |
+
@echo "=========================================="
|
| 121 |
+
@echo " Pre-pulling Sandbox Container Image"
|
| 122 |
+
@echo "=========================================="
|
| 123 |
+
@echo ""
|
| 124 |
+
@IMAGE=$$(grep -A 20 "# sandbox:" config.yaml 2>/dev/null | grep "image:" | awk '{print $$2}' | head -1); \
|
| 125 |
+
if [ -z "$$IMAGE" ]; then \
|
| 126 |
+
IMAGE="enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest"; \
|
| 127 |
+
echo "Using default image: $$IMAGE"; \
|
| 128 |
+
else \
|
| 129 |
+
echo "Using configured image: $$IMAGE"; \
|
| 130 |
+
fi; \
|
| 131 |
+
echo ""; \
|
| 132 |
+
if command -v container >/dev/null 2>&1 && [ "$$(uname)" = "Darwin" ]; then \
|
| 133 |
+
echo "Detected Apple Container on macOS, pulling image..."; \
|
| 134 |
+
container pull "$$IMAGE" || echo "⚠ Apple Container pull failed, will try Docker"; \
|
| 135 |
+
fi; \
|
| 136 |
+
if command -v docker >/dev/null 2>&1; then \
|
| 137 |
+
echo "Pulling image using Docker..."; \
|
| 138 |
+
docker pull "$$IMAGE"; \
|
| 139 |
+
echo ""; \
|
| 140 |
+
echo "✓ Sandbox image pulled successfully"; \
|
| 141 |
+
else \
|
| 142 |
+
echo "✗ Neither Docker nor Apple Container is available"; \
|
| 143 |
+
echo " Please install Docker: https://docs.docker.com/get-docker/"; \
|
| 144 |
+
exit 1; \
|
| 145 |
+
fi
|
| 146 |
+
|
| 147 |
+
# Start all services
|
| 148 |
+
dev:
|
| 149 |
+
@echo "Stopping existing services if any..."
|
| 150 |
+
@-pkill -f "langgraph dev" 2>/dev/null || true
|
| 151 |
+
@-pkill -f "uvicorn src.gateway.app:app" 2>/dev/null || true
|
| 152 |
+
@-pkill -f "next dev" 2>/dev/null || true
|
| 153 |
+
@-nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true
|
| 154 |
+
@sleep 1
|
| 155 |
+
@-pkill -9 nginx 2>/dev/null || true
|
| 156 |
+
@-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
|
| 157 |
+
@sleep 1
|
| 158 |
+
@echo ""
|
| 159 |
+
@echo "=========================================="
|
| 160 |
+
@echo " Starting DeerFlow Development Server"
|
| 161 |
+
@echo "=========================================="
|
| 162 |
+
@echo ""
|
| 163 |
+
@echo "Services starting up..."
|
| 164 |
+
@echo " → Backend: LangGraph + Gateway"
|
| 165 |
+
@echo " → Frontend: Next.js"
|
| 166 |
+
@echo " → Nginx: Reverse Proxy"
|
| 167 |
+
@echo ""
|
| 168 |
+
@cleanup() { \
|
| 169 |
+
echo ""; \
|
| 170 |
+
echo "Shutting down services..."; \
|
| 171 |
+
pkill -f "langgraph dev" 2>/dev/null || true; \
|
| 172 |
+
pkill -f "uvicorn src.gateway.app:app" 2>/dev/null || true; \
|
| 173 |
+
pkill -f "next dev" 2>/dev/null || true; \
|
| 174 |
+
nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true; \
|
| 175 |
+
sleep 1; \
|
| 176 |
+
pkill -9 nginx 2>/dev/null || true; \
|
| 177 |
+
echo "Cleaning up sandbox containers..."; \
|
| 178 |
+
./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true; \
|
| 179 |
+
echo "✓ All services stopped"; \
|
| 180 |
+
exit 0; \
|
| 181 |
+
}; \
|
| 182 |
+
trap cleanup INT TERM; \
|
| 183 |
+
mkdir -p logs; \
|
| 184 |
+
echo "Starting LangGraph server..."; \
|
| 185 |
+
cd backend && NO_COLOR=1 uv run langgraph dev --no-browser --allow-blocking --no-reload > ../logs/langgraph.log 2>&1 & \
|
| 186 |
+
sleep 3; \
|
| 187 |
+
echo "✓ LangGraph server started on localhost:2024"; \
|
| 188 |
+
echo "Starting Gateway API..."; \
|
| 189 |
+
cd backend && uv run uvicorn src.gateway.app:app --host 0.0.0.0 --port 8001 > ../logs/gateway.log 2>&1 & \
|
| 190 |
+
sleep 3; \
|
| 191 |
+
if ! lsof -i :8001 -sTCP:LISTEN -t >/dev/null 2>&1; then \
|
| 192 |
+
echo "✗ Gateway API failed to start. Last log output:"; \
|
| 193 |
+
tail -30 logs/gateway.log; \
|
| 194 |
+
cleanup; \
|
| 195 |
+
fi; \
|
| 196 |
+
echo "✓ Gateway API started on localhost:8001"; \
|
| 197 |
+
echo "Starting Frontend..."; \
|
| 198 |
+
cd frontend && pnpm run dev > ../logs/frontend.log 2>&1 & \
|
| 199 |
+
sleep 3; \
|
| 200 |
+
echo "✓ Frontend started on localhost:3000"; \
|
| 201 |
+
echo "Starting Nginx reverse proxy..."; \
|
| 202 |
+
mkdir -p logs && nginx -g 'daemon off;' -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) > logs/nginx.log 2>&1 & \
|
| 203 |
+
sleep 2; \
|
| 204 |
+
echo "✓ Nginx started on localhost:2026"; \
|
| 205 |
+
echo ""; \
|
| 206 |
+
echo "=========================================="; \
|
| 207 |
+
echo " DeerFlow is ready!"; \
|
| 208 |
+
echo "=========================================="; \
|
| 209 |
+
echo ""; \
|
| 210 |
+
echo " 🌐 Application: http://localhost:2026"; \
|
| 211 |
+
echo " 📡 API Gateway: http://localhost:2026/api/*"; \
|
| 212 |
+
echo " 🤖 LangGraph: http://localhost:2026/api/langgraph/*"; \
|
| 213 |
+
echo ""; \
|
| 214 |
+
echo " 📋 Logs:"; \
|
| 215 |
+
echo " - LangGraph: logs/langgraph.log"; \
|
| 216 |
+
echo " - Gateway: logs/gateway.log"; \
|
| 217 |
+
echo " - Frontend: logs/frontend.log"; \
|
| 218 |
+
echo " - Nginx: logs/nginx.log"; \
|
| 219 |
+
echo ""; \
|
| 220 |
+
echo "Press Ctrl+C to stop all services"; \
|
| 221 |
+
echo ""; \
|
| 222 |
+
wait
|
| 223 |
+
|
| 224 |
+
# Stop all services
|
| 225 |
+
stop:
|
| 226 |
+
@echo "Stopping all services..."
|
| 227 |
+
@-pkill -f "langgraph dev" 2>/dev/null || true
|
| 228 |
+
@-pkill -f "uvicorn src.gateway.app:app" 2>/dev/null || true
|
| 229 |
+
@-pkill -f "next dev" 2>/dev/null || true
|
| 230 |
+
@-nginx -c $(PWD)/docker/nginx/nginx.local.conf -p $(PWD) -s quit 2>/dev/null || true
|
| 231 |
+
@sleep 1
|
| 232 |
+
@-pkill -9 nginx 2>/dev/null || true
|
| 233 |
+
@echo "Cleaning up sandbox containers..."
|
| 234 |
+
@-./scripts/cleanup-containers.sh deer-flow-sandbox 2>/dev/null || true
|
| 235 |
+
@echo "✓ All services stopped"
|
| 236 |
+
|
| 237 |
+
# Clean up
|
| 238 |
+
clean: stop
|
| 239 |
+
@echo "Cleaning up..."
|
| 240 |
+
@-rm -rf logs/*.log 2>/dev/null || true
|
| 241 |
+
@echo "✓ Cleanup complete"
|
| 242 |
+
|
| 243 |
+
# ==========================================
|
| 244 |
+
# Docker Development Commands
|
| 245 |
+
# ==========================================
|
| 246 |
+
|
| 247 |
+
# Initialize Docker containers and install dependencies
|
| 248 |
+
docker-init:
|
| 249 |
+
@./scripts/docker.sh init
|
| 250 |
+
|
| 251 |
+
# Start Docker development environment
|
| 252 |
+
docker-start:
|
| 253 |
+
@./scripts/docker.sh start
|
| 254 |
+
|
| 255 |
+
# Stop Docker development environment
|
| 256 |
+
docker-stop:
|
| 257 |
+
@./scripts/docker.sh stop
|
| 258 |
+
|
| 259 |
+
# View Docker development logs
|
| 260 |
+
docker-logs:
|
| 261 |
+
@./scripts/docker.sh logs
|
| 262 |
+
|
| 263 |
+
# View Docker development logs
|
| 264 |
+
docker-logs-frontend:
|
| 265 |
+
@./scripts/docker.sh logs --frontend
|
| 266 |
+
docker-logs-gateway:
|
| 267 |
+
@./scripts/docker.sh logs --gateway
|
README.md
CHANGED
|
@@ -1,10 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
-
title: Deer Flow Gpt51
|
| 3 |
-
emoji: 📚
|
| 4 |
-
colorFrom: green
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🦌 DeerFlow - 2.0
|
| 2 |
+
|
| 3 |
+
<a href="https://trendshift.io/repositories/14699" target="_blank"><img src="https://trendshift.io/api/badge/repositories/14699" alt="bytedance%2Fdeer-flow | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
| 4 |
+
> On February 28th, 2026, DeerFlow claimed the 🏆 #1 spot on GitHub Trending following the launch of version 2. Thanks a million to our incredible community — you made this happen! 💪🔥
|
| 5 |
+
|
| 6 |
+
DeerFlow (**D**eep **E**xploration and **E**fficient **R**esearch **Flow**) is an open-source **super agent harness** that orchestrates **sub-agents**, **memory**, and **sandboxes** to do almost anything — powered by **extensible skills**.
|
| 7 |
+
|
| 8 |
+
https://github.com/user-attachments/assets/a8bcadc4-e040-4cf2-8fda-dd768b999c18
|
| 9 |
+
|
| 10 |
+
> [!NOTE]
|
| 11 |
+
> **DeerFlow 2.0 is a ground-up rewrite.** It shares no code with v1. If you're looking for the original Deep Research framework, it's maintained on the [`1.x` branch](https://github.com/bytedance/deer-flow/tree/main-1.x) — contributions there are still welcome. Active development has moved to 2.0.
|
| 12 |
+
|
| 13 |
+
## Official Website
|
| 14 |
+
|
| 15 |
+
Learn more and see **real demos** on our official website.
|
| 16 |
+
|
| 17 |
+
**[deerflow.tech](https://deerflow.tech/)**
|
| 18 |
+
|
| 19 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
## Table of Contents
|
| 22 |
+
|
| 23 |
+
- [🦌 DeerFlow - 2.0](#-deerflow---20)
|
| 24 |
+
- [Offiical Website](#offiical-website)
|
| 25 |
+
- [Table of Contents](#table-of-contents)
|
| 26 |
+
- [Quick Start](#quick-start)
|
| 27 |
+
- [Configuration](#configuration)
|
| 28 |
+
- [Running the Application](#running-the-application)
|
| 29 |
+
- [Option 1: Docker (Recommended)](#option-1-docker-recommended)
|
| 30 |
+
- [Option 2: Local Development](#option-2-local-development)
|
| 31 |
+
- [Advanced](#advanced)
|
| 32 |
+
- [Sandbox Mode](#sandbox-mode)
|
| 33 |
+
- [MCP Server](#mcp-server)
|
| 34 |
+
- [From Deep Research to Super Agent Harness](#from-deep-research-to-super-agent-harness)
|
| 35 |
+
- [Core Features](#core-features)
|
| 36 |
+
- [Skills \& Tools](#skills--tools)
|
| 37 |
+
- [Sub-Agents](#sub-agents)
|
| 38 |
+
- [Sandbox \& File System](#sandbox--file-system)
|
| 39 |
+
- [Context Engineering](#context-engineering)
|
| 40 |
+
- [Long-Term Memory](#long-term-memory)
|
| 41 |
+
- [Recommended Models](#recommended-models)
|
| 42 |
+
- [Documentation](#documentation)
|
| 43 |
+
- [Contributing](#contributing)
|
| 44 |
+
- [License](#license)
|
| 45 |
+
- [Acknowledgments](#acknowledgments)
|
| 46 |
+
- [Key Contributors](#key-contributors)
|
| 47 |
+
- [Star History](#star-history)
|
| 48 |
+
|
| 49 |
+
## Quick Start
|
| 50 |
+
|
| 51 |
+
### Configuration
|
| 52 |
+
|
| 53 |
+
1. **Clone the DeerFlow repository**
|
| 54 |
+
|
| 55 |
+
```bash
|
| 56 |
+
git clone https://github.com/bytedance/deer-flow.git
|
| 57 |
+
cd deer-flow
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
2. **Generate local configuration files**
|
| 61 |
+
|
| 62 |
+
From the project root directory (`deer-flow/`), run:
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
make config
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
This command creates local configuration files based on the provided example templates.
|
| 69 |
+
|
| 70 |
+
3. **Configure your preferred model(s)**
|
| 71 |
+
|
| 72 |
+
Edit `config.yaml` and define at least one model:
|
| 73 |
+
|
| 74 |
+
```yaml
|
| 75 |
+
models:
|
| 76 |
+
- name: gpt-4 # Internal identifier
|
| 77 |
+
display_name: GPT-4 # Human-readable name
|
| 78 |
+
use: langchain_openai:ChatOpenAI # LangChain class path
|
| 79 |
+
model: gpt-4 # Model identifier for API
|
| 80 |
+
api_key: $OPENAI_API_KEY # API key (recommended: use env var)
|
| 81 |
+
max_tokens: 4096 # Maximum tokens per request
|
| 82 |
+
temperature: 0.7 # Sampling temperature
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
4. **Set API keys for your configured model(s)**
|
| 87 |
+
|
| 88 |
+
Choose one of the following methods:
|
| 89 |
+
|
| 90 |
+
- Option A: Edit the `.env` file in the project root (Recommended)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
TAVILY_API_KEY=your-tavily-api-key
|
| 95 |
+
OPENAI_API_KEY=your-openai-api-key
|
| 96 |
+
# Add other provider keys as needed
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
- Option B: Export environment variables in your shell
|
| 100 |
+
|
| 101 |
+
```bash
|
| 102 |
+
export OPENAI_API_KEY=your-openai-api-key
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
- Option C: Edit `config.yaml` directly (Not recommended for production)
|
| 106 |
+
|
| 107 |
+
```yaml
|
| 108 |
+
models:
|
| 109 |
+
- name: gpt-4
|
| 110 |
+
api_key: your-actual-api-key-here # Replace placeholder
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### Running the Application
|
| 114 |
+
|
| 115 |
+
#### Option 1: Docker (Recommended)
|
| 116 |
+
|
| 117 |
+
The fastest way to get started with a consistent environment:
|
| 118 |
+
|
| 119 |
+
1. **Initialize and start**:
|
| 120 |
+
```bash
|
| 121 |
+
make docker-init # Pull sandbox image (Only once or when image updates)
|
| 122 |
+
make docker-start # Start services (auto-detects sandbox mode from config.yaml)
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
`make docker-start` now starts `provisioner` only when `config.yaml` uses provisioner mode (`sandbox.use: src.community.aio_sandbox:AioSandboxProvider` with `provisioner_url`).
|
| 126 |
+
|
| 127 |
+
2. **Access**: http://localhost:2026
|
| 128 |
+
|
| 129 |
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed Docker development guide.
|
| 130 |
+
|
| 131 |
+
#### Option 2: Local Development
|
| 132 |
+
|
| 133 |
+
If you prefer running services locally:
|
| 134 |
+
|
| 135 |
+
1. **Check prerequisites**:
|
| 136 |
+
```bash
|
| 137 |
+
make check # Verifies Node.js 22+, pnpm, uv, nginx
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
2. **(Optional) Pre-pull sandbox image**:
|
| 141 |
+
```bash
|
| 142 |
+
# Recommended if using Docker/Container-based sandbox
|
| 143 |
+
make setup-sandbox
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
3. **Start services**:
|
| 147 |
+
```bash
|
| 148 |
+
make dev
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
4. **Access**: http://localhost:2026
|
| 152 |
+
|
| 153 |
+
### Advanced
|
| 154 |
+
#### Sandbox Mode
|
| 155 |
+
|
| 156 |
+
DeerFlow supports multiple sandbox execution modes:
|
| 157 |
+
- **Local Execution** (runs sandbox code directly on the host machine)
|
| 158 |
+
- **Docker Execution** (runs sandbox code in isolated Docker containers)
|
| 159 |
+
- **Docker Execution with Kubernetes** (runs sandbox code in Kubernetes pods via provisioner service)
|
| 160 |
+
|
| 161 |
+
For Docker development, service startup follows `config.yaml` sandbox mode. In Local/Docker modes, `provisioner` is not started.
|
| 162 |
+
|
| 163 |
+
See the [Sandbox Configuration Guide](backend/docs/CONFIGURATION.md#sandbox) to configure your preferred mode.
|
| 164 |
+
|
| 165 |
+
#### MCP Server
|
| 166 |
+
|
| 167 |
+
DeerFlow supports configurable MCP servers and skills to extend its capabilities.
|
| 168 |
+
For HTTP/SSE MCP servers, OAuth token flows are supported (`client_credentials`, `refresh_token`).
|
| 169 |
+
See the [MCP Server Guide](backend/docs/MCP_SERVER.md) for detailed instructions.
|
| 170 |
+
|
| 171 |
+
## From Deep Research to Super Agent Harness
|
| 172 |
+
|
| 173 |
+
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.
|
| 174 |
+
|
| 175 |
+
That told us something important: DeerFlow wasn't just a research tool. It was a **harness** — a runtime that gives agents the infrastructure to actually get work done.
|
| 176 |
+
|
| 177 |
+
So we rebuilt it from scratch.
|
| 178 |
+
|
| 179 |
+
DeerFlow 2.0 is no longer a framework you wire together. It's a super agent harness — batteries included, fully extensible. Built on LangGraph and LangChain, it ships with everything an agent needs out of the box: a filesystem, memory, skills, sandboxed execution, and the ability to plan and spawn sub-agents for complex, multi-step tasks.
|
| 180 |
+
|
| 181 |
+
Use it as-is. Or tear it apart and make it yours.
|
| 182 |
+
|
| 183 |
+
## Core Features
|
| 184 |
+
|
| 185 |
+
### Skills & Tools
|
| 186 |
+
|
| 187 |
+
Skills are what make DeerFlow do *almost anything*.
|
| 188 |
+
|
| 189 |
+
A standard Agent Skill is a structured capability module — a Markdown file that defines a workflow, best practices, and references to supporting resources. DeerFlow ships with built-in skills for research, report generation, slide creation, web pages, image and video generation, and more. But the real power is extensibility: add your own skills, replace the built-in ones, or combine them into compound workflows.
|
| 190 |
+
|
| 191 |
+
Skills are loaded progressively — only when the task needs them, not all at once. This keeps the context window lean and makes DeerFlow work well even with token-sensitive models.
|
| 192 |
+
|
| 193 |
+
Tools follow the same philosophy. DeerFlow comes with a core toolset — web search, web fetch, file operations, bash execution — and supports custom tools via MCP servers and Python functions. Swap anything. Add anything.
|
| 194 |
+
|
| 195 |
+
```
|
| 196 |
+
# Paths inside the sandbox container
|
| 197 |
+
/mnt/skills/public
|
| 198 |
+
├── research/SKILL.md
|
| 199 |
+
├── report-generation/SKILL.md
|
| 200 |
+
├── slide-creation/SKILL.md
|
| 201 |
+
├── web-page/SKILL.md
|
| 202 |
+
└── image-generation/SKILL.md
|
| 203 |
+
|
| 204 |
+
/mnt/skills/custom
|
| 205 |
+
└── your-custom-skill/SKILL.md ← yours
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
### Sub-Agents
|
| 209 |
+
|
| 210 |
+
Complex tasks rarely fit in a single pass. DeerFlow decomposes them.
|
| 211 |
+
|
| 212 |
+
The lead agent can spawn sub-agents on the fly — each with its own scoped context, tools, and termination conditions. Sub-agents run in parallel when possible, report back structured results, and the lead agent synthesizes everything into a coherent output.
|
| 213 |
+
|
| 214 |
+
This is how DeerFlow handles tasks that take minutes to hours: a research task might fan out into a dozen sub-agents, each exploring a different angle, then converge into a single report — or a website — or a slide deck with generated visuals. One harness, many hands.
|
| 215 |
+
|
| 216 |
+
### Sandbox & File System
|
| 217 |
+
|
| 218 |
+
DeerFlow doesn't just *talk* about doing things. It has its own computer.
|
| 219 |
+
|
| 220 |
+
Each task runs inside an isolated Docker container with a full filesystem — skills, workspace, uploads, outputs. The agent reads, writes, and edits files. It executes bash commands and codes. It views images. All sandboxed, all auditable, zero contamination between sessions.
|
| 221 |
+
|
| 222 |
+
This is the difference between a chatbot with tool access and an agent with an actual execution environment.
|
| 223 |
+
|
| 224 |
+
```
|
| 225 |
+
# Paths inside the sandbox container
|
| 226 |
+
/mnt/user-data/
|
| 227 |
+
├── uploads/ ← your files
|
| 228 |
+
├── workspace/ ← agents' working directory
|
| 229 |
+
└── outputs/ ← final deliverables
|
| 230 |
+
```
|
| 231 |
+
|
| 232 |
+
### Context Engineering
|
| 233 |
+
|
| 234 |
+
**Isolated Sub-Agent Context**: Each sub-agent runs in its own isolated context. This means that the sub-agent will not be able to see the context of the main agent or other sub-agents. This is important to ensure that the sub-agent is able to focus on the task at hand and not be distracted by the context of the main agent or other sub-agents.
|
| 235 |
+
|
| 236 |
+
**Summarization**: Within a session, DeerFlow manages context aggressively — summarizing completed sub-tasks, offloading intermediate results to the filesystem, compressing what's no longer immediately relevant. This lets it stay sharp across long, multi-step tasks without blowing the context window.
|
| 237 |
+
|
| 238 |
+
### Long-Term Memory
|
| 239 |
+
|
| 240 |
+
Most agents forget everything the moment a conversation ends. DeerFlow remembers.
|
| 241 |
+
|
| 242 |
+
Across sessions, DeerFlow builds a persistent memory of your profile, preferences, and accumulated knowledge. The more you use it, the better it knows you — your writing style, your technical stack, your recurring workflows. Memory is stored locally and stays under your control.
|
| 243 |
+
|
| 244 |
+
## Recommended Models
|
| 245 |
+
|
| 246 |
+
DeerFlow is model-agnostic — it works with any LLM that implements the OpenAI-compatible API. That said, it performs best with models that support:
|
| 247 |
+
|
| 248 |
+
- **Long context windows** (100k+ tokens) for deep research and multi-step tasks
|
| 249 |
+
- **Reasoning capabilities** for adaptive planning and complex decomposition
|
| 250 |
+
- **Multimodal inputs** for image understanding and video comprehension
|
| 251 |
+
- **Strong tool-use** for reliable function calling and structured outputs
|
| 252 |
+
|
| 253 |
+
## Embedded Python Client
|
| 254 |
+
|
| 255 |
+
DeerFlow can be used as an embedded Python library without running the full HTTP services. The `DeerFlowClient` provides direct in-process access to all agent and Gateway capabilities, returning the same response schemas as the HTTP Gateway API:
|
| 256 |
+
|
| 257 |
+
```python
|
| 258 |
+
from src.client import DeerFlowClient
|
| 259 |
+
|
| 260 |
+
client = DeerFlowClient()
|
| 261 |
+
|
| 262 |
+
# Chat
|
| 263 |
+
response = client.chat("Analyze this paper for me", thread_id="my-thread")
|
| 264 |
+
|
| 265 |
+
# Streaming (LangGraph SSE protocol: values, messages-tuple, end)
|
| 266 |
+
for event in client.stream("hello"):
|
| 267 |
+
if event.type == "messages-tuple" and event.data.get("type") == "ai":
|
| 268 |
+
print(event.data["content"])
|
| 269 |
+
|
| 270 |
+
# Configuration & management — returns Gateway-aligned dicts
|
| 271 |
+
models = client.list_models() # {"models": [...]}
|
| 272 |
+
skills = client.list_skills() # {"skills": [...]}
|
| 273 |
+
client.update_skill("web-search", enabled=True)
|
| 274 |
+
client.upload_files("thread-1", ["./report.pdf"]) # {"success": True, "files": [...]}
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
All dict-returning methods are validated against Gateway Pydantic response models in CI (`TestGatewayConformance`), ensuring the embedded client stays in sync with the HTTP API schemas. See `backend/src/client.py` for full API documentation.
|
| 278 |
+
|
| 279 |
+
## Documentation
|
| 280 |
+
|
| 281 |
+
- [Contributing Guide](CONTRIBUTING.md) - Development environment setup and workflow
|
| 282 |
+
- [Configuration Guide](backend/docs/CONFIGURATION.md) - Setup and configuration instructions
|
| 283 |
+
- [Architecture Overview](backend/CLAUDE.md) - Technical architecture details
|
| 284 |
+
- [Backend Architecture](backend/README.md) - Backend architecture and API reference
|
| 285 |
+
|
| 286 |
+
## Contributing
|
| 287 |
+
|
| 288 |
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, workflow, and guidelines.
|
| 289 |
+
|
| 290 |
+
Regression coverage includes Docker sandbox mode detection and provisioner kubeconfig-path handling tests in `backend/tests/`.
|
| 291 |
+
|
| 292 |
+
## License
|
| 293 |
+
|
| 294 |
+
This project is open source and available under the [MIT License](./LICENSE).
|
| 295 |
+
|
| 296 |
+
## Acknowledgments
|
| 297 |
+
|
| 298 |
+
DeerFlow is built upon the incredible work of the open-source community. We are deeply grateful to all the projects and contributors whose efforts have made DeerFlow possible. Truly, we stand on the shoulders of giants.
|
| 299 |
+
|
| 300 |
+
We would like to extend our sincere appreciation to the following projects for their invaluable contributions:
|
| 301 |
+
|
| 302 |
+
- **[LangChain](https://github.com/langchain-ai/langchain)**: Their exceptional framework powers our LLM interactions and chains, enabling seamless integration and functionality.
|
| 303 |
+
- **[LangGraph](https://github.com/langchain-ai/langgraph)**: Their innovative approach to multi-agent orchestration has been instrumental in enabling DeerFlow's sophisticated workflows.
|
| 304 |
+
|
| 305 |
+
These projects exemplify the transformative power of open-source collaboration, and we are proud to build upon their foundations.
|
| 306 |
+
|
| 307 |
+
### Key Contributors
|
| 308 |
+
|
| 309 |
+
A heartfelt thank you goes out to the core authors of `DeerFlow`, whose vision, passion, and dedication have brought this project to life:
|
| 310 |
+
|
| 311 |
+
- **[Daniel Walnut](https://github.com/hetaoBackend/)**
|
| 312 |
+
- **[Henry Li](https://github.com/magiccube/)**
|
| 313 |
+
|
| 314 |
+
Your unwavering commitment and expertise have been the driving force behind DeerFlow's success. We are honored to have you at the helm of this journey.
|
| 315 |
+
|
| 316 |
+
## Star History
|
| 317 |
+
|
| 318 |
+
[](https://star-history.com/#bytedance/deer-flow&Date)
|
SECURITY.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security Policy
|
| 2 |
+
|
| 3 |
+
## Supported Versions
|
| 4 |
+
|
| 5 |
+
As deer-flow doesn't provide an offical release yet, please use the latest version for the security updates.
|
| 6 |
+
Current we have two branches to maintain:
|
| 7 |
+
* main branch for deer-flow 2.x
|
| 8 |
+
* main-1.x branch for deer-flow 1.x
|
| 9 |
+
|
| 10 |
+
## Reporting a Vulnerability
|
| 11 |
+
|
| 12 |
+
Please go to https://github.com/bytedance/deer-flow/security to report the vulnerability you find.
|
backend/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python-generated files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[oc]
|
| 4 |
+
build/
|
| 5 |
+
dist/
|
| 6 |
+
wheels/
|
| 7 |
+
*.egg-info
|
| 8 |
+
.coverage
|
| 9 |
+
.coverage.*
|
| 10 |
+
.ruff_cache
|
| 11 |
+
agent_history.gif
|
| 12 |
+
static/browser_history/*.gif
|
| 13 |
+
|
| 14 |
+
log/
|
| 15 |
+
log/*
|
| 16 |
+
|
| 17 |
+
# Virtual environments
|
| 18 |
+
.venv
|
| 19 |
+
venv/
|
| 20 |
+
|
| 21 |
+
# User config file
|
| 22 |
+
config.yaml
|
| 23 |
+
|
| 24 |
+
# Langgraph
|
| 25 |
+
.langgraph_api
|
| 26 |
+
|
| 27 |
+
# Claude Code settings
|
| 28 |
+
.claude/settings.local.json
|
backend/.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.12
|
backend/.vscode/extensions.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"recommendations": ["charliermarsh.ruff"]
|
| 3 |
+
}
|
backend/.vscode/settings.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"window.title": "${activeEditorShort}${separator}${separator}deer-flow/backend",
|
| 3 |
+
"[python]": {
|
| 4 |
+
"editor.formatOnSave": true,
|
| 5 |
+
"editor.codeActionsOnSave": {
|
| 6 |
+
"source.fixAll": "explicit",
|
| 7 |
+
"source.organizeImports": "explicit"
|
| 8 |
+
},
|
| 9 |
+
"editor.defaultFormatter": "charliermarsh.ruff"
|
| 10 |
+
}
|
| 11 |
+
}
|
backend/AGENTS.md
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
For the backend architeture and design patterns:
|
| 2 |
+
@./CLAUDE.md
|
backend/CLAUDE.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CLAUDE.md
|
| 2 |
+
|
| 3 |
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
| 4 |
+
|
| 5 |
+
## Project Overview
|
| 6 |
+
|
| 7 |
+
DeerFlow is a LangGraph-based AI super agent system with a full-stack architecture. The backend provides a "super agent" with sandbox execution, persistent memory, subagent delegation, and extensible tool integration - all operating in per-thread isolated environments.
|
| 8 |
+
|
| 9 |
+
**Architecture**:
|
| 10 |
+
- **LangGraph Server** (port 2024): Agent runtime and workflow execution
|
| 11 |
+
- **Gateway API** (port 8001): REST API for models, MCP, skills, memory, artifacts, and uploads
|
| 12 |
+
- **Frontend** (port 3000): Next.js web interface
|
| 13 |
+
- **Nginx** (port 2026): Unified reverse proxy entry point
|
| 14 |
+
- **Provisioner** (port 8002, optional in Docker dev): Started only when sandbox is configured for provisioner/Kubernetes mode
|
| 15 |
+
|
| 16 |
+
**Project Structure**:
|
| 17 |
+
```
|
| 18 |
+
deer-flow/
|
| 19 |
+
├── Makefile # Root commands (check, install, dev, stop)
|
| 20 |
+
├── config.yaml # Main application configuration
|
| 21 |
+
├── extensions_config.json # MCP servers and skills configuration
|
| 22 |
+
├── backend/ # Backend application (this directory)
|
| 23 |
+
│ ├── Makefile # Backend-only commands (dev, gateway, lint)
|
| 24 |
+
│ ├── langgraph.json # LangGraph server configuration
|
| 25 |
+
│ ├── src/
|
| 26 |
+
│ │ ├── agents/ # LangGraph agent system
|
| 27 |
+
│ │ │ ├── lead_agent/ # Main agent (factory + system prompt)
|
| 28 |
+
│ │ │ ├── middlewares/ # 10 middleware components
|
| 29 |
+
│ │ │ ├── memory/ # Memory extraction, queue, prompts
|
| 30 |
+
│ │ │ └── thread_state.py # ThreadState schema
|
| 31 |
+
│ │ ├── gateway/ # FastAPI Gateway API
|
| 32 |
+
│ │ │ ├── app.py # FastAPI application
|
| 33 |
+
│ │ │ └── routers/ # 6 route modules
|
| 34 |
+
│ │ ├── sandbox/ # Sandbox execution system
|
| 35 |
+
│ │ │ ├── local/ # Local filesystem provider
|
| 36 |
+
│ │ │ ├── sandbox.py # Abstract Sandbox interface
|
| 37 |
+
│ │ │ ├── tools.py # bash, ls, read/write/str_replace
|
| 38 |
+
│ │ │ └── middleware.py # Sandbox lifecycle management
|
| 39 |
+
│ │ ├── subagents/ # Subagent delegation system
|
| 40 |
+
│ │ │ ├── builtins/ # general-purpose, bash agents
|
| 41 |
+
│ │ │ ├── executor.py # Background execution engine
|
| 42 |
+
│ │ │ └── registry.py # Agent registry
|
| 43 |
+
│ │ ├── tools/builtins/ # Built-in tools (present_files, ask_clarification, view_image)
|
| 44 |
+
│ │ ├── mcp/ # MCP integration (tools, cache, client)
|
| 45 |
+
│ │ ├── models/ # Model factory with thinking/vision support
|
| 46 |
+
│ │ ├── skills/ # Skills discovery, loading, parsing
|
| 47 |
+
│ │ ├── config/ # Configuration system (app, model, sandbox, tool, etc.)
|
| 48 |
+
│ │ ├── community/ # Community tools (tavily, jina_ai, firecrawl, image_search, aio_sandbox)
|
| 49 |
+
│ │ ├── reflection/ # Dynamic module loading (resolve_variable, resolve_class)
|
| 50 |
+
│ │ ├── utils/ # Utilities (network, readability)
|
| 51 |
+
│ │ └── client.py # Embedded Python client (DeerFlowClient)
|
| 52 |
+
│ ├── tests/ # Test suite
|
| 53 |
+
│ └── docs/ # Documentation
|
| 54 |
+
├── frontend/ # Next.js frontend application
|
| 55 |
+
└── skills/ # Agent skills directory
|
| 56 |
+
├── public/ # Public skills (committed)
|
| 57 |
+
└── custom/ # Custom skills (gitignored)
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
## Important Development Guidelines
|
| 61 |
+
|
| 62 |
+
### Documentation Update Policy
|
| 63 |
+
**CRITICAL: Always update README.md and CLAUDE.md after every code change**
|
| 64 |
+
|
| 65 |
+
When making code changes, you MUST update the relevant documentation:
|
| 66 |
+
- Update `README.md` for user-facing changes (features, setup, usage instructions)
|
| 67 |
+
- Update `CLAUDE.md` for development changes (architecture, commands, workflows, internal systems)
|
| 68 |
+
- Keep documentation synchronized with the codebase at all times
|
| 69 |
+
- Ensure accuracy and timeliness of all documentation
|
| 70 |
+
|
| 71 |
+
## Commands
|
| 72 |
+
|
| 73 |
+
**Root directory** (for full application):
|
| 74 |
+
```bash
|
| 75 |
+
make check # Check system requirements
|
| 76 |
+
make install # Install all dependencies (frontend + backend)
|
| 77 |
+
make dev # Start all services (LangGraph + Gateway + Frontend + Nginx)
|
| 78 |
+
make stop # Stop all services
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
**Backend directory** (for backend development only):
|
| 82 |
+
```bash
|
| 83 |
+
make install # Install backend dependencies
|
| 84 |
+
make dev # Run LangGraph server only (port 2024)
|
| 85 |
+
make gateway # Run Gateway API only (port 8001)
|
| 86 |
+
make test # Run all backend tests
|
| 87 |
+
make lint # Lint with ruff
|
| 88 |
+
make format # Format code with ruff
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
Regression tests related to Docker/provisioner behavior:
|
| 92 |
+
- `tests/test_docker_sandbox_mode_detection.py` (mode detection from `config.yaml`)
|
| 93 |
+
- `tests/test_provisioner_kubeconfig.py` (kubeconfig file/directory handling)
|
| 94 |
+
|
| 95 |
+
CI runs these regression tests for every pull request via [.github/workflows/backend-unit-tests.yml](../.github/workflows/backend-unit-tests.yml).
|
| 96 |
+
|
| 97 |
+
## Architecture
|
| 98 |
+
|
| 99 |
+
### Agent System
|
| 100 |
+
|
| 101 |
+
**Lead Agent** (`src/agents/lead_agent/agent.py`):
|
| 102 |
+
- Entry point: `make_lead_agent(config: RunnableConfig)` registered in `langgraph.json`
|
| 103 |
+
- Dynamic model selection via `create_chat_model()` with thinking/vision support
|
| 104 |
+
- Tools loaded via `get_available_tools()` - combines sandbox, built-in, MCP, community, and subagent tools
|
| 105 |
+
- System prompt generated by `apply_prompt_template()` with skills, memory, and subagent instructions
|
| 106 |
+
|
| 107 |
+
**ThreadState** (`src/agents/thread_state.py`):
|
| 108 |
+
- Extends `AgentState` with: `sandbox`, `thread_data`, `title`, `artifacts`, `todos`, `uploaded_files`, `viewed_images`
|
| 109 |
+
- Uses custom reducers: `merge_artifacts` (deduplicate), `merge_viewed_images` (merge/clear)
|
| 110 |
+
|
| 111 |
+
**Runtime Configuration** (via `config.configurable`):
|
| 112 |
+
- `thinking_enabled` - Enable model's extended thinking
|
| 113 |
+
- `model_name` - Select specific LLM model
|
| 114 |
+
- `is_plan_mode` - Enable TodoList middleware
|
| 115 |
+
- `subagent_enabled` - Enable task delegation tool
|
| 116 |
+
|
| 117 |
+
### Middleware Chain
|
| 118 |
+
|
| 119 |
+
Middlewares execute in strict order in `src/agents/lead_agent/agent.py`:
|
| 120 |
+
|
| 121 |
+
1. **ThreadDataMiddleware** - Creates per-thread directories (`backend/.deer-flow/threads/{thread_id}/user-data/{workspace,uploads,outputs}`)
|
| 122 |
+
2. **UploadsMiddleware** - Tracks and injects newly uploaded files into conversation
|
| 123 |
+
3. **SandboxMiddleware** - Acquires sandbox, stores `sandbox_id` in state
|
| 124 |
+
4. **DanglingToolCallMiddleware** - Injects placeholder ToolMessages for AIMessage tool_calls that lack responses (e.g., due to user interruption)
|
| 125 |
+
5. **SummarizationMiddleware** - Context reduction when approaching token limits (optional, if enabled)
|
| 126 |
+
6. **TodoListMiddleware** - Task tracking with `write_todos` tool (optional, if plan_mode)
|
| 127 |
+
7. **TitleMiddleware** - Auto-generates thread title after first complete exchange
|
| 128 |
+
8. **MemoryMiddleware** - Queues conversations for async memory update (filters to user + final AI responses)
|
| 129 |
+
9. **ViewImageMiddleware** - Injects base64 image data before LLM call (conditional on vision support)
|
| 130 |
+
10. **SubagentLimitMiddleware** - Truncates excess `task` tool calls from model response to enforce `MAX_CONCURRENT_SUBAGENTS` limit (optional, if subagent_enabled)
|
| 131 |
+
11. **ClarificationMiddleware** - Intercepts `ask_clarification` tool calls, interrupts via `Command(goto=END)` (must be last)
|
| 132 |
+
|
| 133 |
+
### Configuration System
|
| 134 |
+
|
| 135 |
+
**Main Configuration** (`config.yaml`):
|
| 136 |
+
|
| 137 |
+
Setup: Copy `config.example.yaml` to `config.yaml` in the **project root** directory.
|
| 138 |
+
|
| 139 |
+
Configuration priority:
|
| 140 |
+
1. Explicit `config_path` argument
|
| 141 |
+
2. `DEER_FLOW_CONFIG_PATH` environment variable
|
| 142 |
+
3. `config.yaml` in current directory (backend/)
|
| 143 |
+
4. `config.yaml` in parent directory (project root - **recommended location**)
|
| 144 |
+
|
| 145 |
+
Config values starting with `$` are resolved as environment variables (e.g., `$OPENAI_API_KEY`).
|
| 146 |
+
|
| 147 |
+
**Extensions Configuration** (`extensions_config.json`):
|
| 148 |
+
|
| 149 |
+
MCP servers and skills are configured together in `extensions_config.json` in project root:
|
| 150 |
+
|
| 151 |
+
Configuration priority:
|
| 152 |
+
1. Explicit `config_path` argument
|
| 153 |
+
2. `DEER_FLOW_EXTENSIONS_CONFIG_PATH` environment variable
|
| 154 |
+
3. `extensions_config.json` in current directory (backend/)
|
| 155 |
+
4. `extensions_config.json` in parent directory (project root - **recommended location**)
|
| 156 |
+
|
| 157 |
+
### Gateway API (`src/gateway/`)
|
| 158 |
+
|
| 159 |
+
FastAPI application on port 8001 with health check at `GET /health`.
|
| 160 |
+
|
| 161 |
+
**Routers**:
|
| 162 |
+
|
| 163 |
+
| Router | Endpoints |
|
| 164 |
+
|--------|-----------|
|
| 165 |
+
| **Models** (`/api/models`) | `GET /` - list models; `GET /{name}` - model details |
|
| 166 |
+
| **MCP** (`/api/mcp`) | `GET /config` - get config; `PUT /config` - update config (saves to extensions_config.json) |
|
| 167 |
+
| **Skills** (`/api/skills`) | `GET /` - list skills; `GET /{name}` - details; `PUT /{name}` - update enabled; `POST /install` - install from .skill archive |
|
| 168 |
+
| **Memory** (`/api/memory`) | `GET /` - memory data; `POST /reload` - force reload; `GET /config` - config; `GET /status` - config + data |
|
| 169 |
+
| **Uploads** (`/api/threads/{id}/uploads`) | `POST /` - upload files (auto-converts PDF/PPT/Excel/Word); `GET /list` - list; `DELETE /{filename}` - delete |
|
| 170 |
+
| **Artifacts** (`/api/threads/{id}/artifacts`) | `GET /{path}` - serve artifacts; `?download=true` for file download |
|
| 171 |
+
|
| 172 |
+
Proxied through nginx: `/api/langgraph/*` → LangGraph, all other `/api/*` → Gateway.
|
| 173 |
+
|
| 174 |
+
### Sandbox System (`src/sandbox/`)
|
| 175 |
+
|
| 176 |
+
**Interface**: Abstract `Sandbox` with `execute_command`, `read_file`, `write_file`, `list_dir`
|
| 177 |
+
**Provider Pattern**: `SandboxProvider` with `acquire`, `get`, `release` lifecycle
|
| 178 |
+
**Implementations**:
|
| 179 |
+
- `LocalSandboxProvider` - Singleton local filesystem execution with path mappings
|
| 180 |
+
- `AioSandboxProvider` (`src/community/`) - Docker-based isolation
|
| 181 |
+
|
| 182 |
+
**Virtual Path System**:
|
| 183 |
+
- Agent sees: `/mnt/user-data/{workspace,uploads,outputs}`, `/mnt/skills`
|
| 184 |
+
- Physical: `backend/.deer-flow/threads/{thread_id}/user-data/...`, `deer-flow/skills/`
|
| 185 |
+
- Translation: `replace_virtual_path()` / `replace_virtual_paths_in_command()`
|
| 186 |
+
- Detection: `is_local_sandbox()` checks `sandbox_id == "local"`
|
| 187 |
+
|
| 188 |
+
**Sandbox Tools** (in `src/sandbox/tools.py`):
|
| 189 |
+
- `bash` - Execute commands with path translation and error handling
|
| 190 |
+
- `ls` - Directory listing (tree format, max 2 levels)
|
| 191 |
+
- `read_file` - Read file contents with optional line range
|
| 192 |
+
- `write_file` - Write/append to files, creates directories
|
| 193 |
+
- `str_replace` - Substring replacement (single or all occurrences)
|
| 194 |
+
|
| 195 |
+
### Subagent System (`src/subagents/`)
|
| 196 |
+
|
| 197 |
+
**Built-in Agents**: `general-purpose` (all tools except `task`) and `bash` (command specialist)
|
| 198 |
+
**Execution**: Dual thread pool - `_scheduler_pool` (3 workers) + `_execution_pool` (3 workers)
|
| 199 |
+
**Concurrency**: `MAX_CONCURRENT_SUBAGENTS = 3` enforced by `SubagentLimitMiddleware` (truncates excess tool calls in `after_model`), 15-minute timeout
|
| 200 |
+
**Flow**: `task()` tool → `SubagentExecutor` → background thread → poll 5s → SSE events → result
|
| 201 |
+
**Events**: `task_started`, `task_running`, `task_completed`/`task_failed`/`task_timed_out`
|
| 202 |
+
|
| 203 |
+
### Tool System (`src/tools/`)
|
| 204 |
+
|
| 205 |
+
`get_available_tools(groups, include_mcp, model_name, subagent_enabled)` assembles:
|
| 206 |
+
1. **Config-defined tools** - Resolved from `config.yaml` via `resolve_variable()`
|
| 207 |
+
2. **MCP tools** - From enabled MCP servers (lazy initialized, cached with mtime invalidation)
|
| 208 |
+
3. **Built-in tools**:
|
| 209 |
+
- `present_files` - Make output files visible to user (only `/mnt/user-data/outputs`)
|
| 210 |
+
- `ask_clarification` - Request clarification (intercepted by ClarificationMiddleware → interrupts)
|
| 211 |
+
- `view_image` - Read image as base64 (added only if model supports vision)
|
| 212 |
+
4. **Subagent tool** (if enabled):
|
| 213 |
+
- `task` - Delegate to subagent (description, prompt, subagent_type, max_turns)
|
| 214 |
+
|
| 215 |
+
**Community tools** (`src/community/`):
|
| 216 |
+
- `tavily/` - Web search (5 results default) and web fetch (4KB limit)
|
| 217 |
+
- `jina_ai/` - Web fetch via Jina reader API with readability extraction
|
| 218 |
+
- `firecrawl/` - Web scraping via Firecrawl API
|
| 219 |
+
- `image_search/` - Image search via DuckDuckGo
|
| 220 |
+
|
| 221 |
+
### MCP System (`src/mcp/`)
|
| 222 |
+
|
| 223 |
+
- Uses `langchain-mcp-adapters` `MultiServerMCPClient` for multi-server management
|
| 224 |
+
- **Lazy initialization**: Tools loaded on first use via `get_cached_mcp_tools()`
|
| 225 |
+
- **Cache invalidation**: Detects config file changes via mtime comparison
|
| 226 |
+
- **Transports**: stdio (command-based), SSE, HTTP
|
| 227 |
+
- **OAuth (HTTP/SSE)**: Supports token endpoint flows (`client_credentials`, `refresh_token`) with automatic token refresh + Authorization header injection
|
| 228 |
+
- **Runtime updates**: Gateway API saves to extensions_config.json; LangGraph detects via mtime
|
| 229 |
+
|
| 230 |
+
### Skills System (`src/skills/`)
|
| 231 |
+
|
| 232 |
+
- **Location**: `deer-flow/skills/{public,custom}/`
|
| 233 |
+
- **Format**: Directory with `SKILL.md` (YAML frontmatter: name, description, license, allowed-tools)
|
| 234 |
+
- **Loading**: `load_skills()` scans directories, parses SKILL.md, reads enabled state from extensions_config.json
|
| 235 |
+
- **Injection**: Enabled skills listed in agent system prompt with container paths
|
| 236 |
+
- **Installation**: `POST /api/skills/install` extracts .skill ZIP archive to custom/ directory
|
| 237 |
+
|
| 238 |
+
### Model Factory (`src/models/factory.py`)
|
| 239 |
+
|
| 240 |
+
- `create_chat_model(name, thinking_enabled)` instantiates LLM from config via reflection
|
| 241 |
+
- Supports `thinking_enabled` flag with per-model `when_thinking_enabled` overrides
|
| 242 |
+
- Supports `supports_vision` flag for image understanding models
|
| 243 |
+
- Config values starting with `$` resolved as environment variables
|
| 244 |
+
|
| 245 |
+
### Memory System (`src/agents/memory/`)
|
| 246 |
+
|
| 247 |
+
**Components**:
|
| 248 |
+
- `updater.py` - LLM-based memory updates with fact extraction and atomic file I/O
|
| 249 |
+
- `queue.py` - Debounced update queue (per-thread deduplication, configurable wait time)
|
| 250 |
+
- `prompt.py` - Prompt templates for memory updates
|
| 251 |
+
|
| 252 |
+
**Data Structure** (stored in `backend/.deer-flow/memory.json`):
|
| 253 |
+
- **User Context**: `workContext`, `personalContext`, `topOfMind` (1-3 sentence summaries)
|
| 254 |
+
- **History**: `recentMonths`, `earlierContext`, `longTermBackground`
|
| 255 |
+
- **Facts**: Discrete facts with `id`, `content`, `category` (preference/knowledge/context/behavior/goal), `confidence` (0-1), `createdAt`, `source`
|
| 256 |
+
|
| 257 |
+
**Workflow**:
|
| 258 |
+
1. `MemoryMiddleware` filters messages (user inputs + final AI responses) and queues conversation
|
| 259 |
+
2. Queue debounces (30s default), batches updates, deduplicates per-thread
|
| 260 |
+
3. Background thread invokes LLM to extract context updates and facts
|
| 261 |
+
4. Applies updates atomically (temp file + rename) with cache invalidation
|
| 262 |
+
5. Next interaction injects top 15 facts + context into `<memory>` tags in system prompt
|
| 263 |
+
|
| 264 |
+
**Configuration** (`config.yaml` → `memory`):
|
| 265 |
+
- `enabled` / `injection_enabled` - Master switches
|
| 266 |
+
- `storage_path` - Path to memory.json
|
| 267 |
+
- `debounce_seconds` - Wait time before processing (default: 30)
|
| 268 |
+
- `model_name` - LLM for updates (null = default model)
|
| 269 |
+
- `max_facts` / `fact_confidence_threshold` - Fact storage limits (100 / 0.7)
|
| 270 |
+
- `max_injection_tokens` - Token limit for prompt injection (2000)
|
| 271 |
+
|
| 272 |
+
### Reflection System (`src/reflection/`)
|
| 273 |
+
|
| 274 |
+
- `resolve_variable(path)` - Import module and return variable (e.g., `module.path:variable_name`)
|
| 275 |
+
- `resolve_class(path, base_class)` - Import and validate class against base class
|
| 276 |
+
|
| 277 |
+
### Config Schema
|
| 278 |
+
|
| 279 |
+
**`config.yaml`** key sections:
|
| 280 |
+
- `models[]` - LLM configs with `use` class path, `supports_thinking`, `supports_vision`, provider-specific fields
|
| 281 |
+
- `tools[]` - Tool configs with `use` variable path and `group`
|
| 282 |
+
- `tool_groups[]` - Logical groupings for tools
|
| 283 |
+
- `sandbox.use` - Sandbox provider class path
|
| 284 |
+
- `skills.path` / `skills.container_path` - Host and container paths to skills directory
|
| 285 |
+
- `title` - Auto-title generation (enabled, max_words, max_chars, prompt_template)
|
| 286 |
+
- `summarization` - Context summarization (enabled, trigger conditions, keep policy)
|
| 287 |
+
- `subagents.enabled` - Master switch for subagent delegation
|
| 288 |
+
- `memory` - Memory system (enabled, storage_path, debounce_seconds, model_name, max_facts, fact_confidence_threshold, injection_enabled, max_injection_tokens)
|
| 289 |
+
|
| 290 |
+
**`extensions_config.json`**:
|
| 291 |
+
- `mcpServers` - Map of server name → config (enabled, type, command, args, env, url, headers, oauth, description)
|
| 292 |
+
- `skills` - Map of skill name → state (enabled)
|
| 293 |
+
|
| 294 |
+
Both can be modified at runtime via Gateway API endpoints or `DeerFlowClient` methods.
|
| 295 |
+
|
| 296 |
+
### Embedded Client (`src/client.py`)
|
| 297 |
+
|
| 298 |
+
`DeerFlowClient` provides direct in-process access to all DeerFlow capabilities without HTTP services. All return types align with the Gateway API response schemas, so consumer code works identically in HTTP and embedded modes.
|
| 299 |
+
|
| 300 |
+
**Architecture**: Imports the same `src/` modules that LangGraph Server and Gateway API use. Shares the same config files and data directories. No FastAPI dependency.
|
| 301 |
+
|
| 302 |
+
**Agent Conversation** (replaces LangGraph Server):
|
| 303 |
+
- `chat(message, thread_id)` — synchronous, returns final text
|
| 304 |
+
- `stream(message, thread_id)` — yields `StreamEvent` aligned with LangGraph SSE protocol:
|
| 305 |
+
- `"values"` — full state snapshot (title, messages, artifacts)
|
| 306 |
+
- `"messages-tuple"` — per-message update (AI text, tool calls, tool results)
|
| 307 |
+
- `"end"` — stream finished
|
| 308 |
+
- Agent created lazily via `create_agent()` + `_build_middlewares()`, same as `make_lead_agent`
|
| 309 |
+
- Supports `checkpointer` parameter for state persistence across turns
|
| 310 |
+
- `reset_agent()` forces agent recreation (e.g. after memory or skill changes)
|
| 311 |
+
|
| 312 |
+
**Gateway Equivalent Methods** (replaces Gateway API):
|
| 313 |
+
|
| 314 |
+
| Category | Methods | Return format |
|
| 315 |
+
|----------|---------|---------------|
|
| 316 |
+
| Models | `list_models()`, `get_model(name)` | `{"models": [...]}`, `{name, display_name, ...}` |
|
| 317 |
+
| MCP | `get_mcp_config()`, `update_mcp_config(servers)` | `{"mcp_servers": {...}}` |
|
| 318 |
+
| Skills | `list_skills()`, `get_skill(name)`, `update_skill(name, enabled)`, `install_skill(path)` | `{"skills": [...]}` |
|
| 319 |
+
| Memory | `get_memory()`, `reload_memory()`, `get_memory_config()`, `get_memory_status()` | dict |
|
| 320 |
+
| Uploads | `upload_files(thread_id, files)`, `list_uploads(thread_id)`, `delete_upload(thread_id, filename)` | `{"success": true, "files": [...]}`, `{"files": [...], "count": N}` |
|
| 321 |
+
| Artifacts | `get_artifact(thread_id, path)` → `(bytes, mime_type)` | tuple |
|
| 322 |
+
|
| 323 |
+
**Key difference from Gateway**: Upload accepts local `Path` objects instead of HTTP `UploadFile`. Artifact returns `(bytes, mime_type)` instead of HTTP Response. `update_mcp_config()` and `update_skill()` automatically invalidate the cached agent.
|
| 324 |
+
|
| 325 |
+
**Tests**: `tests/test_client.py` (77 unit tests including `TestGatewayConformance`), `tests/test_client_live.py` (live integration tests, requires config.yaml)
|
| 326 |
+
|
| 327 |
+
**Gateway Conformance Tests** (`TestGatewayConformance`): Validate that every dict-returning client method conforms to the corresponding Gateway Pydantic response model. Each test parses the client output through the Gateway model — if Gateway adds a required field that the client doesn't provide, Pydantic raises `ValidationError` and CI catches the drift. Covers: `ModelsListResponse`, `ModelResponse`, `SkillsListResponse`, `SkillResponse`, `SkillInstallResponse`, `McpConfigResponse`, `UploadResponse`, `MemoryConfigResponse`, `MemoryStatusResponse`.
|
| 328 |
+
|
| 329 |
+
## Development Workflow
|
| 330 |
+
|
| 331 |
+
### Test-Driven Development (TDD) — MANDATORY
|
| 332 |
+
|
| 333 |
+
**Every new feature or bug fix MUST be accompanied by unit tests. No exceptions.**
|
| 334 |
+
|
| 335 |
+
- Write tests in `backend/tests/` following the existing naming convention `test_<feature>.py`
|
| 336 |
+
- Run the full suite before and after your change: `make test`
|
| 337 |
+
- Tests must pass before a feature is considered complete
|
| 338 |
+
- For lightweight config/utility modules, prefer pure unit tests with no external dependencies
|
| 339 |
+
- If a module causes circular import issues in tests, add a `sys.modules` mock in `tests/conftest.py` (see existing example for `src.subagents.executor`)
|
| 340 |
+
|
| 341 |
+
```bash
|
| 342 |
+
# Run all tests
|
| 343 |
+
make test
|
| 344 |
+
|
| 345 |
+
# Run a specific test file
|
| 346 |
+
PYTHONPATH=. uv run pytest tests/test_<feature>.py -v
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
### Running the Full Application
|
| 350 |
+
|
| 351 |
+
From the **project root** directory:
|
| 352 |
+
```bash
|
| 353 |
+
make dev
|
| 354 |
+
```
|
| 355 |
+
|
| 356 |
+
This starts all services and makes the application available at `http://localhost:2026`.
|
| 357 |
+
|
| 358 |
+
**Nginx routing**:
|
| 359 |
+
- `/api/langgraph/*` → LangGraph Server (2024)
|
| 360 |
+
- `/api/*` (other) → Gateway API (8001)
|
| 361 |
+
- `/` (non-API) → Frontend (3000)
|
| 362 |
+
|
| 363 |
+
### Running Backend Services Separately
|
| 364 |
+
|
| 365 |
+
From the **backend** directory:
|
| 366 |
+
|
| 367 |
+
```bash
|
| 368 |
+
# Terminal 1: LangGraph server
|
| 369 |
+
make dev
|
| 370 |
+
|
| 371 |
+
# Terminal 2: Gateway API
|
| 372 |
+
make gateway
|
| 373 |
+
```
|
| 374 |
+
|
| 375 |
+
Direct access (without nginx):
|
| 376 |
+
- LangGraph: `http://localhost:2024`
|
| 377 |
+
- Gateway: `http://localhost:8001`
|
| 378 |
+
|
| 379 |
+
### Frontend Configuration
|
| 380 |
+
|
| 381 |
+
The frontend uses environment variables to connect to backend services:
|
| 382 |
+
- `NEXT_PUBLIC_LANGGRAPH_BASE_URL` - Defaults to `/api/langgraph` (through nginx)
|
| 383 |
+
- `NEXT_PUBLIC_BACKEND_BASE_URL` - Defaults to empty string (through nginx)
|
| 384 |
+
|
| 385 |
+
When using `make dev` from root, the frontend automatically connects through nginx.
|
| 386 |
+
|
| 387 |
+
## Key Features
|
| 388 |
+
|
| 389 |
+
### File Upload
|
| 390 |
+
|
| 391 |
+
Multi-file upload with automatic document conversion:
|
| 392 |
+
- Endpoint: `POST /api/threads/{thread_id}/uploads`
|
| 393 |
+
- Supports: PDF, PPT, Excel, Word documents (converted via `markitdown`)
|
| 394 |
+
- Files stored in thread-isolated directories
|
| 395 |
+
- Agent receives uploaded file list via `UploadsMiddleware`
|
| 396 |
+
|
| 397 |
+
See [docs/FILE_UPLOAD.md](docs/FILE_UPLOAD.md) for details.
|
| 398 |
+
|
| 399 |
+
### Plan Mode
|
| 400 |
+
|
| 401 |
+
TodoList middleware for complex multi-step tasks:
|
| 402 |
+
- Controlled via runtime config: `config.configurable.is_plan_mode = True`
|
| 403 |
+
- Provides `write_todos` tool for task tracking
|
| 404 |
+
- One task in_progress at a time, real-time updates
|
| 405 |
+
|
| 406 |
+
See [docs/plan_mode_usage.md](docs/plan_mode_usage.md) for details.
|
| 407 |
+
|
| 408 |
+
### Context Summarization
|
| 409 |
+
|
| 410 |
+
Automatic conversation summarization when approaching token limits:
|
| 411 |
+
- Configured in `config.yaml` under `summarization` key
|
| 412 |
+
- Trigger types: tokens, messages, or fraction of max input
|
| 413 |
+
- Keeps recent messages while summarizing older ones
|
| 414 |
+
|
| 415 |
+
See [docs/summarization.md](docs/summarization.md) for details.
|
| 416 |
+
|
| 417 |
+
### Vision Support
|
| 418 |
+
|
| 419 |
+
For models with `supports_vision: true`:
|
| 420 |
+
- `ViewImageMiddleware` processes images in conversation
|
| 421 |
+
- `view_image_tool` added to agent's toolset
|
| 422 |
+
- Images automatically converted to base64 and injected into state
|
| 423 |
+
|
| 424 |
+
## Code Style
|
| 425 |
+
|
| 426 |
+
- Uses `ruff` for linting and formatting
|
| 427 |
+
- Line length: 240 characters
|
| 428 |
+
- Python 3.12+ with type hints
|
| 429 |
+
- Double quotes, space indentation
|
| 430 |
+
|
| 431 |
+
## Documentation
|
| 432 |
+
|
| 433 |
+
See `docs/` directory for detailed documentation:
|
| 434 |
+
- [CONFIGURATION.md](docs/CONFIGURATION.md) - Configuration options
|
| 435 |
+
- [ARCHITECTURE.md](docs/ARCHITECTURE.md) - Architecture details
|
| 436 |
+
- [API.md](docs/API.md) - API reference
|
| 437 |
+
- [SETUP.md](docs/SETUP.md) - Setup guide
|
| 438 |
+
- [FILE_UPLOAD.md](docs/FILE_UPLOAD.md) - File upload feature
|
| 439 |
+
- [PATH_EXAMPLES.md](docs/PATH_EXAMPLES.md) - Path types and usage
|
| 440 |
+
- [summarization.md](docs/summarization.md) - Context summarization
|
| 441 |
+
- [plan_mode_usage.md](docs/plan_mode_usage.md) - Plan mode with TodoList
|
backend/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to DeerFlow Backend
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing to DeerFlow! This document provides guidelines and instructions for contributing to the backend codebase.
|
| 4 |
+
|
| 5 |
+
## Table of Contents
|
| 6 |
+
|
| 7 |
+
- [Getting Started](#getting-started)
|
| 8 |
+
- [Development Setup](#development-setup)
|
| 9 |
+
- [Project Structure](#project-structure)
|
| 10 |
+
- [Code Style](#code-style)
|
| 11 |
+
- [Making Changes](#making-changes)
|
| 12 |
+
- [Testing](#testing)
|
| 13 |
+
- [Pull Request Process](#pull-request-process)
|
| 14 |
+
- [Architecture Guidelines](#architecture-guidelines)
|
| 15 |
+
|
| 16 |
+
## Getting Started
|
| 17 |
+
|
| 18 |
+
### Prerequisites
|
| 19 |
+
|
| 20 |
+
- Python 3.12 or higher
|
| 21 |
+
- [uv](https://docs.astral.sh/uv/) package manager
|
| 22 |
+
- Git
|
| 23 |
+
- Docker (optional, for Docker sandbox testing)
|
| 24 |
+
|
| 25 |
+
### Fork and Clone
|
| 26 |
+
|
| 27 |
+
1. Fork the repository on GitHub
|
| 28 |
+
2. Clone your fork locally:
|
| 29 |
+
```bash
|
| 30 |
+
git clone https://github.com/YOUR_USERNAME/deer-flow.git
|
| 31 |
+
cd deer-flow
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## Development Setup
|
| 35 |
+
|
| 36 |
+
### Install Dependencies
|
| 37 |
+
|
| 38 |
+
```bash
|
| 39 |
+
# From project root
|
| 40 |
+
cp config.example.yaml config.yaml
|
| 41 |
+
|
| 42 |
+
# Install backend dependencies
|
| 43 |
+
cd backend
|
| 44 |
+
make install
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### Configure Environment
|
| 48 |
+
|
| 49 |
+
Set up your API keys for testing:
|
| 50 |
+
|
| 51 |
+
```bash
|
| 52 |
+
export OPENAI_API_KEY="your-api-key"
|
| 53 |
+
# Add other keys as needed
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
### Run the Development Server
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
# Terminal 1: LangGraph server
|
| 60 |
+
make dev
|
| 61 |
+
|
| 62 |
+
# Terminal 2: Gateway API
|
| 63 |
+
make gateway
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## Project Structure
|
| 67 |
+
|
| 68 |
+
```
|
| 69 |
+
backend/src/
|
| 70 |
+
├── agents/ # Agent system
|
| 71 |
+
│ ├── lead_agent/ # Main agent implementation
|
| 72 |
+
│ │ └── agent.py # Agent factory and creation
|
| 73 |
+
│ ├── middlewares/ # Agent middlewares
|
| 74 |
+
│ │ ├── thread_data_middleware.py
|
| 75 |
+
│ │ ├── sandbox_middleware.py
|
| 76 |
+
│ │ ├── title_middleware.py
|
| 77 |
+
│ │ ├── uploads_middleware.py
|
| 78 |
+
│ │ ├── view_image_middleware.py
|
| 79 |
+
│ │ └── clarification_middleware.py
|
| 80 |
+
│ └── thread_state.py # Thread state definition
|
| 81 |
+
│
|
| 82 |
+
├── gateway/ # FastAPI Gateway
|
| 83 |
+
│ ├── app.py # FastAPI application
|
| 84 |
+
│ └── routers/ # Route handlers
|
| 85 |
+
│ ├── models.py # /api/models endpoints
|
| 86 |
+
│ ├── mcp.py # /api/mcp endpoints
|
| 87 |
+
│ ├── skills.py # /api/skills endpoints
|
| 88 |
+
│ ├── artifacts.py # /api/threads/.../artifacts
|
| 89 |
+
│ └── uploads.py # /api/threads/.../uploads
|
| 90 |
+
│
|
| 91 |
+
├── sandbox/ # Sandbox execution
|
| 92 |
+
│ ├── __init__.py # Sandbox interface
|
| 93 |
+
│ ├── local.py # Local sandbox provider
|
| 94 |
+
│ └── tools.py # Sandbox tools (bash, file ops)
|
| 95 |
+
│
|
| 96 |
+
├── tools/ # Agent tools
|
| 97 |
+
│ └── builtins/ # Built-in tools
|
| 98 |
+
│ ├── present_file_tool.py
|
| 99 |
+
│ ├── ask_clarification_tool.py
|
| 100 |
+
│ └── view_image_tool.py
|
| 101 |
+
│
|
| 102 |
+
├── mcp/ # MCP integration
|
| 103 |
+
│ └── manager.py # MCP server management
|
| 104 |
+
│
|
| 105 |
+
├── models/ # Model system
|
| 106 |
+
│ └── factory.py # Model factory
|
| 107 |
+
│
|
| 108 |
+
├── skills/ # Skills system
|
| 109 |
+
│ └── loader.py # Skills loader
|
| 110 |
+
│
|
| 111 |
+
├── config/ # Configuration
|
| 112 |
+
│ ├── app_config.py # Main app config
|
| 113 |
+
│ ├── extensions_config.py # Extensions config
|
| 114 |
+
│ └── summarization_config.py
|
| 115 |
+
│
|
| 116 |
+
├── community/ # Community tools
|
| 117 |
+
│ ├── tavily/ # Tavily web search
|
| 118 |
+
│ ├── jina/ # Jina web fetch
|
| 119 |
+
│ ├── firecrawl/ # Firecrawl scraping
|
| 120 |
+
│ └── aio_sandbox/ # Docker sandbox
|
| 121 |
+
│
|
| 122 |
+
├── reflection/ # Dynamic loading
|
| 123 |
+
│ └── __init__.py # Module resolution
|
| 124 |
+
│
|
| 125 |
+
└── utils/ # Utilities
|
| 126 |
+
└── __init__.py
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
## Code Style
|
| 130 |
+
|
| 131 |
+
### Linting and Formatting
|
| 132 |
+
|
| 133 |
+
We use `ruff` for both linting and formatting:
|
| 134 |
+
|
| 135 |
+
```bash
|
| 136 |
+
# Check for issues
|
| 137 |
+
make lint
|
| 138 |
+
|
| 139 |
+
# Auto-fix and format
|
| 140 |
+
make format
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
### Style Guidelines
|
| 144 |
+
|
| 145 |
+
- **Line length**: 240 characters maximum
|
| 146 |
+
- **Python version**: 3.12+ features allowed
|
| 147 |
+
- **Type hints**: Use type hints for function signatures
|
| 148 |
+
- **Quotes**: Double quotes for strings
|
| 149 |
+
- **Indentation**: 4 spaces (no tabs)
|
| 150 |
+
- **Imports**: Group by standard library, third-party, local
|
| 151 |
+
|
| 152 |
+
### Docstrings
|
| 153 |
+
|
| 154 |
+
Use docstrings for public functions and classes:
|
| 155 |
+
|
| 156 |
+
```python
|
| 157 |
+
def create_chat_model(name: str, thinking_enabled: bool = False) -> BaseChatModel:
|
| 158 |
+
"""Create a chat model instance from configuration.
|
| 159 |
+
|
| 160 |
+
Args:
|
| 161 |
+
name: The model name as defined in config.yaml
|
| 162 |
+
thinking_enabled: Whether to enable extended thinking
|
| 163 |
+
|
| 164 |
+
Returns:
|
| 165 |
+
A configured LangChain chat model instance
|
| 166 |
+
|
| 167 |
+
Raises:
|
| 168 |
+
ValueError: If the model name is not found in configuration
|
| 169 |
+
"""
|
| 170 |
+
...
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
## Making Changes
|
| 174 |
+
|
| 175 |
+
### Branch Naming
|
| 176 |
+
|
| 177 |
+
Use descriptive branch names:
|
| 178 |
+
|
| 179 |
+
- `feature/add-new-tool` - New features
|
| 180 |
+
- `fix/sandbox-timeout` - Bug fixes
|
| 181 |
+
- `docs/update-readme` - Documentation
|
| 182 |
+
- `refactor/config-system` - Code refactoring
|
| 183 |
+
|
| 184 |
+
### Commit Messages
|
| 185 |
+
|
| 186 |
+
Write clear, concise commit messages:
|
| 187 |
+
|
| 188 |
+
```
|
| 189 |
+
feat: add support for Claude 3.5 model
|
| 190 |
+
|
| 191 |
+
- Add model configuration in config.yaml
|
| 192 |
+
- Update model factory to handle Claude-specific settings
|
| 193 |
+
- Add tests for new model
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
Prefix types:
|
| 197 |
+
- `feat:` - New feature
|
| 198 |
+
- `fix:` - Bug fix
|
| 199 |
+
- `docs:` - Documentation
|
| 200 |
+
- `refactor:` - Code refactoring
|
| 201 |
+
- `test:` - Tests
|
| 202 |
+
- `chore:` - Build/config changes
|
| 203 |
+
|
| 204 |
+
## Testing
|
| 205 |
+
|
| 206 |
+
### Running Tests
|
| 207 |
+
|
| 208 |
+
```bash
|
| 209 |
+
uv run pytest
|
| 210 |
+
```
|
| 211 |
+
|
| 212 |
+
### Writing Tests
|
| 213 |
+
|
| 214 |
+
Place tests in the `tests/` directory mirroring the source structure:
|
| 215 |
+
|
| 216 |
+
```
|
| 217 |
+
tests/
|
| 218 |
+
├── test_models/
|
| 219 |
+
│ └── test_factory.py
|
| 220 |
+
├── test_sandbox/
|
| 221 |
+
│ └── test_local.py
|
| 222 |
+
└── test_gateway/
|
| 223 |
+
└── test_models_router.py
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
Example test:
|
| 227 |
+
|
| 228 |
+
```python
|
| 229 |
+
import pytest
|
| 230 |
+
from src.models.factory import create_chat_model
|
| 231 |
+
|
| 232 |
+
def test_create_chat_model_with_valid_name():
|
| 233 |
+
"""Test that a valid model name creates a model instance."""
|
| 234 |
+
model = create_chat_model("gpt-4")
|
| 235 |
+
assert model is not None
|
| 236 |
+
|
| 237 |
+
def test_create_chat_model_with_invalid_name():
|
| 238 |
+
"""Test that an invalid model name raises ValueError."""
|
| 239 |
+
with pytest.raises(ValueError):
|
| 240 |
+
create_chat_model("nonexistent-model")
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
## Pull Request Process
|
| 244 |
+
|
| 245 |
+
### Before Submitting
|
| 246 |
+
|
| 247 |
+
1. **Ensure tests pass**: `uv run pytest`
|
| 248 |
+
2. **Run linter**: `make lint`
|
| 249 |
+
3. **Format code**: `make format`
|
| 250 |
+
4. **Update documentation** if needed
|
| 251 |
+
|
| 252 |
+
### PR Description
|
| 253 |
+
|
| 254 |
+
Include in your PR description:
|
| 255 |
+
|
| 256 |
+
- **What**: Brief description of changes
|
| 257 |
+
- **Why**: Motivation for the change
|
| 258 |
+
- **How**: Implementation approach
|
| 259 |
+
- **Testing**: How you tested the changes
|
| 260 |
+
|
| 261 |
+
### Review Process
|
| 262 |
+
|
| 263 |
+
1. Submit PR with clear description
|
| 264 |
+
2. Address review feedback
|
| 265 |
+
3. Ensure CI passes
|
| 266 |
+
4. Maintainer will merge when approved
|
| 267 |
+
|
| 268 |
+
## Architecture Guidelines
|
| 269 |
+
|
| 270 |
+
### Adding New Tools
|
| 271 |
+
|
| 272 |
+
1. Create tool in `src/tools/builtins/` or `src/community/`:
|
| 273 |
+
|
| 274 |
+
```python
|
| 275 |
+
# src/tools/builtins/my_tool.py
|
| 276 |
+
from langchain_core.tools import tool
|
| 277 |
+
|
| 278 |
+
@tool
|
| 279 |
+
def my_tool(param: str) -> str:
|
| 280 |
+
"""Tool description for the agent.
|
| 281 |
+
|
| 282 |
+
Args:
|
| 283 |
+
param: Description of the parameter
|
| 284 |
+
|
| 285 |
+
Returns:
|
| 286 |
+
Description of return value
|
| 287 |
+
"""
|
| 288 |
+
return f"Result: {param}"
|
| 289 |
+
```
|
| 290 |
+
|
| 291 |
+
2. Register in `config.yaml`:
|
| 292 |
+
|
| 293 |
+
```yaml
|
| 294 |
+
tools:
|
| 295 |
+
- name: my_tool
|
| 296 |
+
group: my_group
|
| 297 |
+
use: src.tools.builtins.my_tool:my_tool
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
### Adding New Middleware
|
| 301 |
+
|
| 302 |
+
1. Create middleware in `src/agents/middlewares/`:
|
| 303 |
+
|
| 304 |
+
```python
|
| 305 |
+
# src/agents/middlewares/my_middleware.py
|
| 306 |
+
from langchain.agents.middleware import BaseMiddleware
|
| 307 |
+
from langchain_core.runnables import RunnableConfig
|
| 308 |
+
|
| 309 |
+
class MyMiddleware(BaseMiddleware):
|
| 310 |
+
"""Middleware description."""
|
| 311 |
+
|
| 312 |
+
def transform_state(self, state: dict, config: RunnableConfig) -> dict:
|
| 313 |
+
"""Transform the state before agent execution."""
|
| 314 |
+
# Modify state as needed
|
| 315 |
+
return state
|
| 316 |
+
```
|
| 317 |
+
|
| 318 |
+
2. Register in `src/agents/lead_agent/agent.py`:
|
| 319 |
+
|
| 320 |
+
```python
|
| 321 |
+
middlewares = [
|
| 322 |
+
ThreadDataMiddleware(),
|
| 323 |
+
SandboxMiddleware(),
|
| 324 |
+
MyMiddleware(), # Add your middleware
|
| 325 |
+
TitleMiddleware(),
|
| 326 |
+
ClarificationMiddleware(),
|
| 327 |
+
]
|
| 328 |
+
```
|
| 329 |
+
|
| 330 |
+
### Adding New API Endpoints
|
| 331 |
+
|
| 332 |
+
1. Create router in `src/gateway/routers/`:
|
| 333 |
+
|
| 334 |
+
```python
|
| 335 |
+
# src/gateway/routers/my_router.py
|
| 336 |
+
from fastapi import APIRouter
|
| 337 |
+
|
| 338 |
+
router = APIRouter(prefix="/my-endpoint", tags=["my-endpoint"])
|
| 339 |
+
|
| 340 |
+
@router.get("/")
|
| 341 |
+
async def get_items():
|
| 342 |
+
"""Get all items."""
|
| 343 |
+
return {"items": []}
|
| 344 |
+
|
| 345 |
+
@router.post("/")
|
| 346 |
+
async def create_item(data: dict):
|
| 347 |
+
"""Create a new item."""
|
| 348 |
+
return {"created": data}
|
| 349 |
+
```
|
| 350 |
+
|
| 351 |
+
2. Register in `src/gateway/app.py`:
|
| 352 |
+
|
| 353 |
+
```python
|
| 354 |
+
from src.gateway.routers import my_router
|
| 355 |
+
|
| 356 |
+
app.include_router(my_router.router)
|
| 357 |
+
```
|
| 358 |
+
|
| 359 |
+
### Configuration Changes
|
| 360 |
+
|
| 361 |
+
When adding new configuration options:
|
| 362 |
+
|
| 363 |
+
1. Update `src/config/app_config.py` with new fields
|
| 364 |
+
2. Add default values in `config.example.yaml`
|
| 365 |
+
3. Document in `docs/CONFIGURATION.md`
|
| 366 |
+
|
| 367 |
+
### MCP Server Integration
|
| 368 |
+
|
| 369 |
+
To add support for a new MCP server:
|
| 370 |
+
|
| 371 |
+
1. Add configuration in `extensions_config.json`:
|
| 372 |
+
|
| 373 |
+
```json
|
| 374 |
+
{
|
| 375 |
+
"mcpServers": {
|
| 376 |
+
"my-server": {
|
| 377 |
+
"enabled": true,
|
| 378 |
+
"type": "stdio",
|
| 379 |
+
"command": "npx",
|
| 380 |
+
"args": ["-y", "@my-org/mcp-server"],
|
| 381 |
+
"description": "My MCP Server"
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
```
|
| 386 |
+
|
| 387 |
+
2. Update `extensions_config.example.json` with the new server
|
| 388 |
+
|
| 389 |
+
### Skills Development
|
| 390 |
+
|
| 391 |
+
To create a new skill:
|
| 392 |
+
|
| 393 |
+
1. Create directory in `skills/public/` or `skills/custom/`:
|
| 394 |
+
|
| 395 |
+
```
|
| 396 |
+
skills/public/my-skill/
|
| 397 |
+
└── SKILL.md
|
| 398 |
+
```
|
| 399 |
+
|
| 400 |
+
2. Write `SKILL.md` with YAML front matter:
|
| 401 |
+
|
| 402 |
+
```markdown
|
| 403 |
+
---
|
| 404 |
+
name: My Skill
|
| 405 |
+
description: What this skill does
|
| 406 |
+
license: MIT
|
| 407 |
+
allowed-tools:
|
| 408 |
+
- read_file
|
| 409 |
+
- write_file
|
| 410 |
+
- bash
|
| 411 |
+
---
|
| 412 |
+
|
| 413 |
+
# My Skill
|
| 414 |
+
|
| 415 |
+
Instructions for the agent when this skill is enabled...
|
| 416 |
+
```
|
| 417 |
+
|
| 418 |
+
## Questions?
|
| 419 |
+
|
| 420 |
+
If you have questions about contributing:
|
| 421 |
+
|
| 422 |
+
1. Check existing documentation in `docs/`
|
| 423 |
+
2. Look for similar issues or PRs on GitHub
|
| 424 |
+
3. Open a discussion or issue on GitHub
|
| 425 |
+
|
| 426 |
+
Thank you for contributing to DeerFlow!
|
backend/Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Backend Development Dockerfile
|
| 2 |
+
FROM python:3.12-slim
|
| 3 |
+
|
| 4 |
+
# Install system dependencies
|
| 5 |
+
RUN apt-get update && apt-get install -y \
|
| 6 |
+
curl \
|
| 7 |
+
build-essential \
|
| 8 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 9 |
+
|
| 10 |
+
# Install uv
|
| 11 |
+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
| 12 |
+
ENV PATH="/root/.local/bin:$PATH"
|
| 13 |
+
|
| 14 |
+
# Set working directory
|
| 15 |
+
WORKDIR /app
|
| 16 |
+
|
| 17 |
+
# Copy frontend source code
|
| 18 |
+
COPY backend ./backend
|
| 19 |
+
|
| 20 |
+
# Install dependencies with cache mount
|
| 21 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 22 |
+
sh -c "cd backend && uv sync"
|
| 23 |
+
|
| 24 |
+
# Expose ports (gateway: 8001, langgraph: 2024)
|
| 25 |
+
EXPOSE 8001 2024
|
| 26 |
+
|
| 27 |
+
# Default command (can be overridden in docker-compose)
|
| 28 |
+
CMD ["sh", "-c", "uv run uvicorn src.gateway.app:app --host 0.0.0.0 --port 8001"]
|
backend/Makefile
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
install:
|
| 2 |
+
uv sync
|
| 3 |
+
|
| 4 |
+
dev:
|
| 5 |
+
uv run langgraph dev --no-browser --allow-blocking --no-reload
|
| 6 |
+
|
| 7 |
+
gateway:
|
| 8 |
+
uv run uvicorn src.gateway.app:app --host 0.0.0.0 --port 8001
|
| 9 |
+
|
| 10 |
+
test:
|
| 11 |
+
PYTHONPATH=. uv run pytest tests/ -v
|
| 12 |
+
|
| 13 |
+
lint:
|
| 14 |
+
uvx ruff check .
|
| 15 |
+
|
| 16 |
+
format:
|
| 17 |
+
uvx ruff check . --fix && uvx ruff format .
|
backend/README.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DeerFlow Backend
|
| 2 |
+
|
| 3 |
+
DeerFlow is a LangGraph-based AI super agent with sandbox execution, persistent memory, and extensible tool integration. The backend enables AI agents to execute code, browse the web, manage files, delegate tasks to subagents, and retain context across conversations - all in isolated, per-thread environments.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Architecture
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
┌──────────────────────────────────────┐
|
| 11 |
+
│ Nginx (Port 2026) │
|
| 12 |
+
│ Unified reverse proxy │
|
| 13 |
+
└───────┬──────────────────┬───────────┘
|
| 14 |
+
│ │
|
| 15 |
+
/api/langgraph/* │ │ /api/* (other)
|
| 16 |
+
▼ ▼
|
| 17 |
+
┌────────────────────┐ ┌────────────────────────┐
|
| 18 |
+
│ LangGraph Server │ │ Gateway API (8001) │
|
| 19 |
+
│ (Port 2024) │ │ FastAPI REST │
|
| 20 |
+
│ │ │ │
|
| 21 |
+
│ ┌────────────────┐ │ │ Models, MCP, Skills, │
|
| 22 |
+
│ │ Lead Agent │ │ │ Memory, Uploads, │
|
| 23 |
+
│ │ ┌──────────┐ │ │ │ Artifacts │
|
| 24 |
+
│ │ │Middleware│ │ │ └────────────────────────┘
|
| 25 |
+
│ │ │ Chain │ │ │
|
| 26 |
+
│ │ └──────────┘ │ │
|
| 27 |
+
│ │ ┌──────────┐ │ │
|
| 28 |
+
│ │ │ Tools │ │ │
|
| 29 |
+
│ │ └──────────┘ │ │
|
| 30 |
+
│ │ ┌──────────┐ │ │
|
| 31 |
+
│ │ │Subagents │ │ │
|
| 32 |
+
│ │ └──────────┘ │ │
|
| 33 |
+
│ └────────────────┘ │
|
| 34 |
+
└────────────────────┘
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
**Request Routing** (via Nginx):
|
| 38 |
+
- `/api/langgraph/*` → LangGraph Server - agent interactions, threads, streaming
|
| 39 |
+
- `/api/*` (other) → Gateway API - models, MCP, skills, memory, artifacts, uploads
|
| 40 |
+
- `/` (non-API) → Frontend - Next.js web interface
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## Core Components
|
| 45 |
+
|
| 46 |
+
### Lead Agent
|
| 47 |
+
|
| 48 |
+
The single LangGraph agent (`lead_agent`) is the runtime entry point, created via `make_lead_agent(config)`. It combines:
|
| 49 |
+
|
| 50 |
+
- **Dynamic model selection** with thinking and vision support
|
| 51 |
+
- **Middleware chain** for cross-cutting concerns (9 middlewares)
|
| 52 |
+
- **Tool system** with sandbox, MCP, community, and built-in tools
|
| 53 |
+
- **Subagent delegation** for parallel task execution
|
| 54 |
+
- **System prompt** with skills injection, memory context, and working directory guidance
|
| 55 |
+
|
| 56 |
+
### Middleware Chain
|
| 57 |
+
|
| 58 |
+
Middlewares execute in strict order, each handling a specific concern:
|
| 59 |
+
|
| 60 |
+
| # | Middleware | Purpose |
|
| 61 |
+
|---|-----------|---------|
|
| 62 |
+
| 1 | **ThreadDataMiddleware** | Creates per-thread isolated directories (workspace, uploads, outputs) |
|
| 63 |
+
| 2 | **UploadsMiddleware** | Injects newly uploaded files into conversation context |
|
| 64 |
+
| 3 | **SandboxMiddleware** | Acquires sandbox environment for code execution |
|
| 65 |
+
| 4 | **SummarizationMiddleware** | Reduces context when approaching token limits (optional) |
|
| 66 |
+
| 5 | **TodoListMiddleware** | Tracks multi-step tasks in plan mode (optional) |
|
| 67 |
+
| 6 | **TitleMiddleware** | Auto-generates conversation titles after first exchange |
|
| 68 |
+
| 7 | **MemoryMiddleware** | Queues conversations for async memory extraction |
|
| 69 |
+
| 8 | **ViewImageMiddleware** | Injects image data for vision-capable models (conditional) |
|
| 70 |
+
| 9 | **ClarificationMiddleware** | Intercepts clarification requests and interrupts execution (must be last) |
|
| 71 |
+
|
| 72 |
+
### Sandbox System
|
| 73 |
+
|
| 74 |
+
Per-thread isolated execution with virtual path translation:
|
| 75 |
+
|
| 76 |
+
- **Abstract interface**: `execute_command`, `read_file`, `write_file`, `list_dir`
|
| 77 |
+
- **Providers**: `LocalSandboxProvider` (filesystem) and `AioSandboxProvider` (Docker, in community/)
|
| 78 |
+
- **Virtual paths**: `/mnt/user-data/{workspace,uploads,outputs}` → thread-specific physical directories
|
| 79 |
+
- **Skills path**: `/mnt/skills` → `deer-flow/skills/` directory
|
| 80 |
+
- **Tools**: `bash`, `ls`, `read_file`, `write_file`, `str_replace`
|
| 81 |
+
|
| 82 |
+
### Subagent System
|
| 83 |
+
|
| 84 |
+
Async task delegation with concurrent execution:
|
| 85 |
+
|
| 86 |
+
- **Built-in agents**: `general-purpose` (full toolset) and `bash` (command specialist)
|
| 87 |
+
- **Concurrency**: Max 3 subagents per turn, 15-minute timeout
|
| 88 |
+
- **Execution**: Background thread pools with status tracking and SSE events
|
| 89 |
+
- **Flow**: Agent calls `task()` tool → executor runs subagent in background → polls for completion → returns result
|
| 90 |
+
|
| 91 |
+
### Memory System
|
| 92 |
+
|
| 93 |
+
LLM-powered persistent context retention across conversations:
|
| 94 |
+
|
| 95 |
+
- **Automatic extraction**: Analyzes conversations for user context, facts, and preferences
|
| 96 |
+
- **Structured storage**: User context (work, personal, top-of-mind), history, and confidence-scored facts
|
| 97 |
+
- **Debounced updates**: Batches updates to minimize LLM calls (configurable wait time)
|
| 98 |
+
- **System prompt injection**: Top facts + context injected into agent prompts
|
| 99 |
+
- **Storage**: JSON file with mtime-based cache invalidation
|
| 100 |
+
|
| 101 |
+
### Tool Ecosystem
|
| 102 |
+
|
| 103 |
+
| Category | Tools |
|
| 104 |
+
|----------|-------|
|
| 105 |
+
| **Sandbox** | `bash`, `ls`, `read_file`, `write_file`, `str_replace` |
|
| 106 |
+
| **Built-in** | `present_files`, `ask_clarification`, `view_image`, `task` (subagent) |
|
| 107 |
+
| **Community** | Tavily (web search), Jina AI (web fetch), Firecrawl (scraping), DuckDuckGo (image search) |
|
| 108 |
+
| **MCP** | Any Model Context Protocol server (stdio, SSE, HTTP transports) |
|
| 109 |
+
| **Skills** | Domain-specific workflows injected via system prompt |
|
| 110 |
+
|
| 111 |
+
### Gateway API
|
| 112 |
+
|
| 113 |
+
FastAPI application providing REST endpoints for frontend integration:
|
| 114 |
+
|
| 115 |
+
| Route | Purpose |
|
| 116 |
+
|-------|---------|
|
| 117 |
+
| `GET /api/models` | List available LLM models |
|
| 118 |
+
| `GET/PUT /api/mcp/config` | Manage MCP server configurations |
|
| 119 |
+
| `GET/PUT /api/skills` | List and manage skills |
|
| 120 |
+
| `POST /api/skills/install` | Install skill from `.skill` archive |
|
| 121 |
+
| `GET /api/memory` | Retrieve memory data |
|
| 122 |
+
| `POST /api/memory/reload` | Force memory reload |
|
| 123 |
+
| `GET /api/memory/config` | Memory configuration |
|
| 124 |
+
| `GET /api/memory/status` | Combined config + data |
|
| 125 |
+
| `POST /api/threads/{id}/uploads` | Upload files (auto-converts PDF/PPT/Excel/Word to Markdown) |
|
| 126 |
+
| `GET /api/threads/{id}/uploads/list` | List uploaded files |
|
| 127 |
+
| `GET /api/threads/{id}/artifacts/{path}` | Serve generated artifacts |
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## Quick Start
|
| 132 |
+
|
| 133 |
+
### Prerequisites
|
| 134 |
+
|
| 135 |
+
- Python 3.12+
|
| 136 |
+
- [uv](https://docs.astral.sh/uv/) package manager
|
| 137 |
+
- API keys for your chosen LLM provider
|
| 138 |
+
|
| 139 |
+
### Installation
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
cd deer-flow
|
| 143 |
+
|
| 144 |
+
# Copy configuration files
|
| 145 |
+
cp config.example.yaml config.yaml
|
| 146 |
+
|
| 147 |
+
# Install backend dependencies
|
| 148 |
+
cd backend
|
| 149 |
+
make install
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
### Configuration
|
| 153 |
+
|
| 154 |
+
Edit `config.yaml` in the project root:
|
| 155 |
+
|
| 156 |
+
```yaml
|
| 157 |
+
models:
|
| 158 |
+
- name: gpt-4o
|
| 159 |
+
display_name: GPT-4o
|
| 160 |
+
use: langchain_openai:ChatOpenAI
|
| 161 |
+
model: gpt-4o
|
| 162 |
+
api_key: $OPENAI_API_KEY
|
| 163 |
+
supports_thinking: false
|
| 164 |
+
supports_vision: true
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
Set your API keys:
|
| 168 |
+
|
| 169 |
+
```bash
|
| 170 |
+
export OPENAI_API_KEY="your-api-key-here"
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
### Running
|
| 174 |
+
|
| 175 |
+
**Full Application** (from project root):
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
make dev # Starts LangGraph + Gateway + Frontend + Nginx
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
Access at: http://localhost:2026
|
| 182 |
+
|
| 183 |
+
**Backend Only** (from backend directory):
|
| 184 |
+
|
| 185 |
+
```bash
|
| 186 |
+
# Terminal 1: LangGraph server
|
| 187 |
+
make dev
|
| 188 |
+
|
| 189 |
+
# Terminal 2: Gateway API
|
| 190 |
+
make gateway
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
Direct access: LangGraph at http://localhost:2024, Gateway at http://localhost:8001
|
| 194 |
+
|
| 195 |
+
---
|
| 196 |
+
|
| 197 |
+
## Project Structure
|
| 198 |
+
|
| 199 |
+
```
|
| 200 |
+
backend/
|
| 201 |
+
├── src/
|
| 202 |
+
│ ├── agents/ # Agent system
|
| 203 |
+
│ │ ├── lead_agent/ # Main agent (factory, prompts)
|
| 204 |
+
│ │ ├── middlewares/ # 9 middleware components
|
| 205 |
+
│ │ ├── memory/ # Memory extraction & storage
|
| 206 |
+
│ │ └── thread_state.py # ThreadState schema
|
| 207 |
+
│ ├── gateway/ # FastAPI Gateway API
|
| 208 |
+
│ │ ├── app.py # Application setup
|
| 209 |
+
│ │ └── routers/ # 6 route modules
|
| 210 |
+
│ ├── sandbox/ # Sandbox execution
|
| 211 |
+
│ │ ├── local/ # Local filesystem provider
|
| 212 |
+
│ │ ├── sandbox.py # Abstract interface
|
| 213 |
+
│ │ ├── tools.py # bash, ls, read/write/str_replace
|
| 214 |
+
│ │ └── middleware.py # Sandbox lifecycle
|
| 215 |
+
│ ├── subagents/ # Subagent delegation
|
| 216 |
+
│ │ ├── builtins/ # general-purpose, bash agents
|
| 217 |
+
│ │ ├── executor.py # Background execution engine
|
| 218 |
+
│ │ └── registry.py # Agent registry
|
| 219 |
+
│ ├── tools/builtins/ # Built-in tools
|
| 220 |
+
│ ├── mcp/ # MCP protocol integration
|
| 221 |
+
│ ├── models/ # Model factory
|
| 222 |
+
│ ├── skills/ # Skill discovery & loading
|
| 223 |
+
│ ├── config/ # Configuration system
|
| 224 |
+
│ ├── community/ # Community tools & providers
|
| 225 |
+
│ ├── reflection/ # Dynamic module loading
|
| 226 |
+
│ └── utils/ # Utilities
|
| 227 |
+
├── docs/ # Documentation
|
| 228 |
+
├── tests/ # Test suite
|
| 229 |
+
├── langgraph.json # LangGraph server configuration
|
| 230 |
+
├── pyproject.toml # Python dependencies
|
| 231 |
+
├── Makefile # Development commands
|
| 232 |
+
└── Dockerfile # Container build
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
---
|
| 236 |
+
|
| 237 |
+
## Configuration
|
| 238 |
+
|
| 239 |
+
### Main Configuration (`config.yaml`)
|
| 240 |
+
|
| 241 |
+
Place in project root. Config values starting with `$` resolve as environment variables.
|
| 242 |
+
|
| 243 |
+
Key sections:
|
| 244 |
+
- `models` - LLM configurations with class paths, API keys, thinking/vision flags
|
| 245 |
+
- `tools` - Tool definitions with module paths and groups
|
| 246 |
+
- `tool_groups` - Logical tool groupings
|
| 247 |
+
- `sandbox` - Execution environment provider
|
| 248 |
+
- `skills` - Skills directory paths
|
| 249 |
+
- `title` - Auto-title generation settings
|
| 250 |
+
- `summarization` - Context summarization settings
|
| 251 |
+
- `subagents` - Subagent system (enabled/disabled)
|
| 252 |
+
- `memory` - Memory system settings (enabled, storage, debounce, facts limits)
|
| 253 |
+
|
| 254 |
+
### Extensions Configuration (`extensions_config.json`)
|
| 255 |
+
|
| 256 |
+
MCP servers and skill states in a single file:
|
| 257 |
+
|
| 258 |
+
```json
|
| 259 |
+
{
|
| 260 |
+
"mcpServers": {
|
| 261 |
+
"github": {
|
| 262 |
+
"enabled": true,
|
| 263 |
+
"type": "stdio",
|
| 264 |
+
"command": "npx",
|
| 265 |
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
| 266 |
+
"env": {"GITHUB_TOKEN": "$GITHUB_TOKEN"}
|
| 267 |
+
},
|
| 268 |
+
"secure-http": {
|
| 269 |
+
"enabled": true,
|
| 270 |
+
"type": "http",
|
| 271 |
+
"url": "https://api.example.com/mcp",
|
| 272 |
+
"oauth": {
|
| 273 |
+
"enabled": true,
|
| 274 |
+
"token_url": "https://auth.example.com/oauth/token",
|
| 275 |
+
"grant_type": "client_credentials",
|
| 276 |
+
"client_id": "$MCP_OAUTH_CLIENT_ID",
|
| 277 |
+
"client_secret": "$MCP_OAUTH_CLIENT_SECRET"
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
},
|
| 281 |
+
"skills": {
|
| 282 |
+
"pdf-processing": {"enabled": true}
|
| 283 |
+
}
|
| 284 |
+
}
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
### Environment Variables
|
| 288 |
+
|
| 289 |
+
- `DEER_FLOW_CONFIG_PATH` - Override config.yaml location
|
| 290 |
+
- `DEER_FLOW_EXTENSIONS_CONFIG_PATH` - Override extensions_config.json location
|
| 291 |
+
- Model API keys: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, etc.
|
| 292 |
+
- Tool API keys: `TAVILY_API_KEY`, `GITHUB_TOKEN`, etc.
|
| 293 |
+
|
| 294 |
+
---
|
| 295 |
+
|
| 296 |
+
## Development
|
| 297 |
+
|
| 298 |
+
### Commands
|
| 299 |
+
|
| 300 |
+
```bash
|
| 301 |
+
make install # Install dependencies
|
| 302 |
+
make dev # Run LangGraph server (port 2024)
|
| 303 |
+
make gateway # Run Gateway API (port 8001)
|
| 304 |
+
make lint # Run linter (ruff)
|
| 305 |
+
make format # Format code (ruff)
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
### Code Style
|
| 309 |
+
|
| 310 |
+
- **Linter/Formatter**: `ruff`
|
| 311 |
+
- **Line length**: 240 characters
|
| 312 |
+
- **Python**: 3.12+ with type hints
|
| 313 |
+
- **Quotes**: Double quotes
|
| 314 |
+
- **Indentation**: 4 spaces
|
| 315 |
+
|
| 316 |
+
### Testing
|
| 317 |
+
|
| 318 |
+
```bash
|
| 319 |
+
uv run pytest
|
| 320 |
+
```
|
| 321 |
+
|
| 322 |
+
---
|
| 323 |
+
|
| 324 |
+
## Technology Stack
|
| 325 |
+
|
| 326 |
+
- **LangGraph** (1.0.6+) - Agent framework and multi-agent orchestration
|
| 327 |
+
- **LangChain** (1.2.3+) - LLM abstractions and tool system
|
| 328 |
+
- **FastAPI** (0.115.0+) - Gateway REST API
|
| 329 |
+
- **langchain-mcp-adapters** - Model Context Protocol support
|
| 330 |
+
- **agent-sandbox** - Sandboxed code execution
|
| 331 |
+
- **markitdown** - Multi-format document conversion
|
| 332 |
+
- **tavily-python** / **firecrawl-py** - Web search and scraping
|
| 333 |
+
|
| 334 |
+
---
|
| 335 |
+
|
| 336 |
+
## Documentation
|
| 337 |
+
|
| 338 |
+
- [Configuration Guide](docs/CONFIGURATION.md)
|
| 339 |
+
- [Architecture Details](docs/ARCHITECTURE.md)
|
| 340 |
+
- [API Reference](docs/API.md)
|
| 341 |
+
- [File Upload](docs/FILE_UPLOAD.md)
|
| 342 |
+
- [Path Examples](docs/PATH_EXAMPLES.md)
|
| 343 |
+
- [Context Summarization](docs/summarization.md)
|
| 344 |
+
- [Plan Mode](docs/plan_mode_usage.md)
|
| 345 |
+
- [Setup Guide](docs/SETUP.md)
|
| 346 |
+
|
| 347 |
+
---
|
| 348 |
+
|
| 349 |
+
## License
|
| 350 |
+
|
| 351 |
+
See the [LICENSE](../LICENSE) file in the project root.
|
| 352 |
+
|
| 353 |
+
## Contributing
|
| 354 |
+
|
| 355 |
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines.
|
backend/debug.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""
|
| 3 |
+
Debug script for lead_agent.
|
| 4 |
+
Run this file directly in VS Code with breakpoints.
|
| 5 |
+
|
| 6 |
+
Usage:
|
| 7 |
+
1. Set breakpoints in agent.py or other files
|
| 8 |
+
2. Press F5 or use "Run and Debug" panel
|
| 9 |
+
3. Input messages in the terminal to interact with the agent
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import asyncio
|
| 13 |
+
import logging
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
# Ensure we can import from src
|
| 18 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 19 |
+
|
| 20 |
+
# Load environment variables
|
| 21 |
+
from dotenv import load_dotenv
|
| 22 |
+
from langchain_core.messages import HumanMessage
|
| 23 |
+
|
| 24 |
+
from src.agents import make_lead_agent
|
| 25 |
+
|
| 26 |
+
load_dotenv()
|
| 27 |
+
|
| 28 |
+
# Configure logging
|
| 29 |
+
logging.basicConfig(
|
| 30 |
+
level=logging.INFO,
|
| 31 |
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
| 32 |
+
datefmt="%Y-%m-%d %H:%M:%S",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
async def main():
|
| 37 |
+
# Initialize MCP tools at startup
|
| 38 |
+
try:
|
| 39 |
+
from src.mcp import initialize_mcp_tools
|
| 40 |
+
|
| 41 |
+
await initialize_mcp_tools()
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"Warning: Failed to initialize MCP tools: {e}")
|
| 44 |
+
|
| 45 |
+
# Create agent with default config
|
| 46 |
+
config = {
|
| 47 |
+
"configurable": {
|
| 48 |
+
"thread_id": "debug-thread-001",
|
| 49 |
+
"thinking_enabled": True,
|
| 50 |
+
"is_plan_mode": True,
|
| 51 |
+
# Uncomment to use a specific model
|
| 52 |
+
"model_name": "kimi-k2.5",
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
agent = make_lead_agent(config)
|
| 57 |
+
|
| 58 |
+
print("=" * 50)
|
| 59 |
+
print("Lead Agent Debug Mode")
|
| 60 |
+
print("Type 'quit' or 'exit' to stop")
|
| 61 |
+
print("=" * 50)
|
| 62 |
+
|
| 63 |
+
while True:
|
| 64 |
+
try:
|
| 65 |
+
user_input = input("\nYou: ").strip()
|
| 66 |
+
if not user_input:
|
| 67 |
+
continue
|
| 68 |
+
if user_input.lower() in ("quit", "exit"):
|
| 69 |
+
print("Goodbye!")
|
| 70 |
+
break
|
| 71 |
+
|
| 72 |
+
# Invoke the agent
|
| 73 |
+
state = {"messages": [HumanMessage(content=user_input)]}
|
| 74 |
+
result = await agent.ainvoke(state, config=config, context={"thread_id": "debug-thread-001"})
|
| 75 |
+
|
| 76 |
+
# Print the response
|
| 77 |
+
if result.get("messages"):
|
| 78 |
+
last_message = result["messages"][-1]
|
| 79 |
+
print(f"\nAgent: {last_message.content}")
|
| 80 |
+
|
| 81 |
+
except KeyboardInterrupt:
|
| 82 |
+
print("\nInterrupted. Goodbye!")
|
| 83 |
+
break
|
| 84 |
+
except Exception as e:
|
| 85 |
+
print(f"\nError: {e}")
|
| 86 |
+
import traceback
|
| 87 |
+
|
| 88 |
+
traceback.print_exc()
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
asyncio.run(main())
|
backend/docs/API.md
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# API Reference
|
| 2 |
+
|
| 3 |
+
This document provides a complete reference for the DeerFlow backend APIs.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
DeerFlow backend exposes two sets of APIs:
|
| 8 |
+
|
| 9 |
+
1. **LangGraph API** - Agent interactions, threads, and streaming (`/api/langgraph/*`)
|
| 10 |
+
2. **Gateway API** - Models, MCP, skills, uploads, and artifacts (`/api/*`)
|
| 11 |
+
|
| 12 |
+
All APIs are accessed through the Nginx reverse proxy at port 2026.
|
| 13 |
+
|
| 14 |
+
## LangGraph API
|
| 15 |
+
|
| 16 |
+
Base URL: `/api/langgraph`
|
| 17 |
+
|
| 18 |
+
The LangGraph API is provided by the LangGraph server and follows the LangGraph SDK conventions.
|
| 19 |
+
|
| 20 |
+
### Threads
|
| 21 |
+
|
| 22 |
+
#### Create Thread
|
| 23 |
+
|
| 24 |
+
```http
|
| 25 |
+
POST /api/langgraph/threads
|
| 26 |
+
Content-Type: application/json
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
**Request Body:**
|
| 30 |
+
```json
|
| 31 |
+
{
|
| 32 |
+
"metadata": {}
|
| 33 |
+
}
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
**Response:**
|
| 37 |
+
```json
|
| 38 |
+
{
|
| 39 |
+
"thread_id": "abc123",
|
| 40 |
+
"created_at": "2024-01-15T10:30:00Z",
|
| 41 |
+
"metadata": {}
|
| 42 |
+
}
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
#### Get Thread State
|
| 46 |
+
|
| 47 |
+
```http
|
| 48 |
+
GET /api/langgraph/threads/{thread_id}/state
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
**Response:**
|
| 52 |
+
```json
|
| 53 |
+
{
|
| 54 |
+
"values": {
|
| 55 |
+
"messages": [...],
|
| 56 |
+
"sandbox": {...},
|
| 57 |
+
"artifacts": [...],
|
| 58 |
+
"thread_data": {...},
|
| 59 |
+
"title": "Conversation Title"
|
| 60 |
+
},
|
| 61 |
+
"next": [],
|
| 62 |
+
"config": {...}
|
| 63 |
+
}
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### Runs
|
| 67 |
+
|
| 68 |
+
#### Create Run
|
| 69 |
+
|
| 70 |
+
Execute the agent with input.
|
| 71 |
+
|
| 72 |
+
```http
|
| 73 |
+
POST /api/langgraph/threads/{thread_id}/runs
|
| 74 |
+
Content-Type: application/json
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
**Request Body:**
|
| 78 |
+
```json
|
| 79 |
+
{
|
| 80 |
+
"input": {
|
| 81 |
+
"messages": [
|
| 82 |
+
{
|
| 83 |
+
"role": "user",
|
| 84 |
+
"content": "Hello, can you help me?"
|
| 85 |
+
}
|
| 86 |
+
]
|
| 87 |
+
},
|
| 88 |
+
"config": {
|
| 89 |
+
"configurable": {
|
| 90 |
+
"model_name": "gpt-4",
|
| 91 |
+
"thinking_enabled": false,
|
| 92 |
+
"is_plan_mode": false
|
| 93 |
+
}
|
| 94 |
+
},
|
| 95 |
+
"stream_mode": ["values", "messages"]
|
| 96 |
+
}
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
**Configurable Options:**
|
| 100 |
+
- `model_name` (string): Override the default model
|
| 101 |
+
- `thinking_enabled` (boolean): Enable extended thinking for supported models
|
| 102 |
+
- `is_plan_mode` (boolean): Enable TodoList middleware for task tracking
|
| 103 |
+
|
| 104 |
+
**Response:** Server-Sent Events (SSE) stream
|
| 105 |
+
|
| 106 |
+
```
|
| 107 |
+
event: values
|
| 108 |
+
data: {"messages": [...], "title": "..."}
|
| 109 |
+
|
| 110 |
+
event: messages
|
| 111 |
+
data: {"content": "Hello! I'd be happy to help.", "role": "assistant"}
|
| 112 |
+
|
| 113 |
+
event: end
|
| 114 |
+
data: {}
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
#### Get Run History
|
| 118 |
+
|
| 119 |
+
```http
|
| 120 |
+
GET /api/langgraph/threads/{thread_id}/runs
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
**Response:**
|
| 124 |
+
```json
|
| 125 |
+
{
|
| 126 |
+
"runs": [
|
| 127 |
+
{
|
| 128 |
+
"run_id": "run123",
|
| 129 |
+
"status": "success",
|
| 130 |
+
"created_at": "2024-01-15T10:30:00Z"
|
| 131 |
+
}
|
| 132 |
+
]
|
| 133 |
+
}
|
| 134 |
+
```
|
| 135 |
+
|
| 136 |
+
#### Stream Run
|
| 137 |
+
|
| 138 |
+
Stream responses in real-time.
|
| 139 |
+
|
| 140 |
+
```http
|
| 141 |
+
POST /api/langgraph/threads/{thread_id}/runs/stream
|
| 142 |
+
Content-Type: application/json
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
Same request body as Create Run. Returns SSE stream.
|
| 146 |
+
|
| 147 |
+
---
|
| 148 |
+
|
| 149 |
+
## Gateway API
|
| 150 |
+
|
| 151 |
+
Base URL: `/api`
|
| 152 |
+
|
| 153 |
+
### Models
|
| 154 |
+
|
| 155 |
+
#### List Models
|
| 156 |
+
|
| 157 |
+
Get all available LLM models from configuration.
|
| 158 |
+
|
| 159 |
+
```http
|
| 160 |
+
GET /api/models
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
**Response:**
|
| 164 |
+
```json
|
| 165 |
+
{
|
| 166 |
+
"models": [
|
| 167 |
+
{
|
| 168 |
+
"name": "gpt-4",
|
| 169 |
+
"display_name": "GPT-4",
|
| 170 |
+
"supports_thinking": false,
|
| 171 |
+
"supports_vision": true
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"name": "claude-3-opus",
|
| 175 |
+
"display_name": "Claude 3 Opus",
|
| 176 |
+
"supports_thinking": false,
|
| 177 |
+
"supports_vision": true
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"name": "deepseek-v3",
|
| 181 |
+
"display_name": "DeepSeek V3",
|
| 182 |
+
"supports_thinking": true,
|
| 183 |
+
"supports_vision": false
|
| 184 |
+
}
|
| 185 |
+
]
|
| 186 |
+
}
|
| 187 |
+
```
|
| 188 |
+
|
| 189 |
+
#### Get Model Details
|
| 190 |
+
|
| 191 |
+
```http
|
| 192 |
+
GET /api/models/{model_name}
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
**Response:**
|
| 196 |
+
```json
|
| 197 |
+
{
|
| 198 |
+
"name": "gpt-4",
|
| 199 |
+
"display_name": "GPT-4",
|
| 200 |
+
"model": "gpt-4",
|
| 201 |
+
"max_tokens": 4096,
|
| 202 |
+
"supports_thinking": false,
|
| 203 |
+
"supports_vision": true
|
| 204 |
+
}
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
### MCP Configuration
|
| 208 |
+
|
| 209 |
+
#### Get MCP Config
|
| 210 |
+
|
| 211 |
+
Get current MCP server configurations.
|
| 212 |
+
|
| 213 |
+
```http
|
| 214 |
+
GET /api/mcp/config
|
| 215 |
+
```
|
| 216 |
+
|
| 217 |
+
**Response:**
|
| 218 |
+
```json
|
| 219 |
+
{
|
| 220 |
+
"mcpServers": {
|
| 221 |
+
"github": {
|
| 222 |
+
"enabled": true,
|
| 223 |
+
"type": "stdio",
|
| 224 |
+
"command": "npx",
|
| 225 |
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
| 226 |
+
"env": {
|
| 227 |
+
"GITHUB_TOKEN": "***"
|
| 228 |
+
},
|
| 229 |
+
"description": "GitHub operations"
|
| 230 |
+
},
|
| 231 |
+
"filesystem": {
|
| 232 |
+
"enabled": false,
|
| 233 |
+
"type": "stdio",
|
| 234 |
+
"command": "npx",
|
| 235 |
+
"args": ["-y", "@modelcontextprotocol/server-filesystem"],
|
| 236 |
+
"description": "File system access"
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
#### Update MCP Config
|
| 243 |
+
|
| 244 |
+
Update MCP server configurations.
|
| 245 |
+
|
| 246 |
+
```http
|
| 247 |
+
PUT /api/mcp/config
|
| 248 |
+
Content-Type: application/json
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
**Request Body:**
|
| 252 |
+
```json
|
| 253 |
+
{
|
| 254 |
+
"mcpServers": {
|
| 255 |
+
"github": {
|
| 256 |
+
"enabled": true,
|
| 257 |
+
"type": "stdio",
|
| 258 |
+
"command": "npx",
|
| 259 |
+
"args": ["-y", "@modelcontextprotocol/server-github"],
|
| 260 |
+
"env": {
|
| 261 |
+
"GITHUB_TOKEN": "$GITHUB_TOKEN"
|
| 262 |
+
},
|
| 263 |
+
"description": "GitHub operations"
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
}
|
| 267 |
+
```
|
| 268 |
+
|
| 269 |
+
**Response:**
|
| 270 |
+
```json
|
| 271 |
+
{
|
| 272 |
+
"success": true,
|
| 273 |
+
"message": "MCP configuration updated"
|
| 274 |
+
}
|
| 275 |
+
```
|
| 276 |
+
|
| 277 |
+
### Skills
|
| 278 |
+
|
| 279 |
+
#### List Skills
|
| 280 |
+
|
| 281 |
+
Get all available skills.
|
| 282 |
+
|
| 283 |
+
```http
|
| 284 |
+
GET /api/skills
|
| 285 |
+
```
|
| 286 |
+
|
| 287 |
+
**Response:**
|
| 288 |
+
```json
|
| 289 |
+
{
|
| 290 |
+
"skills": [
|
| 291 |
+
{
|
| 292 |
+
"name": "pdf-processing",
|
| 293 |
+
"display_name": "PDF Processing",
|
| 294 |
+
"description": "Handle PDF documents efficiently",
|
| 295 |
+
"enabled": true,
|
| 296 |
+
"license": "MIT",
|
| 297 |
+
"path": "public/pdf-processing"
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"name": "frontend-design",
|
| 301 |
+
"display_name": "Frontend Design",
|
| 302 |
+
"description": "Design and build frontend interfaces",
|
| 303 |
+
"enabled": false,
|
| 304 |
+
"license": "MIT",
|
| 305 |
+
"path": "public/frontend-design"
|
| 306 |
+
}
|
| 307 |
+
]
|
| 308 |
+
}
|
| 309 |
+
```
|
| 310 |
+
|
| 311 |
+
#### Get Skill Details
|
| 312 |
+
|
| 313 |
+
```http
|
| 314 |
+
GET /api/skills/{skill_name}
|
| 315 |
+
```
|
| 316 |
+
|
| 317 |
+
**Response:**
|
| 318 |
+
```json
|
| 319 |
+
{
|
| 320 |
+
"name": "pdf-processing",
|
| 321 |
+
"display_name": "PDF Processing",
|
| 322 |
+
"description": "Handle PDF documents efficiently",
|
| 323 |
+
"enabled": true,
|
| 324 |
+
"license": "MIT",
|
| 325 |
+
"path": "public/pdf-processing",
|
| 326 |
+
"allowed_tools": ["read_file", "write_file", "bash"],
|
| 327 |
+
"content": "# PDF Processing\n\nInstructions for the agent..."
|
| 328 |
+
}
|
| 329 |
+
```
|
| 330 |
+
|
| 331 |
+
#### Enable Skill
|
| 332 |
+
|
| 333 |
+
```http
|
| 334 |
+
POST /api/skills/{skill_name}/enable
|
| 335 |
+
```
|
| 336 |
+
|
| 337 |
+
**Response:**
|
| 338 |
+
```json
|
| 339 |
+
{
|
| 340 |
+
"success": true,
|
| 341 |
+
"message": "Skill 'pdf-processing' enabled"
|
| 342 |
+
}
|
| 343 |
+
```
|
| 344 |
+
|
| 345 |
+
#### Disable Skill
|
| 346 |
+
|
| 347 |
+
```http
|
| 348 |
+
POST /api/skills/{skill_name}/disable
|
| 349 |
+
```
|
| 350 |
+
|
| 351 |
+
**Response:**
|
| 352 |
+
```json
|
| 353 |
+
{
|
| 354 |
+
"success": true,
|
| 355 |
+
"message": "Skill 'pdf-processing' disabled"
|
| 356 |
+
}
|
| 357 |
+
```
|
| 358 |
+
|
| 359 |
+
#### Install Skill
|
| 360 |
+
|
| 361 |
+
Install a skill from a `.skill` file.
|
| 362 |
+
|
| 363 |
+
```http
|
| 364 |
+
POST /api/skills/install
|
| 365 |
+
Content-Type: multipart/form-data
|
| 366 |
+
```
|
| 367 |
+
|
| 368 |
+
**Request Body:**
|
| 369 |
+
- `file`: The `.skill` file to install
|
| 370 |
+
|
| 371 |
+
**Response:**
|
| 372 |
+
```json
|
| 373 |
+
{
|
| 374 |
+
"success": true,
|
| 375 |
+
"message": "Skill 'my-skill' installed successfully",
|
| 376 |
+
"skill": {
|
| 377 |
+
"name": "my-skill",
|
| 378 |
+
"display_name": "My Skill",
|
| 379 |
+
"path": "custom/my-skill"
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
```
|
| 383 |
+
|
| 384 |
+
### File Uploads
|
| 385 |
+
|
| 386 |
+
#### Upload Files
|
| 387 |
+
|
| 388 |
+
Upload one or more files to a thread.
|
| 389 |
+
|
| 390 |
+
```http
|
| 391 |
+
POST /api/threads/{thread_id}/uploads
|
| 392 |
+
Content-Type: multipart/form-data
|
| 393 |
+
```
|
| 394 |
+
|
| 395 |
+
**Request Body:**
|
| 396 |
+
- `files`: One or more files to upload
|
| 397 |
+
|
| 398 |
+
**Response:**
|
| 399 |
+
```json
|
| 400 |
+
{
|
| 401 |
+
"success": true,
|
| 402 |
+
"files": [
|
| 403 |
+
{
|
| 404 |
+
"filename": "document.pdf",
|
| 405 |
+
"size": 1234567,
|
| 406 |
+
"path": ".deer-flow/threads/abc123/user-data/uploads/document.pdf",
|
| 407 |
+
"virtual_path": "/mnt/user-data/uploads/document.pdf",
|
| 408 |
+
"artifact_url": "/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf",
|
| 409 |
+
"markdown_file": "document.md",
|
| 410 |
+
"markdown_path": ".deer-flow/threads/abc123/user-data/uploads/document.md",
|
| 411 |
+
"markdown_virtual_path": "/mnt/user-data/uploads/document.md",
|
| 412 |
+
"markdown_artifact_url": "/api/threads/abc123/artifacts/mnt/user-data/uploads/document.md"
|
| 413 |
+
}
|
| 414 |
+
],
|
| 415 |
+
"message": "Successfully uploaded 1 file(s)"
|
| 416 |
+
}
|
| 417 |
+
```
|
| 418 |
+
|
| 419 |
+
**Supported Document Formats** (auto-converted to Markdown):
|
| 420 |
+
- PDF (`.pdf`)
|
| 421 |
+
- PowerPoint (`.ppt`, `.pptx`)
|
| 422 |
+
- Excel (`.xls`, `.xlsx`)
|
| 423 |
+
- Word (`.doc`, `.docx`)
|
| 424 |
+
|
| 425 |
+
#### List Uploaded Files
|
| 426 |
+
|
| 427 |
+
```http
|
| 428 |
+
GET /api/threads/{thread_id}/uploads/list
|
| 429 |
+
```
|
| 430 |
+
|
| 431 |
+
**Response:**
|
| 432 |
+
```json
|
| 433 |
+
{
|
| 434 |
+
"files": [
|
| 435 |
+
{
|
| 436 |
+
"filename": "document.pdf",
|
| 437 |
+
"size": 1234567,
|
| 438 |
+
"path": ".deer-flow/threads/abc123/user-data/uploads/document.pdf",
|
| 439 |
+
"virtual_path": "/mnt/user-data/uploads/document.pdf",
|
| 440 |
+
"artifact_url": "/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf",
|
| 441 |
+
"extension": ".pdf",
|
| 442 |
+
"modified": 1705997600.0
|
| 443 |
+
}
|
| 444 |
+
],
|
| 445 |
+
"count": 1
|
| 446 |
+
}
|
| 447 |
+
```
|
| 448 |
+
|
| 449 |
+
#### Delete File
|
| 450 |
+
|
| 451 |
+
```http
|
| 452 |
+
DELETE /api/threads/{thread_id}/uploads/{filename}
|
| 453 |
+
```
|
| 454 |
+
|
| 455 |
+
**Response:**
|
| 456 |
+
```json
|
| 457 |
+
{
|
| 458 |
+
"success": true,
|
| 459 |
+
"message": "Deleted document.pdf"
|
| 460 |
+
}
|
| 461 |
+
```
|
| 462 |
+
|
| 463 |
+
### Artifacts
|
| 464 |
+
|
| 465 |
+
#### Get Artifact
|
| 466 |
+
|
| 467 |
+
Download or view an artifact generated by the agent.
|
| 468 |
+
|
| 469 |
+
```http
|
| 470 |
+
GET /api/threads/{thread_id}/artifacts/{path}
|
| 471 |
+
```
|
| 472 |
+
|
| 473 |
+
**Path Examples:**
|
| 474 |
+
- `/api/threads/abc123/artifacts/mnt/user-data/outputs/result.txt`
|
| 475 |
+
- `/api/threads/abc123/artifacts/mnt/user-data/uploads/document.pdf`
|
| 476 |
+
|
| 477 |
+
**Query Parameters:**
|
| 478 |
+
- `download` (boolean): If `true`, force download with Content-Disposition header
|
| 479 |
+
|
| 480 |
+
**Response:** File content with appropriate Content-Type
|
| 481 |
+
|
| 482 |
+
---
|
| 483 |
+
|
| 484 |
+
## Error Responses
|
| 485 |
+
|
| 486 |
+
All APIs return errors in a consistent format:
|
| 487 |
+
|
| 488 |
+
```json
|
| 489 |
+
{
|
| 490 |
+
"detail": "Error message describing what went wrong"
|
| 491 |
+
}
|
| 492 |
+
```
|
| 493 |
+
|
| 494 |
+
**HTTP Status Codes:**
|
| 495 |
+
- `400` - Bad Request: Invalid input
|
| 496 |
+
- `404` - Not Found: Resource not found
|
| 497 |
+
- `422` - Validation Error: Request validation failed
|
| 498 |
+
- `500` - Internal Server Error: Server-side error
|
| 499 |
+
|
| 500 |
+
---
|
| 501 |
+
|
| 502 |
+
## Authentication
|
| 503 |
+
|
| 504 |
+
Currently, DeerFlow does not implement authentication. All APIs are accessible without credentials.
|
| 505 |
+
|
| 506 |
+
Note: This is about DeerFlow API authentication. MCP outbound connections can still use OAuth for configured HTTP/SSE MCP servers.
|
| 507 |
+
|
| 508 |
+
For production deployments, it is recommended to:
|
| 509 |
+
1. Use Nginx for basic auth or OAuth integration
|
| 510 |
+
2. Deploy behind a VPN or private network
|
| 511 |
+
3. Implement custom authentication middleware
|
| 512 |
+
|
| 513 |
+
---
|
| 514 |
+
|
| 515 |
+
## Rate Limiting
|
| 516 |
+
|
| 517 |
+
No rate limiting is implemented by default. For production deployments, configure rate limiting in Nginx:
|
| 518 |
+
|
| 519 |
+
```nginx
|
| 520 |
+
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
| 521 |
+
|
| 522 |
+
location /api/ {
|
| 523 |
+
limit_req zone=api burst=20 nodelay;
|
| 524 |
+
proxy_pass http://backend;
|
| 525 |
+
}
|
| 526 |
+
```
|
| 527 |
+
|
| 528 |
+
---
|
| 529 |
+
|
| 530 |
+
## WebSocket Support
|
| 531 |
+
|
| 532 |
+
The LangGraph server supports WebSocket connections for real-time streaming. Connect to:
|
| 533 |
+
|
| 534 |
+
```
|
| 535 |
+
ws://localhost:2026/api/langgraph/threads/{thread_id}/runs/stream
|
| 536 |
+
```
|
| 537 |
+
|
| 538 |
+
---
|
| 539 |
+
|
| 540 |
+
## SDK Usage
|
| 541 |
+
|
| 542 |
+
### Python (LangGraph SDK)
|
| 543 |
+
|
| 544 |
+
```python
|
| 545 |
+
from langgraph_sdk import get_client
|
| 546 |
+
|
| 547 |
+
client = get_client(url="http://localhost:2026/api/langgraph")
|
| 548 |
+
|
| 549 |
+
# Create thread
|
| 550 |
+
thread = await client.threads.create()
|
| 551 |
+
|
| 552 |
+
# Run agent
|
| 553 |
+
async for event in client.runs.stream(
|
| 554 |
+
thread["thread_id"],
|
| 555 |
+
"lead_agent",
|
| 556 |
+
input={"messages": [{"role": "user", "content": "Hello"}]},
|
| 557 |
+
config={"configurable": {"model_name": "gpt-4"}},
|
| 558 |
+
stream_mode=["values", "messages"],
|
| 559 |
+
):
|
| 560 |
+
print(event)
|
| 561 |
+
```
|
| 562 |
+
|
| 563 |
+
### JavaScript/TypeScript
|
| 564 |
+
|
| 565 |
+
```typescript
|
| 566 |
+
// Using fetch for Gateway API
|
| 567 |
+
const response = await fetch('/api/models');
|
| 568 |
+
const data = await response.json();
|
| 569 |
+
console.log(data.models);
|
| 570 |
+
|
| 571 |
+
// Using EventSource for streaming
|
| 572 |
+
const eventSource = new EventSource(
|
| 573 |
+
`/api/langgraph/threads/${threadId}/runs/stream`
|
| 574 |
+
);
|
| 575 |
+
eventSource.onmessage = (event) => {
|
| 576 |
+
console.log(JSON.parse(event.data));
|
| 577 |
+
};
|
| 578 |
+
```
|
| 579 |
+
|
| 580 |
+
### cURL Examples
|
| 581 |
+
|
| 582 |
+
```bash
|
| 583 |
+
# List models
|
| 584 |
+
curl http://localhost:2026/api/models
|
| 585 |
+
|
| 586 |
+
# Get MCP config
|
| 587 |
+
curl http://localhost:2026/api/mcp/config
|
| 588 |
+
|
| 589 |
+
# Upload file
|
| 590 |
+
curl -X POST http://localhost:2026/api/threads/abc123/uploads \
|
| 591 |
+
-F "files=@document.pdf"
|
| 592 |
+
|
| 593 |
+
# Enable skill
|
| 594 |
+
curl -X POST http://localhost:2026/api/skills/pdf-processing/enable
|
| 595 |
+
|
| 596 |
+
# Create thread and run agent
|
| 597 |
+
curl -X POST http://localhost:2026/api/langgraph/threads \
|
| 598 |
+
-H "Content-Type: application/json" \
|
| 599 |
+
-d '{}'
|
| 600 |
+
|
| 601 |
+
curl -X POST http://localhost:2026/api/langgraph/threads/abc123/runs \
|
| 602 |
+
-H "Content-Type: application/json" \
|
| 603 |
+
-d '{
|
| 604 |
+
"input": {"messages": [{"role": "user", "content": "Hello"}]},
|
| 605 |
+
"config": {"configurable": {"model_name": "gpt-4"}}
|
| 606 |
+
}'
|
| 607 |
+
```
|
backend/docs/APPLE_CONTAINER.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Apple Container Support
|
| 2 |
+
|
| 3 |
+
DeerFlow now supports Apple Container as the preferred container runtime on macOS, with automatic fallback to Docker.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
Starting with this version, DeerFlow automatically detects and uses Apple Container on macOS when available, falling back to Docker when:
|
| 8 |
+
- Apple Container is not installed
|
| 9 |
+
- Running on non-macOS platforms
|
| 10 |
+
|
| 11 |
+
This provides better performance on Apple Silicon Macs while maintaining compatibility across all platforms.
|
| 12 |
+
|
| 13 |
+
## Benefits
|
| 14 |
+
|
| 15 |
+
### On Apple Silicon Macs with Apple Container:
|
| 16 |
+
- **Better Performance**: Native ARM64 execution without Rosetta 2 translation
|
| 17 |
+
- **Lower Resource Usage**: Lighter weight than Docker Desktop
|
| 18 |
+
- **Native Integration**: Uses macOS Virtualization.framework
|
| 19 |
+
|
| 20 |
+
### Fallback to Docker:
|
| 21 |
+
- Full backward compatibility
|
| 22 |
+
- Works on all platforms (macOS, Linux, Windows)
|
| 23 |
+
- No configuration changes needed
|
| 24 |
+
|
| 25 |
+
## Requirements
|
| 26 |
+
|
| 27 |
+
### For Apple Container (macOS only):
|
| 28 |
+
- macOS 15.0 or later
|
| 29 |
+
- Apple Silicon (M1/M2/M3/M4)
|
| 30 |
+
- Apple Container CLI installed
|
| 31 |
+
|
| 32 |
+
### Installation:
|
| 33 |
+
```bash
|
| 34 |
+
# Download from GitHub releases
|
| 35 |
+
# https://github.com/apple/container/releases
|
| 36 |
+
|
| 37 |
+
# Verify installation
|
| 38 |
+
container --version
|
| 39 |
+
|
| 40 |
+
# Start the service
|
| 41 |
+
container system start
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
### For Docker (all platforms):
|
| 45 |
+
- Docker Desktop or Docker Engine
|
| 46 |
+
|
| 47 |
+
## How It Works
|
| 48 |
+
|
| 49 |
+
### Automatic Detection
|
| 50 |
+
|
| 51 |
+
The `AioSandboxProvider` automatically detects the available container runtime:
|
| 52 |
+
|
| 53 |
+
1. On macOS: Try `container --version`
|
| 54 |
+
- Success → Use Apple Container
|
| 55 |
+
- Failure → Fall back to Docker
|
| 56 |
+
|
| 57 |
+
2. On other platforms: Use Docker directly
|
| 58 |
+
|
| 59 |
+
### Runtime Differences
|
| 60 |
+
|
| 61 |
+
Both runtimes use nearly identical command syntax:
|
| 62 |
+
|
| 63 |
+
**Container Startup:**
|
| 64 |
+
```bash
|
| 65 |
+
# Apple Container
|
| 66 |
+
container run --rm -d -p 8080:8080 -v /host:/container -e KEY=value image
|
| 67 |
+
|
| 68 |
+
# Docker
|
| 69 |
+
docker run --rm -d -p 8080:8080 -v /host:/container -e KEY=value image
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
**Container Cleanup:**
|
| 73 |
+
```bash
|
| 74 |
+
# Apple Container (with --rm flag)
|
| 75 |
+
container stop <id> # Auto-removes due to --rm
|
| 76 |
+
|
| 77 |
+
# Docker (with --rm flag)
|
| 78 |
+
docker stop <id> # Auto-removes due to --rm
|
| 79 |
+
```
|
| 80 |
+
|
| 81 |
+
### Implementation Details
|
| 82 |
+
|
| 83 |
+
The implementation is in `backend/src/community/aio_sandbox/aio_sandbox_provider.py`:
|
| 84 |
+
|
| 85 |
+
- `_detect_container_runtime()`: Detects available runtime at startup
|
| 86 |
+
- `_start_container()`: Uses detected runtime, skips Docker-specific options for Apple Container
|
| 87 |
+
- `_stop_container()`: Uses appropriate stop command for the runtime
|
| 88 |
+
|
| 89 |
+
## Configuration
|
| 90 |
+
|
| 91 |
+
No configuration changes are needed! The system works automatically.
|
| 92 |
+
|
| 93 |
+
However, you can verify the runtime in use by checking the logs:
|
| 94 |
+
|
| 95 |
+
```
|
| 96 |
+
INFO:src.community.aio_sandbox.aio_sandbox_provider:Detected Apple Container: container version 0.1.0
|
| 97 |
+
INFO:src.community.aio_sandbox.aio_sandbox_provider:Starting sandbox container using container: ...
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
Or for Docker:
|
| 101 |
+
```
|
| 102 |
+
INFO:src.community.aio_sandbox.aio_sandbox_provider:Apple Container not available, falling back to Docker
|
| 103 |
+
INFO:src.community.aio_sandbox.aio_sandbox_provider:Starting sandbox container using docker: ...
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
## Container Images
|
| 107 |
+
|
| 108 |
+
Both runtimes use OCI-compatible images. The default image works with both:
|
| 109 |
+
|
| 110 |
+
```yaml
|
| 111 |
+
sandbox:
|
| 112 |
+
use: src.community.aio_sandbox:AioSandboxProvider
|
| 113 |
+
image: enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest # Default image
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
Make sure your images are available for the appropriate architecture:
|
| 117 |
+
- ARM64 for Apple Container on Apple Silicon
|
| 118 |
+
- AMD64 for Docker on Intel Macs
|
| 119 |
+
- Multi-arch images work on both
|
| 120 |
+
|
| 121 |
+
### Pre-pulling Images (Recommended)
|
| 122 |
+
|
| 123 |
+
**Important**: Container images are typically large (500MB+) and are pulled on first use, which can cause a long wait time without clear feedback.
|
| 124 |
+
|
| 125 |
+
**Best Practice**: Pre-pull the image during setup:
|
| 126 |
+
|
| 127 |
+
```bash
|
| 128 |
+
# From project root
|
| 129 |
+
make setup-sandbox
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
This command will:
|
| 133 |
+
1. Read the configured image from `config.yaml` (or use default)
|
| 134 |
+
2. Detect available runtime (Apple Container or Docker)
|
| 135 |
+
3. Pull the image with progress indication
|
| 136 |
+
4. Verify the image is ready for use
|
| 137 |
+
|
| 138 |
+
**Manual pre-pull**:
|
| 139 |
+
|
| 140 |
+
```bash
|
| 141 |
+
# Using Apple Container
|
| 142 |
+
container pull enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
|
| 143 |
+
|
| 144 |
+
# Using Docker
|
| 145 |
+
docker pull enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
If you skip pre-pulling, the image will be automatically pulled on first agent execution, which may take several minutes depending on your network speed.
|
| 149 |
+
|
| 150 |
+
## Cleanup Scripts
|
| 151 |
+
|
| 152 |
+
The project includes a unified cleanup script that handles both runtimes:
|
| 153 |
+
|
| 154 |
+
**Script:** `scripts/cleanup-containers.sh`
|
| 155 |
+
|
| 156 |
+
**Usage:**
|
| 157 |
+
```bash
|
| 158 |
+
# Clean up all DeerFlow sandbox containers
|
| 159 |
+
./scripts/cleanup-containers.sh deer-flow-sandbox
|
| 160 |
+
|
| 161 |
+
# Custom prefix
|
| 162 |
+
./scripts/cleanup-containers.sh my-prefix
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
**Makefile Integration:**
|
| 166 |
+
|
| 167 |
+
All cleanup commands in `Makefile` automatically handle both runtimes:
|
| 168 |
+
```bash
|
| 169 |
+
make stop # Stops all services and cleans up containers
|
| 170 |
+
make clean # Full cleanup including logs
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
## Testing
|
| 174 |
+
|
| 175 |
+
Test the container runtime detection:
|
| 176 |
+
|
| 177 |
+
```bash
|
| 178 |
+
cd backend
|
| 179 |
+
python test_container_runtime.py
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
This will:
|
| 183 |
+
1. Detect the available runtime
|
| 184 |
+
2. Optionally start a test container
|
| 185 |
+
3. Verify connectivity
|
| 186 |
+
4. Clean up
|
| 187 |
+
|
| 188 |
+
## Troubleshooting
|
| 189 |
+
|
| 190 |
+
### Apple Container not detected on macOS
|
| 191 |
+
|
| 192 |
+
1. Check if installed:
|
| 193 |
+
```bash
|
| 194 |
+
which container
|
| 195 |
+
container --version
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
2. Check if service is running:
|
| 199 |
+
```bash
|
| 200 |
+
container system start
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
3. Check logs for detection:
|
| 204 |
+
```bash
|
| 205 |
+
# Look for detection message in application logs
|
| 206 |
+
grep "container runtime" logs/*.log
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
### Containers not cleaning up
|
| 210 |
+
|
| 211 |
+
1. Manually check running containers:
|
| 212 |
+
```bash
|
| 213 |
+
# Apple Container
|
| 214 |
+
container list
|
| 215 |
+
|
| 216 |
+
# Docker
|
| 217 |
+
docker ps
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
2. Run cleanup script manually:
|
| 221 |
+
```bash
|
| 222 |
+
./scripts/cleanup-containers.sh deer-flow-sandbox
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
### Performance issues
|
| 226 |
+
|
| 227 |
+
- Apple Container should be faster on Apple Silicon
|
| 228 |
+
- If experiencing issues, you can force Docker by temporarily renaming the `container` command:
|
| 229 |
+
```bash
|
| 230 |
+
# Temporary workaround - not recommended for permanent use
|
| 231 |
+
sudo mv /opt/homebrew/bin/container /opt/homebrew/bin/container.bak
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
## References
|
| 235 |
+
|
| 236 |
+
- [Apple Container GitHub](https://github.com/apple/container)
|
| 237 |
+
- [Apple Container Documentation](https://github.com/apple/container/blob/main/docs/)
|
| 238 |
+
- [OCI Image Spec](https://github.com/opencontainers/image-spec)
|
backend/docs/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture Overview
|
| 2 |
+
|
| 3 |
+
This document provides a comprehensive overview of the DeerFlow backend architecture.
|
| 4 |
+
|
| 5 |
+
## System Architecture
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
┌──────────────────────────────────────────────────────────────────────────┐
|
| 9 |
+
│ Client (Browser) │
|
| 10 |
+
└─────────────────────────────────┬────────────────────────────────────────┘
|
| 11 |
+
│
|
| 12 |
+
▼
|
| 13 |
+
┌──────────────────────────────────────────────────────────────────────────┐
|
| 14 |
+
│ Nginx (Port 2026) │
|
| 15 |
+
│ Unified Reverse Proxy Entry Point │
|
| 16 |
+
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
| 17 |
+
│ │ /api/langgraph/* → LangGraph Server (2024) │ │
|
| 18 |
+
│ │ /api/* → Gateway API (8001) │ │
|
| 19 |
+
│ │ /* → Frontend (3000) │ │
|
| 20 |
+
│ └────────────────────────────────────────────────────────────────────┘ │
|
| 21 |
+
└─────────────────────────────────┬────────────────────────────────────────┘
|
| 22 |
+
│
|
| 23 |
+
┌───────────────────────┼───────────────────────┐
|
| 24 |
+
│ │ │
|
| 25 |
+
▼ ▼ ▼
|
| 26 |
+
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
| 27 |
+
│ LangGraph Server │ │ Gateway API │ │ Frontend │
|
| 28 |
+
│ (Port 2024) │ │ (Port 8001) │ │ (Port 3000) │
|
| 29 |
+
│ │ │ │ │ │
|
| 30 |
+
│ - Agent Runtime │ │ - Models API │ │ - Next.js App │
|
| 31 |
+
│ - Thread Mgmt │ │ - MCP Config │ │ - React UI │
|
| 32 |
+
│ - SSE Streaming │ │ - Skills Mgmt │ │ - Chat Interface │
|
| 33 |
+
│ - Checkpointing │ │ - File Uploads │ │ │
|
| 34 |
+
│ │ │ - Artifacts │ │ │
|
| 35 |
+
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
| 36 |
+
│ │
|
| 37 |
+
│ ┌─────────────────┘
|
| 38 |
+
│ │
|
| 39 |
+
▼ ▼
|
| 40 |
+
┌──────────────────────────────────────────────────────────────────────────┐
|
| 41 |
+
│ Shared Configuration │
|
| 42 |
+
│ ┌─────────────────────────┐ ┌────────────────────────────────────────┐ │
|
| 43 |
+
│ │ config.yaml │ │ extensions_config.json │ │
|
| 44 |
+
│ │ - Models │ │ - MCP Servers │ │
|
| 45 |
+
│ │ - Tools │ │ - Skills State │ │
|
| 46 |
+
│ │ - Sandbox │ │ │ │
|
| 47 |
+
│ │ - Summarization │ │ │ │
|
| 48 |
+
│ └─────────────────────────┘ └────────────────────────────────────────┘ │
|
| 49 |
+
└──────────────────────────────────────────────────────────────────────────┘
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
## Component Details
|
| 53 |
+
|
| 54 |
+
### LangGraph Server
|
| 55 |
+
|
| 56 |
+
The LangGraph server is the core agent runtime, built on LangGraph for robust multi-agent workflow orchestration.
|
| 57 |
+
|
| 58 |
+
**Entry Point**: `src/agents/lead_agent/agent.py:make_lead_agent`
|
| 59 |
+
|
| 60 |
+
**Key Responsibilities**:
|
| 61 |
+
- Agent creation and configuration
|
| 62 |
+
- Thread state management
|
| 63 |
+
- Middleware chain execution
|
| 64 |
+
- Tool execution orchestration
|
| 65 |
+
- SSE streaming for real-time responses
|
| 66 |
+
|
| 67 |
+
**Configuration**: `langgraph.json`
|
| 68 |
+
|
| 69 |
+
```json
|
| 70 |
+
{
|
| 71 |
+
"agent": {
|
| 72 |
+
"type": "agent",
|
| 73 |
+
"path": "src.agents:make_lead_agent"
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### Gateway API
|
| 79 |
+
|
| 80 |
+
FastAPI application providing REST endpoints for non-agent operations.
|
| 81 |
+
|
| 82 |
+
**Entry Point**: `src/gateway/app.py`
|
| 83 |
+
|
| 84 |
+
**Routers**:
|
| 85 |
+
- `models.py` - `/api/models` - Model listing and details
|
| 86 |
+
- `mcp.py` - `/api/mcp` - MCP server configuration
|
| 87 |
+
- `skills.py` - `/api/skills` - Skills management
|
| 88 |
+
- `uploads.py` - `/api/threads/{id}/uploads` - File upload
|
| 89 |
+
- `artifacts.py` - `/api/threads/{id}/artifacts` - Artifact serving
|
| 90 |
+
|
| 91 |
+
### Agent Architecture
|
| 92 |
+
|
| 93 |
+
```
|
| 94 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 95 |
+
│ make_lead_agent(config) │
|
| 96 |
+
└────────────────────────────────────┬────────────────────────────────────┘
|
| 97 |
+
│
|
| 98 |
+
▼
|
| 99 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 100 |
+
│ Middleware Chain │
|
| 101 |
+
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
| 102 |
+
│ │ 1. ThreadDataMiddleware - Initialize workspace/uploads/outputs │ │
|
| 103 |
+
│ │ 2. UploadsMiddleware - Process uploaded files │ │
|
| 104 |
+
│ │ 3. SandboxMiddleware - Acquire sandbox environment │ │
|
| 105 |
+
│ │ 4. SummarizationMiddleware - Context reduction (if enabled) │ │
|
| 106 |
+
│ │ 5. TitleMiddleware - Auto-generate titles │ │
|
| 107 |
+
│ │ 6. TodoListMiddleware - Task tracking (if plan_mode) │ │
|
| 108 |
+
│ │ 7. ViewImageMiddleware - Vision model support │ │
|
| 109 |
+
│ │ 8. ClarificationMiddleware - Handle clarifications │ │
|
| 110 |
+
│ └──────────────────────────────────────────────────────────────────┘ │
|
| 111 |
+
└────────────────────────────────────┬────────────────────────────────────┘
|
| 112 |
+
│
|
| 113 |
+
▼
|
| 114 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 115 |
+
│ Agent Core │
|
| 116 |
+
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────────┐ │
|
| 117 |
+
│ │ Model │ │ Tools │ │ System Prompt │ │
|
| 118 |
+
│ │ (from factory) │ │ (configured + │ │ (with skills) │ │
|
| 119 |
+
│ │ │ │ MCP + builtin) │ │ │ │
|
| 120 |
+
│ └──────────────────┘ └──────────────────┘ └──────────────────────┘ │
|
| 121 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 122 |
+
```
|
| 123 |
+
|
| 124 |
+
### Thread State
|
| 125 |
+
|
| 126 |
+
The `ThreadState` extends LangGraph's `AgentState` with additional fields:
|
| 127 |
+
|
| 128 |
+
```python
|
| 129 |
+
class ThreadState(AgentState):
|
| 130 |
+
# Core state from AgentState
|
| 131 |
+
messages: list[BaseMessage]
|
| 132 |
+
|
| 133 |
+
# DeerFlow extensions
|
| 134 |
+
sandbox: dict # Sandbox environment info
|
| 135 |
+
artifacts: list[str] # Generated file paths
|
| 136 |
+
thread_data: dict # {workspace, uploads, outputs} paths
|
| 137 |
+
title: str | None # Auto-generated conversation title
|
| 138 |
+
todos: list[dict] # Task tracking (plan mode)
|
| 139 |
+
viewed_images: dict # Vision model image data
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
### Sandbox System
|
| 143 |
+
|
| 144 |
+
```
|
| 145 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 146 |
+
│ Sandbox Architecture │
|
| 147 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 148 |
+
|
| 149 |
+
┌─────────────────────────┐
|
| 150 |
+
│ SandboxProvider │ (Abstract)
|
| 151 |
+
│ - acquire() │
|
| 152 |
+
│ - get() │
|
| 153 |
+
│ - release() │
|
| 154 |
+
└────────────┬────────────┘
|
| 155 |
+
│
|
| 156 |
+
┌────────────────────┼────────────────────┐
|
| 157 |
+
│ │
|
| 158 |
+
▼ ▼
|
| 159 |
+
┌─────────────────────────┐ ┌─────────────────────────┐
|
| 160 |
+
│ LocalSandboxProvider │ │ AioSandboxProvider │
|
| 161 |
+
│ (src/sandbox/local.py) │ │ (src/community/) │
|
| 162 |
+
│ │ │ │
|
| 163 |
+
│ - Singleton instance │ │ - Docker-based │
|
| 164 |
+
│ - Direct execution │ │ - Isolated containers │
|
| 165 |
+
│ - Development use │ │ - Production use │
|
| 166 |
+
└─────────────────────────┘ └─────────────────────────┘
|
| 167 |
+
|
| 168 |
+
┌─────────────────────────┐
|
| 169 |
+
│ Sandbox │ (Abstract)
|
| 170 |
+
│ - execute_command() │
|
| 171 |
+
│ - read_file() │
|
| 172 |
+
│ - write_file() │
|
| 173 |
+
│ - list_dir() │
|
| 174 |
+
└─────────────────────────┘
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
**Virtual Path Mapping**:
|
| 178 |
+
|
| 179 |
+
| Virtual Path | Physical Path |
|
| 180 |
+
|-------------|---------------|
|
| 181 |
+
| `/mnt/user-data/workspace` | `backend/.deer-flow/threads/{thread_id}/user-data/workspace` |
|
| 182 |
+
| `/mnt/user-data/uploads` | `backend/.deer-flow/threads/{thread_id}/user-data/uploads` |
|
| 183 |
+
| `/mnt/user-data/outputs` | `backend/.deer-flow/threads/{thread_id}/user-data/outputs` |
|
| 184 |
+
| `/mnt/skills` | `deer-flow/skills/` |
|
| 185 |
+
|
| 186 |
+
### Tool System
|
| 187 |
+
|
| 188 |
+
```
|
| 189 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 190 |
+
│ Tool Sources │
|
| 191 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 192 |
+
|
| 193 |
+
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
| 194 |
+
│ Built-in Tools │ │ Configured Tools │ │ MCP Tools │
|
| 195 |
+
│ (src/tools/) │ │ (config.yaml) │ │ (extensions.json) │
|
| 196 |
+
├─────────────────────┤ ├─────────────────────┤ ├─────────────────────┤
|
| 197 |
+
│ - present_file │ │ - web_search │ │ - github │
|
| 198 |
+
│ - ask_clarification │ │ - web_fetch │ │ - filesystem │
|
| 199 |
+
│ - view_image │ │ - bash │ │ - postgres │
|
| 200 |
+
│ │ │ - read_file │ │ - brave-search │
|
| 201 |
+
│ │ │ - write_file │ │ - puppeteer │
|
| 202 |
+
│ │ │ - str_replace │ │ - ... │
|
| 203 |
+
│ │ │ - ls │ │ │
|
| 204 |
+
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
| 205 |
+
│ │ │
|
| 206 |
+
└───────────────────────┴───────────────────────┘
|
| 207 |
+
│
|
| 208 |
+
▼
|
| 209 |
+
┌─────────────────────────┐
|
| 210 |
+
│ get_available_tools() │
|
| 211 |
+
│ (src/tools/__init__) │
|
| 212 |
+
└─────────────────────────┘
|
| 213 |
+
```
|
| 214 |
+
|
| 215 |
+
### Model Factory
|
| 216 |
+
|
| 217 |
+
```
|
| 218 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 219 |
+
│ Model Factory │
|
| 220 |
+
│ (src/models/factory.py) │
|
| 221 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 222 |
+
|
| 223 |
+
config.yaml:
|
| 224 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 225 |
+
│ models: │
|
| 226 |
+
│ - name: gpt-4 │
|
| 227 |
+
│ display_name: GPT-4 │
|
| 228 |
+
│ use: langchain_openai:ChatOpenAI │
|
| 229 |
+
│ model: gpt-4 │
|
| 230 |
+
│ api_key: $OPENAI_API_KEY │
|
| 231 |
+
│ max_tokens: 4096 │
|
| 232 |
+
│ supports_thinking: false │
|
| 233 |
+
│ supports_vision: true │
|
| 234 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 235 |
+
│
|
| 236 |
+
▼
|
| 237 |
+
┌─────────────────────────┐
|
| 238 |
+
│ create_chat_model() │
|
| 239 |
+
│ - name: str │
|
| 240 |
+
│ - thinking_enabled │
|
| 241 |
+
└────────────┬────────────┘
|
| 242 |
+
│
|
| 243 |
+
▼
|
| 244 |
+
┌─────────────────────────┐
|
| 245 |
+
│ resolve_class() │
|
| 246 |
+
│ (reflection system) │
|
| 247 |
+
└────────────┬────────────┘
|
| 248 |
+
│
|
| 249 |
+
▼
|
| 250 |
+
┌─────────────────────────┐
|
| 251 |
+
│ BaseChatModel │
|
| 252 |
+
│ (LangChain instance) │
|
| 253 |
+
└─────────────────────────┘
|
| 254 |
+
```
|
| 255 |
+
|
| 256 |
+
**Supported Providers**:
|
| 257 |
+
- OpenAI (`langchain_openai:ChatOpenAI`)
|
| 258 |
+
- Anthropic (`langchain_anthropic:ChatAnthropic`)
|
| 259 |
+
- DeepSeek (`langchain_deepseek:ChatDeepSeek`)
|
| 260 |
+
- Custom via LangChain integrations
|
| 261 |
+
|
| 262 |
+
### MCP Integration
|
| 263 |
+
|
| 264 |
+
```
|
| 265 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 266 |
+
│ MCP Integration │
|
| 267 |
+
│ (src/mcp/manager.py) │
|
| 268 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 269 |
+
|
| 270 |
+
extensions_config.json:
|
| 271 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 272 |
+
│ { │
|
| 273 |
+
│ "mcpServers": { │
|
| 274 |
+
│ "github": { │
|
| 275 |
+
│ "enabled": true, │
|
| 276 |
+
│ "type": "stdio", │
|
| 277 |
+
│ "command": "npx", │
|
| 278 |
+
│ "args": ["-y", "@modelcontextprotocol/server-github"], │
|
| 279 |
+
│ "env": {"GITHUB_TOKEN": "$GITHUB_TOKEN"} │
|
| 280 |
+
│ } │
|
| 281 |
+
│ } │
|
| 282 |
+
│ } │
|
| 283 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 284 |
+
│
|
| 285 |
+
▼
|
| 286 |
+
┌─────────────────────────┐
|
| 287 |
+
│ MultiServerMCPClient │
|
| 288 |
+
│ (langchain-mcp-adapters)│
|
| 289 |
+
└────────────┬────────────┘
|
| 290 |
+
│
|
| 291 |
+
┌────────────────────┼────────────────────┐
|
| 292 |
+
│ │ │
|
| 293 |
+
▼ ▼ ▼
|
| 294 |
+
┌───────────┐ ┌───────────┐ ┌───────────┐
|
| 295 |
+
│ stdio │ │ SSE │ │ HTTP │
|
| 296 |
+
│ transport │ │ transport │ │ transport │
|
| 297 |
+
└───────────┘ └───────────┘ └───────────┘
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
### Skills System
|
| 301 |
+
|
| 302 |
+
```
|
| 303 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 304 |
+
│ Skills System │
|
| 305 |
+
│ (src/skills/loader.py) │
|
| 306 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 307 |
+
|
| 308 |
+
Directory Structure:
|
| 309 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 310 |
+
│ skills/ │
|
| 311 |
+
│ ├── public/ # Public skills (committed) │
|
| 312 |
+
│ │ ├── pdf-processing/ │
|
| 313 |
+
│ │ │ └── SKILL.md │
|
| 314 |
+
│ │ ├── frontend-design/ │
|
| 315 |
+
│ │ │ └── SKILL.md │
|
| 316 |
+
│ │ └── ... │
|
| 317 |
+
│ └── custom/ # Custom skills (gitignored) │
|
| 318 |
+
│ └── user-installed/ │
|
| 319 |
+
│ └── SKILL.md │
|
| 320 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 321 |
+
|
| 322 |
+
SKILL.md Format:
|
| 323 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 324 |
+
│ --- │
|
| 325 |
+
│ name: PDF Processing │
|
| 326 |
+
│ description: Handle PDF documents efficiently │
|
| 327 |
+
│ license: MIT │
|
| 328 |
+
│ allowed-tools: │
|
| 329 |
+
│ - read_file │
|
| 330 |
+
│ - write_file │
|
| 331 |
+
│ - bash │
|
| 332 |
+
│ --- │
|
| 333 |
+
│ │
|
| 334 |
+
│ # Skill Instructions │
|
| 335 |
+
│ Content injected into system prompt... │
|
| 336 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
### Request Flow
|
| 340 |
+
|
| 341 |
+
```
|
| 342 |
+
┌─────────────────────────────────────────────────────────────────────────┐
|
| 343 |
+
│ Request Flow Example │
|
| 344 |
+
│ User sends message to agent │
|
| 345 |
+
└─────────────────────────────────────────────────────────────────────────┘
|
| 346 |
+
|
| 347 |
+
1. Client → Nginx
|
| 348 |
+
POST /api/langgraph/threads/{thread_id}/runs
|
| 349 |
+
{"input": {"messages": [{"role": "user", "content": "Hello"}]}}
|
| 350 |
+
|
| 351 |
+
2. Nginx → LangGraph Server (2024)
|
| 352 |
+
Proxied to LangGraph server
|
| 353 |
+
|
| 354 |
+
3. LangGraph Server
|
| 355 |
+
a. Load/create thread state
|
| 356 |
+
b. Execute middleware chain:
|
| 357 |
+
- ThreadDataMiddleware: Set up paths
|
| 358 |
+
- UploadsMiddleware: Inject file list
|
| 359 |
+
- SandboxMiddleware: Acquire sandbox
|
| 360 |
+
- SummarizationMiddleware: Check token limits
|
| 361 |
+
- TitleMiddleware: Generate title if needed
|
| 362 |
+
- TodoListMiddleware: Load todos (if plan mode)
|
| 363 |
+
- ViewImageMiddleware: Process images
|
| 364 |
+
- ClarificationMiddleware: Check for clarifications
|
| 365 |
+
|
| 366 |
+
c. Execute agent:
|
| 367 |
+
- Model processes messages
|
| 368 |
+
- May call tools (bash, web_search, etc.)
|
| 369 |
+
- Tools execute via sandbox
|
| 370 |
+
- Results added to messages
|
| 371 |
+
|
| 372 |
+
d. Stream response via SSE
|
| 373 |
+
|
| 374 |
+
4. Client receives streaming response
|
| 375 |
+
```
|
| 376 |
+
|
| 377 |
+
## Data Flow
|
| 378 |
+
|
| 379 |
+
### File Upload Flow
|
| 380 |
+
|
| 381 |
+
```
|
| 382 |
+
1. Client uploads file
|
| 383 |
+
POST /api/threads/{thread_id}/uploads
|
| 384 |
+
Content-Type: multipart/form-data
|
| 385 |
+
|
| 386 |
+
2. Gateway receives file
|
| 387 |
+
- Validates file
|
| 388 |
+
- Stores in .deer-flow/threads/{thread_id}/user-data/uploads/
|
| 389 |
+
- If document: converts to Markdown via markitdown
|
| 390 |
+
|
| 391 |
+
3. Returns response
|
| 392 |
+
{
|
| 393 |
+
"files": [{
|
| 394 |
+
"filename": "doc.pdf",
|
| 395 |
+
"path": ".deer-flow/.../uploads/doc.pdf",
|
| 396 |
+
"virtual_path": "/mnt/user-data/uploads/doc.pdf",
|
| 397 |
+
"artifact_url": "/api/threads/.../artifacts/mnt/.../doc.pdf"
|
| 398 |
+
}]
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
4. Next agent run
|
| 402 |
+
- UploadsMiddleware lists files
|
| 403 |
+
- Injects file list into messages
|
| 404 |
+
- Agent can access via virtual_path
|
| 405 |
+
```
|
| 406 |
+
|
| 407 |
+
### Configuration Reload
|
| 408 |
+
|
| 409 |
+
```
|
| 410 |
+
1. Client updates MCP config
|
| 411 |
+
PUT /api/mcp/config
|
| 412 |
+
|
| 413 |
+
2. Gateway writes extensions_config.json
|
| 414 |
+
- Updates mcpServers section
|
| 415 |
+
- File mtime changes
|
| 416 |
+
|
| 417 |
+
3. MCP Manager detects change
|
| 418 |
+
- get_cached_mcp_tools() checks mtime
|
| 419 |
+
- If changed: reinitializes MCP client
|
| 420 |
+
- Loads updated server configurations
|
| 421 |
+
|
| 422 |
+
4. Next agent run uses new tools
|
| 423 |
+
```
|
| 424 |
+
|
| 425 |
+
## Security Considerations
|
| 426 |
+
|
| 427 |
+
### Sandbox Isolation
|
| 428 |
+
|
| 429 |
+
- Agent code executes within sandbox boundaries
|
| 430 |
+
- Local sandbox: Direct execution (development only)
|
| 431 |
+
- Docker sandbox: Container isolation (production recommended)
|
| 432 |
+
- Path traversal prevention in file operations
|
| 433 |
+
|
| 434 |
+
### API Security
|
| 435 |
+
|
| 436 |
+
- Thread isolation: Each thread has separate data directories
|
| 437 |
+
- File validation: Uploads checked for path safety
|
| 438 |
+
- Environment variable resolution: Secrets not stored in config
|
| 439 |
+
|
| 440 |
+
### MCP Security
|
| 441 |
+
|
| 442 |
+
- Each MCP server runs in its own process
|
| 443 |
+
- Environment variables resolved at runtime
|
| 444 |
+
- Servers can be enabled/disabled independently
|
| 445 |
+
|
| 446 |
+
## Performance Considerations
|
| 447 |
+
|
| 448 |
+
### Caching
|
| 449 |
+
|
| 450 |
+
- MCP tools cached with file mtime invalidation
|
| 451 |
+
- Configuration loaded once, reloaded on file change
|
| 452 |
+
- Skills parsed once at startup, cached in memory
|
| 453 |
+
|
| 454 |
+
### Streaming
|
| 455 |
+
|
| 456 |
+
- SSE used for real-time response streaming
|
| 457 |
+
- Reduces time to first token
|
| 458 |
+
- Enables progress visibility for long operations
|
| 459 |
+
|
| 460 |
+
### Context Management
|
| 461 |
+
|
| 462 |
+
- Summarization middleware reduces context when limits approached
|
| 463 |
+
- Configurable triggers: tokens, messages, or fraction
|
| 464 |
+
- Preserves recent messages while summarizing older ones
|
backend/docs/AUTO_TITLE_GENERATION.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 自动 Thread Title 生成功能
|
| 2 |
+
|
| 3 |
+
## 功能说明
|
| 4 |
+
|
| 5 |
+
自动为对话线程生成标题,在用户首次提问并收到回复后自动触发。
|
| 6 |
+
|
| 7 |
+
## 实现方式
|
| 8 |
+
|
| 9 |
+
使用 `TitleMiddleware` 在 `after_agent` 钩子中:
|
| 10 |
+
1. 检测是否是首次对话(1个用户消息 + 1个助手回复)
|
| 11 |
+
2. 检查 state 是否已有 title
|
| 12 |
+
3. 调用 LLM 生成简洁的标题(默认最多6个词)
|
| 13 |
+
4. 将 title 存储到 `ThreadState` 中(会被 checkpointer 持久化)
|
| 14 |
+
|
| 15 |
+
## ⚠️ 重要:存储机制
|
| 16 |
+
|
| 17 |
+
### Title 存储位置
|
| 18 |
+
|
| 19 |
+
Title 存储在 **`ThreadState.title`** 中,而非 thread metadata:
|
| 20 |
+
|
| 21 |
+
```python
|
| 22 |
+
class ThreadState(AgentState):
|
| 23 |
+
sandbox: SandboxState | None = None
|
| 24 |
+
title: str | None = None # ✅ Title stored here
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### 持久化说明
|
| 28 |
+
|
| 29 |
+
| 部署方式 | 持久化 | 说明 |
|
| 30 |
+
|---------|--------|------|
|
| 31 |
+
| **LangGraph Studio (本地)** | ❌ 否 | 仅内存存储,重启后丢失 |
|
| 32 |
+
| **LangGraph Platform** | ✅ 是 | 自动持久化到数据库 |
|
| 33 |
+
| **自定义 + Checkpointer** | ✅ 是 | 需配置 PostgreSQL/SQLite checkpointer |
|
| 34 |
+
|
| 35 |
+
### 如何启用持久化
|
| 36 |
+
|
| 37 |
+
如果需要在本地开发时也持久化 title,需要配置 checkpointer:
|
| 38 |
+
|
| 39 |
+
```python
|
| 40 |
+
# 在 langgraph.json 同级目录创建 checkpointer.py
|
| 41 |
+
from langgraph.checkpoint.postgres import PostgresSaver
|
| 42 |
+
|
| 43 |
+
checkpointer = PostgresSaver.from_conn_string(
|
| 44 |
+
"postgresql://user:pass@localhost/dbname"
|
| 45 |
+
)
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
然后在 `langgraph.json` 中引用:
|
| 49 |
+
|
| 50 |
+
```json
|
| 51 |
+
{
|
| 52 |
+
"graphs": {
|
| 53 |
+
"lead_agent": "src.agents:lead_agent"
|
| 54 |
+
},
|
| 55 |
+
"checkpointer": "checkpointer:checkpointer"
|
| 56 |
+
}
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
## 配置
|
| 60 |
+
|
| 61 |
+
在 `config.yaml` 中添加(可选):
|
| 62 |
+
|
| 63 |
+
```yaml
|
| 64 |
+
title:
|
| 65 |
+
enabled: true
|
| 66 |
+
max_words: 6
|
| 67 |
+
max_chars: 60
|
| 68 |
+
model_name: null # 使用默认模型
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
或在代码中配置:
|
| 72 |
+
|
| 73 |
+
```python
|
| 74 |
+
from src.config.title_config import TitleConfig, set_title_config
|
| 75 |
+
|
| 76 |
+
set_title_config(TitleConfig(
|
| 77 |
+
enabled=True,
|
| 78 |
+
max_words=8,
|
| 79 |
+
max_chars=80,
|
| 80 |
+
))
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
## 客户端使用
|
| 84 |
+
|
| 85 |
+
### 获取 Thread Title
|
| 86 |
+
|
| 87 |
+
```typescript
|
| 88 |
+
// 方式1: 从 thread state 获取
|
| 89 |
+
const state = await client.threads.getState(threadId);
|
| 90 |
+
const title = state.values.title || "New Conversation";
|
| 91 |
+
|
| 92 |
+
// 方式2: 监听 stream 事件
|
| 93 |
+
for await (const chunk of client.runs.stream(threadId, assistantId, {
|
| 94 |
+
input: { messages: [{ role: "user", content: "Hello" }] }
|
| 95 |
+
})) {
|
| 96 |
+
if (chunk.event === "values" && chunk.data.title) {
|
| 97 |
+
console.log("Title:", chunk.data.title);
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### 显示 Title
|
| 103 |
+
|
| 104 |
+
```typescript
|
| 105 |
+
// 在对话列表中显示
|
| 106 |
+
function ConversationList() {
|
| 107 |
+
const [threads, setThreads] = useState([]);
|
| 108 |
+
|
| 109 |
+
useEffect(() => {
|
| 110 |
+
async function loadThreads() {
|
| 111 |
+
const allThreads = await client.threads.list();
|
| 112 |
+
|
| 113 |
+
// 获取每个 thread 的 state 来读取 title
|
| 114 |
+
const threadsWithTitles = await Promise.all(
|
| 115 |
+
allThreads.map(async (t) => {
|
| 116 |
+
const state = await client.threads.getState(t.thread_id);
|
| 117 |
+
return {
|
| 118 |
+
id: t.thread_id,
|
| 119 |
+
title: state.values.title || "New Conversation",
|
| 120 |
+
updatedAt: t.updated_at,
|
| 121 |
+
};
|
| 122 |
+
})
|
| 123 |
+
);
|
| 124 |
+
|
| 125 |
+
setThreads(threadsWithTitles);
|
| 126 |
+
}
|
| 127 |
+
loadThreads();
|
| 128 |
+
}, []);
|
| 129 |
+
|
| 130 |
+
return (
|
| 131 |
+
<ul>
|
| 132 |
+
{threads.map(thread => (
|
| 133 |
+
<li key={thread.id}>
|
| 134 |
+
<a href={`/chat/${thread.id}`}>{thread.title}</a>
|
| 135 |
+
</li>
|
| 136 |
+
))}
|
| 137 |
+
</ul>
|
| 138 |
+
);
|
| 139 |
+
}
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
## 工作流程
|
| 143 |
+
|
| 144 |
+
```mermaid
|
| 145 |
+
sequenceDiagram
|
| 146 |
+
participant User
|
| 147 |
+
participant Client
|
| 148 |
+
participant LangGraph
|
| 149 |
+
participant TitleMiddleware
|
| 150 |
+
participant LLM
|
| 151 |
+
participant Checkpointer
|
| 152 |
+
|
| 153 |
+
User->>Client: 发送首条消息
|
| 154 |
+
Client->>LangGraph: POST /threads/{id}/runs
|
| 155 |
+
LangGraph->>Agent: 处理消息
|
| 156 |
+
Agent-->>LangGraph: 返回回复
|
| 157 |
+
LangGraph->>TitleMiddleware: after_agent()
|
| 158 |
+
TitleMiddleware->>TitleMiddleware: 检查是否需要生成 title
|
| 159 |
+
TitleMiddleware->>LLM: 生成 title
|
| 160 |
+
LLM-->>TitleMiddleware: 返回 title
|
| 161 |
+
TitleMiddleware->>LangGraph: return {"title": "..."}
|
| 162 |
+
LangGraph->>Checkpointer: 保存 state (含 title)
|
| 163 |
+
LangGraph-->>Client: 返回响应
|
| 164 |
+
Client->>Client: 从 state.values.title 读取
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
## 优势
|
| 168 |
+
|
| 169 |
+
✅ **可靠持久化** - 使用 LangGraph 的 state 机制,自动持久化
|
| 170 |
+
✅ **完全后端处理** - 客户端无需额外逻辑
|
| 171 |
+
✅ **自动触发** - 首次对话后自动生成
|
| 172 |
+
✅ **可配置** - 支持自定义长度、模型等
|
| 173 |
+
✅ **容错性强** - 失败时使用 fallback 策略
|
| 174 |
+
✅ **架构一致** - 与现有 SandboxMiddleware 保持一致
|
| 175 |
+
|
| 176 |
+
## 注意事项
|
| 177 |
+
|
| 178 |
+
1. **读取方式不同**:Title 在 `state.values.title` 而非 `thread.metadata.title`
|
| 179 |
+
2. **性能考虑**:title 生成会增加约 0.5-1 秒延迟,可通过使用更快的模型优化
|
| 180 |
+
3. **并发安全**:middleware 在 agent 执行后运行,不会阻塞主流程
|
| 181 |
+
4. **Fallback 策略**:如果 LLM 调用失败,会使用用户消息的前几个词作为 title
|
| 182 |
+
|
| 183 |
+
## 测试
|
| 184 |
+
|
| 185 |
+
```python
|
| 186 |
+
# 测试 title 生成
|
| 187 |
+
import pytest
|
| 188 |
+
from src.agents.title_middleware import TitleMiddleware
|
| 189 |
+
|
| 190 |
+
def test_title_generation():
|
| 191 |
+
# TODO: 添加单元测试
|
| 192 |
+
pass
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
## 故障排查
|
| 196 |
+
|
| 197 |
+
### Title 没有生成
|
| 198 |
+
|
| 199 |
+
1. 检查配置是否启用:`get_title_config().enabled == True`
|
| 200 |
+
2. 检查日志:查找 "Generated thread title" 或错误信息
|
| 201 |
+
3. 确认是首次对话:只有 1 个用户消息和 1 个助手回复时才会触发
|
| 202 |
+
|
| 203 |
+
### Title 生成但客户端看不到
|
| 204 |
+
|
| 205 |
+
1. 确认读取位置:应该从 `state.values.title` 读取,而非 `thread.metadata.title`
|
| 206 |
+
2. 检查 API 响应:确认 state 中包含 title 字段
|
| 207 |
+
3. 尝试重新获取 state:`client.threads.getState(threadId)`
|
| 208 |
+
|
| 209 |
+
### Title 重启后丢失
|
| 210 |
+
|
| 211 |
+
1. 检查是否配置了 checkpointer(本地开发需要)
|
| 212 |
+
2. 确认部署方式:LangGraph Platform 会自动持久化
|
| 213 |
+
3. 查看数据库:确认 checkpointer 正常工作
|
| 214 |
+
|
| 215 |
+
## 架构设计
|
| 216 |
+
|
| 217 |
+
### 为什么使用 State 而非 Metadata?
|
| 218 |
+
|
| 219 |
+
| 特性 | State | Metadata |
|
| 220 |
+
|------|-------|----------|
|
| 221 |
+
| **持久化** | ✅ 自动(通过 checkpointer) | ⚠️ 取决于实现 |
|
| 222 |
+
| **版本控制** | ✅ 支持时间旅行 | ❌ 不支持 |
|
| 223 |
+
| **类型安全** | ✅ TypedDict 定义 | ❌ 任意字典 |
|
| 224 |
+
| **可追溯** | ✅ 每次更新都记录 | ⚠️ 只有最新值 |
|
| 225 |
+
| **标准化** | ✅ LangGraph 核心机制 | ⚠️ 扩展功能 |
|
| 226 |
+
|
| 227 |
+
### 实现细节
|
| 228 |
+
|
| 229 |
+
```python
|
| 230 |
+
# TitleMiddleware 核心逻辑
|
| 231 |
+
@override
|
| 232 |
+
def after_agent(self, state: TitleMiddlewareState, runtime: Runtime) -> dict | None:
|
| 233 |
+
"""Generate and set thread title after the first agent response."""
|
| 234 |
+
if self._should_generate_title(state, runtime):
|
| 235 |
+
title = self._generate_title(runtime)
|
| 236 |
+
print(f"Generated thread title: {title}")
|
| 237 |
+
|
| 238 |
+
# ✅ 返回 state 更新,会被 checkpointer 自动持久化
|
| 239 |
+
return {"title": title}
|
| 240 |
+
|
| 241 |
+
return None
|
| 242 |
+
```
|
| 243 |
+
|
| 244 |
+
## 相关文件
|
| 245 |
+
|
| 246 |
+
- [`src/agents/thread_state.py`](../src/agents/thread_state.py) - ThreadState 定义
|
| 247 |
+
- [`src/agents/title_middleware.py`](../src/agents/title_middleware.py) - TitleMiddleware 实现
|
| 248 |
+
- [`src/config/title_config.py`](../src/config/title_config.py) - 配置管理
|
| 249 |
+
- [`config.yaml`](../config.yaml) - 配置文件
|
| 250 |
+
- [`src/agents/lead_agent/agent.py`](../src/agents/lead_agent/agent.py) - Middleware 注册
|
| 251 |
+
|
| 252 |
+
## 参考资料
|
| 253 |
+
|
| 254 |
+
- [LangGraph Checkpointer 文档](https://langchain-ai.github.io/langgraph/concepts/persistence/)
|
| 255 |
+
- [LangGraph State 管理](https://langchain-ai.github.io/langgraph/concepts/low_level/#state)
|
| 256 |
+
- [LangGraph Middleware](https://langchain-ai.github.io/langgraph/concepts/middleware/)
|
backend/docs/CONFIGURATION.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Configuration Guide
|
| 2 |
+
|
| 3 |
+
This guide explains how to configure DeerFlow for your environment.
|
| 4 |
+
|
| 5 |
+
## Configuration Sections
|
| 6 |
+
|
| 7 |
+
### Models
|
| 8 |
+
|
| 9 |
+
Configure the LLM models available to the agent:
|
| 10 |
+
|
| 11 |
+
```yaml
|
| 12 |
+
models:
|
| 13 |
+
- name: gpt-4 # Internal identifier
|
| 14 |
+
display_name: GPT-4 # Human-readable name
|
| 15 |
+
use: langchain_openai:ChatOpenAI # LangChain class path
|
| 16 |
+
model: gpt-4 # Model identifier for API
|
| 17 |
+
api_key: $OPENAI_API_KEY # API key (use env var)
|
| 18 |
+
max_tokens: 4096 # Max tokens per request
|
| 19 |
+
temperature: 0.7 # Sampling temperature
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
**Supported Providers**:
|
| 23 |
+
- OpenAI (`langchain_openai:ChatOpenAI`)
|
| 24 |
+
- Anthropic (`langchain_anthropic:ChatAnthropic`)
|
| 25 |
+
- DeepSeek (`langchain_deepseek:ChatDeepSeek`)
|
| 26 |
+
- Any LangChain-compatible provider
|
| 27 |
+
|
| 28 |
+
For OpenAI-compatible gateways (for example Novita), keep using `langchain_openai:ChatOpenAI` and set `base_url`:
|
| 29 |
+
|
| 30 |
+
```yaml
|
| 31 |
+
models:
|
| 32 |
+
- name: novita-deepseek-v3.2
|
| 33 |
+
display_name: Novita DeepSeek V3.2
|
| 34 |
+
use: langchain_openai:ChatOpenAI
|
| 35 |
+
model: deepseek/deepseek-v3.2
|
| 36 |
+
api_key: $NOVITA_API_KEY
|
| 37 |
+
base_url: https://api.novita.ai/openai
|
| 38 |
+
supports_thinking: true
|
| 39 |
+
when_thinking_enabled:
|
| 40 |
+
extra_body:
|
| 41 |
+
thinking:
|
| 42 |
+
type: enabled
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
**Thinking Models**:
|
| 46 |
+
Some models support "thinking" mode for complex reasoning:
|
| 47 |
+
|
| 48 |
+
```yaml
|
| 49 |
+
models:
|
| 50 |
+
- name: deepseek-v3
|
| 51 |
+
supports_thinking: true
|
| 52 |
+
when_thinking_enabled:
|
| 53 |
+
extra_body:
|
| 54 |
+
thinking:
|
| 55 |
+
type: enabled
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### Tool Groups
|
| 59 |
+
|
| 60 |
+
Organize tools into logical groups:
|
| 61 |
+
|
| 62 |
+
```yaml
|
| 63 |
+
tool_groups:
|
| 64 |
+
- name: web # Web browsing and search
|
| 65 |
+
- name: file:read # Read-only file operations
|
| 66 |
+
- name: file:write # Write file operations
|
| 67 |
+
- name: bash # Shell command execution
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
### Tools
|
| 71 |
+
|
| 72 |
+
Configure specific tools available to the agent:
|
| 73 |
+
|
| 74 |
+
```yaml
|
| 75 |
+
tools:
|
| 76 |
+
- name: web_search
|
| 77 |
+
group: web
|
| 78 |
+
use: src.community.tavily.tools:web_search_tool
|
| 79 |
+
max_results: 5
|
| 80 |
+
# api_key: $TAVILY_API_KEY # Optional
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
**Built-in Tools**:
|
| 84 |
+
- `web_search` - Search the web (Tavily)
|
| 85 |
+
- `web_fetch` - Fetch web pages (Jina AI)
|
| 86 |
+
- `ls` - List directory contents
|
| 87 |
+
- `read_file` - Read file contents
|
| 88 |
+
- `write_file` - Write file contents
|
| 89 |
+
- `str_replace` - String replacement in files
|
| 90 |
+
- `bash` - Execute bash commands
|
| 91 |
+
|
| 92 |
+
### Sandbox
|
| 93 |
+
|
| 94 |
+
DeerFlow supports multiple sandbox execution modes. Configure your preferred mode in `config.yaml`:
|
| 95 |
+
|
| 96 |
+
**Local Execution** (runs sandbox code directly on the host machine):
|
| 97 |
+
```yaml
|
| 98 |
+
sandbox:
|
| 99 |
+
use: src.sandbox.local:LocalSandboxProvider # Local execution
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
**Docker Execution** (runs sandbox code in isolated Docker containers):
|
| 103 |
+
```yaml
|
| 104 |
+
sandbox:
|
| 105 |
+
use: src.community.aio_sandbox:AioSandboxProvider # Docker-based sandbox
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
**Docker Execution with Kubernetes** (runs sandbox code in Kubernetes pods via provisioner service):
|
| 109 |
+
|
| 110 |
+
This mode runs each sandbox in an isolated Kubernetes Pod on your **host machine's cluster**. Requires Docker Desktop K8s, OrbStack, or similar local K8s setup.
|
| 111 |
+
|
| 112 |
+
```yaml
|
| 113 |
+
sandbox:
|
| 114 |
+
use: src.community.aio_sandbox:AioSandboxProvider
|
| 115 |
+
provisioner_url: http://provisioner:8002
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
When using Docker development (`make docker-start`), DeerFlow starts the `provisioner` service only if this provisioner mode is configured. In local or plain Docker sandbox modes, `provisioner` is skipped.
|
| 119 |
+
|
| 120 |
+
See [Provisioner Setup Guide](docker/provisioner/README.md) for detailed configuration, prerequisites, and troubleshooting.
|
| 121 |
+
|
| 122 |
+
Choose between local execution or Docker-based isolation:
|
| 123 |
+
|
| 124 |
+
**Option 1: Local Sandbox** (default, simpler setup):
|
| 125 |
+
```yaml
|
| 126 |
+
sandbox:
|
| 127 |
+
use: src.sandbox.local:LocalSandboxProvider
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
**Option 2: Docker Sandbox** (isolated, more secure):
|
| 131 |
+
```yaml
|
| 132 |
+
sandbox:
|
| 133 |
+
use: src.community.aio_sandbox:AioSandboxProvider
|
| 134 |
+
port: 8080
|
| 135 |
+
auto_start: true
|
| 136 |
+
container_prefix: deer-flow-sandbox
|
| 137 |
+
|
| 138 |
+
# Optional: Additional mounts
|
| 139 |
+
mounts:
|
| 140 |
+
- host_path: /path/on/host
|
| 141 |
+
container_path: /path/in/container
|
| 142 |
+
read_only: false
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
### Skills
|
| 146 |
+
|
| 147 |
+
Configure the skills directory for specialized workflows:
|
| 148 |
+
|
| 149 |
+
```yaml
|
| 150 |
+
skills:
|
| 151 |
+
# Host path (optional, default: ../skills)
|
| 152 |
+
path: /custom/path/to/skills
|
| 153 |
+
|
| 154 |
+
# Container mount path (default: /mnt/skills)
|
| 155 |
+
container_path: /mnt/skills
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
**How Skills Work**:
|
| 159 |
+
- Skills are stored in `deer-flow/skills/{public,custom}/`
|
| 160 |
+
- Each skill has a `SKILL.md` file with metadata
|
| 161 |
+
- Skills are automatically discovered and loaded
|
| 162 |
+
- Available in both local and Docker sandbox via path mapping
|
| 163 |
+
|
| 164 |
+
### Title Generation
|
| 165 |
+
|
| 166 |
+
Automatic conversation title generation:
|
| 167 |
+
|
| 168 |
+
```yaml
|
| 169 |
+
title:
|
| 170 |
+
enabled: true
|
| 171 |
+
max_words: 6
|
| 172 |
+
max_chars: 60
|
| 173 |
+
model_name: null # Use first model in list
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## Environment Variables
|
| 177 |
+
|
| 178 |
+
DeerFlow supports environment variable substitution using the `$` prefix:
|
| 179 |
+
|
| 180 |
+
```yaml
|
| 181 |
+
models:
|
| 182 |
+
- api_key: $OPENAI_API_KEY # Reads from environment
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
**Common Environment Variables**:
|
| 186 |
+
- `OPENAI_API_KEY` - OpenAI API key
|
| 187 |
+
- `ANTHROPIC_API_KEY` - Anthropic API key
|
| 188 |
+
- `DEEPSEEK_API_KEY` - DeepSeek API key
|
| 189 |
+
- `NOVITA_API_KEY` - Novita API key (OpenAI-compatible endpoint)
|
| 190 |
+
- `TAVILY_API_KEY` - Tavily search API key
|
| 191 |
+
- `DEER_FLOW_CONFIG_PATH` - Custom config file path
|
| 192 |
+
|
| 193 |
+
## Configuration Location
|
| 194 |
+
|
| 195 |
+
The configuration file should be placed in the **project root directory** (`deer-flow/config.yaml`), not in the backend directory.
|
| 196 |
+
|
| 197 |
+
## Configuration Priority
|
| 198 |
+
|
| 199 |
+
DeerFlow searches for configuration in this order:
|
| 200 |
+
|
| 201 |
+
1. Path specified in code via `config_path` argument
|
| 202 |
+
2. Path from `DEER_FLOW_CONFIG_PATH` environment variable
|
| 203 |
+
3. `config.yaml` in current working directory (typically `backend/` when running)
|
| 204 |
+
4. `config.yaml` in parent directory (project root: `deer-flow/`)
|
| 205 |
+
|
| 206 |
+
## Best Practices
|
| 207 |
+
|
| 208 |
+
1. **Place `config.yaml` in project root** - Not in `backend/` directory
|
| 209 |
+
2. **Never commit `config.yaml`** - It's already in `.gitignore`
|
| 210 |
+
3. **Use environment variables for secrets** - Don't hardcode API keys
|
| 211 |
+
4. **Keep `config.example.yaml` updated** - Document all new options
|
| 212 |
+
5. **Test configuration changes locally** - Before deploying
|
| 213 |
+
6. **Use Docker sandbox for production** - Better isolation and security
|
| 214 |
+
|
| 215 |
+
## Troubleshooting
|
| 216 |
+
|
| 217 |
+
### "Config file not found"
|
| 218 |
+
- Ensure `config.yaml` exists in the **project root** directory (`deer-flow/config.yaml`)
|
| 219 |
+
- The backend searches parent directory by default, so root location is preferred
|
| 220 |
+
- Alternatively, set `DEER_FLOW_CONFIG_PATH` environment variable to custom location
|
| 221 |
+
|
| 222 |
+
### "Invalid API key"
|
| 223 |
+
- Verify environment variables are set correctly
|
| 224 |
+
- Check that `$` prefix is used for env var references
|
| 225 |
+
|
| 226 |
+
### "Skills not loading"
|
| 227 |
+
- Check that `deer-flow/skills/` directory exists
|
| 228 |
+
- Verify skills have valid `SKILL.md` files
|
| 229 |
+
- Check `skills.path` configuration if using custom path
|
| 230 |
+
|
| 231 |
+
### "Docker sandbox fails to start"
|
| 232 |
+
- Ensure Docker is running
|
| 233 |
+
- Check port 8080 (or configured port) is available
|
| 234 |
+
- Verify Docker image is accessible
|
| 235 |
+
|
| 236 |
+
## Examples
|
| 237 |
+
|
| 238 |
+
See `config.example.yaml` for complete examples of all configuration options.
|
backend/docs/FILE_UPLOAD.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 文件上传功能
|
| 2 |
+
|
| 3 |
+
## 概述
|
| 4 |
+
|
| 5 |
+
DeerFlow 后端提供了完整的文件上传功能,支持多文件上传,并自动将 Office 文档和 PDF 转换为 Markdown 格式。
|
| 6 |
+
|
| 7 |
+
## 功能特性
|
| 8 |
+
|
| 9 |
+
- ✅ 支持多文件同时上传
|
| 10 |
+
- ✅ 自动转换文档为 Markdown(PDF、PPT、Excel、Word)
|
| 11 |
+
- ✅ 文件存储在线程隔离的目录中
|
| 12 |
+
- ✅ Agent 自动感知已上传的文件
|
| 13 |
+
- ✅ 支持文件列表查询和删除
|
| 14 |
+
|
| 15 |
+
## API 端点
|
| 16 |
+
|
| 17 |
+
### 1. 上传文件
|
| 18 |
+
```
|
| 19 |
+
POST /api/threads/{thread_id}/uploads
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
**请求体:** `multipart/form-data`
|
| 23 |
+
- `files`: 一个或多个文件
|
| 24 |
+
|
| 25 |
+
**响应:**
|
| 26 |
+
```json
|
| 27 |
+
{
|
| 28 |
+
"success": true,
|
| 29 |
+
"files": [
|
| 30 |
+
{
|
| 31 |
+
"filename": "document.pdf",
|
| 32 |
+
"size": 1234567,
|
| 33 |
+
"path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.pdf",
|
| 34 |
+
"virtual_path": "/mnt/user-data/uploads/document.pdf",
|
| 35 |
+
"artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf",
|
| 36 |
+
"markdown_file": "document.md",
|
| 37 |
+
"markdown_path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.md",
|
| 38 |
+
"markdown_virtual_path": "/mnt/user-data/uploads/document.md",
|
| 39 |
+
"markdown_artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.md"
|
| 40 |
+
}
|
| 41 |
+
],
|
| 42 |
+
"message": "Successfully uploaded 1 file(s)"
|
| 43 |
+
}
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
**路径说明:**
|
| 47 |
+
- `path`: 实际文件系统路径(相对于 `backend/` 目录)
|
| 48 |
+
- `virtual_path`: Agent 在沙箱中使用的虚拟路径
|
| 49 |
+
- `artifact_url`: 前端通过 HTTP 访问文件的 URL
|
| 50 |
+
|
| 51 |
+
### 2. 列出已上传文件
|
| 52 |
+
```
|
| 53 |
+
GET /api/threads/{thread_id}/uploads/list
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
**响应:**
|
| 57 |
+
```json
|
| 58 |
+
{
|
| 59 |
+
"files": [
|
| 60 |
+
{
|
| 61 |
+
"filename": "document.pdf",
|
| 62 |
+
"size": 1234567,
|
| 63 |
+
"path": ".deer-flow/threads/{thread_id}/user-data/uploads/document.pdf",
|
| 64 |
+
"virtual_path": "/mnt/user-data/uploads/document.pdf",
|
| 65 |
+
"artifact_url": "/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf",
|
| 66 |
+
"extension": ".pdf",
|
| 67 |
+
"modified": 1705997600.0
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"count": 1
|
| 71 |
+
}
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### 3. 删除文件
|
| 75 |
+
```
|
| 76 |
+
DELETE /api/threads/{thread_id}/uploads/{filename}
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
**响应:**
|
| 80 |
+
```json
|
| 81 |
+
{
|
| 82 |
+
"success": true,
|
| 83 |
+
"message": "Deleted document.pdf"
|
| 84 |
+
}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## 支持的文档格式
|
| 88 |
+
|
| 89 |
+
以下格式会自动转换为 Markdown:
|
| 90 |
+
- PDF (`.pdf`)
|
| 91 |
+
- PowerPoint (`.ppt`, `.pptx`)
|
| 92 |
+
- Excel (`.xls`, `.xlsx`)
|
| 93 |
+
- Word (`.doc`, `.docx`)
|
| 94 |
+
|
| 95 |
+
转换后的 Markdown 文件会保存在同一目录下,文件名为原文件名 + `.md` 扩展名。
|
| 96 |
+
|
| 97 |
+
## Agent 集成
|
| 98 |
+
|
| 99 |
+
### 自动文件列举
|
| 100 |
+
|
| 101 |
+
Agent 在每次请求时会自动收到已上传文件的列表,格式如下:
|
| 102 |
+
|
| 103 |
+
```xml
|
| 104 |
+
<uploaded_files>
|
| 105 |
+
The following files have been uploaded and are available for use:
|
| 106 |
+
|
| 107 |
+
- document.pdf (1.2 MB)
|
| 108 |
+
Path: /mnt/user-data/uploads/document.pdf
|
| 109 |
+
|
| 110 |
+
- document.md (45.3 KB)
|
| 111 |
+
Path: /mnt/user-data/uploads/document.md
|
| 112 |
+
|
| 113 |
+
You can read these files using the `read_file` tool with the paths shown above.
|
| 114 |
+
</uploaded_files>
|
| 115 |
+
```
|
| 116 |
+
|
| 117 |
+
### 使用上传的文件
|
| 118 |
+
|
| 119 |
+
Agent 在沙箱中运行,使用虚拟路径访问文件。Agent 可以直接使用 `read_file` 工具读取上传的文件:
|
| 120 |
+
|
| 121 |
+
```python
|
| 122 |
+
# 读取原始 PDF(如果支持)
|
| 123 |
+
read_file(path="/mnt/user-data/uploads/document.pdf")
|
| 124 |
+
|
| 125 |
+
# 读取转换后的 Markdown(推荐)
|
| 126 |
+
read_file(path="/mnt/user-data/uploads/document.md")
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
**路径映射关系:**
|
| 130 |
+
- Agent 使用:`/mnt/user-data/uploads/document.pdf`(虚拟路径)
|
| 131 |
+
- 实际存储:`backend/.deer-flow/threads/{thread_id}/user-data/uploads/document.pdf`
|
| 132 |
+
- 前端访问:`/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf`(HTTP URL)
|
| 133 |
+
|
| 134 |
+
上传流程采用“线程目录优先”策略:
|
| 135 |
+
- 先写入 `backend/.deer-flow/threads/{thread_id}/user-data/uploads/` 作为权威存储
|
| 136 |
+
- 本地沙箱(`sandbox_id=local`)直接使用线程目录内容
|
| 137 |
+
- 非本地沙箱会额外同步到 `/mnt/user-data/uploads/*`,确保运行时可见
|
| 138 |
+
|
| 139 |
+
## 测试示例
|
| 140 |
+
|
| 141 |
+
### 使用 curl 测试
|
| 142 |
+
|
| 143 |
+
```bash
|
| 144 |
+
# 1. 上传单个文件
|
| 145 |
+
curl -X POST http://localhost:2026/api/threads/test-thread/uploads \
|
| 146 |
+
-F "files=@/path/to/document.pdf"
|
| 147 |
+
|
| 148 |
+
# 2. 上传多个文件
|
| 149 |
+
curl -X POST http://localhost:2026/api/threads/test-thread/uploads \
|
| 150 |
+
-F "files=@/path/to/document.pdf" \
|
| 151 |
+
-F "files=@/path/to/presentation.pptx" \
|
| 152 |
+
-F "files=@/path/to/spreadsheet.xlsx"
|
| 153 |
+
|
| 154 |
+
# 3. 列出已上传文件
|
| 155 |
+
curl http://localhost:2026/api/threads/test-thread/uploads/list
|
| 156 |
+
|
| 157 |
+
# 4. 删除文件
|
| 158 |
+
curl -X DELETE http://localhost:2026/api/threads/test-thread/uploads/document.pdf
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
### 使用 Python 测试
|
| 162 |
+
|
| 163 |
+
```python
|
| 164 |
+
import requests
|
| 165 |
+
|
| 166 |
+
thread_id = "test-thread"
|
| 167 |
+
base_url = "http://localhost:2026"
|
| 168 |
+
|
| 169 |
+
# 上传文件
|
| 170 |
+
files = [
|
| 171 |
+
("files", open("document.pdf", "rb")),
|
| 172 |
+
("files", open("presentation.pptx", "rb")),
|
| 173 |
+
]
|
| 174 |
+
response = requests.post(
|
| 175 |
+
f"{base_url}/api/threads/{thread_id}/uploads",
|
| 176 |
+
files=files
|
| 177 |
+
)
|
| 178 |
+
print(response.json())
|
| 179 |
+
|
| 180 |
+
# 列出文件
|
| 181 |
+
response = requests.get(f"{base_url}/api/threads/{thread_id}/uploads/list")
|
| 182 |
+
print(response.json())
|
| 183 |
+
|
| 184 |
+
# 删除文件
|
| 185 |
+
response = requests.delete(
|
| 186 |
+
f"{base_url}/api/threads/{thread_id}/uploads/document.pdf"
|
| 187 |
+
)
|
| 188 |
+
print(response.json())
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
## 文件存储结构
|
| 192 |
+
|
| 193 |
+
```
|
| 194 |
+
backend/.deer-flow/threads/
|
| 195 |
+
└── {thread_id}/
|
| 196 |
+
└── user-data/
|
| 197 |
+
└── uploads/
|
| 198 |
+
├── document.pdf # 原始文件
|
| 199 |
+
├── document.md # 转换后的 Markdown
|
| 200 |
+
├── presentation.pptx
|
| 201 |
+
├── presentation.md
|
| 202 |
+
└── ...
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
## 限制
|
| 206 |
+
|
| 207 |
+
- 最大文件大小:100MB(可在 nginx.conf 中配置 `client_max_body_size`)
|
| 208 |
+
- 文件名安全性:系统会自动验证文件路径,防止目录遍历攻击
|
| 209 |
+
- 线程隔离:每个线程的上传文件相互隔离,无法跨线程访问
|
| 210 |
+
|
| 211 |
+
## 技术实现
|
| 212 |
+
|
| 213 |
+
### 组件
|
| 214 |
+
|
| 215 |
+
1. **Upload Router** (`src/gateway/routers/uploads.py`)
|
| 216 |
+
- 处理文件上传、列表、删除请求
|
| 217 |
+
- 使用 markitdown 转换文档
|
| 218 |
+
|
| 219 |
+
2. **Uploads Middleware** (`src/agents/middlewares/uploads_middleware.py`)
|
| 220 |
+
- 在每次 Agent 请求前注入文件列表
|
| 221 |
+
- 自动生成格式化的文件列表消息
|
| 222 |
+
|
| 223 |
+
3. **Nginx 配置** (`nginx.conf`)
|
| 224 |
+
- 路由上传请求到 Gateway API
|
| 225 |
+
- 配置大文件上传支持
|
| 226 |
+
|
| 227 |
+
### 依赖
|
| 228 |
+
|
| 229 |
+
- `markitdown>=0.0.1a2` - 文档转换
|
| 230 |
+
- `python-multipart>=0.0.20` - 文件上传处理
|
| 231 |
+
|
| 232 |
+
## 故障排查
|
| 233 |
+
|
| 234 |
+
### 文件上传失败
|
| 235 |
+
|
| 236 |
+
1. 检查文件大小是否超过限制
|
| 237 |
+
2. 检查 Gateway API 是否正常运行
|
| 238 |
+
3. 检查磁盘空间是否充足
|
| 239 |
+
4. 查看 Gateway 日志:`make gateway`
|
| 240 |
+
|
| 241 |
+
### 文档转换失败
|
| 242 |
+
|
| 243 |
+
1. 检查 markitdown 是否正确安装:`uv run python -c "import markitdown"`
|
| 244 |
+
2. 查看日志中的具体错误信息
|
| 245 |
+
3. 某些损坏或加密的文档可能无法转换,但原文件仍会保存
|
| 246 |
+
|
| 247 |
+
### Agent 看不到上传的文件
|
| 248 |
+
|
| 249 |
+
1. 确认 UploadsMiddleware 已在 agent.py 中注册
|
| 250 |
+
2. 检查 thread_id 是否正确
|
| 251 |
+
3. 确认文件确实已上传到 `backend/.deer-flow/threads/{thread_id}/user-data/uploads/`
|
| 252 |
+
4. 非本地沙箱场景下,确认上传接口没有报错(需要成功完成 sandbox 同步)
|
| 253 |
+
|
| 254 |
+
## 开发建议
|
| 255 |
+
|
| 256 |
+
### 前端集成
|
| 257 |
+
|
| 258 |
+
```typescript
|
| 259 |
+
// 上传文件示例
|
| 260 |
+
async function uploadFiles(threadId: string, files: File[]) {
|
| 261 |
+
const formData = new FormData();
|
| 262 |
+
files.forEach(file => {
|
| 263 |
+
formData.append('files', file);
|
| 264 |
+
});
|
| 265 |
+
|
| 266 |
+
const response = await fetch(
|
| 267 |
+
`/api/threads/${threadId}/uploads`,
|
| 268 |
+
{
|
| 269 |
+
method: 'POST',
|
| 270 |
+
body: formData,
|
| 271 |
+
}
|
| 272 |
+
);
|
| 273 |
+
|
| 274 |
+
return response.json();
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
// 列出文件
|
| 278 |
+
async function listFiles(threadId: string) {
|
| 279 |
+
const response = await fetch(
|
| 280 |
+
`/api/threads/${threadId}/uploads/list`
|
| 281 |
+
);
|
| 282 |
+
return response.json();
|
| 283 |
+
}
|
| 284 |
+
```
|
| 285 |
+
|
| 286 |
+
### 扩展功能建议
|
| 287 |
+
|
| 288 |
+
1. **文件预览**:添加预览端点,支持在浏览器中直接查看文件
|
| 289 |
+
2. **批量删除**:支持一次删除多个文件
|
| 290 |
+
3. **文件搜索**:支持按文件名或类型搜索
|
| 291 |
+
4. **版本控制**:保留文件的多个版本
|
| 292 |
+
5. **压缩包支持**:自动解压 zip 文件
|
| 293 |
+
6. **图片 OCR**:对上传的图片进行 OCR 识别
|
backend/docs/MCP_SERVER.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MCP (Model Context Protocol) Configuration
|
| 2 |
+
|
| 3 |
+
DeerFlow supports configurable MCP servers and skills to extend its capabilities, which are loaded from a dedicated `extensions_config.json` file in the project root directory.
|
| 4 |
+
|
| 5 |
+
## Setup
|
| 6 |
+
|
| 7 |
+
1. Copy `extensions_config.example.json` to `extensions_config.json` in the project root directory.
|
| 8 |
+
```bash
|
| 9 |
+
# Copy example configuration
|
| 10 |
+
cp extensions_config.example.json extensions_config.json
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
2. Enable the desired MCP servers or skills by setting `"enabled": true`.
|
| 14 |
+
3. Configure each server’s command, arguments, and environment variables as needed.
|
| 15 |
+
4. Restart the application to load and register MCP tools.
|
| 16 |
+
|
| 17 |
+
## OAuth Support (HTTP/SSE MCP Servers)
|
| 18 |
+
|
| 19 |
+
For `http` and `sse` MCP servers, DeerFlow supports OAuth token acquisition and automatic token refresh.
|
| 20 |
+
|
| 21 |
+
- Supported grants: `client_credentials`, `refresh_token`
|
| 22 |
+
- Configure per-server `oauth` block in `extensions_config.json`
|
| 23 |
+
- Secrets should be provided via environment variables (for example: `$MCP_OAUTH_CLIENT_SECRET`)
|
| 24 |
+
|
| 25 |
+
Example:
|
| 26 |
+
|
| 27 |
+
```json
|
| 28 |
+
{
|
| 29 |
+
"mcpServers": {
|
| 30 |
+
"secure-http-server": {
|
| 31 |
+
"enabled": true,
|
| 32 |
+
"type": "http",
|
| 33 |
+
"url": "https://api.example.com/mcp",
|
| 34 |
+
"oauth": {
|
| 35 |
+
"enabled": true,
|
| 36 |
+
"token_url": "https://auth.example.com/oauth/token",
|
| 37 |
+
"grant_type": "client_credentials",
|
| 38 |
+
"client_id": "$MCP_OAUTH_CLIENT_ID",
|
| 39 |
+
"client_secret": "$MCP_OAUTH_CLIENT_SECRET",
|
| 40 |
+
"scope": "mcp.read",
|
| 41 |
+
"refresh_skew_seconds": 60
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
## How It Works
|
| 49 |
+
|
| 50 |
+
MCP servers expose tools that are automatically discovered and integrated into DeerFlow’s agent system at runtime. Once enabled, these tools become available to agents without additional code changes.
|
| 51 |
+
|
| 52 |
+
## Example Capabilities
|
| 53 |
+
|
| 54 |
+
MCP servers can provide access to:
|
| 55 |
+
|
| 56 |
+
- **File systems**
|
| 57 |
+
- **Databases** (e.g., PostgreSQL)
|
| 58 |
+
- **External APIs** (e.g., GitHub, Brave Search)
|
| 59 |
+
- **Browser automation** (e.g., Puppeteer)
|
| 60 |
+
- **Custom MCP server implementations**
|
| 61 |
+
|
| 62 |
+
## Learn More
|
| 63 |
+
|
| 64 |
+
For detailed documentation about the Model Context Protocol, visit:
|
| 65 |
+
https://modelcontextprotocol.io
|
backend/docs/MEMORY_IMPROVEMENTS.md
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Memory System Improvements
|
| 2 |
+
|
| 3 |
+
This document describes recent improvements to the memory system's fact injection mechanism.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
Two major improvements have been made to the `format_memory_for_injection` function:
|
| 8 |
+
|
| 9 |
+
1. **Similarity-Based Fact Retrieval**: Uses TF-IDF to select facts most relevant to current conversation context
|
| 10 |
+
2. **Accurate Token Counting**: Uses tiktoken for precise token estimation instead of rough character-based approximation
|
| 11 |
+
|
| 12 |
+
## 1. Similarity-Based Fact Retrieval
|
| 13 |
+
|
| 14 |
+
### Problem
|
| 15 |
+
The original implementation selected facts based solely on confidence scores, taking the top 15 highest-confidence facts regardless of their relevance to the current conversation. This could result in injecting irrelevant facts while omitting contextually important ones.
|
| 16 |
+
|
| 17 |
+
### Solution
|
| 18 |
+
The new implementation uses **TF-IDF (Term Frequency-Inverse Document Frequency)** vectorization with cosine similarity to measure how relevant each fact is to the current conversation context.
|
| 19 |
+
|
| 20 |
+
**Scoring Formula**:
|
| 21 |
+
```
|
| 22 |
+
final_score = (similarity × 0.6) + (confidence × 0.4)
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
- **Similarity (60% weight)**: Cosine similarity between fact content and current context
|
| 26 |
+
- **Confidence (40% weight)**: LLM-assigned confidence score (0-1)
|
| 27 |
+
|
| 28 |
+
### Benefits
|
| 29 |
+
- **Context-Aware**: Prioritizes facts relevant to what the user is currently discussing
|
| 30 |
+
- **Dynamic**: Different facts surface based on conversation topic
|
| 31 |
+
- **Balanced**: Considers both relevance and reliability
|
| 32 |
+
- **Fallback**: Gracefully degrades to confidence-only ranking if context is unavailable
|
| 33 |
+
|
| 34 |
+
### Example
|
| 35 |
+
Given facts about Python, React, and Docker:
|
| 36 |
+
- User asks: *"How should I write Python tests?"*
|
| 37 |
+
- Prioritizes: Python testing, type hints, pytest
|
| 38 |
+
- User asks: *"How to optimize my Next.js app?"*
|
| 39 |
+
- Prioritizes: React/Next.js experience, performance optimization
|
| 40 |
+
|
| 41 |
+
### Configuration
|
| 42 |
+
Customize weights in `config.yaml` (optional):
|
| 43 |
+
```yaml
|
| 44 |
+
memory:
|
| 45 |
+
similarity_weight: 0.6 # Weight for TF-IDF similarity (0-1)
|
| 46 |
+
confidence_weight: 0.4 # Weight for confidence score (0-1)
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
**Note**: Weights should sum to 1.0 for best results.
|
| 50 |
+
|
| 51 |
+
## 2. Accurate Token Counting
|
| 52 |
+
|
| 53 |
+
### Problem
|
| 54 |
+
The original implementation estimated tokens using a simple formula:
|
| 55 |
+
```python
|
| 56 |
+
max_chars = max_tokens * 4
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
This assumes ~4 characters per token, which is:
|
| 60 |
+
- Inaccurate for many languages and content types
|
| 61 |
+
- Can lead to over-injection (exceeding token limits)
|
| 62 |
+
- Can lead to under-injection (wasting available budget)
|
| 63 |
+
|
| 64 |
+
### Solution
|
| 65 |
+
The new implementation uses **tiktoken**, OpenAI's official tokenizer library, to count tokens accurately:
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
import tiktoken
|
| 69 |
+
|
| 70 |
+
def _count_tokens(text: str, encoding_name: str = "cl100k_base") -> int:
|
| 71 |
+
encoding = tiktoken.get_encoding(encoding_name)
|
| 72 |
+
return len(encoding.encode(text))
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
- Uses `cl100k_base` encoding (GPT-4, GPT-3.5, text-embedding-ada-002)
|
| 76 |
+
- Provides exact token counts for budget management
|
| 77 |
+
- Falls back to character-based estimation if tiktoken fails
|
| 78 |
+
|
| 79 |
+
### Benefits
|
| 80 |
+
- **Precision**: Exact token counts match what the model sees
|
| 81 |
+
- **Budget Optimization**: Maximizes use of available token budget
|
| 82 |
+
- **No Overflows**: Prevents exceeding `max_injection_tokens` limit
|
| 83 |
+
- **Better Planning**: Each section's token cost is known precisely
|
| 84 |
+
|
| 85 |
+
### Example
|
| 86 |
+
```python
|
| 87 |
+
text = "This is a test string to count tokens accurately using tiktoken."
|
| 88 |
+
|
| 89 |
+
# Old method
|
| 90 |
+
char_count = len(text) # 64 characters
|
| 91 |
+
old_estimate = char_count // 4 # 16 tokens (overestimate)
|
| 92 |
+
|
| 93 |
+
# New method
|
| 94 |
+
accurate_count = _count_tokens(text) # 13 tokens (exact)
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
**Result**: 3-token difference (18.75% error rate)
|
| 98 |
+
|
| 99 |
+
In production, errors can be much larger for:
|
| 100 |
+
- Code snippets (more tokens per character)
|
| 101 |
+
- Non-English text (variable token ratios)
|
| 102 |
+
- Technical jargon (often multi-token words)
|
| 103 |
+
|
| 104 |
+
## Implementation Details
|
| 105 |
+
|
| 106 |
+
### Function Signature
|
| 107 |
+
```python
|
| 108 |
+
def format_memory_for_injection(
|
| 109 |
+
memory_data: dict[str, Any],
|
| 110 |
+
max_tokens: int = 2000,
|
| 111 |
+
current_context: str | None = None,
|
| 112 |
+
) -> str:
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
**New Parameter**:
|
| 116 |
+
- `current_context`: Optional string containing recent conversation messages for similarity calculation
|
| 117 |
+
|
| 118 |
+
### Backward Compatibility
|
| 119 |
+
The function remains **100% backward compatible**:
|
| 120 |
+
- If `current_context` is `None` or empty, falls back to confidence-only ranking
|
| 121 |
+
- Existing callers without the parameter work exactly as before
|
| 122 |
+
- Token counting is always accurate (transparent improvement)
|
| 123 |
+
|
| 124 |
+
### Integration Point
|
| 125 |
+
Memory is **dynamically injected** via `MemoryMiddleware.before_model()`:
|
| 126 |
+
|
| 127 |
+
```python
|
| 128 |
+
# src/agents/middlewares/memory_middleware.py
|
| 129 |
+
|
| 130 |
+
def _extract_conversation_context(messages: list, max_turns: int = 3) -> str:
|
| 131 |
+
"""Extract recent conversation (user input + final responses only)."""
|
| 132 |
+
context_parts = []
|
| 133 |
+
turn_count = 0
|
| 134 |
+
|
| 135 |
+
for msg in reversed(messages):
|
| 136 |
+
if msg.type == "human":
|
| 137 |
+
# Always include user messages
|
| 138 |
+
context_parts.append(extract_text(msg))
|
| 139 |
+
turn_count += 1
|
| 140 |
+
if turn_count >= max_turns:
|
| 141 |
+
break
|
| 142 |
+
|
| 143 |
+
elif msg.type == "ai" and not msg.tool_calls:
|
| 144 |
+
# Only include final AI responses (no tool_calls)
|
| 145 |
+
context_parts.append(extract_text(msg))
|
| 146 |
+
|
| 147 |
+
# Skip tool messages and AI messages with tool_calls
|
| 148 |
+
|
| 149 |
+
return " ".join(reversed(context_parts))
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class MemoryMiddleware:
|
| 153 |
+
def before_model(self, state, runtime):
|
| 154 |
+
"""Inject memory before EACH LLM call (not just before_agent)."""
|
| 155 |
+
|
| 156 |
+
# Get recent conversation context (filtered)
|
| 157 |
+
conversation_context = _extract_conversation_context(
|
| 158 |
+
state["messages"],
|
| 159 |
+
max_turns=3
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
# Load memory with context-aware fact selection
|
| 163 |
+
memory_data = get_memory_data()
|
| 164 |
+
memory_content = format_memory_for_injection(
|
| 165 |
+
memory_data,
|
| 166 |
+
max_tokens=config.max_injection_tokens,
|
| 167 |
+
current_context=conversation_context, # ✅ Clean conversation only
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
# Inject as system message
|
| 171 |
+
memory_message = SystemMessage(
|
| 172 |
+
content=f"<memory>\n{memory_content}\n</memory>",
|
| 173 |
+
name="memory_context",
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
return {"messages": [memory_message] + state["messages"]}
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
### How It Works
|
| 180 |
+
|
| 181 |
+
1. **User continues conversation**:
|
| 182 |
+
```
|
| 183 |
+
Turn 1: "I'm working on a Python project"
|
| 184 |
+
Turn 2: "It uses FastAPI and SQLAlchemy"
|
| 185 |
+
Turn 3: "How do I write tests?" ← Current query
|
| 186 |
+
```
|
| 187 |
+
|
| 188 |
+
2. **Extract recent context**: Last 3 turns combined:
|
| 189 |
+
```
|
| 190 |
+
"I'm working on a Python project. It uses FastAPI and SQLAlchemy. How do I write tests?"
|
| 191 |
+
```
|
| 192 |
+
|
| 193 |
+
3. **TF-IDF scoring**: Ranks facts by relevance to this context
|
| 194 |
+
- High score: "Prefers pytest for testing" (testing + Python)
|
| 195 |
+
- High score: "Likes type hints in Python" (Python related)
|
| 196 |
+
- High score: "Expert in Python and FastAPI" (Python + FastAPI)
|
| 197 |
+
- Low score: "Uses Docker for containerization" (less relevant)
|
| 198 |
+
|
| 199 |
+
4. **Injection**: Top-ranked facts injected into system prompt's `<memory>` section
|
| 200 |
+
|
| 201 |
+
5. **Agent sees**: Full system prompt with relevant memory context
|
| 202 |
+
|
| 203 |
+
### Benefits of Dynamic System Prompt
|
| 204 |
+
|
| 205 |
+
- **Multi-Turn Context**: Uses last 3 turns, not just current question
|
| 206 |
+
- Captures ongoing conversation flow
|
| 207 |
+
- Better understanding of user's current focus
|
| 208 |
+
- **Query-Specific Facts**: Different facts surface based on conversation topic
|
| 209 |
+
- **Clean Architecture**: No middleware message manipulation
|
| 210 |
+
- **LangChain Native**: Uses built-in dynamic system prompt support
|
| 211 |
+
- **Runtime Flexibility**: Memory regenerated for each agent invocation
|
| 212 |
+
|
| 213 |
+
## Dependencies
|
| 214 |
+
|
| 215 |
+
New dependencies added to `pyproject.toml`:
|
| 216 |
+
```toml
|
| 217 |
+
dependencies = [
|
| 218 |
+
# ... existing dependencies ...
|
| 219 |
+
"tiktoken>=0.8.0", # Accurate token counting
|
| 220 |
+
"scikit-learn>=1.6.1", # TF-IDF vectorization
|
| 221 |
+
]
|
| 222 |
+
```
|
| 223 |
+
|
| 224 |
+
Install with:
|
| 225 |
+
```bash
|
| 226 |
+
cd backend
|
| 227 |
+
uv sync
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
## Testing
|
| 231 |
+
|
| 232 |
+
Run the test script to verify improvements:
|
| 233 |
+
```bash
|
| 234 |
+
cd backend
|
| 235 |
+
python test_memory_improvement.py
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
Expected output shows:
|
| 239 |
+
- Different fact ordering based on context
|
| 240 |
+
- Accurate token counts vs old estimates
|
| 241 |
+
- Budget-respecting fact selection
|
| 242 |
+
|
| 243 |
+
## Performance Impact
|
| 244 |
+
|
| 245 |
+
### Computational Cost
|
| 246 |
+
- **TF-IDF Calculation**: O(n × m) where n=facts, m=vocabulary
|
| 247 |
+
- Negligible for typical fact counts (10-100 facts)
|
| 248 |
+
- Caching opportunities if context doesn't change
|
| 249 |
+
- **Token Counting**: ~10-100µs per call
|
| 250 |
+
- Faster than the old character-counting approach
|
| 251 |
+
- Minimal overhead compared to LLM inference
|
| 252 |
+
|
| 253 |
+
### Memory Usage
|
| 254 |
+
- **TF-IDF Vectorizer**: ~1-5MB for typical vocabulary
|
| 255 |
+
- Instantiated once per injection call
|
| 256 |
+
- Garbage collected after use
|
| 257 |
+
- **Tiktoken Encoding**: ~1MB (cached singleton)
|
| 258 |
+
- Loaded once per process lifetime
|
| 259 |
+
|
| 260 |
+
### Recommendations
|
| 261 |
+
- Current implementation is optimized for accuracy over caching
|
| 262 |
+
- For high-throughput scenarios, consider:
|
| 263 |
+
- Pre-computing fact embeddings (store in memory.json)
|
| 264 |
+
- Caching TF-IDF vectorizer between calls
|
| 265 |
+
- Using approximate nearest neighbor search for >1000 facts
|
| 266 |
+
|
| 267 |
+
## Summary
|
| 268 |
+
|
| 269 |
+
| Aspect | Before | After |
|
| 270 |
+
|--------|--------|-------|
|
| 271 |
+
| Fact Selection | Top 15 by confidence only | Relevance-based (similarity + confidence) |
|
| 272 |
+
| Token Counting | `len(text) // 4` | `tiktoken.encode(text)` |
|
| 273 |
+
| Context Awareness | None | TF-IDF cosine similarity |
|
| 274 |
+
| Accuracy | ±25% token estimate | Exact token count |
|
| 275 |
+
| Configuration | Fixed weights | Customizable similarity/confidence weights |
|
| 276 |
+
|
| 277 |
+
These improvements result in:
|
| 278 |
+
- **More relevant** facts injected into context
|
| 279 |
+
- **Better utilization** of available token budget
|
| 280 |
+
- **Fewer hallucinations** due to focused context
|
| 281 |
+
- **Higher quality** agent responses
|
backend/docs/MEMORY_IMPROVEMENTS_SUMMARY.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Memory System Improvements - Summary
|
| 2 |
+
|
| 3 |
+
## 改进概述
|
| 4 |
+
|
| 5 |
+
针对你提出的两个问题进行了优化:
|
| 6 |
+
1. ✅ **粗糙的 token 计算**(`字符数 * 4`)→ 使用 tiktoken 精确计算
|
| 7 |
+
2. ✅ **缺乏相似度召回** → 使用 TF-IDF + 最近对话上下文
|
| 8 |
+
|
| 9 |
+
## 核心改进
|
| 10 |
+
|
| 11 |
+
### 1. 基于对话上下文的智能 Facts 召回
|
| 12 |
+
|
| 13 |
+
**之前**:
|
| 14 |
+
- 只按 confidence 排序取前 15 个
|
| 15 |
+
- 无论用户在讨论什么都注入相同的 facts
|
| 16 |
+
|
| 17 |
+
**现在**:
|
| 18 |
+
- 提取最近 **3 轮对话**(human + AI 消息)作为上下文
|
| 19 |
+
- 使用 **TF-IDF 余弦相似度**计算每个 fact 与对话的相关性
|
| 20 |
+
- 综合评分:`相似度(60%) + 置信度(40%)`
|
| 21 |
+
- 动态选择最相关的 facts
|
| 22 |
+
|
| 23 |
+
**示例**:
|
| 24 |
+
```
|
| 25 |
+
对话历史:
|
| 26 |
+
Turn 1: "我在做一个 Python 项目"
|
| 27 |
+
Turn 2: "使用 FastAPI 和 SQLAlchemy"
|
| 28 |
+
Turn 3: "怎么写测试?"
|
| 29 |
+
|
| 30 |
+
上下文: "我在做一个 Python 项目 使用 FastAPI 和 SQLAlchemy 怎么写测试?"
|
| 31 |
+
|
| 32 |
+
相关度高的 facts:
|
| 33 |
+
✓ "Prefers pytest for testing" (Python + 测试)
|
| 34 |
+
✓ "Expert in Python and FastAPI" (Python + FastAPI)
|
| 35 |
+
✓ "Likes type hints in Python" (Python)
|
| 36 |
+
|
| 37 |
+
相关度低的 facts:
|
| 38 |
+
✗ "Uses Docker for containerization" (不相关)
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
### 2. 精确的 Token 计算
|
| 42 |
+
|
| 43 |
+
**之前**:
|
| 44 |
+
```python
|
| 45 |
+
max_chars = max_tokens * 4 # 粗糙估算
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
**现在**:
|
| 49 |
+
```python
|
| 50 |
+
import tiktoken
|
| 51 |
+
|
| 52 |
+
def _count_tokens(text: str) -> int:
|
| 53 |
+
encoding = tiktoken.get_encoding("cl100k_base") # GPT-4/3.5
|
| 54 |
+
return len(encoding.encode(text))
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
**效果对比**:
|
| 58 |
+
```python
|
| 59 |
+
text = "This is a test string to count tokens accurately."
|
| 60 |
+
旧方法: len(text) // 4 = 12 tokens (估算)
|
| 61 |
+
新方法: tiktoken.encode = 10 tokens (精确)
|
| 62 |
+
误差: 20%
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
### 3. 多轮对话上下文
|
| 66 |
+
|
| 67 |
+
**之前的担心**:
|
| 68 |
+
> "只传最近一条 human message 会不会上下文不太够?"
|
| 69 |
+
|
| 70 |
+
**现在的解决方案**:
|
| 71 |
+
- 提取最近 **3 轮对话**(可配置)
|
| 72 |
+
- 包括 human 和 AI 消息
|
| 73 |
+
- 更完整的对话上下文
|
| 74 |
+
|
| 75 |
+
**示例**:
|
| 76 |
+
```
|
| 77 |
+
单条消息: "怎么写测试?"
|
| 78 |
+
→ 缺少上下文,不知道是什么项目
|
| 79 |
+
|
| 80 |
+
3轮对话: "Python 项目 + FastAPI + 怎么写测试?"
|
| 81 |
+
→ 完整上下文,能选择更相关的 facts
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
## 实现方式
|
| 85 |
+
|
| 86 |
+
### Middleware 动态注入
|
| 87 |
+
|
| 88 |
+
使用 `before_model` 钩子在**每次 LLM 调用前**注入 memory:
|
| 89 |
+
|
| 90 |
+
```python
|
| 91 |
+
# src/agents/middlewares/memory_middleware.py
|
| 92 |
+
|
| 93 |
+
def _extract_conversation_context(messages: list, max_turns: int = 3) -> str:
|
| 94 |
+
"""提取最近 3 轮对话(只包含用户输入和最终回复)"""
|
| 95 |
+
context_parts = []
|
| 96 |
+
turn_count = 0
|
| 97 |
+
|
| 98 |
+
for msg in reversed(messages):
|
| 99 |
+
msg_type = getattr(msg, "type", None)
|
| 100 |
+
|
| 101 |
+
if msg_type == "human":
|
| 102 |
+
# ✅ 总是包含用户消息
|
| 103 |
+
content = extract_text(msg)
|
| 104 |
+
if content:
|
| 105 |
+
context_parts.append(content)
|
| 106 |
+
turn_count += 1
|
| 107 |
+
if turn_count >= max_turns:
|
| 108 |
+
break
|
| 109 |
+
|
| 110 |
+
elif msg_type == "ai":
|
| 111 |
+
# ✅ 只包含没有 tool_calls 的 AI 消息(最终回复)
|
| 112 |
+
tool_calls = getattr(msg, "tool_calls", None)
|
| 113 |
+
if not tool_calls:
|
| 114 |
+
content = extract_text(msg)
|
| 115 |
+
if content:
|
| 116 |
+
context_parts.append(content)
|
| 117 |
+
|
| 118 |
+
# ✅ 跳过 tool messages 和带 tool_calls 的 AI 消息
|
| 119 |
+
|
| 120 |
+
return " ".join(reversed(context_parts))
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class MemoryMiddleware:
|
| 124 |
+
def before_model(self, state, runtime):
|
| 125 |
+
"""在每次 LLM 调用前注入 memory(不是 before_agent)"""
|
| 126 |
+
|
| 127 |
+
# 1. 提取最近 3 轮对话(过滤掉 tool calls)
|
| 128 |
+
messages = state["messages"]
|
| 129 |
+
conversation_context = _extract_conversation_context(messages, max_turns=3)
|
| 130 |
+
|
| 131 |
+
# 2. 使用干净的对话上下文选择相关 facts
|
| 132 |
+
memory_data = get_memory_data()
|
| 133 |
+
memory_content = format_memory_for_injection(
|
| 134 |
+
memory_data,
|
| 135 |
+
max_tokens=config.max_injection_tokens,
|
| 136 |
+
current_context=conversation_context, # ✅ 只包含真实对话内容
|
| 137 |
+
)
|
| 138 |
+
|
| 139 |
+
# 3. 作为 system message 注入到消息列表开头
|
| 140 |
+
memory_message = SystemMessage(
|
| 141 |
+
content=f"<memory>\n{memory_content}\n</memory>",
|
| 142 |
+
name="memory_context", # 用于去重检测
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
# 4. 插入到消息列表开头
|
| 146 |
+
updated_messages = [memory_message] + messages
|
| 147 |
+
return {"messages": updated_messages}
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
### 为什么这样设计?
|
| 151 |
+
|
| 152 |
+
基于你的三个重要观察:
|
| 153 |
+
|
| 154 |
+
1. **应该用 `before_model` 而不是 `before_agent`**
|
| 155 |
+
- ✅ `before_agent`: 只在整个 agent 开始时调用一次
|
| 156 |
+
- ✅ `before_model`: 在**每次 LLM 调用前**都会调用
|
| 157 |
+
- ✅ 这样每次 LLM 推理都能看到最新的相关 memory
|
| 158 |
+
|
| 159 |
+
2. **messages 数组里只有 human/ai/tool,没有 system**
|
| 160 |
+
- ✅ 虽然不常见,但 LangChain 允许在对话中插入 system message
|
| 161 |
+
- ✅ Middleware 可以修改 messages 数组
|
| 162 |
+
- ✅ 使用 `name="memory_context"` 防止重复注入
|
| 163 |
+
|
| 164 |
+
3. **��该剔除 tool call 的 AI messages,只传用户输入和最终输出**
|
| 165 |
+
- ✅ 过滤掉带 `tool_calls` 的 AI 消息(中间步骤)
|
| 166 |
+
- ✅ 只保留: - Human 消息(用户输入)
|
| 167 |
+
- AI 消息但无 tool_calls(最终回复)
|
| 168 |
+
- ✅ 上下文更干净,TF-IDF 相似度计算更准确
|
| 169 |
+
|
| 170 |
+
## 配置选项
|
| 171 |
+
|
| 172 |
+
在 `config.yaml` 中可以调整:
|
| 173 |
+
|
| 174 |
+
```yaml
|
| 175 |
+
memory:
|
| 176 |
+
enabled: true
|
| 177 |
+
max_injection_tokens: 2000 # ✅ 使用精确 token 计数
|
| 178 |
+
|
| 179 |
+
# 高级设置(可选)
|
| 180 |
+
# max_context_turns: 3 # 对话轮数(默认 3)
|
| 181 |
+
# similarity_weight: 0.6 # 相似度权重
|
| 182 |
+
# confidence_weight: 0.4 # 置信度权重
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
## 依赖变更
|
| 186 |
+
|
| 187 |
+
新增依赖:
|
| 188 |
+
```toml
|
| 189 |
+
dependencies = [
|
| 190 |
+
"tiktoken>=0.8.0", # 精确 token 计数
|
| 191 |
+
"scikit-learn>=1.6.1", # TF-IDF 向量化
|
| 192 |
+
]
|
| 193 |
+
```
|
| 194 |
+
|
| 195 |
+
安装:
|
| 196 |
+
```bash
|
| 197 |
+
cd backend
|
| 198 |
+
uv sync
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
## 性能影响
|
| 202 |
+
|
| 203 |
+
- **TF-IDF 计算**:O(n × m),n=facts 数量,m=词汇表大小
|
| 204 |
+
- 典型场景(10-100 facts):< 10ms
|
| 205 |
+
- **Token 计数**:~100µs per call
|
| 206 |
+
- 比字符计数还快
|
| 207 |
+
- **总开销**:可忽略(相比 LLM 推理)
|
| 208 |
+
|
| 209 |
+
## 向后兼容性
|
| 210 |
+
|
| 211 |
+
✅ 完全向后兼容:
|
| 212 |
+
- 如果没有 `current_context`,退化为按 confidence 排序
|
| 213 |
+
- 所有现有配置继续工作
|
| 214 |
+
- 不影响其他功能
|
| 215 |
+
|
| 216 |
+
## 文件变更清单
|
| 217 |
+
|
| 218 |
+
1. **核心功能**
|
| 219 |
+
- `src/agents/memory/prompt.py` - 添加 TF-IDF 召回和精确 token 计数
|
| 220 |
+
- `src/agents/lead_agent/prompt.py` - 动态系统提示
|
| 221 |
+
- `src/agents/lead_agent/agent.py` - 传入函数而非字符串
|
| 222 |
+
|
| 223 |
+
2. **依赖**
|
| 224 |
+
- `pyproject.toml` - 添加 tiktoken 和 scikit-learn
|
| 225 |
+
|
| 226 |
+
3. **文档**
|
| 227 |
+
- `docs/MEMORY_IMPROVEMENTS.md` - 详细技术文档
|
| 228 |
+
- `docs/MEMORY_IMPROVEMENTS_SUMMARY.md` - 改进总结(本文件)
|
| 229 |
+
- `CLAUDE.md` - 更新架构说明
|
| 230 |
+
- `config.example.yaml` - 添加配置说明
|
| 231 |
+
|
| 232 |
+
## 测试验证
|
| 233 |
+
|
| 234 |
+
运行项目验证:
|
| 235 |
+
```bash
|
| 236 |
+
cd backend
|
| 237 |
+
make dev
|
| 238 |
+
```
|
| 239 |
+
|
| 240 |
+
在对话中测试:
|
| 241 |
+
1. 讨论不同主题(Python、React、Docker 等)
|
| 242 |
+
2. 观察不同对话注入的 facts 是否不同
|
| 243 |
+
3. 检查 token 预算是否被准确控制
|
| 244 |
+
|
| 245 |
+
## 总结
|
| 246 |
+
|
| 247 |
+
| 问题 | 之前 | 现在 |
|
| 248 |
+
|------|------|------|
|
| 249 |
+
| Token 计算 | `len(text) // 4` (±25% 误差) | `tiktoken.encode()` (精确) |
|
| 250 |
+
| Facts 选择 | 按 confidence 固定排序 | TF-IDF 相似度 + confidence |
|
| 251 |
+
| 上下文 | 无 | 最近 3 轮对话 |
|
| 252 |
+
| 实现方式 | 静态系统提示 | 动态系统提示函数 |
|
| 253 |
+
| 配置灵活性 | 有限 | 可调轮数和权重 |
|
| 254 |
+
|
| 255 |
+
所有改进都实现了,并且:
|
| 256 |
+
- ✅ 不修改 messages 数组
|
| 257 |
+
- ✅ 使用多轮对话上下文
|
| 258 |
+
- ✅ 精确 token 计数
|
| 259 |
+
- ✅ 智能相似度召回
|
| 260 |
+
- ✅ 完全向后兼容
|
backend/docs/PATH_EXAMPLES.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 文件路径使用示例
|
| 2 |
+
|
| 3 |
+
## 三种路径类型
|
| 4 |
+
|
| 5 |
+
DeerFlow 的文件上传系统返回三种不同的路径,每种路径用于不同的场景:
|
| 6 |
+
|
| 7 |
+
### 1. 实际文件系统路径 (path)
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
.deer-flow/threads/{thread_id}/user-data/uploads/document.pdf
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
**用途:**
|
| 14 |
+
- 文件在服务器文件系统中的实际位置
|
| 15 |
+
- 相对于 `backend/` 目录
|
| 16 |
+
- 用于直接文件系统访问、备份、调试等
|
| 17 |
+
|
| 18 |
+
**示例:**
|
| 19 |
+
```python
|
| 20 |
+
# Python 代码中直接访问
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
file_path = Path("backend/.deer-flow/threads/abc123/user-data/uploads/document.pdf")
|
| 23 |
+
content = file_path.read_bytes()
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
### 2. 虚拟路径 (virtual_path)
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
/mnt/user-data/uploads/document.pdf
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
**用途:**
|
| 33 |
+
- Agent 在沙箱环境中使用的路径
|
| 34 |
+
- 沙箱系统会自动映射到实际路径
|
| 35 |
+
- Agent 的所有文件操作工具都使用这个路径
|
| 36 |
+
|
| 37 |
+
**示例:**
|
| 38 |
+
Agent 在对话中使用:
|
| 39 |
+
```python
|
| 40 |
+
# Agent 使用 read_file 工具
|
| 41 |
+
read_file(path="/mnt/user-data/uploads/document.pdf")
|
| 42 |
+
|
| 43 |
+
# Agent 使用 bash 工具
|
| 44 |
+
bash(command="cat /mnt/user-data/uploads/document.pdf")
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### 3. HTTP 访问 URL (artifact_url)
|
| 48 |
+
|
| 49 |
+
```
|
| 50 |
+
/api/threads/{thread_id}/artifacts/mnt/user-data/uploads/document.pdf
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
**用途:**
|
| 54 |
+
- 前端通过 HTTP 访问文件
|
| 55 |
+
- 用于下载、预览文件
|
| 56 |
+
- 可以直接在浏览器中打开
|
| 57 |
+
|
| 58 |
+
**示例:**
|
| 59 |
+
```typescript
|
| 60 |
+
// 前端 TypeScript/JavaScript 代码
|
| 61 |
+
const threadId = 'abc123';
|
| 62 |
+
const filename = 'document.pdf';
|
| 63 |
+
|
| 64 |
+
// 下载文件
|
| 65 |
+
const downloadUrl = `/api/threads/${threadId}/artifacts/mnt/user-data/uploads/${filename}?download=true`;
|
| 66 |
+
window.open(downloadUrl);
|
| 67 |
+
|
| 68 |
+
// 在新窗口预览
|
| 69 |
+
const viewUrl = `/api/threads/${threadId}/artifacts/mnt/user-data/uploads/${filename}`;
|
| 70 |
+
window.open(viewUrl, '_blank');
|
| 71 |
+
|
| 72 |
+
// 使用 fetch API 获取
|
| 73 |
+
const response = await fetch(viewUrl);
|
| 74 |
+
const blob = await response.blob();
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
## 完整使用流程示例
|
| 78 |
+
|
| 79 |
+
### 场景:前端上传文件并让 Agent 处理
|
| 80 |
+
|
| 81 |
+
```typescript
|
| 82 |
+
// 1. 前端上传文件
|
| 83 |
+
async function uploadAndProcess(threadId: string, file: File) {
|
| 84 |
+
// 上传文件
|
| 85 |
+
const formData = new FormData();
|
| 86 |
+
formData.append('files', file);
|
| 87 |
+
|
| 88 |
+
const uploadResponse = await fetch(
|
| 89 |
+
`/api/threads/${threadId}/uploads`,
|
| 90 |
+
{
|
| 91 |
+
method: 'POST',
|
| 92 |
+
body: formData
|
| 93 |
+
}
|
| 94 |
+
);
|
| 95 |
+
|
| 96 |
+
const uploadData = await uploadResponse.json();
|
| 97 |
+
const fileInfo = uploadData.files[0];
|
| 98 |
+
|
| 99 |
+
console.log('文件信息:', fileInfo);
|
| 100 |
+
// {
|
| 101 |
+
// filename: "report.pdf",
|
| 102 |
+
// path: ".deer-flow/threads/abc123/user-data/uploads/report.pdf",
|
| 103 |
+
// virtual_path: "/mnt/user-data/uploads/report.pdf",
|
| 104 |
+
// artifact_url: "/api/threads/abc123/artifacts/mnt/user-data/uploads/report.pdf",
|
| 105 |
+
// markdown_file: "report.md",
|
| 106 |
+
// markdown_path: ".deer-flow/threads/abc123/user-data/uploads/report.md",
|
| 107 |
+
// markdown_virtual_path: "/mnt/user-data/uploads/report.md",
|
| 108 |
+
// markdown_artifact_url: "/api/threads/abc123/artifacts/mnt/user-data/uploads/report.md"
|
| 109 |
+
// }
|
| 110 |
+
|
| 111 |
+
// 2. 发送消息给 Agent
|
| 112 |
+
await sendMessage(threadId, "请分析刚上传的 PDF 文件");
|
| 113 |
+
|
| 114 |
+
// Agent 会自动看到文件列表,包含:
|
| 115 |
+
// - report.pdf (虚拟路径: /mnt/user-data/uploads/report.pdf)
|
| 116 |
+
// - report.md (虚拟路径: /mnt/user-data/uploads/report.md)
|
| 117 |
+
|
| 118 |
+
// 3. 前端可以直接访问转换后的 Markdown
|
| 119 |
+
const mdResponse = await fetch(fileInfo.markdown_artifact_url);
|
| 120 |
+
const markdownContent = await mdResponse.text();
|
| 121 |
+
console.log('Markdown 内容:', markdownContent);
|
| 122 |
+
|
| 123 |
+
// 4. 或者下载原始 PDF
|
| 124 |
+
const downloadLink = document.createElement('a');
|
| 125 |
+
downloadLink.href = fileInfo.artifact_url + '?download=true';
|
| 126 |
+
downloadLink.download = fileInfo.filename;
|
| 127 |
+
downloadLink.click();
|
| 128 |
+
}
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## 路径转换表
|
| 132 |
+
|
| 133 |
+
| 场景 | 使用的路径类型 | 示例 |
|
| 134 |
+
|------|---------------|------|
|
| 135 |
+
| 服务器后端代码直接访问 | `path` | `.deer-flow/threads/abc123/user-data/uploads/file.pdf` |
|
| 136 |
+
| Agent 工具调用 | `virtual_path` | `/mnt/user-data/uploads/file.pdf` |
|
| 137 |
+
| 前端下载/预览 | `artifact_url` | `/api/threads/abc123/artifacts/mnt/user-data/uploads/file.pdf` |
|
| 138 |
+
| 备份脚本 | `path` | `.deer-flow/threads/abc123/user-data/uploads/file.pdf` |
|
| 139 |
+
| 日志记录 | `path` | `.deer-flow/threads/abc123/user-data/uploads/file.pdf` |
|
| 140 |
+
|
| 141 |
+
## 代码示例集合
|
| 142 |
+
|
| 143 |
+
### Python - 后端处理
|
| 144 |
+
|
| 145 |
+
```python
|
| 146 |
+
from pathlib import Path
|
| 147 |
+
from src.agents.middlewares.thread_data_middleware import THREAD_DATA_BASE_DIR
|
| 148 |
+
|
| 149 |
+
def process_uploaded_file(thread_id: str, filename: str):
|
| 150 |
+
# 使用实际路径
|
| 151 |
+
base_dir = Path.cwd() / THREAD_DATA_BASE_DIR / thread_id / "user-data" / "uploads"
|
| 152 |
+
file_path = base_dir / filename
|
| 153 |
+
|
| 154 |
+
# 直接读取
|
| 155 |
+
with open(file_path, 'rb') as f:
|
| 156 |
+
content = f.read()
|
| 157 |
+
|
| 158 |
+
return content
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
### JavaScript - 前端访问
|
| 162 |
+
|
| 163 |
+
```javascript
|
| 164 |
+
// 列出已上传的文件
|
| 165 |
+
async function listUploadedFiles(threadId) {
|
| 166 |
+
const response = await fetch(`/api/threads/${threadId}/uploads/list`);
|
| 167 |
+
const data = await response.json();
|
| 168 |
+
|
| 169 |
+
// 为每个文件创建下载链接
|
| 170 |
+
data.files.forEach(file => {
|
| 171 |
+
console.log(`文件: ${file.filename}`);
|
| 172 |
+
console.log(`下载: ${file.artifact_url}?download=true`);
|
| 173 |
+
console.log(`预览: ${file.artifact_url}`);
|
| 174 |
+
|
| 175 |
+
// 如果是文档,还有 Markdown 版本
|
| 176 |
+
if (file.markdown_artifact_url) {
|
| 177 |
+
console.log(`Markdown: ${file.markdown_artifact_url}`);
|
| 178 |
+
}
|
| 179 |
+
});
|
| 180 |
+
|
| 181 |
+
return data.files;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// 删除文件
|
| 185 |
+
async function deleteFile(threadId, filename) {
|
| 186 |
+
const response = await fetch(
|
| 187 |
+
`/api/threads/${threadId}/uploads/${filename}`,
|
| 188 |
+
{ method: 'DELETE' }
|
| 189 |
+
);
|
| 190 |
+
return response.json();
|
| 191 |
+
}
|
| 192 |
+
```
|
| 193 |
+
|
| 194 |
+
### React 组件示例
|
| 195 |
+
|
| 196 |
+
```tsx
|
| 197 |
+
import React, { useState, useEffect } from 'react';
|
| 198 |
+
|
| 199 |
+
interface UploadedFile {
|
| 200 |
+
filename: string;
|
| 201 |
+
size: number;
|
| 202 |
+
path: string;
|
| 203 |
+
virtual_path: string;
|
| 204 |
+
artifact_url: string;
|
| 205 |
+
extension: string;
|
| 206 |
+
modified: number;
|
| 207 |
+
markdown_artifact_url?: string;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
function FileUploadList({ threadId }: { threadId: string }) {
|
| 211 |
+
const [files, setFiles] = useState<UploadedFile[]>([]);
|
| 212 |
+
|
| 213 |
+
useEffect(() => {
|
| 214 |
+
fetchFiles();
|
| 215 |
+
}, [threadId]);
|
| 216 |
+
|
| 217 |
+
async function fetchFiles() {
|
| 218 |
+
const response = await fetch(`/api/threads/${threadId}/uploads/list`);
|
| 219 |
+
const data = await response.json();
|
| 220 |
+
setFiles(data.files);
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
async function handleUpload(event: React.ChangeEvent<HTMLInputElement>) {
|
| 224 |
+
const fileList = event.target.files;
|
| 225 |
+
if (!fileList) return;
|
| 226 |
+
|
| 227 |
+
const formData = new FormData();
|
| 228 |
+
Array.from(fileList).forEach(file => {
|
| 229 |
+
formData.append('files', file);
|
| 230 |
+
});
|
| 231 |
+
|
| 232 |
+
await fetch(`/api/threads/${threadId}/uploads`, {
|
| 233 |
+
method: 'POST',
|
| 234 |
+
body: formData
|
| 235 |
+
});
|
| 236 |
+
|
| 237 |
+
fetchFiles(); // 刷新列表
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
async function handleDelete(filename: string) {
|
| 241 |
+
await fetch(`/api/threads/${threadId}/uploads/${filename}`, {
|
| 242 |
+
method: 'DELETE'
|
| 243 |
+
});
|
| 244 |
+
fetchFiles(); // 刷新列表
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
return (
|
| 248 |
+
<div>
|
| 249 |
+
<input type="file" multiple onChange={handleUpload} />
|
| 250 |
+
|
| 251 |
+
<ul>
|
| 252 |
+
{files.map(file => (
|
| 253 |
+
<li key={file.filename}>
|
| 254 |
+
<span>{file.filename}</span>
|
| 255 |
+
<a href={file.artifact_url} target="_blank">预览</a>
|
| 256 |
+
<a href={`${file.artifact_url}?download=true`}>下载</a>
|
| 257 |
+
{file.markdown_artifact_url && (
|
| 258 |
+
<a href={file.markdown_artifact_url} target="_blank">Markdown</a>
|
| 259 |
+
)}
|
| 260 |
+
<button onClick={() => handleDelete(file.filename)}>删除</button>
|
| 261 |
+
</li>
|
| 262 |
+
))}
|
| 263 |
+
</ul>
|
| 264 |
+
</div>
|
| 265 |
+
);
|
| 266 |
+
}
|
| 267 |
+
```
|
| 268 |
+
|
| 269 |
+
## 注意事项
|
| 270 |
+
|
| 271 |
+
1. **路径安全性**
|
| 272 |
+
- 实际路径(`path`)包含线程 ID,确保隔离
|
| 273 |
+
- API 会验证路径,防止目录遍历攻击
|
| 274 |
+
- 前端不应直接使用 `path`,而应使用 `artifact_url`
|
| 275 |
+
|
| 276 |
+
2. **Agent 使用**
|
| 277 |
+
- Agent 只能看到和使用 `virtual_path`
|
| 278 |
+
- 沙箱系统自动映射到实际路径
|
| 279 |
+
- Agent 不需要知道实际的文件系统结构
|
| 280 |
+
|
| 281 |
+
3. **前端集成**
|
| 282 |
+
- 始终使用 `artifact_url` 访问文件
|
| 283 |
+
- 不要尝试直接访问文件系统路径
|
| 284 |
+
- 使用 `?download=true` 参数强制下载
|
| 285 |
+
|
| 286 |
+
4. **Markdown 转换**
|
| 287 |
+
- 转换成功时,会返回额外的 `markdown_*` 字段
|
| 288 |
+
- 建议优先使用 Markdown 版本(更易处理)
|
| 289 |
+
- 原始文件始终保留
|
backend/docs/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Documentation
|
| 2 |
+
|
| 3 |
+
This directory contains detailed documentation for the DeerFlow backend.
|
| 4 |
+
|
| 5 |
+
## Quick Links
|
| 6 |
+
|
| 7 |
+
| Document | Description |
|
| 8 |
+
|----------|-------------|
|
| 9 |
+
| [ARCHITECTURE.md](ARCHITECTURE.md) | System architecture overview |
|
| 10 |
+
| [API.md](API.md) | Complete API reference |
|
| 11 |
+
| [CONFIGURATION.md](CONFIGURATION.md) | Configuration options |
|
| 12 |
+
| [SETUP.md](SETUP.md) | Quick setup guide |
|
| 13 |
+
|
| 14 |
+
## Feature Documentation
|
| 15 |
+
|
| 16 |
+
| Document | Description |
|
| 17 |
+
|----------|-------------|
|
| 18 |
+
| [FILE_UPLOAD.md](FILE_UPLOAD.md) | File upload functionality |
|
| 19 |
+
| [PATH_EXAMPLES.md](PATH_EXAMPLES.md) | Path types and usage examples |
|
| 20 |
+
| [summarization.md](summarization.md) | Context summarization feature |
|
| 21 |
+
| [plan_mode_usage.md](plan_mode_usage.md) | Plan mode with TodoList |
|
| 22 |
+
| [AUTO_TITLE_GENERATION.md](AUTO_TITLE_GENERATION.md) | Automatic title generation |
|
| 23 |
+
|
| 24 |
+
## Development
|
| 25 |
+
|
| 26 |
+
| Document | Description |
|
| 27 |
+
|----------|-------------|
|
| 28 |
+
| [TODO.md](TODO.md) | Planned features and known issues |
|
| 29 |
+
|
| 30 |
+
## Getting Started
|
| 31 |
+
|
| 32 |
+
1. **New to DeerFlow?** Start with [SETUP.md](SETUP.md) for quick installation
|
| 33 |
+
2. **Configuring the system?** See [CONFIGURATION.md](CONFIGURATION.md)
|
| 34 |
+
3. **Understanding the architecture?** Read [ARCHITECTURE.md](ARCHITECTURE.md)
|
| 35 |
+
4. **Building integrations?** Check [API.md](API.md) for API reference
|
| 36 |
+
|
| 37 |
+
## Document Organization
|
| 38 |
+
|
| 39 |
+
```
|
| 40 |
+
docs/
|
| 41 |
+
├── README.md # This file
|
| 42 |
+
├── ARCHITECTURE.md # System architecture
|
| 43 |
+
├── API.md # API reference
|
| 44 |
+
├── CONFIGURATION.md # Configuration guide
|
| 45 |
+
├── SETUP.md # Setup instructions
|
| 46 |
+
├── FILE_UPLOAD.md # File upload feature
|
| 47 |
+
├── PATH_EXAMPLES.md # Path usage examples
|
| 48 |
+
├── summarization.md # Summarization feature
|
| 49 |
+
├── plan_mode_usage.md # Plan mode feature
|
| 50 |
+
├── AUTO_TITLE_GENERATION.md # Title generation
|
| 51 |
+
├── TITLE_GENERATION_IMPLEMENTATION.md # Title implementation details
|
| 52 |
+
└── TODO.md # Roadmap and issues
|
| 53 |
+
```
|
backend/docs/SETUP.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Setup Guide
|
| 2 |
+
|
| 3 |
+
Quick setup instructions for DeerFlow.
|
| 4 |
+
|
| 5 |
+
## Configuration Setup
|
| 6 |
+
|
| 7 |
+
DeerFlow uses a YAML configuration file that should be placed in the **project root directory**.
|
| 8 |
+
|
| 9 |
+
### Steps
|
| 10 |
+
|
| 11 |
+
1. **Navigate to project root**:
|
| 12 |
+
```bash
|
| 13 |
+
cd /path/to/deer-flow
|
| 14 |
+
```
|
| 15 |
+
|
| 16 |
+
2. **Copy example configuration**:
|
| 17 |
+
```bash
|
| 18 |
+
cp config.example.yaml config.yaml
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
3. **Edit configuration**:
|
| 22 |
+
```bash
|
| 23 |
+
# Option A: Set environment variables (recommended)
|
| 24 |
+
export OPENAI_API_KEY="your-key-here"
|
| 25 |
+
|
| 26 |
+
# Option B: Edit config.yaml directly
|
| 27 |
+
vim config.yaml # or your preferred editor
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
4. **Verify configuration**:
|
| 31 |
+
```bash
|
| 32 |
+
cd backend
|
| 33 |
+
python -c "from src.config import get_app_config; print('✓ Config loaded:', get_app_config().models[0].name)"
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
## Important Notes
|
| 37 |
+
|
| 38 |
+
- **Location**: `config.yaml` should be in `deer-flow/` (project root), not `deer-flow/backend/`
|
| 39 |
+
- **Git**: `config.yaml` is automatically ignored by git (contains secrets)
|
| 40 |
+
- **Priority**: If both `backend/config.yaml` and `../config.yaml` exist, backend version takes precedence
|
| 41 |
+
|
| 42 |
+
## Configuration File Locations
|
| 43 |
+
|
| 44 |
+
The backend searches for `config.yaml` in this order:
|
| 45 |
+
|
| 46 |
+
1. `DEER_FLOW_CONFIG_PATH` environment variable (if set)
|
| 47 |
+
2. `backend/config.yaml` (current directory when running from backend/)
|
| 48 |
+
3. `deer-flow/config.yaml` (parent directory - **recommended location**)
|
| 49 |
+
|
| 50 |
+
**Recommended**: Place `config.yaml` in project root (`deer-flow/config.yaml`).
|
| 51 |
+
|
| 52 |
+
## Sandbox Setup (Optional but Recommended)
|
| 53 |
+
|
| 54 |
+
If you plan to use Docker/Container-based sandbox (configured in `config.yaml` under `sandbox.use: src.community.aio_sandbox:AioSandboxProvider`), it's highly recommended to pre-pull the container image:
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
# From project root
|
| 58 |
+
make setup-sandbox
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
**Why pre-pull?**
|
| 62 |
+
- The sandbox image (~500MB+) is pulled on first use, causing a long wait
|
| 63 |
+
- Pre-pulling provides clear progress indication
|
| 64 |
+
- Avoids confusion when first using the agent
|
| 65 |
+
|
| 66 |
+
If you skip this step, the image will be automatically pulled on first agent execution, which may take several minutes depending on your network speed.
|
| 67 |
+
|
| 68 |
+
## Troubleshooting
|
| 69 |
+
|
| 70 |
+
### Config file not found
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
# Check where the backend is looking
|
| 74 |
+
cd deer-flow/backend
|
| 75 |
+
python -c "from src.config.app_config import AppConfig; print(AppConfig.resolve_config_path())"
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
If it can't find the config:
|
| 79 |
+
1. Ensure you've copied `config.example.yaml` to `config.yaml`
|
| 80 |
+
2. Verify you're in the correct directory
|
| 81 |
+
3. Check the file exists: `ls -la ../config.yaml`
|
| 82 |
+
|
| 83 |
+
### Permission denied
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
chmod 600 ../config.yaml # Protect sensitive configuration
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
## See Also
|
| 90 |
+
|
| 91 |
+
- [Configuration Guide](docs/CONFIGURATION.md) - Detailed configuration options
|
| 92 |
+
- [Architecture Overview](CLAUDE.md) - System architecture
|
backend/docs/TITLE_GENERATION_IMPLEMENTATION.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 自动 Title 生成功能实现总结
|
| 2 |
+
|
| 3 |
+
## ✅ 已完成的工作
|
| 4 |
+
|
| 5 |
+
### 1. 核心实现文件
|
| 6 |
+
|
| 7 |
+
#### [`src/agents/thread_state.py`](../src/agents/thread_state.py)
|
| 8 |
+
- ✅ 添加 `title: str | None = None` 字段到 `ThreadState`
|
| 9 |
+
|
| 10 |
+
#### [`src/config/title_config.py`](../src/config/title_config.py) (新建)
|
| 11 |
+
- ✅ 创建 `TitleConfig` 配置类
|
| 12 |
+
- ✅ 支持配置:enabled, max_words, max_chars, model_name, prompt_template
|
| 13 |
+
- ✅ 提供 `get_title_config()` 和 `set_title_config()` 函数
|
| 14 |
+
- ✅ 提供 `load_title_config_from_dict()` 从配置文件加载
|
| 15 |
+
|
| 16 |
+
#### [`src/agents/title_middleware.py`](../src/agents/title_middleware.py) (新建)
|
| 17 |
+
- ✅ 创建 `TitleMiddleware` 类
|
| 18 |
+
- ✅ 实现 `_should_generate_title()` 检查是否需要生成
|
| 19 |
+
- ✅ 实现 `_generate_title()` 调用 LLM 生成标题
|
| 20 |
+
- ✅ 实现 `after_agent()` 钩子,在首次对话后自动触发
|
| 21 |
+
- ✅ 包含 fallback 策略(LLM 失败时使用用户消息前几个词)
|
| 22 |
+
|
| 23 |
+
#### [`src/config/app_config.py`](../src/config/app_config.py)
|
| 24 |
+
- ✅ 导入 `load_title_config_from_dict`
|
| 25 |
+
- ✅ 在 `from_file()` 中加载 title 配置
|
| 26 |
+
|
| 27 |
+
#### [`src/agents/lead_agent/agent.py`](../src/agents/lead_agent/agent.py)
|
| 28 |
+
- ✅ 导入 `TitleMiddleware`
|
| 29 |
+
- ✅ 注册到 `middleware` 列表:`[SandboxMiddleware(), TitleMiddleware()]`
|
| 30 |
+
|
| 31 |
+
### 2. 配置文件
|
| 32 |
+
|
| 33 |
+
#### [`config.yaml`](../config.yaml)
|
| 34 |
+
- ✅ 添加 title 配置段:
|
| 35 |
+
```yaml
|
| 36 |
+
title:
|
| 37 |
+
enabled: true
|
| 38 |
+
max_words: 6
|
| 39 |
+
max_chars: 60
|
| 40 |
+
model_name: null
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
### 3. 文档
|
| 44 |
+
|
| 45 |
+
#### [`docs/AUTO_TITLE_GENERATION.md`](../docs/AUTO_TITLE_GENERATION.md) (新建)
|
| 46 |
+
- ✅ 完整的功能说明文档
|
| 47 |
+
- ✅ 实现方式和架构设计
|
| 48 |
+
- ✅ 配置说明
|
| 49 |
+
- ✅ 客户端使用示例(TypeScript)
|
| 50 |
+
- ✅ 工作流程图(Mermaid)
|
| 51 |
+
- ✅ 故障排查指南
|
| 52 |
+
- ✅ State vs Metadata 对比
|
| 53 |
+
|
| 54 |
+
#### [`BACKEND_TODO.md`](../BACKEND_TODO.md)
|
| 55 |
+
- ✅ 添加功能完成记录
|
| 56 |
+
|
| 57 |
+
### 4. 测试
|
| 58 |
+
|
| 59 |
+
#### [`tests/test_title_generation.py`](../tests/test_title_generation.py) (新建)
|
| 60 |
+
- ✅ 配置类测试
|
| 61 |
+
- ✅ Middleware 初始化测试
|
| 62 |
+
- ✅ TODO: 集成测试(需要 mock Runtime)
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
## 🎯 核心设计决策
|
| 67 |
+
|
| 68 |
+
### 为什么使用 State 而非 Metadata?
|
| 69 |
+
|
| 70 |
+
| 方面 | State (✅ 采用) | Metadata (❌ 未采用) |
|
| 71 |
+
|------|----------------|---------------------|
|
| 72 |
+
| **持久化** | 自动(通过 checkpointer) | 取决于实现,不可靠 |
|
| 73 |
+
| **版本控制** | 支持时间旅行 | 不支持 |
|
| 74 |
+
| **类型安全** | TypedDict 定义 | 任意字典 |
|
| 75 |
+
| **标准化** | LangGraph 核心机制 | 扩展功能 |
|
| 76 |
+
|
| 77 |
+
### 工作流程
|
| 78 |
+
|
| 79 |
+
```
|
| 80 |
+
用户发送首条消息
|
| 81 |
+
↓
|
| 82 |
+
Agent 处理并返回回复
|
| 83 |
+
↓
|
| 84 |
+
TitleMiddleware.after_agent() 触发
|
| 85 |
+
↓
|
| 86 |
+
检查:是否首次对话?是否已有 title?
|
| 87 |
+
↓
|
| 88 |
+
调用 LLM 生成 title
|
| 89 |
+
↓
|
| 90 |
+
返回 {"title": "..."} 更新 state
|
| 91 |
+
↓
|
| 92 |
+
Checkpointer 自动持久化(如果配置了)
|
| 93 |
+
↓
|
| 94 |
+
客户端从 state.values.title 读取
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## 📋 使用指南
|
| 100 |
+
|
| 101 |
+
### 后端配置
|
| 102 |
+
|
| 103 |
+
1. **启用/禁用功能**
|
| 104 |
+
```yaml
|
| 105 |
+
# config.yaml
|
| 106 |
+
title:
|
| 107 |
+
enabled: true # 设为 false 禁用
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
2. **自定义配置**
|
| 111 |
+
```yaml
|
| 112 |
+
title:
|
| 113 |
+
enabled: true
|
| 114 |
+
max_words: 8 # 标题最多 8 个词
|
| 115 |
+
max_chars: 80 # 标题最多 80 个字符
|
| 116 |
+
model_name: null # 使用默认模型
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
3. **配置持久化(可选)**
|
| 120 |
+
|
| 121 |
+
如果需要在本地开发时持久化 title:
|
| 122 |
+
|
| 123 |
+
```python
|
| 124 |
+
# checkpointer.py
|
| 125 |
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
| 126 |
+
|
| 127 |
+
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
```json
|
| 131 |
+
// langgraph.json
|
| 132 |
+
{
|
| 133 |
+
"graphs": {
|
| 134 |
+
"lead_agent": "src.agents:lead_agent"
|
| 135 |
+
},
|
| 136 |
+
"checkpointer": "checkpointer:checkpointer"
|
| 137 |
+
}
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
### 客户端使用
|
| 141 |
+
|
| 142 |
+
```typescript
|
| 143 |
+
// 获取 thread title
|
| 144 |
+
const state = await client.threads.getState(threadId);
|
| 145 |
+
const title = state.values.title || "New Conversation";
|
| 146 |
+
|
| 147 |
+
// 显示在对话列表
|
| 148 |
+
<li>{title}</li>
|
| 149 |
+
```
|
| 150 |
+
|
| 151 |
+
**⚠️ 注意**:Title 在 `state.values.title`,而非 `thread.metadata.title`
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
## 🧪 测试
|
| 156 |
+
|
| 157 |
+
```bash
|
| 158 |
+
# 运行测试
|
| 159 |
+
pytest tests/test_title_generation.py -v
|
| 160 |
+
|
| 161 |
+
# 运行所有测试
|
| 162 |
+
pytest
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
---
|
| 166 |
+
|
| 167 |
+
## 🔍 故障排查
|
| 168 |
+
|
| 169 |
+
### Title 没有生成?
|
| 170 |
+
|
| 171 |
+
1. 检查配置:`title.enabled = true`
|
| 172 |
+
2. 查看日志:搜索 "Generated thread title"
|
| 173 |
+
3. 确认是首次对话(1 个用户消息 + 1 个助手回复)
|
| 174 |
+
|
| 175 |
+
### Title 生成但看不到?
|
| 176 |
+
|
| 177 |
+
1. 确认读取位置:`state.values.title`(不是 `thread.metadata.title`)
|
| 178 |
+
2. 检查 API 响应是否包含 title
|
| 179 |
+
3. 重新获取 state
|
| 180 |
+
|
| 181 |
+
### Title 重启后丢失?
|
| 182 |
+
|
| 183 |
+
1. 本地开发需要配置 checkpointer
|
| 184 |
+
2. LangGraph Platform 会自动持久化
|
| 185 |
+
3. 检查数据库确认 checkpointer 工作正常
|
| 186 |
+
|
| 187 |
+
---
|
| 188 |
+
|
| 189 |
+
## 📊 性能影响
|
| 190 |
+
|
| 191 |
+
- **延迟增加**:约 0.5-1 秒(LLM 调用)
|
| 192 |
+
- **并发安全**:在 `after_agent` 中运行,不阻塞主流程
|
| 193 |
+
- **资源消耗**:每个 thread 只生成一次
|
| 194 |
+
|
| 195 |
+
### 优化建议
|
| 196 |
+
|
| 197 |
+
1. 使用更快的模型(如 `gpt-3.5-turbo`)
|
| 198 |
+
2. 减少 `max_words` 和 `max_chars`
|
| 199 |
+
3. 调整 prompt 使其更简洁
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
## �� 下一步
|
| 204 |
+
|
| 205 |
+
- [ ] 添加集成测试(需要 mock LangGraph Runtime)
|
| 206 |
+
- [ ] 支持自定义 prompt template
|
| 207 |
+
- [ ] 支持多语言 title 生成
|
| 208 |
+
- [ ] 添加 title 重新生成功能
|
| 209 |
+
- [ ] 监控 title 生成成功率和延迟
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## 📚 相关资源
|
| 214 |
+
|
| 215 |
+
- [完整文档](../docs/AUTO_TITLE_GENERATION.md)
|
| 216 |
+
- [LangGraph Middleware](https://langchain-ai.github.io/langgraph/concepts/middleware/)
|
| 217 |
+
- [LangGraph State 管理](https://langchain-ai.github.io/langgraph/concepts/low_level/#state)
|
| 218 |
+
- [LangGraph Checkpointer](https://langchain-ai.github.io/langgraph/concepts/persistence/)
|
| 219 |
+
|
| 220 |
+
---
|
| 221 |
+
|
| 222 |
+
*实现完成时间: 2026-01-14*
|
backend/docs/TODO.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TODO List
|
| 2 |
+
|
| 3 |
+
## Completed Features
|
| 4 |
+
|
| 5 |
+
- [x] Launch the sandbox only after the first file system or bash tool is called
|
| 6 |
+
- [x] Add Clarification Process for the whole process
|
| 7 |
+
- [x] Implement Context Summarization Mechanism to avoid context explosion
|
| 8 |
+
- [x] Integrate MCP (Model Context Protocol) for extensible tools
|
| 9 |
+
- [x] Add file upload support with automatic document conversion
|
| 10 |
+
- [x] Implement automatic thread title generation
|
| 11 |
+
- [x] Add Plan Mode with TodoList middleware
|
| 12 |
+
- [x] Add vision model support with ViewImageMiddleware
|
| 13 |
+
- [x] Skills system with SKILL.md format
|
| 14 |
+
|
| 15 |
+
## Planned Features
|
| 16 |
+
|
| 17 |
+
- [ ] Pooling the sandbox resources to reduce the number of sandbox containers
|
| 18 |
+
- [ ] Add authentication/authorization layer
|
| 19 |
+
- [ ] Implement rate limiting
|
| 20 |
+
- [ ] Add metrics and monitoring
|
| 21 |
+
- [ ] Support for more document formats in upload
|
| 22 |
+
- [ ] Skill marketplace / remote skill installation
|
| 23 |
+
|
| 24 |
+
## Resolved Issues
|
| 25 |
+
|
| 26 |
+
- [x] Make sure that no duplicated files in `state.artifacts`
|
| 27 |
+
- [x] Long thinking but with empty content (answer inside thinking process)
|
backend/docs/plan_mode_usage.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Plan Mode with TodoList Middleware
|
| 2 |
+
|
| 3 |
+
This document describes how to enable and use the Plan Mode feature with TodoList middleware in DeerFlow 2.0.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
Plan Mode adds a TodoList middleware to the agent, which provides a `write_todos` tool that helps the agent:
|
| 8 |
+
- Break down complex tasks into smaller, manageable steps
|
| 9 |
+
- Track progress as work progresses
|
| 10 |
+
- Provide visibility to users about what's being done
|
| 11 |
+
|
| 12 |
+
The TodoList middleware is built on LangChain's `TodoListMiddleware`.
|
| 13 |
+
|
| 14 |
+
## Configuration
|
| 15 |
+
|
| 16 |
+
### Enabling Plan Mode
|
| 17 |
+
|
| 18 |
+
Plan mode is controlled via **runtime configuration** through the `is_plan_mode` parameter in the `configurable` section of `RunnableConfig`. This allows you to dynamically enable or disable plan mode on a per-request basis.
|
| 19 |
+
|
| 20 |
+
```python
|
| 21 |
+
from langchain_core.runnables import RunnableConfig
|
| 22 |
+
from src.agents.lead_agent.agent import make_lead_agent
|
| 23 |
+
|
| 24 |
+
# Enable plan mode via runtime configuration
|
| 25 |
+
config = RunnableConfig(
|
| 26 |
+
configurable={
|
| 27 |
+
"thread_id": "example-thread",
|
| 28 |
+
"thinking_enabled": True,
|
| 29 |
+
"is_plan_mode": True, # Enable plan mode
|
| 30 |
+
}
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# Create agent with plan mode enabled
|
| 34 |
+
agent = make_lead_agent(config)
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
### Configuration Options
|
| 38 |
+
|
| 39 |
+
- **is_plan_mode** (bool): Whether to enable plan mode with TodoList middleware. Default: `False`
|
| 40 |
+
- Pass via `config.get("configurable", {}).get("is_plan_mode", False)`
|
| 41 |
+
- Can be set dynamically for each agent invocation
|
| 42 |
+
- No global configuration needed
|
| 43 |
+
|
| 44 |
+
## Default Behavior
|
| 45 |
+
|
| 46 |
+
When plan mode is enabled with default settings, the agent will have access to a `write_todos` tool with the following behavior:
|
| 47 |
+
|
| 48 |
+
### When to Use TodoList
|
| 49 |
+
|
| 50 |
+
The agent will use the todo list for:
|
| 51 |
+
1. Complex multi-step tasks (3+ distinct steps)
|
| 52 |
+
2. Non-trivial tasks requiring careful planning
|
| 53 |
+
3. When user explicitly requests a todo list
|
| 54 |
+
4. When user provides multiple tasks
|
| 55 |
+
|
| 56 |
+
### When NOT to Use TodoList
|
| 57 |
+
|
| 58 |
+
The agent will skip using the todo list for:
|
| 59 |
+
1. Single, straightforward tasks
|
| 60 |
+
2. Trivial tasks (< 3 steps)
|
| 61 |
+
3. Purely conversational or informational requests
|
| 62 |
+
|
| 63 |
+
### Task States
|
| 64 |
+
|
| 65 |
+
- **pending**: Task not yet started
|
| 66 |
+
- **in_progress**: Currently working on (can have multiple parallel tasks)
|
| 67 |
+
- **completed**: Task finished successfully
|
| 68 |
+
|
| 69 |
+
## Usage Examples
|
| 70 |
+
|
| 71 |
+
### Basic Usage
|
| 72 |
+
|
| 73 |
+
```python
|
| 74 |
+
from langchain_core.runnables import RunnableConfig
|
| 75 |
+
from src.agents.lead_agent.agent import make_lead_agent
|
| 76 |
+
|
| 77 |
+
# Create agent with plan mode ENABLED
|
| 78 |
+
config_with_plan_mode = RunnableConfig(
|
| 79 |
+
configurable={
|
| 80 |
+
"thread_id": "example-thread",
|
| 81 |
+
"thinking_enabled": True,
|
| 82 |
+
"is_plan_mode": True, # TodoList middleware will be added
|
| 83 |
+
}
|
| 84 |
+
)
|
| 85 |
+
agent_with_todos = make_lead_agent(config_with_plan_mode)
|
| 86 |
+
|
| 87 |
+
# Create agent with plan mode DISABLED (default)
|
| 88 |
+
config_without_plan_mode = RunnableConfig(
|
| 89 |
+
configurable={
|
| 90 |
+
"thread_id": "another-thread",
|
| 91 |
+
"thinking_enabled": True,
|
| 92 |
+
"is_plan_mode": False, # No TodoList middleware
|
| 93 |
+
}
|
| 94 |
+
)
|
| 95 |
+
agent_without_todos = make_lead_agent(config_without_plan_mode)
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### Dynamic Plan Mode per Request
|
| 99 |
+
|
| 100 |
+
You can enable/disable plan mode dynamically for different conversations or tasks:
|
| 101 |
+
|
| 102 |
+
```python
|
| 103 |
+
from langchain_core.runnables import RunnableConfig
|
| 104 |
+
from src.agents.lead_agent.agent import make_lead_agent
|
| 105 |
+
|
| 106 |
+
def create_agent_for_task(task_complexity: str):
|
| 107 |
+
"""Create agent with plan mode based on task complexity."""
|
| 108 |
+
is_complex = task_complexity in ["high", "very_high"]
|
| 109 |
+
|
| 110 |
+
config = RunnableConfig(
|
| 111 |
+
configurable={
|
| 112 |
+
"thread_id": f"task-{task_complexity}",
|
| 113 |
+
"thinking_enabled": True,
|
| 114 |
+
"is_plan_mode": is_complex, # Enable only for complex tasks
|
| 115 |
+
}
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
return make_lead_agent(config)
|
| 119 |
+
|
| 120 |
+
# Simple task - no TodoList needed
|
| 121 |
+
simple_agent = create_agent_for_task("low")
|
| 122 |
+
|
| 123 |
+
# Complex task - TodoList enabled for better tracking
|
| 124 |
+
complex_agent = create_agent_for_task("high")
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
## How It Works
|
| 128 |
+
|
| 129 |
+
1. When `make_lead_agent(config)` is called, it extracts `is_plan_mode` from `config.configurable`
|
| 130 |
+
2. The config is passed to `_build_middlewares(config)`
|
| 131 |
+
3. `_build_middlewares()` reads `is_plan_mode` and calls `_create_todo_list_middleware(is_plan_mode)`
|
| 132 |
+
4. If `is_plan_mode=True`, a `TodoListMiddleware` instance is created and added to the middleware chain
|
| 133 |
+
5. The middleware automatically adds a `write_todos` tool to the agent's toolset
|
| 134 |
+
6. The agent can use this tool to manage tasks during execution
|
| 135 |
+
7. The middleware handles the todo list state and provides it to the agent
|
| 136 |
+
|
| 137 |
+
## Architecture
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
make_lead_agent(config)
|
| 141 |
+
│
|
| 142 |
+
├─> Extracts: is_plan_mode = config.configurable.get("is_plan_mode", False)
|
| 143 |
+
│
|
| 144 |
+
└─> _build_middlewares(config)
|
| 145 |
+
│
|
| 146 |
+
├─> ThreadDataMiddleware
|
| 147 |
+
├─> SandboxMiddleware
|
| 148 |
+
├─> SummarizationMiddleware (if enabled via global config)
|
| 149 |
+
├─> TodoListMiddleware (if is_plan_mode=True) ← NEW
|
| 150 |
+
├─> TitleMiddleware
|
| 151 |
+
└─> ClarificationMiddleware
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
## Implementation Details
|
| 155 |
+
|
| 156 |
+
### Agent Module
|
| 157 |
+
- **Location**: `src/agents/lead_agent/agent.py`
|
| 158 |
+
- **Function**: `_create_todo_list_middleware(is_plan_mode: bool)` - Creates TodoListMiddleware if plan mode is enabled
|
| 159 |
+
- **Function**: `_build_middlewares(config: RunnableConfig)` - Builds middleware chain based on runtime config
|
| 160 |
+
- **Function**: `make_lead_agent(config: RunnableConfig)` - Creates agent with appropriate middlewares
|
| 161 |
+
|
| 162 |
+
### Runtime Configuration
|
| 163 |
+
Plan mode is controlled via the `is_plan_mode` parameter in `RunnableConfig.configurable`:
|
| 164 |
+
```python
|
| 165 |
+
config = RunnableConfig(
|
| 166 |
+
configurable={
|
| 167 |
+
"is_plan_mode": True, # Enable plan mode
|
| 168 |
+
# ... other configurable options
|
| 169 |
+
}
|
| 170 |
+
)
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
## Key Benefits
|
| 174 |
+
|
| 175 |
+
1. **Dynamic Control**: Enable/disable plan mode per request without global state
|
| 176 |
+
2. **Flexibility**: Different conversations can have different plan mode settings
|
| 177 |
+
3. **Simplicity**: No need for global configuration management
|
| 178 |
+
4. **Context-Aware**: Plan mode decision can be based on task complexity, user preferences, etc.
|
| 179 |
+
|
| 180 |
+
## Custom Prompts
|
| 181 |
+
|
| 182 |
+
DeerFlow uses custom `system_prompt` and `tool_description` for the TodoListMiddleware that match the overall DeerFlow prompt style:
|
| 183 |
+
|
| 184 |
+
### System Prompt Features
|
| 185 |
+
- Uses XML tags (`<todo_list_system>`) for structure consistency with DeerFlow's main prompt
|
| 186 |
+
- Emphasizes CRITICAL rules and best practices
|
| 187 |
+
- Clear "When to Use" vs "When NOT to Use" guidelines
|
| 188 |
+
- Focuses on real-time updates and immediate task completion
|
| 189 |
+
|
| 190 |
+
### Tool Description Features
|
| 191 |
+
- Detailed usage scenarios with examples
|
| 192 |
+
- Strong emphasis on NOT using for simple tasks
|
| 193 |
+
- Clear task state definitions (pending, in_progress, completed)
|
| 194 |
+
- Comprehensive best practices section
|
| 195 |
+
- Task completion requirements to prevent premature marking
|
| 196 |
+
|
| 197 |
+
The custom prompts are defined in `_create_todo_list_middleware()` in `/Users/hetao/workspace/deer-flow/backend/src/agents/lead_agent/agent.py:57`.
|
| 198 |
+
|
| 199 |
+
## Notes
|
| 200 |
+
|
| 201 |
+
- TodoList middleware uses LangChain's built-in `TodoListMiddleware` with **custom DeerFlow-style prompts**
|
| 202 |
+
- Plan mode is **disabled by default** (`is_plan_mode=False`) to maintain backward compatibility
|
| 203 |
+
- The middleware is positioned before `ClarificationMiddleware` to allow todo management during clarification flows
|
| 204 |
+
- Custom prompts emphasize the same principles as DeerFlow's main system prompt (clarity, action-oriented, critical rules)
|
backend/docs/summarization.md
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Conversation Summarization
|
| 2 |
+
|
| 3 |
+
DeerFlow includes automatic conversation summarization to handle long conversations that approach model token limits. When enabled, the system automatically condenses older messages while preserving recent context.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
The summarization feature uses LangChain's `SummarizationMiddleware` to monitor conversation history and trigger summarization based on configurable thresholds. When activated, it:
|
| 8 |
+
|
| 9 |
+
1. Monitors message token counts in real-time
|
| 10 |
+
2. Triggers summarization when thresholds are met
|
| 11 |
+
3. Keeps recent messages intact while summarizing older exchanges
|
| 12 |
+
4. Maintains AI/Tool message pairs together for context continuity
|
| 13 |
+
5. Injects the summary back into the conversation
|
| 14 |
+
|
| 15 |
+
## Configuration
|
| 16 |
+
|
| 17 |
+
Summarization is configured in `config.yaml` under the `summarization` key:
|
| 18 |
+
|
| 19 |
+
```yaml
|
| 20 |
+
summarization:
|
| 21 |
+
enabled: true
|
| 22 |
+
model_name: null # Use default model or specify a lightweight model
|
| 23 |
+
|
| 24 |
+
# Trigger conditions (OR logic - any condition triggers summarization)
|
| 25 |
+
trigger:
|
| 26 |
+
- type: tokens
|
| 27 |
+
value: 4000
|
| 28 |
+
# Additional triggers (optional)
|
| 29 |
+
# - type: messages
|
| 30 |
+
# value: 50
|
| 31 |
+
# - type: fraction
|
| 32 |
+
# value: 0.8 # 80% of model's max input tokens
|
| 33 |
+
|
| 34 |
+
# Context retention policy
|
| 35 |
+
keep:
|
| 36 |
+
type: messages
|
| 37 |
+
value: 20
|
| 38 |
+
|
| 39 |
+
# Token trimming for summarization call
|
| 40 |
+
trim_tokens_to_summarize: 4000
|
| 41 |
+
|
| 42 |
+
# Custom summary prompt (optional)
|
| 43 |
+
summary_prompt: null
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### Configuration Options
|
| 47 |
+
|
| 48 |
+
#### `enabled`
|
| 49 |
+
- **Type**: Boolean
|
| 50 |
+
- **Default**: `false`
|
| 51 |
+
- **Description**: Enable or disable automatic summarization
|
| 52 |
+
|
| 53 |
+
#### `model_name`
|
| 54 |
+
- **Type**: String or null
|
| 55 |
+
- **Default**: `null` (uses default model)
|
| 56 |
+
- **Description**: Model to use for generating summaries. Recommended to use a lightweight, cost-effective model like `gpt-4o-mini` or equivalent.
|
| 57 |
+
|
| 58 |
+
#### `trigger`
|
| 59 |
+
- **Type**: Single `ContextSize` or list of `ContextSize` objects
|
| 60 |
+
- **Required**: At least one trigger must be specified when enabled
|
| 61 |
+
- **Description**: Thresholds that trigger summarization. Uses OR logic - summarization runs when ANY threshold is met.
|
| 62 |
+
|
| 63 |
+
**ContextSize Types:**
|
| 64 |
+
|
| 65 |
+
1. **Token-based trigger**: Activates when token count reaches the specified value
|
| 66 |
+
```yaml
|
| 67 |
+
trigger:
|
| 68 |
+
type: tokens
|
| 69 |
+
value: 4000
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
2. **Message-based trigger**: Activates when message count reaches the specified value
|
| 73 |
+
```yaml
|
| 74 |
+
trigger:
|
| 75 |
+
type: messages
|
| 76 |
+
value: 50
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
3. **Fraction-based trigger**: Activates when token usage reaches a percentage of the model's maximum input tokens
|
| 80 |
+
```yaml
|
| 81 |
+
trigger:
|
| 82 |
+
type: fraction
|
| 83 |
+
value: 0.8 # 80% of max input tokens
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
**Multiple Triggers:**
|
| 87 |
+
```yaml
|
| 88 |
+
trigger:
|
| 89 |
+
- type: tokens
|
| 90 |
+
value: 4000
|
| 91 |
+
- type: messages
|
| 92 |
+
value: 50
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
#### `keep`
|
| 96 |
+
- **Type**: `ContextSize` object
|
| 97 |
+
- **Default**: `{type: messages, value: 20}`
|
| 98 |
+
- **Description**: Specifies how much recent conversation history to preserve after summarization.
|
| 99 |
+
|
| 100 |
+
**Examples:**
|
| 101 |
+
```yaml
|
| 102 |
+
# Keep most recent 20 messages
|
| 103 |
+
keep:
|
| 104 |
+
type: messages
|
| 105 |
+
value: 20
|
| 106 |
+
|
| 107 |
+
# Keep most recent 3000 tokens
|
| 108 |
+
keep:
|
| 109 |
+
type: tokens
|
| 110 |
+
value: 3000
|
| 111 |
+
|
| 112 |
+
# Keep most recent 30% of model's max input tokens
|
| 113 |
+
keep:
|
| 114 |
+
type: fraction
|
| 115 |
+
value: 0.3
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
#### `trim_tokens_to_summarize`
|
| 119 |
+
- **Type**: Integer or null
|
| 120 |
+
- **Default**: `4000`
|
| 121 |
+
- **Description**: Maximum tokens to include when preparing messages for the summarization call itself. Set to `null` to skip trimming (not recommended for very long conversations).
|
| 122 |
+
|
| 123 |
+
#### `summary_prompt`
|
| 124 |
+
- **Type**: String or null
|
| 125 |
+
- **Default**: `null` (uses LangChain's default prompt)
|
| 126 |
+
- **Description**: Custom prompt template for generating summaries. The prompt should guide the model to extract the most important context.
|
| 127 |
+
|
| 128 |
+
**Default Prompt Behavior:**
|
| 129 |
+
The default LangChain prompt instructs the model to:
|
| 130 |
+
- Extract highest quality/most relevant context
|
| 131 |
+
- Focus on information critical to the overall goal
|
| 132 |
+
- Avoid repeating completed actions
|
| 133 |
+
- Return only the extracted context
|
| 134 |
+
|
| 135 |
+
## How It Works
|
| 136 |
+
|
| 137 |
+
### Summarization Flow
|
| 138 |
+
|
| 139 |
+
1. **Monitoring**: Before each model call, the middleware counts tokens in the message history
|
| 140 |
+
2. **Trigger Check**: If any configured threshold is met, summarization is triggered
|
| 141 |
+
3. **Message Partitioning**: Messages are split into:
|
| 142 |
+
- Messages to summarize (older messages beyond the `keep` threshold)
|
| 143 |
+
- Messages to preserve (recent messages within the `keep` threshold)
|
| 144 |
+
4. **Summary Generation**: The model generates a concise summary of the older messages
|
| 145 |
+
5. **Context Replacement**: The message history is updated:
|
| 146 |
+
- All old messages are removed
|
| 147 |
+
- A single summary message is added
|
| 148 |
+
- Recent messages are preserved
|
| 149 |
+
6. **AI/Tool Pair Protection**: The system ensures AI messages and their corresponding tool messages stay together
|
| 150 |
+
|
| 151 |
+
### Token Counting
|
| 152 |
+
|
| 153 |
+
- Uses approximate token counting based on character count
|
| 154 |
+
- For Anthropic models: ~3.3 characters per token
|
| 155 |
+
- For other models: Uses LangChain's default estimation
|
| 156 |
+
- Can be customized with a custom `token_counter` function
|
| 157 |
+
|
| 158 |
+
### Message Preservation
|
| 159 |
+
|
| 160 |
+
The middleware intelligently preserves message context:
|
| 161 |
+
|
| 162 |
+
- **Recent Messages**: Always kept intact based on `keep` configuration
|
| 163 |
+
- **AI/Tool Pairs**: Never split - if a cutoff point falls within tool messages, the system adjusts to keep the entire AI + Tool message sequence together
|
| 164 |
+
- **Summary Format**: Summary is injected as a HumanMessage with the format:
|
| 165 |
+
```
|
| 166 |
+
Here is a summary of the conversation to date:
|
| 167 |
+
|
| 168 |
+
[Generated summary text]
|
| 169 |
+
```
|
| 170 |
+
|
| 171 |
+
## Best Practices
|
| 172 |
+
|
| 173 |
+
### Choosing Trigger Thresholds
|
| 174 |
+
|
| 175 |
+
1. **Token-based triggers**: Recommended for most use cases
|
| 176 |
+
- Set to 60-80% of your model's context window
|
| 177 |
+
- Example: For 8K context, use 4000-6000 tokens
|
| 178 |
+
|
| 179 |
+
2. **Message-based triggers**: Useful for controlling conversation length
|
| 180 |
+
- Good for applications with many short messages
|
| 181 |
+
- Example: 50-100 messages depending on average message length
|
| 182 |
+
|
| 183 |
+
3. **Fraction-based triggers**: Ideal when using multiple models
|
| 184 |
+
- Automatically adapts to each model's capacity
|
| 185 |
+
- Example: 0.8 (80% of model's max input tokens)
|
| 186 |
+
|
| 187 |
+
### Choosing Retention Policy (`keep`)
|
| 188 |
+
|
| 189 |
+
1. **Message-based retention**: Best for most scenarios
|
| 190 |
+
- Preserves natural conversation flow
|
| 191 |
+
- Recommended: 15-25 messages
|
| 192 |
+
|
| 193 |
+
2. **Token-based retention**: Use when precise control is needed
|
| 194 |
+
- Good for managing exact token budgets
|
| 195 |
+
- Recommended: 2000-4000 tokens
|
| 196 |
+
|
| 197 |
+
3. **Fraction-based retention**: For multi-model setups
|
| 198 |
+
- Automatically scales with model capacity
|
| 199 |
+
- Recommended: 0.2-0.4 (20-40% of max input)
|
| 200 |
+
|
| 201 |
+
### Model Selection
|
| 202 |
+
|
| 203 |
+
- **Recommended**: Use a lightweight, cost-effective model for summaries
|
| 204 |
+
- Examples: `gpt-4o-mini`, `claude-haiku`, or equivalent
|
| 205 |
+
- Summaries don't require the most powerful models
|
| 206 |
+
- Significant cost savings on high-volume applications
|
| 207 |
+
|
| 208 |
+
- **Default**: If `model_name` is `null`, uses the default model
|
| 209 |
+
- May be more expensive but ensures consistency
|
| 210 |
+
- Good for simple setups
|
| 211 |
+
|
| 212 |
+
### Optimization Tips
|
| 213 |
+
|
| 214 |
+
1. **Balance triggers**: Combine token and message triggers for robust handling
|
| 215 |
+
```yaml
|
| 216 |
+
trigger:
|
| 217 |
+
- type: tokens
|
| 218 |
+
value: 4000
|
| 219 |
+
- type: messages
|
| 220 |
+
value: 50
|
| 221 |
+
```
|
| 222 |
+
|
| 223 |
+
2. **Conservative retention**: Keep more messages initially, adjust based on performance
|
| 224 |
+
```yaml
|
| 225 |
+
keep:
|
| 226 |
+
type: messages
|
| 227 |
+
value: 25 # Start higher, reduce if needed
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
3. **Trim strategically**: Limit tokens sent to summarization model
|
| 231 |
+
```yaml
|
| 232 |
+
trim_tokens_to_summarize: 4000 # Prevents expensive summarization calls
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
4. **Monitor and iterate**: Track summary quality and adjust configuration
|
| 236 |
+
|
| 237 |
+
## Troubleshooting
|
| 238 |
+
|
| 239 |
+
### Summary Quality Issues
|
| 240 |
+
|
| 241 |
+
**Problem**: Summaries losing important context
|
| 242 |
+
|
| 243 |
+
**Solutions**:
|
| 244 |
+
1. Increase `keep` value to preserve more messages
|
| 245 |
+
2. Decrease trigger thresholds to summarize earlier
|
| 246 |
+
3. Customize `summary_prompt` to emphasize key information
|
| 247 |
+
4. Use a more capable model for summarization
|
| 248 |
+
|
| 249 |
+
### Performance Issues
|
| 250 |
+
|
| 251 |
+
**Problem**: Summarization calls taking too long
|
| 252 |
+
|
| 253 |
+
**Solutions**:
|
| 254 |
+
1. Use a faster model for summaries (e.g., `gpt-4o-mini`)
|
| 255 |
+
2. Reduce `trim_tokens_to_summarize` to send less context
|
| 256 |
+
3. Increase trigger thresholds to summarize less frequently
|
| 257 |
+
|
| 258 |
+
### Token Limit Errors
|
| 259 |
+
|
| 260 |
+
**Problem**: Still hitting token limits despite summarization
|
| 261 |
+
|
| 262 |
+
**Solutions**:
|
| 263 |
+
1. Lower trigger thresholds to summarize earlier
|
| 264 |
+
2. Reduce `keep` value to preserve fewer messages
|
| 265 |
+
3. Check if individual messages are very large
|
| 266 |
+
4. Consider using fraction-based triggers
|
| 267 |
+
|
| 268 |
+
## Implementation Details
|
| 269 |
+
|
| 270 |
+
### Code Structure
|
| 271 |
+
|
| 272 |
+
- **Configuration**: `src/config/summarization_config.py`
|
| 273 |
+
- **Integration**: `src/agents/lead_agent/agent.py`
|
| 274 |
+
- **Middleware**: Uses `langchain.agents.middleware.SummarizationMiddleware`
|
| 275 |
+
|
| 276 |
+
### Middleware Order
|
| 277 |
+
|
| 278 |
+
Summarization runs after ThreadData and Sandbox initialization but before Title and Clarification:
|
| 279 |
+
|
| 280 |
+
1. ThreadDataMiddleware
|
| 281 |
+
2. SandboxMiddleware
|
| 282 |
+
3. **SummarizationMiddleware** ← Runs here
|
| 283 |
+
4. TitleMiddleware
|
| 284 |
+
5. ClarificationMiddleware
|
| 285 |
+
|
| 286 |
+
### State Management
|
| 287 |
+
|
| 288 |
+
- Summarization is stateless - configuration is loaded once at startup
|
| 289 |
+
- Summaries are added as regular messages in the conversation history
|
| 290 |
+
- The checkpointer persists the summarized history automatically
|
| 291 |
+
|
| 292 |
+
## Example Configurations
|
| 293 |
+
|
| 294 |
+
### Minimal Configuration
|
| 295 |
+
```yaml
|
| 296 |
+
summarization:
|
| 297 |
+
enabled: true
|
| 298 |
+
trigger:
|
| 299 |
+
type: tokens
|
| 300 |
+
value: 4000
|
| 301 |
+
keep:
|
| 302 |
+
type: messages
|
| 303 |
+
value: 20
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
### Production Configuration
|
| 307 |
+
```yaml
|
| 308 |
+
summarization:
|
| 309 |
+
enabled: true
|
| 310 |
+
model_name: gpt-4o-mini # Lightweight model for cost efficiency
|
| 311 |
+
trigger:
|
| 312 |
+
- type: tokens
|
| 313 |
+
value: 6000
|
| 314 |
+
- type: messages
|
| 315 |
+
value: 75
|
| 316 |
+
keep:
|
| 317 |
+
type: messages
|
| 318 |
+
value: 25
|
| 319 |
+
trim_tokens_to_summarize: 5000
|
| 320 |
+
```
|
| 321 |
+
|
| 322 |
+
### Multi-Model Configuration
|
| 323 |
+
```yaml
|
| 324 |
+
summarization:
|
| 325 |
+
enabled: true
|
| 326 |
+
model_name: gpt-4o-mini
|
| 327 |
+
trigger:
|
| 328 |
+
type: fraction
|
| 329 |
+
value: 0.7 # 70% of model's max input
|
| 330 |
+
keep:
|
| 331 |
+
type: fraction
|
| 332 |
+
value: 0.3 # Keep 30% of max input
|
| 333 |
+
trim_tokens_to_summarize: 4000
|
| 334 |
+
```
|
| 335 |
+
|
| 336 |
+
### Conservative Configuration (High Quality)
|
| 337 |
+
```yaml
|
| 338 |
+
summarization:
|
| 339 |
+
enabled: true
|
| 340 |
+
model_name: gpt-4 # Use full model for high-quality summaries
|
| 341 |
+
trigger:
|
| 342 |
+
type: tokens
|
| 343 |
+
value: 8000
|
| 344 |
+
keep:
|
| 345 |
+
type: messages
|
| 346 |
+
value: 40 # Keep more context
|
| 347 |
+
trim_tokens_to_summarize: null # No trimming
|
| 348 |
+
```
|
| 349 |
+
|
| 350 |
+
## References
|
| 351 |
+
|
| 352 |
+
- [LangChain Summarization Middleware Documentation](https://docs.langchain.com/oss/python/langchain/middleware/built-in#summarization)
|
| 353 |
+
- [LangChain Source Code](https://github.com/langchain-ai/langchain)
|
backend/docs/task_tool_improvements.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Task Tool Improvements
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
|
| 5 |
+
The task tool has been improved to eliminate wasteful LLM polling. Previously, when using background tasks, the LLM had to repeatedly call `task_status` to poll for completion, causing unnecessary API requests.
|
| 6 |
+
|
| 7 |
+
## Changes Made
|
| 8 |
+
|
| 9 |
+
### 1. Removed `run_in_background` Parameter
|
| 10 |
+
|
| 11 |
+
The `run_in_background` parameter has been removed from the `task` tool. All subagent tasks now run asynchronously by default, but the tool handles completion automatically.
|
| 12 |
+
|
| 13 |
+
**Before:**
|
| 14 |
+
```python
|
| 15 |
+
# LLM had to manage polling
|
| 16 |
+
task_id = task(
|
| 17 |
+
subagent_type="bash",
|
| 18 |
+
prompt="Run tests",
|
| 19 |
+
description="Run tests",
|
| 20 |
+
run_in_background=True
|
| 21 |
+
)
|
| 22 |
+
# Then LLM had to poll repeatedly:
|
| 23 |
+
while True:
|
| 24 |
+
status = task_status(task_id)
|
| 25 |
+
if completed:
|
| 26 |
+
break
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
**After:**
|
| 30 |
+
```python
|
| 31 |
+
# Tool blocks until complete, polling happens in backend
|
| 32 |
+
result = task(
|
| 33 |
+
subagent_type="bash",
|
| 34 |
+
prompt="Run tests",
|
| 35 |
+
description="Run tests"
|
| 36 |
+
)
|
| 37 |
+
# Result is available immediately after the call returns
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
### 2. Backend Polling
|
| 41 |
+
|
| 42 |
+
The `task_tool` now:
|
| 43 |
+
- Starts the subagent task asynchronously
|
| 44 |
+
- Polls for completion in the backend (every 2 seconds)
|
| 45 |
+
- Blocks the tool call until completion
|
| 46 |
+
- Returns the final result directly
|
| 47 |
+
|
| 48 |
+
This means:
|
| 49 |
+
- ✅ LLM makes only ONE tool call
|
| 50 |
+
- ✅ No wasteful LLM polling requests
|
| 51 |
+
- ✅ Backend handles all status checking
|
| 52 |
+
- ✅ Timeout protection (5 minutes max)
|
| 53 |
+
|
| 54 |
+
### 3. Removed `task_status` from LLM Tools
|
| 55 |
+
|
| 56 |
+
The `task_status_tool` is no longer exposed to the LLM. It's kept in the codebase for potential internal/debugging use, but the LLM cannot call it.
|
| 57 |
+
|
| 58 |
+
### 4. Updated Documentation
|
| 59 |
+
|
| 60 |
+
- Updated `SUBAGENT_SECTION` in `prompt.py` to remove all references to background tasks and polling
|
| 61 |
+
- Simplified usage examples
|
| 62 |
+
- Made it clear that the tool automatically waits for completion
|
| 63 |
+
|
| 64 |
+
## Implementation Details
|
| 65 |
+
|
| 66 |
+
### Polling Logic
|
| 67 |
+
|
| 68 |
+
Located in `src/tools/builtins/task_tool.py`:
|
| 69 |
+
|
| 70 |
+
```python
|
| 71 |
+
# Start background execution
|
| 72 |
+
task_id = executor.execute_async(prompt)
|
| 73 |
+
|
| 74 |
+
# Poll for task completion in backend
|
| 75 |
+
while True:
|
| 76 |
+
result = get_background_task_result(task_id)
|
| 77 |
+
|
| 78 |
+
# Check if task completed or failed
|
| 79 |
+
if result.status == SubagentStatus.COMPLETED:
|
| 80 |
+
return f"[Subagent: {subagent_type}]\n\n{result.result}"
|
| 81 |
+
elif result.status == SubagentStatus.FAILED:
|
| 82 |
+
return f"[Subagent: {subagent_type}] Task failed: {result.error}"
|
| 83 |
+
|
| 84 |
+
# Wait before next poll
|
| 85 |
+
time.sleep(2)
|
| 86 |
+
|
| 87 |
+
# Timeout protection (5 minutes)
|
| 88 |
+
if poll_count > 150:
|
| 89 |
+
return "Task timed out after 5 minutes"
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
### Execution Timeout
|
| 93 |
+
|
| 94 |
+
In addition to polling timeout, subagent execution now has a built-in timeout mechanism:
|
| 95 |
+
|
| 96 |
+
**Configuration** (`src/subagents/config.py`):
|
| 97 |
+
```python
|
| 98 |
+
@dataclass
|
| 99 |
+
class SubagentConfig:
|
| 100 |
+
# ...
|
| 101 |
+
timeout_seconds: int = 300 # 5 minutes default
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
**Thread Pool Architecture**:
|
| 105 |
+
|
| 106 |
+
To avoid nested thread pools and resource waste, we use two dedicated thread pools:
|
| 107 |
+
|
| 108 |
+
1. **Scheduler Pool** (`_scheduler_pool`):
|
| 109 |
+
- Max workers: 4
|
| 110 |
+
- Purpose: Orchestrates background task execution
|
| 111 |
+
- Runs `run_task()` function that manages task lifecycle
|
| 112 |
+
|
| 113 |
+
2. **Execution Pool** (`_execution_pool`):
|
| 114 |
+
- Max workers: 8 (larger to avoid blocking)
|
| 115 |
+
- Purpose: Actual subagent execution with timeout support
|
| 116 |
+
- Runs `execute()` method that invokes the agent
|
| 117 |
+
|
| 118 |
+
**How it works**:
|
| 119 |
+
```python
|
| 120 |
+
# In execute_async():
|
| 121 |
+
_scheduler_pool.submit(run_task) # Submit orchestration task
|
| 122 |
+
|
| 123 |
+
# In run_task():
|
| 124 |
+
future = _execution_pool.submit(self.execute, task) # Submit execution
|
| 125 |
+
exec_result = future.result(timeout=timeout_seconds) # Wait with timeout
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
**Benefits**:
|
| 129 |
+
- ✅ Clean separation of concerns (scheduling vs execution)
|
| 130 |
+
- ✅ No nested thread pools
|
| 131 |
+
- ✅ Timeout enforcement at the right level
|
| 132 |
+
- ✅ Better resource utilization
|
| 133 |
+
|
| 134 |
+
**Two-Level Timeout Protection**:
|
| 135 |
+
1. **Execution Timeout**: Subagent execution itself has a 5-minute timeout (configurable in SubagentConfig)
|
| 136 |
+
2. **Polling Timeout**: Tool polling has a 5-minute timeout (30 polls × 10 seconds)
|
| 137 |
+
|
| 138 |
+
This ensures that even if subagent execution hangs, the system won't wait indefinitely.
|
| 139 |
+
|
| 140 |
+
### Benefits
|
| 141 |
+
|
| 142 |
+
1. **Reduced API Costs**: No more repeated LLM requests for polling
|
| 143 |
+
2. **Simpler UX**: LLM doesn't need to manage polling logic
|
| 144 |
+
3. **Better Reliability**: Backend handles all status checking consistently
|
| 145 |
+
4. **Timeout Protection**: Two-level timeout prevents infinite waiting (execution + polling)
|
| 146 |
+
|
| 147 |
+
## Testing
|
| 148 |
+
|
| 149 |
+
To verify the changes work correctly:
|
| 150 |
+
|
| 151 |
+
1. Start a subagent task that takes a few seconds
|
| 152 |
+
2. Verify the tool call blocks until completion
|
| 153 |
+
3. Verify the result is returned directly
|
| 154 |
+
4. Verify no `task_status` calls are made
|
| 155 |
+
|
| 156 |
+
Example test scenario:
|
| 157 |
+
```python
|
| 158 |
+
# This should block for ~10 seconds then return result
|
| 159 |
+
result = task(
|
| 160 |
+
subagent_type="bash",
|
| 161 |
+
prompt="sleep 10 && echo 'Done'",
|
| 162 |
+
description="Test task"
|
| 163 |
+
)
|
| 164 |
+
# result should contain "Done"
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
## Migration Notes
|
| 168 |
+
|
| 169 |
+
For users/code that previously used `run_in_background=True`:
|
| 170 |
+
- Simply remove the parameter
|
| 171 |
+
- Remove any polling logic
|
| 172 |
+
- The tool will automatically wait for completion
|
| 173 |
+
|
| 174 |
+
No other changes needed - the API is backward compatible (minus the removed parameter).
|
backend/langgraph.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://langgra.ph/schema.json",
|
| 3 |
+
"dependencies": [
|
| 4 |
+
"."
|
| 5 |
+
],
|
| 6 |
+
"env": ".env",
|
| 7 |
+
"graphs": {
|
| 8 |
+
"lead_agent": "src.agents:make_lead_agent"
|
| 9 |
+
}
|
| 10 |
+
}
|
backend/pyproject.toml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "deer-flow"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "LangGraph-based AI agent system with sandbox execution capabilities"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.12"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"agent-sandbox>=0.0.19",
|
| 9 |
+
"dotenv>=0.9.9",
|
| 10 |
+
"fastapi>=0.115.0",
|
| 11 |
+
"httpx>=0.28.0",
|
| 12 |
+
"kubernetes>=30.0.0",
|
| 13 |
+
"langchain>=1.2.3",
|
| 14 |
+
"langchain-deepseek>=1.0.1",
|
| 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",
|
| 21 |
+
"pydantic>=2.12.5",
|
| 22 |
+
"python-multipart>=0.0.20",
|
| 23 |
+
"pyyaml>=6.0.3",
|
| 24 |
+
"readabilipy>=0.3.0",
|
| 25 |
+
"sse-starlette>=2.1.0",
|
| 26 |
+
"tavily-python>=0.7.17",
|
| 27 |
+
"firecrawl-py>=1.15.0",
|
| 28 |
+
"tiktoken>=0.8.0",
|
| 29 |
+
"uvicorn[standard]>=0.34.0",
|
| 30 |
+
"ddgs>=9.10.0",
|
| 31 |
+
"duckdb>=1.4.4",
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
[dependency-groups]
|
| 35 |
+
dev = ["pytest>=8.0.0", "ruff>=0.14.11"]
|
backend/ruff.toml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
line-length = 240
|
| 2 |
+
target-version = "py312"
|
| 3 |
+
|
| 4 |
+
[lint]
|
| 5 |
+
select = ["E", "F", "I", "UP"]
|
| 6 |
+
ignore = []
|
| 7 |
+
|
| 8 |
+
[format]
|
| 9 |
+
quote-style = "double"
|
| 10 |
+
indent-style = "space"
|
backend/src/__init__.py
ADDED
|
File without changes
|
backend/src/agents/__init__.py
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .lead_agent import make_lead_agent
|
| 2 |
+
from .thread_state import SandboxState, ThreadState
|
| 3 |
+
|
| 4 |
+
__all__ = ["make_lead_agent", "SandboxState", "ThreadState"]
|
backend/src/agents/lead_agent/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .agent import make_lead_agent
|
| 2 |
+
|
| 3 |
+
__all__ = ["make_lead_agent"]
|
backend/src/agents/lead_agent/agent.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
from langchain.agents import create_agent
|
| 4 |
+
from langchain.agents.middleware import SummarizationMiddleware, TodoListMiddleware
|
| 5 |
+
from langchain_core.runnables import RunnableConfig
|
| 6 |
+
|
| 7 |
+
from src.agents.lead_agent.prompt import apply_prompt_template
|
| 8 |
+
from src.agents.middlewares.clarification_middleware import ClarificationMiddleware
|
| 9 |
+
from src.agents.middlewares.dangling_tool_call_middleware import DanglingToolCallMiddleware
|
| 10 |
+
from src.agents.middlewares.memory_middleware import MemoryMiddleware
|
| 11 |
+
from src.agents.middlewares.subagent_limit_middleware import SubagentLimitMiddleware
|
| 12 |
+
from src.agents.middlewares.thread_data_middleware import ThreadDataMiddleware
|
| 13 |
+
from src.agents.middlewares.title_middleware import TitleMiddleware
|
| 14 |
+
from src.agents.middlewares.uploads_middleware import UploadsMiddleware
|
| 15 |
+
from src.agents.middlewares.view_image_middleware import ViewImageMiddleware
|
| 16 |
+
from src.agents.thread_state import ThreadState
|
| 17 |
+
from src.config.app_config import get_app_config
|
| 18 |
+
from src.config.summarization_config import get_summarization_config
|
| 19 |
+
from src.models import create_chat_model
|
| 20 |
+
from src.sandbox.middleware import SandboxMiddleware
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _resolve_model_name(requested_model_name: str | None) -> str:
|
| 26 |
+
"""Resolve a runtime model name safely, falling back to default if invalid. Returns None if no models are configured."""
|
| 27 |
+
app_config = get_app_config()
|
| 28 |
+
default_model_name = app_config.models[0].name if app_config.models else None
|
| 29 |
+
if default_model_name is None:
|
| 30 |
+
raise ValueError(
|
| 31 |
+
"No chat models are configured. Please configure at least one model in config.yaml."
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
if requested_model_name and app_config.get_model_config(requested_model_name):
|
| 35 |
+
return requested_model_name
|
| 36 |
+
|
| 37 |
+
if requested_model_name and requested_model_name != default_model_name:
|
| 38 |
+
logger.warning(f"Model '{requested_model_name}' not found in config; fallback to default model '{default_model_name}'.")
|
| 39 |
+
return default_model_name
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _create_summarization_middleware() -> SummarizationMiddleware | None:
|
| 43 |
+
"""Create and configure the summarization middleware from config."""
|
| 44 |
+
config = get_summarization_config()
|
| 45 |
+
|
| 46 |
+
if not config.enabled:
|
| 47 |
+
return None
|
| 48 |
+
|
| 49 |
+
# Prepare trigger parameter
|
| 50 |
+
trigger = None
|
| 51 |
+
if config.trigger is not None:
|
| 52 |
+
if isinstance(config.trigger, list):
|
| 53 |
+
trigger = [t.to_tuple() for t in config.trigger]
|
| 54 |
+
else:
|
| 55 |
+
trigger = config.trigger.to_tuple()
|
| 56 |
+
|
| 57 |
+
# Prepare keep parameter
|
| 58 |
+
keep = config.keep.to_tuple()
|
| 59 |
+
|
| 60 |
+
# Prepare model parameter
|
| 61 |
+
if config.model_name:
|
| 62 |
+
model = config.model_name
|
| 63 |
+
else:
|
| 64 |
+
# Use a lightweight model for summarization to save costs
|
| 65 |
+
# Falls back to default model if not explicitly specified
|
| 66 |
+
model = create_chat_model(thinking_enabled=False)
|
| 67 |
+
|
| 68 |
+
# Prepare kwargs
|
| 69 |
+
kwargs = {
|
| 70 |
+
"model": model,
|
| 71 |
+
"trigger": trigger,
|
| 72 |
+
"keep": keep,
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
if config.trim_tokens_to_summarize is not None:
|
| 76 |
+
kwargs["trim_tokens_to_summarize"] = config.trim_tokens_to_summarize
|
| 77 |
+
|
| 78 |
+
if config.summary_prompt is not None:
|
| 79 |
+
kwargs["summary_prompt"] = config.summary_prompt
|
| 80 |
+
|
| 81 |
+
return SummarizationMiddleware(**kwargs)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _create_todo_list_middleware(is_plan_mode: bool) -> TodoListMiddleware | None:
|
| 85 |
+
"""Create and configure the TodoList middleware.
|
| 86 |
+
|
| 87 |
+
Args:
|
| 88 |
+
is_plan_mode: Whether to enable plan mode with TodoList middleware.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
TodoListMiddleware instance if plan mode is enabled, None otherwise.
|
| 92 |
+
"""
|
| 93 |
+
if not is_plan_mode:
|
| 94 |
+
return None
|
| 95 |
+
|
| 96 |
+
# Custom prompts matching DeerFlow's style
|
| 97 |
+
system_prompt = """
|
| 98 |
+
<todo_list_system>
|
| 99 |
+
You have access to the `write_todos` tool to help you manage and track complex multi-step objectives.
|
| 100 |
+
|
| 101 |
+
**CRITICAL RULES:**
|
| 102 |
+
- Mark todos as completed IMMEDIATELY after finishing each step - do NOT batch completions
|
| 103 |
+
- Keep EXACTLY ONE task as `in_progress` at any time (unless tasks can run in parallel)
|
| 104 |
+
- Update the todo list in REAL-TIME as you work - this gives users visibility into your progress
|
| 105 |
+
- DO NOT use this tool for simple tasks (< 3 steps) - just complete them directly
|
| 106 |
+
|
| 107 |
+
**When to Use:**
|
| 108 |
+
This tool is designed for complex objectives that require systematic tracking:
|
| 109 |
+
- Complex multi-step tasks requiring 3+ distinct steps
|
| 110 |
+
- Non-trivial tasks needing careful planning and execution
|
| 111 |
+
- User explicitly requests a todo list
|
| 112 |
+
- User provides multiple tasks (numbered or comma-separated list)
|
| 113 |
+
- The plan may need revisions based on intermediate results
|
| 114 |
+
|
| 115 |
+
**When NOT to Use:**
|
| 116 |
+
- Single, straightforward tasks
|
| 117 |
+
- Trivial tasks (< 3 steps)
|
| 118 |
+
- Purely conversational or informational requests
|
| 119 |
+
- Simple tool calls where the approach is obvious
|
| 120 |
+
|
| 121 |
+
**Best Practices:**
|
| 122 |
+
- Break down complex tasks into smaller, actionable steps
|
| 123 |
+
- Use clear, descriptive task names
|
| 124 |
+
- Remove tasks that become irrelevant
|
| 125 |
+
- Add new tasks discovered during implementation
|
| 126 |
+
- Don't be afraid to revise the todo list as you learn more
|
| 127 |
+
|
| 128 |
+
**Task Management:**
|
| 129 |
+
Writing todos takes time and tokens - use it when helpful for managing complex problems, not for simple requests.
|
| 130 |
+
</todo_list_system>
|
| 131 |
+
"""
|
| 132 |
+
|
| 133 |
+
tool_description = """Use this tool to create and manage a structured task list for complex work sessions.
|
| 134 |
+
|
| 135 |
+
**IMPORTANT: Only use this tool for complex tasks (3+ steps). For simple requests, just do the work directly.**
|
| 136 |
+
|
| 137 |
+
## When to Use
|
| 138 |
+
|
| 139 |
+
Use this tool in these scenarios:
|
| 140 |
+
1. **Complex multi-step tasks**: When a task requires 3 or more distinct steps or actions
|
| 141 |
+
2. **Non-trivial tasks**: Tasks requiring careful planning or multiple operations
|
| 142 |
+
3. **User explicitly requests todo list**: When the user directly asks you to track tasks
|
| 143 |
+
4. **Multiple tasks**: When users provide a list of things to be done
|
| 144 |
+
5. **Dynamic planning**: When the plan may need updates based on intermediate results
|
| 145 |
+
|
| 146 |
+
## When NOT to Use
|
| 147 |
+
|
| 148 |
+
Skip this tool when:
|
| 149 |
+
1. The task is straightforward and takes less than 3 steps
|
| 150 |
+
2. The task is trivial and tracking provides no benefit
|
| 151 |
+
3. The task is purely conversational or informational
|
| 152 |
+
4. It's clear what needs to be done and you can just do it
|
| 153 |
+
|
| 154 |
+
## How to Use
|
| 155 |
+
|
| 156 |
+
1. **Starting a task**: Mark it as `in_progress` BEFORE beginning work
|
| 157 |
+
2. **Completing a task**: Mark it as `completed` IMMEDIATELY after finishing
|
| 158 |
+
3. **Updating the list**: Add new tasks, remove irrelevant ones, or update descriptions as needed
|
| 159 |
+
4. **Multiple updates**: You can make several updates at once (e.g., complete one task and start the next)
|
| 160 |
+
|
| 161 |
+
## Task States
|
| 162 |
+
|
| 163 |
+
- `pending`: Task not yet started
|
| 164 |
+
- `in_progress`: Currently working on (can have multiple if tasks run in parallel)
|
| 165 |
+
- `completed`: Task finished successfully
|
| 166 |
+
|
| 167 |
+
## Task Completion Requirements
|
| 168 |
+
|
| 169 |
+
**CRITICAL: Only mark a task as completed when you have FULLY accomplished it.**
|
| 170 |
+
|
| 171 |
+
Never mark a task as completed if:
|
| 172 |
+
- There are unresolved issues or errors
|
| 173 |
+
- Work is partial or incomplete
|
| 174 |
+
- You encountered blockers preventing completion
|
| 175 |
+
- You couldn't find necessary resources or dependencies
|
| 176 |
+
- Quality standards haven't been met
|
| 177 |
+
|
| 178 |
+
If blocked, keep the task as `in_progress` and create a new task describing what needs to be resolved.
|
| 179 |
+
|
| 180 |
+
## Best Practices
|
| 181 |
+
|
| 182 |
+
- Create specific, actionable items
|
| 183 |
+
- Break complex tasks into smaller, manageable steps
|
| 184 |
+
- Use clear, descriptive task names
|
| 185 |
+
- Update task status in real-time as you work
|
| 186 |
+
- Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
|
| 187 |
+
- Remove tasks that are no longer relevant
|
| 188 |
+
- **IMPORTANT**: When you write the todo list, mark your first task(s) as `in_progress` immediately
|
| 189 |
+
- **IMPORTANT**: Unless all tasks are completed, always have at least one task `in_progress` to show progress
|
| 190 |
+
|
| 191 |
+
Being proactive with task management demonstrates thoroughness and ensures all requirements are completed successfully.
|
| 192 |
+
|
| 193 |
+
**Remember**: If you only need a few tool calls to complete a task and it's clear what to do, it's better to just do the task directly and NOT use this tool at all.
|
| 194 |
+
"""
|
| 195 |
+
|
| 196 |
+
return TodoListMiddleware(system_prompt=system_prompt, tool_description=tool_description)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
# ThreadDataMiddleware must be before SandboxMiddleware to ensure thread_id is available
|
| 200 |
+
# UploadsMiddleware should be after ThreadDataMiddleware to access thread_id
|
| 201 |
+
# DanglingToolCallMiddleware patches missing ToolMessages before model sees the history
|
| 202 |
+
# SummarizationMiddleware should be early to reduce context before other processing
|
| 203 |
+
# TodoListMiddleware should be before ClarificationMiddleware to allow todo management
|
| 204 |
+
# TitleMiddleware generates title after first exchange
|
| 205 |
+
# MemoryMiddleware queues conversation for memory update (after TitleMiddleware)
|
| 206 |
+
# ViewImageMiddleware should be before ClarificationMiddleware to inject image details before LLM
|
| 207 |
+
# ClarificationMiddleware should be last to intercept clarification requests after model calls
|
| 208 |
+
def _build_middlewares(config: RunnableConfig, model_name: str | None):
|
| 209 |
+
"""Build middleware chain based on runtime configuration.
|
| 210 |
+
|
| 211 |
+
Args:
|
| 212 |
+
config: Runtime configuration containing configurable options like is_plan_mode.
|
| 213 |
+
|
| 214 |
+
Returns:
|
| 215 |
+
List of middleware instances.
|
| 216 |
+
"""
|
| 217 |
+
middlewares = [ThreadDataMiddleware(), UploadsMiddleware(), SandboxMiddleware(), DanglingToolCallMiddleware()]
|
| 218 |
+
|
| 219 |
+
# Add summarization middleware if enabled
|
| 220 |
+
summarization_middleware = _create_summarization_middleware()
|
| 221 |
+
if summarization_middleware is not None:
|
| 222 |
+
middlewares.append(summarization_middleware)
|
| 223 |
+
|
| 224 |
+
# Add TodoList middleware if plan mode is enabled
|
| 225 |
+
is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False)
|
| 226 |
+
todo_list_middleware = _create_todo_list_middleware(is_plan_mode)
|
| 227 |
+
if todo_list_middleware is not None:
|
| 228 |
+
middlewares.append(todo_list_middleware)
|
| 229 |
+
|
| 230 |
+
# Add TitleMiddleware
|
| 231 |
+
middlewares.append(TitleMiddleware())
|
| 232 |
+
|
| 233 |
+
# Add MemoryMiddleware (after TitleMiddleware)
|
| 234 |
+
middlewares.append(MemoryMiddleware())
|
| 235 |
+
|
| 236 |
+
# Add ViewImageMiddleware only if the current model supports vision.
|
| 237 |
+
# Use the resolved runtime model_name from make_lead_agent to avoid stale config values.
|
| 238 |
+
app_config = get_app_config()
|
| 239 |
+
model_config = app_config.get_model_config(model_name) if model_name else None
|
| 240 |
+
if model_config is not None and model_config.supports_vision:
|
| 241 |
+
middlewares.append(ViewImageMiddleware())
|
| 242 |
+
|
| 243 |
+
# Add SubagentLimitMiddleware to truncate excess parallel task calls
|
| 244 |
+
subagent_enabled = config.get("configurable", {}).get("subagent_enabled", False)
|
| 245 |
+
if subagent_enabled:
|
| 246 |
+
max_concurrent_subagents = config.get("configurable", {}).get("max_concurrent_subagents", 3)
|
| 247 |
+
middlewares.append(SubagentLimitMiddleware(max_concurrent=max_concurrent_subagents))
|
| 248 |
+
|
| 249 |
+
# ClarificationMiddleware should always be last
|
| 250 |
+
middlewares.append(ClarificationMiddleware())
|
| 251 |
+
return middlewares
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def make_lead_agent(config: RunnableConfig):
|
| 255 |
+
# Lazy import to avoid circular dependency
|
| 256 |
+
from src.tools import get_available_tools
|
| 257 |
+
|
| 258 |
+
thinking_enabled = config.get("configurable", {}).get("thinking_enabled", True)
|
| 259 |
+
requested_model_name = config.get("configurable", {}).get("model_name") or config.get("configurable", {}).get("model")
|
| 260 |
+
model_name = _resolve_model_name(requested_model_name)
|
| 261 |
+
if model_name is None:
|
| 262 |
+
raise ValueError(
|
| 263 |
+
"No chat model could be resolved. Please configure at least one model in "
|
| 264 |
+
"config.yaml or provide a valid 'model_name'/'model' in the request."
|
| 265 |
+
)
|
| 266 |
+
is_plan_mode = config.get("configurable", {}).get("is_plan_mode", False)
|
| 267 |
+
subagent_enabled = config.get("configurable", {}).get("subagent_enabled", False)
|
| 268 |
+
max_concurrent_subagents = config.get("configurable", {}).get("max_concurrent_subagents", 3)
|
| 269 |
+
|
| 270 |
+
app_config = get_app_config()
|
| 271 |
+
model_config = app_config.get_model_config(model_name) if model_name else None
|
| 272 |
+
if thinking_enabled and model_config is not None and not model_config.supports_thinking:
|
| 273 |
+
logger.warning(f"Thinking mode is enabled but model '{model_name}' does not support it; fallback to non-thinking mode.")
|
| 274 |
+
thinking_enabled = False
|
| 275 |
+
|
| 276 |
+
logger.info(
|
| 277 |
+
"thinking_enabled: %s, model_name: %s, is_plan_mode: %s, subagent_enabled: %s, max_concurrent_subagents: %s",
|
| 278 |
+
thinking_enabled,
|
| 279 |
+
model_name,
|
| 280 |
+
is_plan_mode,
|
| 281 |
+
subagent_enabled,
|
| 282 |
+
max_concurrent_subagents,
|
| 283 |
+
)
|
| 284 |
+
|
| 285 |
+
# Inject run metadata for LangSmith trace tagging
|
| 286 |
+
if "metadata" not in config:
|
| 287 |
+
config["metadata"] = {}
|
| 288 |
+
config["metadata"].update(
|
| 289 |
+
{
|
| 290 |
+
"model_name": model_name or "default",
|
| 291 |
+
"thinking_enabled": thinking_enabled,
|
| 292 |
+
"is_plan_mode": is_plan_mode,
|
| 293 |
+
"subagent_enabled": subagent_enabled,
|
| 294 |
+
}
|
| 295 |
+
)
|
| 296 |
+
|
| 297 |
+
return create_agent(
|
| 298 |
+
model=create_chat_model(name=model_name, thinking_enabled=thinking_enabled),
|
| 299 |
+
tools=get_available_tools(model_name=model_name, subagent_enabled=subagent_enabled),
|
| 300 |
+
middleware=_build_middlewares(config, model_name=model_name),
|
| 301 |
+
system_prompt=apply_prompt_template(subagent_enabled=subagent_enabled, max_concurrent_subagents=max_concurrent_subagents),
|
| 302 |
+
state_schema=ThreadState,
|
| 303 |
+
)
|
backend/src/agents/lead_agent/prompt.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime
|
| 2 |
+
|
| 3 |
+
from src.skills import load_skills
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def _build_subagent_section(max_concurrent: int) -> str:
|
| 7 |
+
"""Build the subagent system prompt section with dynamic concurrency limit.
|
| 8 |
+
|
| 9 |
+
Args:
|
| 10 |
+
max_concurrent: Maximum number of concurrent subagent calls allowed per response.
|
| 11 |
+
|
| 12 |
+
Returns:
|
| 13 |
+
Formatted subagent section string.
|
| 14 |
+
"""
|
| 15 |
+
n = max_concurrent
|
| 16 |
+
return f"""<subagent_system>
|
| 17 |
+
**🚀 SUBAGENT MODE ACTIVE - DECOMPOSE, DELEGATE, SYNTHESIZE**
|
| 18 |
+
|
| 19 |
+
You are running with subagent capabilities enabled. Your role is to be a **task orchestrator**:
|
| 20 |
+
1. **DECOMPOSE**: Break complex tasks into parallel sub-tasks
|
| 21 |
+
2. **DELEGATE**: Launch multiple subagents simultaneously using parallel `task` calls
|
| 22 |
+
3. **SYNTHESIZE**: Collect and integrate results into a coherent answer
|
| 23 |
+
|
| 24 |
+
**CORE PRINCIPLE: Complex tasks should be decomposed and distributed across multiple subagents for parallel execution.**
|
| 25 |
+
|
| 26 |
+
**⛔ HARD CONCURRENCY LIMIT: MAXIMUM {n} `task` CALLS PER RESPONSE. THIS IS NOT OPTIONAL.**
|
| 27 |
+
- Each response, you may include **at most {n}** `task` tool calls. Any excess calls are **silently discarded** by the system — you will lose that work.
|
| 28 |
+
- **Before launching subagents, you MUST count your sub-tasks in your thinking:**
|
| 29 |
+
- If count ≤ {n}: Launch all in this response.
|
| 30 |
+
- If count > {n}: **Pick the {n} most important/foundational sub-tasks for this turn.** Save the rest for the next turn.
|
| 31 |
+
- **Multi-batch execution** (for >{n} sub-tasks):
|
| 32 |
+
- Turn 1: Launch sub-tasks 1-{n} in parallel → wait for results
|
| 33 |
+
- Turn 2: Launch next batch in parallel → wait for results
|
| 34 |
+
- ... continue until all sub-tasks are complete
|
| 35 |
+
- Final turn: Synthesize ALL results into a coherent answer
|
| 36 |
+
- **Example thinking pattern**: "I identified 6 sub-tasks. Since the limit is {n} per turn, I will launch the first {n} now, and the rest in the next turn."
|
| 37 |
+
|
| 38 |
+
**Available Subagents:**
|
| 39 |
+
- **general-purpose**: For ANY non-trivial task - web research, code exploration, file operations, analysis, etc.
|
| 40 |
+
- **bash**: For command execution (git, build, test, deploy operations)
|
| 41 |
+
|
| 42 |
+
**Your Orchestration Strategy:**
|
| 43 |
+
|
| 44 |
+
✅ **DECOMPOSE + PARALLEL EXECUTION (Preferred Approach):**
|
| 45 |
+
|
| 46 |
+
For complex queries, break them down into focused sub-tasks and execute in parallel batches (max {n} per turn):
|
| 47 |
+
|
| 48 |
+
**Example 1: "Why is Tencent's stock price declining?" (3 sub-tasks → 1 batch)**
|
| 49 |
+
→ Turn 1: Launch 3 subagents in parallel:
|
| 50 |
+
- Subagent 1: Recent financial reports, earnings data, and revenue trends
|
| 51 |
+
- Subagent 2: Negative news, controversies, and regulatory issues
|
| 52 |
+
- Subagent 3: Industry trends, competitor performance, and market sentiment
|
| 53 |
+
→ Turn 2: Synthesize results
|
| 54 |
+
|
| 55 |
+
**Example 2: "Compare 5 cloud providers" (5 sub-tasks → multi-batch)**
|
| 56 |
+
→ Turn 1: Launch {n} subagents in parallel (first batch)
|
| 57 |
+
→ Turn 2: Launch remaining subagents in parallel
|
| 58 |
+
→ Final turn: Synthesize ALL results into comprehensive comparison
|
| 59 |
+
|
| 60 |
+
**Example 3: "Refactor the authentication system"**
|
| 61 |
+
→ Turn 1: Launch 3 subagents in parallel:
|
| 62 |
+
- Subagent 1: Analyze current auth implementation and technical debt
|
| 63 |
+
- Subagent 2: Research best practices and security patterns
|
| 64 |
+
- Subagent 3: Review related tests, documentation, and vulnerabilities
|
| 65 |
+
→ Turn 2: Synthesize results
|
| 66 |
+
|
| 67 |
+
✅ **USE Parallel Subagents (max {n} per turn) when:**
|
| 68 |
+
- **Complex research questions**: Requires multiple information sources or perspectives
|
| 69 |
+
- **Multi-aspect analysis**: Task has several independent dimensions to explore
|
| 70 |
+
- **Large codebases**: Need to analyze different parts simultaneously
|
| 71 |
+
- **Comprehensive investigations**: Questions requiring thorough coverage from multiple angles
|
| 72 |
+
|
| 73 |
+
❌ **DO NOT use subagents (execute directly) when:**
|
| 74 |
+
- **Task cannot be decomposed**: If you can't break it into 2+ meaningful parallel sub-tasks, execute directly
|
| 75 |
+
- **Ultra-simple actions**: Read one file, quick edits, single commands
|
| 76 |
+
- **Need immediate clarification**: Must ask user before proceeding
|
| 77 |
+
- **Meta conversation**: Questions about conversation history
|
| 78 |
+
- **Sequential dependencies**: Each step depends on previous results (do steps yourself sequentially)
|
| 79 |
+
|
| 80 |
+
**CRITICAL WORKFLOW** (STRICTLY follow this before EVERY action):
|
| 81 |
+
1. **COUNT**: In your thinking, list all sub-tasks and count them explicitly: "I have N sub-tasks"
|
| 82 |
+
2. **PLAN BATCHES**: If N > {n}, explicitly plan which sub-tasks go in which batch:
|
| 83 |
+
- "Batch 1 (this turn): first {n} sub-tasks"
|
| 84 |
+
- "Batch 2 (next turn): next batch of sub-tasks"
|
| 85 |
+
3. **EXECUTE**: Launch ONLY the current batch (max {n} `task` calls). Do NOT launch sub-tasks from future batches.
|
| 86 |
+
4. **REPEAT**: After results return, launch the next batch. Continue until all batches complete.
|
| 87 |
+
5. **SYNTHESIZE**: After ALL batches are done, synthesize all results.
|
| 88 |
+
6. **Cannot decompose** → Execute directly using available tools (bash, read_file, web_search, etc.)
|
| 89 |
+
|
| 90 |
+
**⛔ VIOLATION: Launching more than {n} `task` calls in a single response is a HARD ERROR. The system WILL discard excess calls and you WILL lose work. Always batch.**
|
| 91 |
+
|
| 92 |
+
**Remember: Subagents are for parallel decomposition, not for wrapping single tasks.**
|
| 93 |
+
|
| 94 |
+
**How It Works:**
|
| 95 |
+
- The task tool runs subagents asynchronously in the background
|
| 96 |
+
- The backend automatically polls for completion (you don't need to poll)
|
| 97 |
+
- The tool call will block until the subagent completes its work
|
| 98 |
+
- Once complete, the result is returned to you directly
|
| 99 |
+
|
| 100 |
+
**Usage Example 1 - Single Batch (≤{n} sub-tasks):**
|
| 101 |
+
|
| 102 |
+
```python
|
| 103 |
+
# User asks: "Why is Tencent's stock price declining?"
|
| 104 |
+
# Thinking: 3 sub-tasks → fits in 1 batch
|
| 105 |
+
|
| 106 |
+
# Turn 1: Launch 3 subagents in parallel
|
| 107 |
+
task(description="Tencent financial data", prompt="...", subagent_type="general-purpose")
|
| 108 |
+
task(description="Tencent news & regulation", prompt="...", subagent_type="general-purpose")
|
| 109 |
+
task(description="Industry & market trends", prompt="...", subagent_type="general-purpose")
|
| 110 |
+
# All 3 run in parallel → synthesize results
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
**Usage Example 2 - Multiple Batches (>{n} sub-tasks):**
|
| 114 |
+
|
| 115 |
+
```python
|
| 116 |
+
# User asks: "Compare AWS, Azure, GCP, Alibaba Cloud, and Oracle Cloud"
|
| 117 |
+
# Thinking: 5 sub-tasks → need multiple batches (max {n} per batch)
|
| 118 |
+
|
| 119 |
+
# Turn 1: Launch first batch of {n}
|
| 120 |
+
task(description="AWS analysis", prompt="...", subagent_type="general-purpose")
|
| 121 |
+
task(description="Azure analysis", prompt="...", subagent_type="general-purpose")
|
| 122 |
+
task(description="GCP analysis", prompt="...", subagent_type="general-purpose")
|
| 123 |
+
|
| 124 |
+
# Turn 2: Launch remaining batch (after first batch completes)
|
| 125 |
+
task(description="Alibaba Cloud analysis", prompt="...", subagent_type="general-purpose")
|
| 126 |
+
task(description="Oracle Cloud analysis", prompt="...", subagent_type="general-purpose")
|
| 127 |
+
|
| 128 |
+
# Turn 3: Synthesize ALL results from both batches
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
**Counter-Example - Direct Execution (NO subagents):**
|
| 132 |
+
|
| 133 |
+
```python
|
| 134 |
+
# User asks: "Run the tests"
|
| 135 |
+
# Thinking: Cannot decompose into parallel sub-tasks
|
| 136 |
+
# → Execute directly
|
| 137 |
+
|
| 138 |
+
bash("npm test") # Direct execution, not task()
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
**CRITICAL**:
|
| 142 |
+
- **Max {n} `task` calls per turn** - the system enforces this, excess calls are discarded
|
| 143 |
+
- Only use `task` when you can launch 2+ subagents in parallel
|
| 144 |
+
- Single task = No value from subagents = Execute directly
|
| 145 |
+
- For >{n} sub-tasks, use sequential batches of {n} across multiple turns
|
| 146 |
+
</subagent_system>"""
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
SYSTEM_PROMPT_TEMPLATE = """
|
| 150 |
+
<role>
|
| 151 |
+
You are DeerFlow 2.0, an open-source super agent.
|
| 152 |
+
</role>
|
| 153 |
+
|
| 154 |
+
{memory_context}
|
| 155 |
+
|
| 156 |
+
<thinking_style>
|
| 157 |
+
- Think concisely and strategically about the user's request BEFORE taking action
|
| 158 |
+
- Break down the task: What is clear? What is ambiguous? What is missing?
|
| 159 |
+
- **PRIORITY CHECK: If anything is unclear, missing, or has multiple interpretations, you MUST ask for clarification FIRST - do NOT proceed with work**
|
| 160 |
+
{subagent_thinking}- Never write down your full final answer or report in thinking process, but only outline
|
| 161 |
+
- CRITICAL: After thinking, you MUST provide your actual response to the user. Thinking is for planning, the response is for delivery.
|
| 162 |
+
- Your response must contain the actual answer, not just a reference to what you thought about
|
| 163 |
+
</thinking_style>
|
| 164 |
+
|
| 165 |
+
<clarification_system>
|
| 166 |
+
**WORKFLOW PRIORITY: CLARIFY → PLAN → ACT**
|
| 167 |
+
1. **FIRST**: Analyze the request in your thinking - identify what's unclear, missing, or ambiguous
|
| 168 |
+
2. **SECOND**: If clarification is needed, call `ask_clarification` tool IMMEDIATELY - do NOT start working
|
| 169 |
+
3. **THIRD**: Only after all clarifications are resolved, proceed with planning and execution
|
| 170 |
+
|
| 171 |
+
**CRITICAL RULE: Clarification ALWAYS comes BEFORE action. Never start working and clarify mid-execution.**
|
| 172 |
+
|
| 173 |
+
**MANDATORY Clarification Scenarios - You MUST call ask_clarification BEFORE starting work when:**
|
| 174 |
+
|
| 175 |
+
1. **Missing Information** (`missing_info`): Required details not provided
|
| 176 |
+
- Example: User says "create a web scraper" but doesn't specify the target website
|
| 177 |
+
- Example: "Deploy the app" without specifying environment
|
| 178 |
+
- **REQUIRED ACTION**: Call ask_clarification to get the missing information
|
| 179 |
+
|
| 180 |
+
2. **Ambiguous Requirements** (`ambiguous_requirement`): Multiple valid interpretations exist
|
| 181 |
+
- Example: "Optimize the code" could mean performance, readability, or memory usage
|
| 182 |
+
- Example: "Make it better" is unclear what aspect to improve
|
| 183 |
+
- **REQUIRED ACTION**: Call ask_clarification to clarify the exact requirement
|
| 184 |
+
|
| 185 |
+
3. **Approach Choices** (`approach_choice`): Several valid approaches exist
|
| 186 |
+
- Example: "Add authentication" could use JWT, OAuth, session-based, or API keys
|
| 187 |
+
- Example: "Store data" could use database, files, cache, etc.
|
| 188 |
+
- **REQUIRED ACTION**: Call ask_clarification to let user choose the approach
|
| 189 |
+
|
| 190 |
+
4. **Risky Operations** (`risk_confirmation`): Destructive actions need confirmation
|
| 191 |
+
- Example: Deleting files, modifying production configs, database operations
|
| 192 |
+
- Example: Overwriting existing code or data
|
| 193 |
+
- **REQUIRED ACTION**: Call ask_clarification to get explicit confirmation
|
| 194 |
+
|
| 195 |
+
5. **Suggestions** (`suggestion`): You have a recommendation but want approval
|
| 196 |
+
- Example: "I recommend refactoring this code. Should I proceed?"
|
| 197 |
+
- **REQUIRED ACTION**: Call ask_clarification to get approval
|
| 198 |
+
|
| 199 |
+
**STRICT ENFORCEMENT:**
|
| 200 |
+
- ❌ DO NOT start working and then ask for clarification mid-execution - clarify FIRST
|
| 201 |
+
- ❌ DO NOT skip clarification for "efficiency" - accuracy matters more than speed
|
| 202 |
+
- ❌ DO NOT make assumptions when information is missing - ALWAYS ask
|
| 203 |
+
- ❌ DO NOT proceed with guesses - STOP and call ask_clarification first
|
| 204 |
+
- ✅ Analyze the request in thinking → Identify unclear aspects → Ask BEFORE any action
|
| 205 |
+
- ✅ If you identify the need for clarification in your thinking, you MUST call the tool IMMEDIATELY
|
| 206 |
+
- ✅ After calling ask_clarification, execution will be interrupted automatically
|
| 207 |
+
- ✅ Wait for user response - do NOT continue with assumptions
|
| 208 |
+
|
| 209 |
+
**How to Use:**
|
| 210 |
+
```python
|
| 211 |
+
ask_clarification(
|
| 212 |
+
question="Your specific question here?",
|
| 213 |
+
clarification_type="missing_info", # or other type
|
| 214 |
+
context="Why you need this information", # optional but recommended
|
| 215 |
+
options=["option1", "option2"] # optional, for choices
|
| 216 |
+
)
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
**Example:**
|
| 220 |
+
User: "Deploy the application"
|
| 221 |
+
You (thinking): Missing environment info - I MUST ask for clarification
|
| 222 |
+
You (action): ask_clarification(
|
| 223 |
+
question="Which environment should I deploy to?",
|
| 224 |
+
clarification_type="approach_choice",
|
| 225 |
+
context="I need to know the target environment for proper configuration",
|
| 226 |
+
options=["development", "staging", "production"]
|
| 227 |
+
)
|
| 228 |
+
[Execution stops - wait for user response]
|
| 229 |
+
|
| 230 |
+
User: "staging"
|
| 231 |
+
You: "Deploying to staging..." [proceed]
|
| 232 |
+
</clarification_system>
|
| 233 |
+
|
| 234 |
+
{skills_section}
|
| 235 |
+
|
| 236 |
+
{subagent_section}
|
| 237 |
+
|
| 238 |
+
<working_directory existed="true">
|
| 239 |
+
- User uploads: `/mnt/user-data/uploads` - Files uploaded by the user (automatically listed in context)
|
| 240 |
+
- User workspace: `/mnt/user-data/workspace` - Working directory for temporary files
|
| 241 |
+
- Output files: `/mnt/user-data/outputs` - Final deliverables must be saved here
|
| 242 |
+
|
| 243 |
+
**File Management:**
|
| 244 |
+
- Uploaded files are automatically listed in the <uploaded_files> section before each request
|
| 245 |
+
- Use `read_file` tool to read uploaded files using their paths from the list
|
| 246 |
+
- For PDF, PPT, Excel, and Word files, converted Markdown versions (*.md) are available alongside originals
|
| 247 |
+
- All temporary work happens in `/mnt/user-data/workspace`
|
| 248 |
+
- Final deliverables must be copied to `/mnt/user-data/outputs` and presented using `present_file` tool
|
| 249 |
+
</working_directory>
|
| 250 |
+
|
| 251 |
+
<response_style>
|
| 252 |
+
- Clear and Concise: Avoid over-formatting unless requested
|
| 253 |
+
- Natural Tone: Use paragraphs and prose, not bullet points by default
|
| 254 |
+
- Action-Oriented: Focus on delivering results, not explaining processes
|
| 255 |
+
</response_style>
|
| 256 |
+
|
| 257 |
+
<citations>
|
| 258 |
+
- When to Use: After web_search, include citations if applicable
|
| 259 |
+
- Format: Use Markdown link format `[citation:TITLE](URL)`
|
| 260 |
+
- Example:
|
| 261 |
+
```markdown
|
| 262 |
+
The key AI trends for 2026 include enhanced reasoning capabilities and multimodal integration
|
| 263 |
+
[citation:AI Trends 2026](https://techcrunch.com/ai-trends).
|
| 264 |
+
Recent breakthroughs in language models have also accelerated progress
|
| 265 |
+
[citation:OpenAI Research](https://openai.com/research).
|
| 266 |
+
```
|
| 267 |
+
</citations>
|
| 268 |
+
|
| 269 |
+
<critical_reminders>
|
| 270 |
+
- **Clarification First**: ALWAYS clarify unclear/missing/ambiguous requirements BEFORE starting work - never assume or guess
|
| 271 |
+
{subagent_reminder}- Skill First: Always load the relevant skill before starting **complex** tasks.
|
| 272 |
+
- Progressive Loading: Load resources incrementally as referenced in skills
|
| 273 |
+
- Output Files: Final deliverables must be in `/mnt/user-data/outputs`
|
| 274 |
+
- Clarity: Be direct and helpful, avoid unnecessary meta-commentary
|
| 275 |
+
- Including Images and Mermaid: Images and Mermaid diagrams are always welcomed in the Markdown format, and you're encouraged to use `\n\n` or "```mermaid" to display images in response or Markdown files
|
| 276 |
+
- Multi-task: Better utilize parallel tool calling to call multiple tools at one time for better performance
|
| 277 |
+
- Language Consistency: Keep using the same language as user's
|
| 278 |
+
- Always Respond: Your thinking is internal. You MUST always provide a visible response to the user after thinking.
|
| 279 |
+
</critical_reminders>
|
| 280 |
+
"""
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def _get_memory_context() -> str:
|
| 284 |
+
"""Get memory context for injection into system prompt.
|
| 285 |
+
|
| 286 |
+
Returns:
|
| 287 |
+
Formatted memory context string wrapped in XML tags, or empty string if disabled.
|
| 288 |
+
"""
|
| 289 |
+
try:
|
| 290 |
+
from src.agents.memory import format_memory_for_injection, get_memory_data
|
| 291 |
+
from src.config.memory_config import get_memory_config
|
| 292 |
+
|
| 293 |
+
config = get_memory_config()
|
| 294 |
+
if not config.enabled or not config.injection_enabled:
|
| 295 |
+
return ""
|
| 296 |
+
|
| 297 |
+
memory_data = get_memory_data()
|
| 298 |
+
memory_content = format_memory_for_injection(memory_data, max_tokens=config.max_injection_tokens)
|
| 299 |
+
|
| 300 |
+
if not memory_content.strip():
|
| 301 |
+
return ""
|
| 302 |
+
|
| 303 |
+
return f"""<memory>
|
| 304 |
+
{memory_content}
|
| 305 |
+
</memory>
|
| 306 |
+
"""
|
| 307 |
+
except Exception as e:
|
| 308 |
+
print(f"Failed to load memory context: {e}")
|
| 309 |
+
return ""
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def get_skills_prompt_section() -> str:
|
| 313 |
+
"""Generate the skills prompt section with available skills list.
|
| 314 |
+
|
| 315 |
+
Returns the <skill_system>...</skill_system> block listing all enabled skills,
|
| 316 |
+
suitable for injection into any agent's system prompt.
|
| 317 |
+
"""
|
| 318 |
+
skills = load_skills(enabled_only=True)
|
| 319 |
+
|
| 320 |
+
try:
|
| 321 |
+
from src.config import get_app_config
|
| 322 |
+
|
| 323 |
+
config = get_app_config()
|
| 324 |
+
container_base_path = config.skills.container_path
|
| 325 |
+
except Exception:
|
| 326 |
+
container_base_path = "/mnt/skills"
|
| 327 |
+
|
| 328 |
+
if not skills:
|
| 329 |
+
return ""
|
| 330 |
+
|
| 331 |
+
skill_items = "\n".join(
|
| 332 |
+
f" <skill>\n <name>{skill.name}</name>\n <description>{skill.description}</description>\n <location>{skill.get_container_file_path(container_base_path)}</location>\n </skill>" for skill in skills
|
| 333 |
+
)
|
| 334 |
+
skills_list = f"<available_skills>\n{skill_items}\n</available_skills>"
|
| 335 |
+
|
| 336 |
+
return f"""<skill_system>
|
| 337 |
+
You have access to skills that provide optimized workflows for specific tasks. Each skill contains best practices, frameworks, and references to additional resources.
|
| 338 |
+
|
| 339 |
+
**Progressive Loading Pattern:**
|
| 340 |
+
1. When a user query matches a skill's use case, immediately call `read_file` on the skill's main file using the path attribute provided in the skill tag below
|
| 341 |
+
2. Read and understand the skill's workflow and instructions
|
| 342 |
+
3. The skill file contains references to external resources under the same folder
|
| 343 |
+
4. Load referenced resources only when needed during execution
|
| 344 |
+
5. Follow the skill's instructions precisely
|
| 345 |
+
|
| 346 |
+
**Skills are located at:** {container_base_path}
|
| 347 |
+
|
| 348 |
+
{skills_list}
|
| 349 |
+
|
| 350 |
+
</skill_system>"""
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def apply_prompt_template(subagent_enabled: bool = False, max_concurrent_subagents: int = 3) -> str:
|
| 354 |
+
# Get memory context
|
| 355 |
+
memory_context = _get_memory_context()
|
| 356 |
+
|
| 357 |
+
# Include subagent section only if enabled (from runtime parameter)
|
| 358 |
+
n = max_concurrent_subagents
|
| 359 |
+
subagent_section = _build_subagent_section(n) if subagent_enabled else ""
|
| 360 |
+
|
| 361 |
+
# Add subagent reminder to critical_reminders if enabled
|
| 362 |
+
subagent_reminder = (
|
| 363 |
+
"- **Orchestrator Mode**: You are a task orchestrator - decompose complex tasks into parallel sub-tasks. "
|
| 364 |
+
f"**HARD LIMIT: max {n} `task` calls per response.** "
|
| 365 |
+
f"If >{n} sub-tasks, split into sequential batches of ≤{n}. Synthesize after ALL batches complete.\n"
|
| 366 |
+
if subagent_enabled
|
| 367 |
+
else ""
|
| 368 |
+
)
|
| 369 |
+
|
| 370 |
+
# Add subagent thinking guidance if enabled
|
| 371 |
+
subagent_thinking = (
|
| 372 |
+
"- **DECOMPOSITION CHECK: Can this task be broken into 2+ parallel sub-tasks? If YES, COUNT them. "
|
| 373 |
+
f"If count > {n}, you MUST plan batches of ≤{n} and only launch the FIRST batch now. "
|
| 374 |
+
f"NEVER launch more than {n} `task` calls in one response.**\n"
|
| 375 |
+
if subagent_enabled
|
| 376 |
+
else ""
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
# Get skills section
|
| 380 |
+
skills_section = get_skills_prompt_section()
|
| 381 |
+
|
| 382 |
+
# Format the prompt with dynamic skills and memory
|
| 383 |
+
prompt = SYSTEM_PROMPT_TEMPLATE.format(
|
| 384 |
+
skills_section=skills_section,
|
| 385 |
+
memory_context=memory_context,
|
| 386 |
+
subagent_section=subagent_section,
|
| 387 |
+
subagent_reminder=subagent_reminder,
|
| 388 |
+
subagent_thinking=subagent_thinking,
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
return prompt + f"\n<current_date>{datetime.now().strftime('%Y-%m-%d, %A')}</current_date>"
|
backend/src/agents/memory/__init__.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Memory module for DeerFlow.
|
| 2 |
+
|
| 3 |
+
This module provides a global memory mechanism that:
|
| 4 |
+
- Stores user context and conversation history in memory.json
|
| 5 |
+
- Uses LLM to summarize and extract facts from conversations
|
| 6 |
+
- Injects relevant memory into system prompts for personalized responses
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from src.agents.memory.prompt import (
|
| 10 |
+
FACT_EXTRACTION_PROMPT,
|
| 11 |
+
MEMORY_UPDATE_PROMPT,
|
| 12 |
+
format_conversation_for_update,
|
| 13 |
+
format_memory_for_injection,
|
| 14 |
+
)
|
| 15 |
+
from src.agents.memory.queue import (
|
| 16 |
+
ConversationContext,
|
| 17 |
+
MemoryUpdateQueue,
|
| 18 |
+
get_memory_queue,
|
| 19 |
+
reset_memory_queue,
|
| 20 |
+
)
|
| 21 |
+
from src.agents.memory.updater import (
|
| 22 |
+
MemoryUpdater,
|
| 23 |
+
get_memory_data,
|
| 24 |
+
reload_memory_data,
|
| 25 |
+
update_memory_from_conversation,
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
__all__ = [
|
| 29 |
+
# Prompt utilities
|
| 30 |
+
"MEMORY_UPDATE_PROMPT",
|
| 31 |
+
"FACT_EXTRACTION_PROMPT",
|
| 32 |
+
"format_memory_for_injection",
|
| 33 |
+
"format_conversation_for_update",
|
| 34 |
+
# Queue
|
| 35 |
+
"ConversationContext",
|
| 36 |
+
"MemoryUpdateQueue",
|
| 37 |
+
"get_memory_queue",
|
| 38 |
+
"reset_memory_queue",
|
| 39 |
+
# Updater
|
| 40 |
+
"MemoryUpdater",
|
| 41 |
+
"get_memory_data",
|
| 42 |
+
"reload_memory_data",
|
| 43 |
+
"update_memory_from_conversation",
|
| 44 |
+
]
|
backend/src/agents/memory/prompt.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prompt templates for memory update and injection."""
|
| 2 |
+
|
| 3 |
+
from typing import Any
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
import tiktoken
|
| 7 |
+
|
| 8 |
+
TIKTOKEN_AVAILABLE = True
|
| 9 |
+
except ImportError:
|
| 10 |
+
TIKTOKEN_AVAILABLE = False
|
| 11 |
+
|
| 12 |
+
# Prompt template for updating memory based on conversation
|
| 13 |
+
MEMORY_UPDATE_PROMPT = """You are a memory management system. Your task is to analyze a conversation and update the user's memory profile.
|
| 14 |
+
|
| 15 |
+
Current Memory State:
|
| 16 |
+
<current_memory>
|
| 17 |
+
{current_memory}
|
| 18 |
+
</current_memory>
|
| 19 |
+
|
| 20 |
+
New Conversation to Process:
|
| 21 |
+
<conversation>
|
| 22 |
+
{conversation}
|
| 23 |
+
</conversation>
|
| 24 |
+
|
| 25 |
+
Instructions:
|
| 26 |
+
1. Analyze the conversation for important information about the user
|
| 27 |
+
2. Extract relevant facts, preferences, and context with specific details (numbers, names, technologies)
|
| 28 |
+
3. Update the memory sections as needed following the detailed length guidelines below
|
| 29 |
+
|
| 30 |
+
Memory Section Guidelines:
|
| 31 |
+
|
| 32 |
+
**User Context** (Current state - concise summaries):
|
| 33 |
+
- workContext: Professional role, company, key projects, main technologies (2-3 sentences)
|
| 34 |
+
Example: Core contributor, project names with metrics (16k+ stars), technical stack
|
| 35 |
+
- personalContext: Languages, communication preferences, key interests (1-2 sentences)
|
| 36 |
+
Example: Bilingual capabilities, specific interest areas, expertise domains
|
| 37 |
+
- topOfMind: Multiple ongoing focus areas and priorities (3-5 sentences, detailed paragraph)
|
| 38 |
+
Example: Primary project work, parallel technical investigations, ongoing learning/tracking
|
| 39 |
+
Include: Active implementation work, troubleshooting issues, market/research interests
|
| 40 |
+
Note: This captures SEVERAL concurrent focus areas, not just one task
|
| 41 |
+
|
| 42 |
+
**History** (Temporal context - rich paragraphs):
|
| 43 |
+
- recentMonths: Detailed summary of recent activities (4-6 sentences or 1-2 paragraphs)
|
| 44 |
+
Timeline: Last 1-3 months of interactions
|
| 45 |
+
Include: Technologies explored, projects worked on, problems solved, interests demonstrated
|
| 46 |
+
- earlierContext: Important historical patterns (3-5 sentences or 1 paragraph)
|
| 47 |
+
Timeline: 3-12 months ago
|
| 48 |
+
Include: Past projects, learning journeys, established patterns
|
| 49 |
+
- longTermBackground: Persistent background and foundational context (2-4 sentences)
|
| 50 |
+
Timeline: Overall/foundational information
|
| 51 |
+
Include: Core expertise, longstanding interests, fundamental working style
|
| 52 |
+
|
| 53 |
+
**Facts Extraction**:
|
| 54 |
+
- Extract specific, quantifiable details (e.g., "16k+ GitHub stars", "200+ datasets")
|
| 55 |
+
- Include proper nouns (company names, project names, technology names)
|
| 56 |
+
- Preserve technical terminology and version numbers
|
| 57 |
+
- Categories:
|
| 58 |
+
* preference: Tools, styles, approaches user prefers/dislikes
|
| 59 |
+
* knowledge: Specific expertise, technologies mastered, domain knowledge
|
| 60 |
+
* context: Background facts (job title, projects, locations, languages)
|
| 61 |
+
* behavior: Working patterns, communication habits, problem-solving approaches
|
| 62 |
+
* goal: Stated objectives, learning targets, project ambitions
|
| 63 |
+
- Confidence levels:
|
| 64 |
+
* 0.9-1.0: Explicitly stated facts ("I work on X", "My role is Y")
|
| 65 |
+
* 0.7-0.8: Strongly implied from actions/discussions
|
| 66 |
+
* 0.5-0.6: Inferred patterns (use sparingly, only for clear patterns)
|
| 67 |
+
|
| 68 |
+
**What Goes Where**:
|
| 69 |
+
- workContext: Current job, active projects, primary tech stack
|
| 70 |
+
- personalContext: Languages, personality, interests outside direct work tasks
|
| 71 |
+
- topOfMind: Multiple ongoing priorities and focus areas user cares about recently (gets updated most frequently)
|
| 72 |
+
Should capture 3-5 concurrent themes: main work, side explorations, learning/tracking interests
|
| 73 |
+
- recentMonths: Detailed account of recent technical explorations and work
|
| 74 |
+
- earlierContext: Patterns from slightly older interactions still relevant
|
| 75 |
+
- longTermBackground: Unchanging foundational facts about the user
|
| 76 |
+
|
| 77 |
+
**Multilingual Content**:
|
| 78 |
+
- Preserve original language for proper nouns and company names
|
| 79 |
+
- Keep technical terms in their original form (DeepSeek, LangGraph, etc.)
|
| 80 |
+
- Note language capabilities in personalContext
|
| 81 |
+
|
| 82 |
+
Output Format (JSON):
|
| 83 |
+
{{
|
| 84 |
+
"user": {{
|
| 85 |
+
"workContext": {{ "summary": "...", "shouldUpdate": true/false }},
|
| 86 |
+
"personalContext": {{ "summary": "...", "shouldUpdate": true/false }},
|
| 87 |
+
"topOfMind": {{ "summary": "...", "shouldUpdate": true/false }}
|
| 88 |
+
}},
|
| 89 |
+
"history": {{
|
| 90 |
+
"recentMonths": {{ "summary": "...", "shouldUpdate": true/false }},
|
| 91 |
+
"earlierContext": {{ "summary": "...", "shouldUpdate": true/false }},
|
| 92 |
+
"longTermBackground": {{ "summary": "...", "shouldUpdate": true/false }}
|
| 93 |
+
}},
|
| 94 |
+
"newFacts": [
|
| 95 |
+
{{ "content": "...", "category": "preference|knowledge|context|behavior|goal", "confidence": 0.0-1.0 }}
|
| 96 |
+
],
|
| 97 |
+
"factsToRemove": ["fact_id_1", "fact_id_2"]
|
| 98 |
+
}}
|
| 99 |
+
|
| 100 |
+
Important Rules:
|
| 101 |
+
- Only set shouldUpdate=true if there's meaningful new information
|
| 102 |
+
- Follow length guidelines: workContext/personalContext are concise (1-3 sentences), topOfMind and history sections are detailed (paragraphs)
|
| 103 |
+
- Include specific metrics, version numbers, and proper nouns in facts
|
| 104 |
+
- Only add facts that are clearly stated (0.9+) or strongly implied (0.7+)
|
| 105 |
+
- Remove facts that are contradicted by new information
|
| 106 |
+
- When updating topOfMind, integrate new focus areas while removing completed/abandoned ones
|
| 107 |
+
Keep 3-5 concurrent focus themes that are still active and relevant
|
| 108 |
+
- For history sections, integrate new information chronologically into appropriate time period
|
| 109 |
+
- Preserve technical accuracy - keep exact names of technologies, companies, projects
|
| 110 |
+
- Focus on information useful for future interactions and personalization
|
| 111 |
+
|
| 112 |
+
Return ONLY valid JSON, no explanation or markdown."""
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# Prompt template for extracting facts from a single message
|
| 116 |
+
FACT_EXTRACTION_PROMPT = """Extract factual information about the user from this message.
|
| 117 |
+
|
| 118 |
+
Message:
|
| 119 |
+
{message}
|
| 120 |
+
|
| 121 |
+
Extract facts in this JSON format:
|
| 122 |
+
{{
|
| 123 |
+
"facts": [
|
| 124 |
+
{{ "content": "...", "category": "preference|knowledge|context|behavior|goal", "confidence": 0.0-1.0 }}
|
| 125 |
+
]
|
| 126 |
+
}}
|
| 127 |
+
|
| 128 |
+
Categories:
|
| 129 |
+
- preference: User preferences (likes/dislikes, styles, tools)
|
| 130 |
+
- knowledge: User's expertise or knowledge areas
|
| 131 |
+
- context: Background context (location, job, projects)
|
| 132 |
+
- behavior: Behavioral patterns
|
| 133 |
+
- goal: User's goals or objectives
|
| 134 |
+
|
| 135 |
+
Rules:
|
| 136 |
+
- Only extract clear, specific facts
|
| 137 |
+
- Confidence should reflect certainty (explicit statement = 0.9+, implied = 0.6-0.8)
|
| 138 |
+
- Skip vague or temporary information
|
| 139 |
+
|
| 140 |
+
Return ONLY valid JSON."""
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def _count_tokens(text: str, encoding_name: str = "cl100k_base") -> int:
|
| 144 |
+
"""Count tokens in text using tiktoken.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
text: The text to count tokens for.
|
| 148 |
+
encoding_name: The encoding to use (default: cl100k_base for GPT-4/3.5).
|
| 149 |
+
|
| 150 |
+
Returns:
|
| 151 |
+
The number of tokens in the text.
|
| 152 |
+
"""
|
| 153 |
+
if not TIKTOKEN_AVAILABLE:
|
| 154 |
+
# Fallback to character-based estimation if tiktoken is not available
|
| 155 |
+
return len(text) // 4
|
| 156 |
+
|
| 157 |
+
try:
|
| 158 |
+
encoding = tiktoken.get_encoding(encoding_name)
|
| 159 |
+
return len(encoding.encode(text))
|
| 160 |
+
except Exception:
|
| 161 |
+
# Fallback to character-based estimation on error
|
| 162 |
+
return len(text) // 4
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
def format_memory_for_injection(memory_data: dict[str, Any], max_tokens: int = 2000) -> str:
|
| 166 |
+
"""Format memory data for injection into system prompt.
|
| 167 |
+
|
| 168 |
+
Args:
|
| 169 |
+
memory_data: The memory data dictionary.
|
| 170 |
+
max_tokens: Maximum tokens to use (counted via tiktoken for accuracy).
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
Formatted memory string for system prompt injection.
|
| 174 |
+
"""
|
| 175 |
+
if not memory_data:
|
| 176 |
+
return ""
|
| 177 |
+
|
| 178 |
+
sections = []
|
| 179 |
+
|
| 180 |
+
# Format user context
|
| 181 |
+
user_data = memory_data.get("user", {})
|
| 182 |
+
if user_data:
|
| 183 |
+
user_sections = []
|
| 184 |
+
|
| 185 |
+
work_ctx = user_data.get("workContext", {})
|
| 186 |
+
if work_ctx.get("summary"):
|
| 187 |
+
user_sections.append(f"Work: {work_ctx['summary']}")
|
| 188 |
+
|
| 189 |
+
personal_ctx = user_data.get("personalContext", {})
|
| 190 |
+
if personal_ctx.get("summary"):
|
| 191 |
+
user_sections.append(f"Personal: {personal_ctx['summary']}")
|
| 192 |
+
|
| 193 |
+
top_of_mind = user_data.get("topOfMind", {})
|
| 194 |
+
if top_of_mind.get("summary"):
|
| 195 |
+
user_sections.append(f"Current Focus: {top_of_mind['summary']}")
|
| 196 |
+
|
| 197 |
+
if user_sections:
|
| 198 |
+
sections.append("User Context:\n" + "\n".join(f"- {s}" for s in user_sections))
|
| 199 |
+
|
| 200 |
+
# Format history
|
| 201 |
+
history_data = memory_data.get("history", {})
|
| 202 |
+
if history_data:
|
| 203 |
+
history_sections = []
|
| 204 |
+
|
| 205 |
+
recent = history_data.get("recentMonths", {})
|
| 206 |
+
if recent.get("summary"):
|
| 207 |
+
history_sections.append(f"Recent: {recent['summary']}")
|
| 208 |
+
|
| 209 |
+
earlier = history_data.get("earlierContext", {})
|
| 210 |
+
if earlier.get("summary"):
|
| 211 |
+
history_sections.append(f"Earlier: {earlier['summary']}")
|
| 212 |
+
|
| 213 |
+
if history_sections:
|
| 214 |
+
sections.append("History:\n" + "\n".join(f"- {s}" for s in history_sections))
|
| 215 |
+
|
| 216 |
+
if not sections:
|
| 217 |
+
return ""
|
| 218 |
+
|
| 219 |
+
result = "\n\n".join(sections)
|
| 220 |
+
|
| 221 |
+
# Use accurate token counting with tiktoken
|
| 222 |
+
token_count = _count_tokens(result)
|
| 223 |
+
if token_count > max_tokens:
|
| 224 |
+
# Truncate to fit within token limit
|
| 225 |
+
# Estimate characters to remove based on token ratio
|
| 226 |
+
char_per_token = len(result) / token_count
|
| 227 |
+
target_chars = int(max_tokens * char_per_token * 0.95) # 95% to leave margin
|
| 228 |
+
result = result[:target_chars] + "\n..."
|
| 229 |
+
|
| 230 |
+
return result
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def format_conversation_for_update(messages: list[Any]) -> str:
|
| 234 |
+
"""Format conversation messages for memory update prompt.
|
| 235 |
+
|
| 236 |
+
Args:
|
| 237 |
+
messages: List of conversation messages.
|
| 238 |
+
|
| 239 |
+
Returns:
|
| 240 |
+
Formatted conversation string.
|
| 241 |
+
"""
|
| 242 |
+
lines = []
|
| 243 |
+
for msg in messages:
|
| 244 |
+
role = getattr(msg, "type", "unknown")
|
| 245 |
+
content = getattr(msg, "content", str(msg))
|
| 246 |
+
|
| 247 |
+
# Handle content that might be a list (multimodal)
|
| 248 |
+
if isinstance(content, list):
|
| 249 |
+
text_parts = [p.get("text", "") for p in content if isinstance(p, dict) and "text" in p]
|
| 250 |
+
content = " ".join(text_parts) if text_parts else str(content)
|
| 251 |
+
|
| 252 |
+
# Truncate very long messages
|
| 253 |
+
if len(str(content)) > 1000:
|
| 254 |
+
content = str(content)[:1000] + "..."
|
| 255 |
+
|
| 256 |
+
if role == "human":
|
| 257 |
+
lines.append(f"User: {content}")
|
| 258 |
+
elif role == "ai":
|
| 259 |
+
lines.append(f"Assistant: {content}")
|
| 260 |
+
|
| 261 |
+
return "\n\n".join(lines)
|
backend/src/agents/memory/queue.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Memory update queue with debounce mechanism."""
|
| 2 |
+
|
| 3 |
+
import threading
|
| 4 |
+
import time
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
from typing import Any
|
| 8 |
+
|
| 9 |
+
from src.config.memory_config import get_memory_config
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class ConversationContext:
|
| 14 |
+
"""Context for a conversation to be processed for memory update."""
|
| 15 |
+
|
| 16 |
+
thread_id: str
|
| 17 |
+
messages: list[Any]
|
| 18 |
+
timestamp: datetime = field(default_factory=datetime.utcnow)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class MemoryUpdateQueue:
|
| 22 |
+
"""Queue for memory updates with debounce mechanism.
|
| 23 |
+
|
| 24 |
+
This queue collects conversation contexts and processes them after
|
| 25 |
+
a configurable debounce period. Multiple conversations received within
|
| 26 |
+
the debounce window are batched together.
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
def __init__(self):
|
| 30 |
+
"""Initialize the memory update queue."""
|
| 31 |
+
self._queue: list[ConversationContext] = []
|
| 32 |
+
self._lock = threading.Lock()
|
| 33 |
+
self._timer: threading.Timer | None = None
|
| 34 |
+
self._processing = False
|
| 35 |
+
|
| 36 |
+
def add(self, thread_id: str, messages: list[Any]) -> None:
|
| 37 |
+
"""Add a conversation to the update queue.
|
| 38 |
+
|
| 39 |
+
Args:
|
| 40 |
+
thread_id: The thread ID.
|
| 41 |
+
messages: The conversation messages.
|
| 42 |
+
"""
|
| 43 |
+
config = get_memory_config()
|
| 44 |
+
if not config.enabled:
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
context = ConversationContext(
|
| 48 |
+
thread_id=thread_id,
|
| 49 |
+
messages=messages,
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
with self._lock:
|
| 53 |
+
# Check if this thread already has a pending update
|
| 54 |
+
# If so, replace it with the newer one
|
| 55 |
+
self._queue = [c for c in self._queue if c.thread_id != thread_id]
|
| 56 |
+
self._queue.append(context)
|
| 57 |
+
|
| 58 |
+
# Reset or start the debounce timer
|
| 59 |
+
self._reset_timer()
|
| 60 |
+
|
| 61 |
+
print(f"Memory update queued for thread {thread_id}, queue size: {len(self._queue)}")
|
| 62 |
+
|
| 63 |
+
def _reset_timer(self) -> None:
|
| 64 |
+
"""Reset the debounce timer."""
|
| 65 |
+
config = get_memory_config()
|
| 66 |
+
|
| 67 |
+
# Cancel existing timer if any
|
| 68 |
+
if self._timer is not None:
|
| 69 |
+
self._timer.cancel()
|
| 70 |
+
|
| 71 |
+
# Start new timer
|
| 72 |
+
self._timer = threading.Timer(
|
| 73 |
+
config.debounce_seconds,
|
| 74 |
+
self._process_queue,
|
| 75 |
+
)
|
| 76 |
+
self._timer.daemon = True
|
| 77 |
+
self._timer.start()
|
| 78 |
+
|
| 79 |
+
print(f"Memory update timer set for {config.debounce_seconds}s")
|
| 80 |
+
|
| 81 |
+
def _process_queue(self) -> None:
|
| 82 |
+
"""Process all queued conversation contexts."""
|
| 83 |
+
# Import here to avoid circular dependency
|
| 84 |
+
from src.agents.memory.updater import MemoryUpdater
|
| 85 |
+
|
| 86 |
+
with self._lock:
|
| 87 |
+
if self._processing:
|
| 88 |
+
# Already processing, reschedule
|
| 89 |
+
self._reset_timer()
|
| 90 |
+
return
|
| 91 |
+
|
| 92 |
+
if not self._queue:
|
| 93 |
+
return
|
| 94 |
+
|
| 95 |
+
self._processing = True
|
| 96 |
+
contexts_to_process = self._queue.copy()
|
| 97 |
+
self._queue.clear()
|
| 98 |
+
self._timer = None
|
| 99 |
+
|
| 100 |
+
print(f"Processing {len(contexts_to_process)} queued memory updates")
|
| 101 |
+
|
| 102 |
+
try:
|
| 103 |
+
updater = MemoryUpdater()
|
| 104 |
+
|
| 105 |
+
for context in contexts_to_process:
|
| 106 |
+
try:
|
| 107 |
+
print(f"Updating memory for thread {context.thread_id}")
|
| 108 |
+
success = updater.update_memory(
|
| 109 |
+
messages=context.messages,
|
| 110 |
+
thread_id=context.thread_id,
|
| 111 |
+
)
|
| 112 |
+
if success:
|
| 113 |
+
print(f"Memory updated successfully for thread {context.thread_id}")
|
| 114 |
+
else:
|
| 115 |
+
print(f"Memory update skipped/failed for thread {context.thread_id}")
|
| 116 |
+
except Exception as e:
|
| 117 |
+
print(f"Error updating memory for thread {context.thread_id}: {e}")
|
| 118 |
+
|
| 119 |
+
# Small delay between updates to avoid rate limiting
|
| 120 |
+
if len(contexts_to_process) > 1:
|
| 121 |
+
time.sleep(0.5)
|
| 122 |
+
|
| 123 |
+
finally:
|
| 124 |
+
with self._lock:
|
| 125 |
+
self._processing = False
|
| 126 |
+
|
| 127 |
+
def flush(self) -> None:
|
| 128 |
+
"""Force immediate processing of the queue.
|
| 129 |
+
|
| 130 |
+
This is useful for testing or graceful shutdown.
|
| 131 |
+
"""
|
| 132 |
+
with self._lock:
|
| 133 |
+
if self._timer is not None:
|
| 134 |
+
self._timer.cancel()
|
| 135 |
+
self._timer = None
|
| 136 |
+
|
| 137 |
+
self._process_queue()
|
| 138 |
+
|
| 139 |
+
def clear(self) -> None:
|
| 140 |
+
"""Clear the queue without processing.
|
| 141 |
+
|
| 142 |
+
This is useful for testing.
|
| 143 |
+
"""
|
| 144 |
+
with self._lock:
|
| 145 |
+
if self._timer is not None:
|
| 146 |
+
self._timer.cancel()
|
| 147 |
+
self._timer = None
|
| 148 |
+
self._queue.clear()
|
| 149 |
+
self._processing = False
|
| 150 |
+
|
| 151 |
+
@property
|
| 152 |
+
def pending_count(self) -> int:
|
| 153 |
+
"""Get the number of pending updates."""
|
| 154 |
+
with self._lock:
|
| 155 |
+
return len(self._queue)
|
| 156 |
+
|
| 157 |
+
@property
|
| 158 |
+
def is_processing(self) -> bool:
|
| 159 |
+
"""Check if the queue is currently being processed."""
|
| 160 |
+
with self._lock:
|
| 161 |
+
return self._processing
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
# Global singleton instance
|
| 165 |
+
_memory_queue: MemoryUpdateQueue | None = None
|
| 166 |
+
_queue_lock = threading.Lock()
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def get_memory_queue() -> MemoryUpdateQueue:
|
| 170 |
+
"""Get the global memory update queue singleton.
|
| 171 |
+
|
| 172 |
+
Returns:
|
| 173 |
+
The memory update queue instance.
|
| 174 |
+
"""
|
| 175 |
+
global _memory_queue
|
| 176 |
+
with _queue_lock:
|
| 177 |
+
if _memory_queue is None:
|
| 178 |
+
_memory_queue = MemoryUpdateQueue()
|
| 179 |
+
return _memory_queue
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def reset_memory_queue() -> None:
|
| 183 |
+
"""Reset the global memory queue.
|
| 184 |
+
|
| 185 |
+
This is useful for testing.
|
| 186 |
+
"""
|
| 187 |
+
global _memory_queue
|
| 188 |
+
with _queue_lock:
|
| 189 |
+
if _memory_queue is not None:
|
| 190 |
+
_memory_queue.clear()
|
| 191 |
+
_memory_queue = None
|