mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-27 21:50:36 +00:00
* fix(subagents): report model_length_capped when a subagent hits the output limit ModelLengthFinishReasonMiddleware only ran on the lead chain. A subagent whose response was cut at the provider output limit (finish_reason=length, stop_reason=max_tokens, MAX_TOKENS, Responses max_output_tokens) reached the lead as a clean `completed`, and a tool call truncated at that boundary still executed with partial arguments. Register the same middleware on the subagent chain, in the lead's relative position (after configured extensions, before Safety). It now also records each detected cap per context run_id and exposes `consume_stop_reason`, so the executor's existing duck-typed guard collection reports `stop_reason=model_length_capped`. Add the value to the subagent status contract (Python enum, contract fixture, frontend whitelist) with the label "model output length limit", and update the subagent docs. Tests cover the middleware channel, chain placement, the contract, and an end-to-end executor run with a fake model. Co-Authored-By: Claude Code <noreply@anthropic.com> * test(tasks): cover cap reasons on completed and failed runs Extend the existing shared-contract round-trip matrix to preserve both terminal statuses alongside every supported stop reason, including model_length_capped and loop_capped. --------- Co-authored-by: huangzz <110116@nd.com> Co-authored-by: Claude Code <noreply@anthropic.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
441 lines
12 KiB
Markdown
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 or clarification stage, so configured middleware is followed
|
|
by the model-length guard, 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!
|