deer-flow/backend/CONTRIBUTING.md
wutongyuonce 4470932118
feat(extensions): allow constructor kwargs on config-declared middlewares (#5312)
* feat(extensions): allow constructor kwargs on config-declared middlewares

extensions.middlewares entries may be a class-path string or {class, kwargs}.
String entries keep the zero-argument constructor. Unknown fields and blank
class paths fail at config validation. Constructor errors still fail at
agent creation.

Fixes #5311

* fix(extensions): coerce middleware kwargs to JSON types

YAML timestamps became datetime objects while JSON kept strings, so
constructors and to_file_dict() json.dump saw different types. Validate
kwargs as JSON types at config load, stringify dates, reject NaN and
other non-JSON values, and cover the raw-dict loader branch.

* style(extensions): wrap middleware Field description for ruff E501

make lint failed: the middlewares description was 289 chars (limit 240).
Wrap it and run ruff format on the two files this PR last touched.

* docs: compact configured middleware guidance to satisfy size limit

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-09-12 10:57:40 +08:00

441 lines
12 KiB
Markdown

# Contributing to DeerFlow Backend
Thank you for your interest in contributing to DeerFlow! This document provides guidelines and instructions for contributing to the backend codebase.
## Table of Contents
- [Getting Started](#getting-started)
- [Development Setup](#development-setup)
- [Project Structure](#project-structure)
- [Code Style](#code-style)
- [Making Changes](#making-changes)
- [Testing](#testing)
- [Pull Request Process](#pull-request-process)
- [Architecture Guidelines](#architecture-guidelines)
## Getting Started
### Prerequisites
- Python 3.12 or higher
- [uv](https://docs.astral.sh/uv/) package manager
- Git
- Docker (optional, for Docker sandbox testing)
### Fork and Clone
1. Fork the repository on GitHub
2. Clone your fork locally:
```bash
git clone https://github.com/YOUR_USERNAME/deer-flow.git
cd deer-flow
```
## Development Setup
### Install Dependencies
```bash
# From project root
cp config.example.yaml config.yaml
# Install backend dependencies
cd backend
make install
```
### Configure Environment
Set up your API keys for testing:
```bash
export OPENAI_API_KEY="your-api-key"
# Add other keys as needed
```
### Run the Development Server
```bash
# Gateway API + embedded agent runtime
make dev
```
## Project Structure
```
backend/
├── packages/harness/deerflow/ # deerflow-harness package (import: deerflow.*)
│ ├── agents/ # Agent system
│ │ ├── lead_agent/ # Main agent (agent.py factory, prompt.py)
│ │ ├── middlewares/ # Agent middleware chain
│ │ ├── memory/ # Memory extraction & storage
│ │ └── thread_state.py # Thread state definition
│ ├── sandbox/ # Sandbox execution
│ │ ├── local/ # Local sandbox provider
│ │ ├── sandbox.py # Abstract interface
│ │ ├── tools.py # Sandbox tools (bash, file ops)
│ │ └── middleware.py # Sandbox lifecycle
│ ├── subagents/ # Subagent delegation
│ ├── tools/builtins/ # Built-in tools
│ ├── mcp/ # MCP integration
│ ├── models/ # Model factory
│ ├── skills/ # Skills system
│ ├── config/ # Configuration system
│ ├── runtime/ # Embedded run execution (RunManager, StreamBridge)
│ ├── persistence/ # Checkpointer/store engines & schema migrations
│ ├── guardrails/ # Pre-tool-call authorization providers
│ ├── tracing/ # Tracer factory & trace metadata
│ ├── uploads/ # Uploads manager
│ ├── tui/ # Terminal UI (`deerflow` console script)
│ ├── community/ # Community tools (tavily/, jina_ai/, firecrawl/, …)
│ ├── reflection/ # Dynamic module loading
│ └── utils/ # Utilities
└── app/ # FastAPI Gateway + IM channels (import: app.*)
├── gateway/ # Gateway API
│ ├── app.py # FastAPI application
│ └── routers/ # Route handlers (threads, models, mcp, skills, uploads, …)
└── channels/ # IM channel integrations (Feishu, Slack, Telegram, …)
```
See [AGENTS.md](AGENTS.md) for the full module-by-module breakdown.
## Code Style
### Linting and Formatting
We use `ruff` for both linting and formatting:
```bash
# Check for issues
make lint
# Auto-fix and format
make format
```
### Style Guidelines
- **Line length**: 240 characters maximum
- **Python version**: 3.12+ features allowed
- **Type hints**: Use type hints for function signatures
- **Quotes**: Double quotes for strings
- **Indentation**: 4 spaces (no tabs)
- **Imports**: Group by standard library, third-party, local
### Docstrings
Use docstrings for public functions and classes:
```python
def create_chat_model(name: str, thinking_enabled: bool = False) -> BaseChatModel:
"""Create a chat model instance from configuration.
Args:
name: The model name as defined in config.yaml
thinking_enabled: Whether to enable extended thinking
Returns:
A configured LangChain chat model instance
Raises:
ValueError: If the model name is not found in configuration
"""
...
```
## Making Changes
### Branch Naming
Use descriptive branch names:
- `feature/add-new-tool` - New features
- `fix/sandbox-timeout` - Bug fixes
- `docs/update-readme` - Documentation
- `refactor/config-system` - Code refactoring
### Commit Messages
Write clear, concise commit messages:
```
feat: add support for Claude 3.5 model
- Add model configuration in config.yaml
- Update model factory to handle Claude-specific settings
- Add tests for new model
```
Prefix types:
- `feat:` - New feature
- `fix:` - Bug fix
- `docs:` - Documentation
- `refactor:` - Code refactoring
- `test:` - Tests
- `chore:` - Build/config changes
## Testing
### Running Tests
```bash
uv run pytest
```
### Writing Tests
Place tests in the `tests/` directory mirroring the source structure:
```
tests/
├── test_models/
│ └── test_factory.py
├── test_sandbox/
│ └── test_local.py
└── test_gateway/
└── test_models_router.py
```
Example test:
```python
import pytest
from deerflow.models.factory import create_chat_model
def test_create_chat_model_with_valid_name():
"""Test that a valid model name creates a model instance."""
model = create_chat_model("gpt-4")
assert model is not None
def test_create_chat_model_with_invalid_name():
"""Test that an invalid model name raises ValueError."""
with pytest.raises(ValueError):
create_chat_model("nonexistent-model")
```
## Pull Request Process
### Before Submitting
1. **Ensure tests pass**: `uv run pytest`
2. **Run linter**: `make lint`
3. **Format code**: `make format`
4. **Update documentation** if needed
### PR Description
Include in your PR description:
- **What**: Brief description of changes
- **Why**: Motivation for the change
- **How**: Implementation approach
- **Testing**: How you tested the changes
### Review Process
1. Submit PR with clear description
2. Address review feedback
3. Ensure CI passes
4. Maintainer will merge when approved
## Architecture Guidelines
### Adding New Tools
1. Create tool in `packages/harness/deerflow/tools/builtins/` or `packages/harness/deerflow/community/`:
```python
# packages/harness/deerflow/tools/builtins/my_tool.py
from langchain_core.tools import tool
@tool
def my_tool(param: str) -> str:
"""Tool description for the agent.
Args:
param: Description of the parameter
Returns:
Description of return value
"""
return f"Result: {param}"
```
2. Register in `config.yaml`:
```yaml
tools:
- name: my_tool
group: my_group
use: deerflow.tools.builtins.my_tool:my_tool
```
### Adding New Middleware
1. Create middleware in `packages/harness/deerflow/agents/middlewares/`:
```python
# packages/harness/deerflow/agents/middlewares/my_middleware.py
from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
from langgraph.runtime import Runtime
class MyMiddleware(AgentMiddleware[AgentState]):
"""Middleware description."""
def before_model(self, state: AgentState, runtime: Runtime) -> dict | None:
"""Run before each model call."""
print(f"Model input contains {len(state.get('messages', []))} messages")
return None
def after_model(self, state: AgentState, runtime: Runtime) -> dict | None:
"""Run after each model call."""
messages = state.get("messages", [])
last_message = messages[-1] if messages else None
print(f"Last message type: {type(last_message).__name__ if last_message else 'none'}")
return None
```
Lifecycle hooks can return a dictionary of state updates, which LangChain merges
into the agent state, or `None` when they only observe state.
2. Register the middleware class in `config.yaml`. A class path uses the
zero-argument constructor; `{class, kwargs}` passes constructor arguments.
`kwargs` values must be JSON types (object, array, string, number, boolean,
or null); YAML dates and timestamps are coerced to ISO strings so they match JSON:
```yaml
extensions:
middlewares:
- deerflow.agents.middlewares.my_middleware:MyMiddleware
- class: deerflow.agents.middlewares.my_middleware:MyMiddleware
kwargs:
max_tool_calls: 5
```
Configured middleware runs after the built-in middleware and optional loop/token
guards. On the lead-agent pipeline, it runs before the terminal-response,
model-length, safety, and clarification tail; subagents have no
terminal-response, model-length, or clarification stage, so configured middleware is
followed by the optional safety guard, `DurableContextMiddleware`, optional
`SummarizationMiddleware`, then `SubagentDateContextMiddleware` and
`SystemMessageCoalescingMiddleware`. Treat middleware class paths as trusted
operator configuration because loading one executes Python code.
Embedded callers can instead use `DeerFlowClient(middlewares=[...])`, which
builds the full lead-agent chain and places middleware before its
terminal-response, model-length, safety, and clarification tail.
`create_deerflow_agent(extra_middleware=[...])` instead builds a smaller
feature-based lead-agent chain; unanchored extras are placed immediately before
`ClarificationMiddleware` (anchored extras follow their `@Next`/`@Prev`
placement, but the anchor must be present in this smaller chain). Neither API
forwards middleware to subagents.
Choose the registration path by ownership and placement. The fixed-slot (not deprecated)
`extensions.middlewares` list is accepted in `config.yaml` and
`extensions_config.json` (`config.yaml` wins) and applies to both lead and
subagent pipelines. Packaged extensions registered through the top-level
`plugins:` list contribute middleware at semantic extension points. Contributor
code that needs committed, programmatic lead-only wiring can use
`build_middlewares(..., custom_middlewares=[MyMiddleware()])` at the
`build_middlewares` call in
`packages/harness/deerflow/agents/lead_agent/agent.py` (reached through
`make_lead_agent`).
### Adding New API Endpoints
1. Create router in `app/gateway/routers/`:
```python
# app/gateway/routers/my_router.py
from fastapi import APIRouter
router = APIRouter(prefix="/my-endpoint", tags=["my-endpoint"])
@router.get("/")
async def get_items():
"""Get all items."""
return {"items": []}
@router.post("/")
async def create_item(data: dict):
"""Create a new item."""
return {"created": data}
```
2. Register in `app/gateway/app.py`:
```python
from app.gateway.routers import my_router
app.include_router(my_router.router)
```
### Configuration Changes
When adding new configuration options:
1. Update `packages/harness/deerflow/config/app_config.py` with new fields
2. Add default values in `config.example.yaml`
3. Document in `docs/CONFIGURATION.md`
### MCP Server Integration
To add support for a new MCP server:
1. Add configuration in `extensions_config.json`:
```json
{
"mcpServers": {
"my-server": {
"enabled": true,
"type": "stdio",
"command": "npx",
"args": ["-y", "@my-org/mcp-server"],
"description": "My MCP Server"
}
}
}
```
2. Update `extensions_config.example.json` with the new server
### Skills Development
To create a new skill:
1. Create directory in `skills/public/` or `skills/custom/`:
```
skills/public/my-skill/
└── SKILL.md
```
2. Write `SKILL.md` with YAML front matter:
```markdown
---
name: My Skill
description: What this skill does
license: MIT
allowed-tools:
- read_file
- write_file
- bash
---
# My Skill
Instructions for the agent when this skill is enabled...
```
## Questions?
If you have questions about contributing:
1. Check existing documentation in `docs/`
2. Look for similar issues or PRs on GitHub
3. Open a discussion or issue on GitHub
Thank you for contributing to DeerFlow!