Skip to content

Commit c224f98

Browse files
committed
fix(agents): explain agents_api setup in custom agent UI
1 parent b107444 commit c224f98

10 files changed

Lines changed: 422 additions & 98 deletions

File tree

README.md

Lines changed: 58 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -104,35 +104,45 @@ That prompt is intended for coding agents. It tells the agent to clone the repo
104104
cd deer-flow
105105
```
106106

107-
2. **Generate local configuration files**
107+
2. **Run the setup wizard**
108108

109109
From the project root directory (`deer-flow/`), run:
110110

111111
```bash
112-
make config
112+
make setup
113113
```
114114

115-
This command creates local configuration files based on the provided example templates.
115+
This launches an interactive wizard that guides you through choosing an LLM provider, optional web search, and execution/safety preferences such as sandbox mode, bash access, and file-write tools. It generates a minimal `config.yaml` and writes your keys to `.env`. Takes about 2 minutes.
116116

117-
3. **Configure your preferred model(s)**
117+
The wizard also lets you configure an optional web search provider, or skip it for now.
118118

119-
Edit `config.yaml` and define at least one model:
119+
Run `make doctor` at any time to verify your setup and get actionable fix hints.
120+
121+
> **Advanced / manual configuration**: If you prefer to edit `config.yaml` directly, run `make config` instead to copy the full template. See `config.example.yaml` for the complete reference including CLI-backed providers (Codex CLI, Claude Code OAuth), OpenRouter, Responses API, and more.
122+
123+
> **Custom Agent UI**: If you want to create and manage custom agents from the web UI, enable the protected HTTP API in `config.yaml`:
124+
>
125+
> ```yaml
126+
> agents_api:
127+
> enabled: true
128+
> ```
129+
130+
<details>
131+
<summary>Manual model configuration examples</summary>
120132
121133
```yaml
122134
models:
123-
- name: gpt-4 # Internal identifier
124-
display_name: GPT-4 # Human-readable name
125-
use: langchain_openai:ChatOpenAI # LangChain class path
126-
model: gpt-4 # Model identifier for API
127-
api_key: $OPENAI_API_KEY # API key (recommended: use env var)
128-
max_tokens: 4096 # Maximum tokens per request
129-
temperature: 0.7 # Sampling temperature
135+
- name: gpt-4o
136+
display_name: GPT-4o
137+
use: langchain_openai:ChatOpenAI
138+
model: gpt-4o
139+
api_key: $OPENAI_API_KEY
130140
131141
- name: openrouter-gemini-2.5-flash
132142
display_name: Gemini 2.5 Flash (OpenRouter)
133143
use: langchain_openai:ChatOpenAI
134144
model: google/gemini-2.5-flash-preview
135-
api_key: $OPENAI_API_KEY # OpenRouter still uses the OpenAI-compatible field name here
145+
api_key: $OPENROUTER_API_KEY
136146
base_url: https://openrouter.ai/api/v1
137147
138148
- name: gpt-5-responses
@@ -182,47 +192,22 @@ That prompt is intended for coding agents. It tells the agent to clone the repo
182192
```
183193
184194
- Codex CLI reads `~/.codex/auth.json`
185-
- The Codex Responses endpoint currently rejects `max_tokens` and `max_output_tokens`, so `CodexChatModel` does not expose a request-level token cap
186-
- Claude Code accepts `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR`, `CLAUDE_CODE_CREDENTIALS_PATH`, or plaintext `~/.claude/.credentials.json`
187-
- ACP agent entries are separate from model providers. If you configure `acp_agents.codex`, point it at a Codex ACP adapter such as `npx -y @zed-industries/codex-acp`; the standard `codex` CLI binary is not ACP-compatible by itself
188-
- On macOS, DeerFlow does not probe Keychain automatically. Export Claude Code auth explicitly if needed:
195+
- Claude Code accepts `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_AUTH_TOKEN`, `CLAUDE_CODE_CREDENTIALS_PATH`, or `~/.claude/.credentials.json`
196+
- ACP agent entries are separate from model providers — if you configure `acp_agents.codex`, point it at a Codex ACP adapter such as `npx -y @zed-industries/codex-acp`
197+
- On macOS, export Claude Code auth explicitly if needed:
189198
190199
```bash
191200
eval "$(python3 scripts/export_claude_code_oauth.py --print-export)"
192201
```
193-
194-
4. **Set API keys for your configured model(s)**
195-
196-
Choose one of the following methods:
197-
198-
- Option A: Edit the `.env` file in the project root (Recommended)
199202
203+
API keys can also be set manually in `.env` (recommended) or exported in your shell:
200204
201205
```bash
202-
TAVILY_API_KEY=your-tavily-api-key
203206
OPENAI_API_KEY=your-openai-api-key
204-
# OpenRouter also uses OPENAI_API_KEY when your config uses langchain_openai:ChatOpenAI + base_url.
205-
# Add other provider keys as needed
206-
INFOQUEST_API_KEY=your-infoquest-api-key
207-
```
208-
209-
- Option B: Export environment variables in your shell
210-
211-
```bash
212-
export OPENAI_API_KEY=your-openai-api-key
207+
TAVILY_API_KEY=your-tavily-api-key
213208
```
214209
215-
For CLI-backed providers:
216-
- Codex CLI: `~/.codex/auth.json`
217-
- Claude Code OAuth: explicit env/file handoff or `~/.claude/.credentials.json`
218-
219-
- Option C: Edit `config.yaml` directly (Not recommended for production)
220-
221-
```yaml
222-
models:
223-
- name: gpt-4
224-
api_key: your-actual-api-key-here # Replace placeholder
225-
```
210+
</details>
226211
227212
### Running the Application
228213
@@ -276,7 +261,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed Docker development guide.
276261
277262
If you prefer running services locally:
278263
279-
Prerequisite: complete the "Configuration" steps above first (`make config` and model API keys). `make dev` requires a valid configuration file (defaults to `config.yaml` in the project root; can be overridden via `DEER_FLOW_CONFIG_PATH`).
264+
Prerequisite: complete the "Configuration" steps above first (`make setup`). `make dev` requires a valid `config.yaml` in the project root (can be overridden via `DEER_FLOW_CONFIG_PATH`). Run `make doctor` to verify your setup before starting.
280265
On Windows, run the local development flow from Git Bash. Native `cmd.exe` and PowerShell shells are not supported for the bash-based service scripts, and WSL is not guaranteed because some scripts rely on Git for Windows utilities such as `cygpath`.
281266
282267
1. **Check prerequisites**:
@@ -390,6 +375,7 @@ DeerFlow supports receiving tasks from messaging apps. Channels auto-start when
390375
| Telegram | Bot API (long-polling) | Easy |
391376
| Slack | Socket Mode | Moderate |
392377
| Feishu / Lark | WebSocket | Moderate |
378+
| WeChat | Tencent iLink (long-polling) | Moderate |
393379
| WeCom | WebSocket | Moderate |
394380
395381
**Configuration in `config.yaml`:**
@@ -434,6 +420,19 @@ channels:
434420
bot_token: $TELEGRAM_BOT_TOKEN
435421
allowed_users: [] # empty = allow all
436422
423+
wechat:
424+
enabled: false
425+
bot_token: $WECHAT_BOT_TOKEN
426+
ilink_bot_id: $WECHAT_ILINK_BOT_ID
427+
qrcode_login_enabled: true # optional: allow first-time QR bootstrap when bot_token is absent
428+
allowed_users: [] # empty = allow all
429+
polling_timeout: 35
430+
state_dir: ./.deer-flow/wechat/state
431+
max_inbound_image_bytes: 20971520
432+
max_outbound_image_bytes: 20971520
433+
max_inbound_file_bytes: 52428800
434+
max_outbound_file_bytes: 52428800
435+
437436
# Optional: per-channel / per-user session settings
438437
session:
439438
assistant_id: mobile-agent # custom agent names are also supported here
@@ -467,6 +466,10 @@ SLACK_APP_TOKEN=xapp-...
467466
FEISHU_APP_ID=cli_xxxx
468467
FEISHU_APP_SECRET=your_app_secret
469468
469+
# WeChat iLink
470+
WECHAT_BOT_TOKEN=your_ilink_bot_token
471+
WECHAT_ILINK_BOT_ID=your_ilink_bot_id
472+
470473
# WeCom
471474
WECOM_BOT_ID=your_bot_id
472475
WECOM_BOT_SECRET=your_bot_secret
@@ -492,6 +495,14 @@ WECOM_BOT_SECRET=your_bot_secret
492495
3. Under **Events**, subscribe to `im.message.receive_v1` and select **Long Connection** mode.
493496
4. Copy the App ID and App Secret. Set `FEISHU_APP_ID` and `FEISHU_APP_SECRET` in `.env` and enable the channel in `config.yaml`.
494497
498+
**WeChat Setup**
499+
500+
1. Enable the `wechat` channel in `config.yaml`.
501+
2. Either set `WECHAT_BOT_TOKEN` in `.env`, or set `qrcode_login_enabled: true` for first-time QR bootstrap.
502+
3. When `bot_token` is absent and QR bootstrap is enabled, watch backend logs for the QR content returned by iLink and complete the binding flow.
503+
4. After the QR flow succeeds, DeerFlow persists the acquired token under `state_dir` for later restarts.
504+
5. For Docker Compose deployments, keep `state_dir` on a persistent volume so the `get_updates_buf` cursor and saved auth state survive restarts.
505+
495506
**WeCom Setup**
496507
497508
1. Create a bot on the WeCom AI Bot platform and obtain the `bot_id` and `bot_secret`.
@@ -654,6 +665,8 @@ This is the difference between a chatbot with tool access and an agent with an a
654665

655666
**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.
656667

668+
**Strict Tool-Call Recovery**: When a provider or middleware interrupts a tool-call loop, DeerFlow now strips provider-level raw tool-call metadata on forced-stop assistant messages and injects placeholder tool results for dangling calls before the next model invocation. This keeps OpenAI-compatible reasoning models that strictly validate `tool_call_id` sequences from failing with malformed history errors.
669+
657670
### Long-Term Memory
658671

659672
Most agents forget everything the moment a conversation ends. DeerFlow remembers.

backend/app/gateway/routers/agents.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from fastapi import APIRouter, HTTPException
99
from pydantic import BaseModel, Field
1010

11+
from deerflow.config.agents_api_config import get_agents_api_config
1112
from deerflow.config.agents_config import AgentConfig, list_custom_agents, load_agent_config, load_agent_soul
1213
from deerflow.config.paths import get_paths
1314

@@ -33,6 +34,12 @@ class AgentsListResponse(BaseModel):
3334
agents: list[AgentResponse]
3435

3536

37+
class AgentsApiStatusResponse(BaseModel):
38+
"""Response model for the custom-agent management API status."""
39+
40+
enabled: bool = Field(..., description="Whether the HTTP custom-agent management API is enabled")
41+
42+
3643
class AgentCreateRequest(BaseModel):
3744
"""Request body for creating a custom agent."""
3845

@@ -73,6 +80,15 @@ def _normalize_agent_name(name: str) -> str:
7380
return name.lower()
7481

7582

83+
def _require_agents_api_enabled() -> None:
84+
"""Reject access unless the custom-agent management API is explicitly enabled."""
85+
if not get_agents_api_config().enabled:
86+
raise HTTPException(
87+
status_code=403,
88+
detail=("Custom-agent management API is disabled. Set agents_api.enabled=true to expose agent and user-profile routes over HTTP."),
89+
)
90+
91+
7692
def _agent_config_to_response(agent_cfg: AgentConfig, include_soul: bool = False) -> AgentResponse:
7793
"""Convert AgentConfig to AgentResponse."""
7894
soul: str | None = None
@@ -100,6 +116,8 @@ async def list_agents() -> AgentsListResponse:
100116
Returns:
101117
List of all custom agents with their metadata and soul content.
102118
"""
119+
_require_agents_api_enabled()
120+
103121
try:
104122
agents = list_custom_agents()
105123
return AgentsListResponse(agents=[_agent_config_to_response(a, include_soul=True) for a in agents])
@@ -125,12 +143,24 @@ async def check_agent_name(name: str) -> dict:
125143
Raises:
126144
HTTPException: 422 if the name is invalid.
127145
"""
146+
_require_agents_api_enabled()
128147
_validate_agent_name(name)
129148
normalized = _normalize_agent_name(name)
130149
available = not get_paths().agent_dir(normalized).exists()
131150
return {"available": available, "name": normalized}
132151

133152

153+
@router.get(
154+
"/agents/status",
155+
response_model=AgentsApiStatusResponse,
156+
summary="Get Agents API Status",
157+
description="Return whether the protected custom-agent management API is enabled.",
158+
)
159+
async def get_agents_api_status() -> AgentsApiStatusResponse:
160+
"""Return the current custom-agent management API exposure status."""
161+
return AgentsApiStatusResponse(enabled=get_agents_api_config().enabled)
162+
163+
134164
@router.get(
135165
"/agents/{name}",
136166
response_model=AgentResponse,
@@ -149,6 +179,7 @@ async def get_agent(name: str) -> AgentResponse:
149179
Raises:
150180
HTTPException: 404 if agent not found.
151181
"""
182+
_require_agents_api_enabled()
152183
_validate_agent_name(name)
153184
name = _normalize_agent_name(name)
154185

@@ -181,6 +212,7 @@ async def create_agent_endpoint(request: AgentCreateRequest) -> AgentResponse:
181212
Raises:
182213
HTTPException: 409 if agent already exists, 422 if name is invalid.
183214
"""
215+
_require_agents_api_enabled()
184216
_validate_agent_name(request.name)
185217
normalized_name = _normalize_agent_name(request.name)
186218

@@ -243,6 +275,7 @@ async def update_agent(name: str, request: AgentUpdateRequest) -> AgentResponse:
243275
Raises:
244276
HTTPException: 404 if agent not found.
245277
"""
278+
_require_agents_api_enabled()
246279
_validate_agent_name(name)
247280
name = _normalize_agent_name(name)
248281

@@ -315,6 +348,8 @@ async def get_user_profile() -> UserProfileResponse:
315348
Returns:
316349
UserProfileResponse with content=None if USER.md does not exist yet.
317350
"""
351+
_require_agents_api_enabled()
352+
318353
try:
319354
user_md_path = get_paths().user_md_file
320355
if not user_md_path.exists():
@@ -341,6 +376,8 @@ async def update_user_profile(request: UserProfileUpdateRequest) -> UserProfileR
341376
Returns:
342377
UserProfileResponse with the saved content.
343378
"""
379+
_require_agents_api_enabled()
380+
344381
try:
345382
paths = get_paths()
346383
paths.base_dir.mkdir(parents=True, exist_ok=True)
@@ -367,6 +404,7 @@ async def delete_agent(name: str) -> None:
367404
Raises:
368405
HTTPException: 404 if agent not found.
369406
"""
407+
_require_agents_api_enabled()
370408
_validate_agent_name(name)
371409
name = _normalize_agent_name(name)
372410

0 commit comments

Comments
 (0)