Read this in other languages: π¨π³ δΈζ Β· π«π· FranΓ§ais Β· πͺπΈ EspaΓ±ol
An open replication of OpenAI's Deep Research with a modern web UI β adapted from HuggingFace smolagents with simplified configuration for easy self-hosting.
Read more about the original implementation in the HuggingFace blog post.
This agent achieves 55% pass@1 on the GAIA validation set, compared to 67% for OpenAI's Deep Research.
- Parallel background research β fire off multiple research tasks simultaneously, monitor them independently, and come back to results later β even after closing the browser
- Multi-agent research pipeline β Manager + search sub-agents with real-time streaming output
- Modern Web UI β Preact-based SPA with collapsible sections, model selector, and copy support
- Flexible model support β OpenAI, Anthropic, DeepSeek, Ollama, and any OpenAI-compatible provider
- Multiple search engines β DuckDuckGo (free), SerpAPI, MetaSo with automatic fallback
- Session history β SQLite-backed session storage with replay support
- Three run modes β Live (real-time), Background (persistent), Auto-kill (one-shot)
- Model auto-discovery β Detects available models from configured providers
- Vision & media tools β Image QA, PDF analysis, audio transcription, YouTube transcripts
- Production-ready β Docker, Gunicorn, multi-worker, health checks, configurable via JSON
Screenshots:
Clean input interface with model selection
Real-time display of agent reasoning, tool calls, and observations
Highlighted final answer with collapsible sections
Deep research tasks are slow β a single run can take 10β30 minutes. Most tools block the UI until the task completes, forcing you to wait.
This project takes a different approach: fire off as many research tasks as you want and let them run in the background β simultaneously.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Question A: "What are the latest advances in LLMs?" β β running
β Question B: "Compare top vector databases in 2025" β β running
β Question C: "EU AI Act compliance checklist" β β completed β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
All visible in the sidebar. Click any to inspect.
How it works:
- Select Background or Auto-kill run mode (the default)
- Submit your first research question β the agent starts immediately in a subprocess
- The UI is not locked β submit a second question, a third, as many as you need
- Each agent runs independently, persisting all its reasoning steps and results to SQLite
- Use the sidebar to switch between running sessions in real-time
- Close the browser β in Background mode, agents keep running on the server
- Return later and click any session to replay the full research trace
Run mode comparison:
| Mode | Multiple at once | Survives browser close | UI locked |
|---|---|---|---|
| Background | β | β | β |
| Auto-kill | β | β (killed on tab close) | β |
| Live | β | β | β |
This is particularly useful for:
- Batch research workflows where you queue several related questions and review results together
- Long-running queries where you don't want to keep a tab open
- Teams sharing a self-hosted instance with multiple concurrent users
-
One-command Docker install, zero config to start β
docker run -p 5080:5080 ghcr.io/extracurricular-ai/open-deep-research-with-web-ui:latestand a working web UI is up. DuckDuckGo search is built-in; one model API key is enough to begin. -
No LiteLLM dependency β direct OpenAI + Anthropic SDK calls only. Removes the intermediary translation layer that LiteLLM has had repeated security advisories for. Safer for enterprise / internal deployments.
-
Air-gap-friendly, self-hostable β no telemetry, no external service dependencies beyond the model + search APIs you explicitly configure. Pair with Ollama / LM Studio / vLLM for fully offline operation behind any firewall.
-
Built to be forked β ~3K LOC Python on top of smolagents. Add a tool by dropping a file in
scripts/; swap providers viascripts/model_routing.py; hook into agent step callbacks (seescripts/compaction.py). A starting point for your internal research agent, not a closed product. -
Multi-provider search with auto-fallback β DDGS, Tavily, SerpAPI, MetaSo, Bocha β wired up out of the box. Configure as an ordered list; the agent walks the chain on empty results or rate-limit errors. Cross-regional, China-hosted, and air-gapped friendly.
-
Parallel background research β the most unique feature in this space. Run multiple research tasks simultaneously; each persists to SQLite. Close the browser, return hours later, results are waiting. No other open-source deep research tool supports this.
| Feature | This project | nickscamara/open-deep-research | gpt-researcher | langchain/open_deep_research | smolagents |
|---|---|---|---|---|---|
| Docker / one-command deploy | β Pre-built image on GHCR | β Dockerfile | β Docker Compose | β Manual | β Library only |
| No LiteLLM dependency | β Direct OpenAI + Anthropic SDKs | β | |||
| Air-gapped / internal deploy | β No telemetry, no external deps | β | |||
| Multi-provider search w/ fallback | β DDGS + Tavily + SerpAPI + MetaSo + Bocha | β Firecrawl only | |||
| Regional model providers | β DeepSeek first-class | β | |||
| No-build frontend | β Preact + htm (no build step) | β Next.js build required | β Next.js build required | β LangGraph Studio | β |
| Free search out of the box | β DuckDuckGo (no key needed) | β Firecrawl API required | β | ||
| Local model support | β Ollama, LM Studio | β Ollama/Groq | β | β | |
| Parallel background tasks | β Multiple simultaneous runs | β | β | β | β |
| Session history / replay | β SQLite-backed | β | β | β | β |
| Streaming UI | β SSE, 3 run modes | β Real-time activity | β WebSocket | β Type-safe stream | β |
| Vision / image analysis | β PDF screenshots, visual QA | β | β | ||
| Audio / YouTube | β Transcription, speech | β | β | β | β |
| GAIA benchmark score | 55% pass@1 | β | β | β | 55% (original) |
git clone https://github.com/extracurricular-ai/open-deep-research-with-web-ui.git
cd open-deep-research-with-web-uiThe project requires FFmpeg for audio processing.
- macOS:
brew install ffmpeg - Linux:
sudo apt-get install ffmpeg - Windows:
choco install ffmpegor download from ffmpeg.org
Verify: ffmpeg -version
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install -e .Copy the example config and add your API keys:
cp odr-config.example.json odr-config.jsonEdit odr-config.json to set your model provider and API keys (see Configuration below).
# Web UI (recommended)
python web_app.py
# Open http://localhost:5080
# CLI
python run.py --model-id "gpt-4o" "Your research question here"Two layers of configuration:
odr-config.jsonβ primary, JSON, controls everything (models, agent behavior, search providers, browser, limits, compaction). Auto-created fromodr-config.example.jsonon first run..envβ optional, for secrets you'd rather keep out of JSON or for Docker deployments.
API keys in odr-config.json take precedence over .env values when both are set.
Copy odr-config.example.json to odr-config.json and edit. The full schema:
{
"agent": {
"search_agent_max_steps": 20,
"manager_agent_max_steps": 12,
"planning_interval": 4,
"verbosity_level": 2
},
"model": {
"providers": [
{"provider": "openai", "api_key": "sk-...", "base_url": ""},
{"provider": "deepseek", "api_key": "", "base_url": ""},
{"provider": "anthropic", "api_key": "", "base_url": ""}
],
"default_model_id": "o1",
"max_completion_tokens": 32768,
"reasoning_effort": "high",
"retry_max_attempts": 5,
"retry_wait_seconds": 30
},
"search": {
"providers": [
{"provider": "DDGS", "key": ""},
{"provider": "TAVILY", "key": ""},
{"provider": "SERPAPI", "key": ""},
{"provider": "META_SOTA", "key": ""},
{"provider": "BOCHA", "key": ""}
],
"max_results": 10
},
"browser": {
"viewport_size": 5120,
"request_timeout": 300
},
"limits": {
"text_limit": 100000,
"max_field_length": 50000
},
"compaction": {
"enabled": true,
"summarizer_model_id": null,
"summary_threshold_tokens": 1000,
"summary_max_tokens": 600,
"summary_input_cap_tokens": 6000,
"plan_keep_back": 3,
"gap_summary_max_tokens": 500,
"max_retries": 10
},
"other_keys": {"hf_token": ""},
"models": [ /* UI dropdown β list of {id, name, description} */ ]
}The UI exposes a settings panel that edits the same file. Server-side edits via the UI are gated behind CONFIG_ADMIN_PASSWORD if ENABLE_CONFIG_UI=true.
| Key | Default | Effect |
|---|---|---|
search_agent_max_steps |
20 |
Max ReAct steps the search sub-agent takes per task. Each step is one LLM call + one tool call (web search, browse, text inspect). Bigger = deeper research per sub-task, but each extra step accumulates ~5β30K tokens of observation in context. |
manager_agent_max_steps |
12 |
Max steps the manager takes. Each step usually delegates to a sub-agent or synthesizes results. Rarely needs raising; if you hit the cap, the question is probably better split. |
planning_interval |
4 |
Insert a "re-plan" step every N action steps. Lower = more course-correction (better when the agent loses focus); higher = fewer planning calls (cheaper, faster). |
verbosity_level |
2 |
Logger verbosity. 0 silent, 1 info, 2 debug. |
| Key | Default | Effect |
|---|---|---|
providers[] |
OpenAI/DeepSeek/Anthropic stubs | List of credentials. Each entry: {"provider": "<openai|deepseek|anthropic|...>", "api_key": "...", "base_url": ""}. The base_url field lets you point at a self-hosted or proxy endpoint that speaks the provider's wire format (e.g. Ollama's OpenAI-compatible API). The first provider matching the chosen default_model_id's routing is used. |
default_model_id |
"o1" |
Which model the agent uses. Routing is automatic based on the prefix β see Supported Models. Override per-run with --model-id. |
max_completion_tokens |
32768 |
Output token cap before clamping. Each model has a hard ceiling (gpt-4o-mini: 16K, deepseek-chat: 8K, o1: 100K, claude-sonnet-4: 64K). The effective value passed to the API is min(this_setting, model_cap) β if you keep the default 32768, smaller models silently clamp to their own ceiling so you never get a 4xx for "max_tokens too large". Lowering only helps if you want shorter outputs; raising past the model cap is a no-op. |
reasoning_effort |
"high" |
Only used when default_model_id is "o1". Values: "low", "medium", "high". Trade-off between latency/cost and reasoning depth. |
retry_max_attempts |
5 |
How many times to retry transient errors (HTTP 429, connection drops, partial reads). Note: this does not retry on context-overflow / 400-class errors, which are unrecoverable. |
retry_wait_seconds |
30 |
Initial backoff between retries. Doubles each attempt with jitter (exponential backoff). |
| Key | Default | Effect |
|---|---|---|
providers[] |
DDGS first, others empty | Ordered fallback chain. The agent tries the first provider; if it returns no results or errors, it moves to the next. Add a key field per entry (DDGS doesn't need one). See Search Engines below for the full provider list. |
max_results |
10 |
How many search results returned per query. Each result is a title + snippet + URL (~few hundred tokens). Bigger = wider net, but more tokens in observation. Lower if you're hitting context limits and not using compaction. |
| Key | Default | Effect |
|---|---|---|
viewport_size |
5120 |
Characters visible per page-view in the simulated browser. The agent uses page_up/page_down to scroll. Larger = fewer scroll calls but bigger observations. Smaller = more navigation steps but each observation is smaller. |
request_timeout |
300 |
Seconds to wait for an HTTP fetch before giving up. Slow sites or tiny VMs may need higher. |
| Key | Default | Effect |
|---|---|---|
text_limit |
100000 |
Max characters returned by text_inspector_tool (the file-reader for PDFs / large docs). Keeps a single inspect_file_as_text call from blowing up the agent's memory. |
max_field_length |
50000 |
Max characters per SSE event field sent to the frontend (display-side only β does not reduce LLM input). Lowering this just saves bandwidth between server and browser. |
Without this, smolagents accumulates every step's raw observation forever and 20-step research runs reliably blow past model context windows. See scripts/compaction.py for the implementation.
| Key | Default | Effect |
|---|---|---|
enabled |
true |
Master switch. Set false to fall back to raw-observation behavior (faster per step, but long runs may crash on context overflow). |
summarizer_model_id |
null |
null = use the agent's main model (simplest, no extra config). Override with a cheap model id (e.g. "deepseek-chat") to lower cost/latency of summarization. Currently the override path is reserved for a future PR; today the value is read but the main model is always used. |
summary_threshold_tokens |
1000 |
Layer 1: skip per-step summarization if the observation is shorter than this (in tokens, counted via tiktoken cl100k_base). Below 1000 tokens, the savings don't outweigh the LLM call cost. |
summary_max_tokens |
600 |
Layer 1: target output length of the per-step summary. The summary preserves facts, numbers, and URLs; drops navigation chrome and repetitive HTML. |
summary_input_cap_tokens |
6000 |
Layer 1: max input fed to the summarizer (head + tail trim if observation is bigger). Caps the summarizer's own context cost. |
plan_keep_back |
3 |
Layer 2: how many recent plan-gaps stay uncompacted. With planning_interval=4 and 20 search-agent steps, this fires once during a typical run (compacting the oldest gap). Lower (2 or 1) to consolidate more aggressively. |
gap_summary_max_tokens |
500 |
Layer 2: target length of each consolidated gap summary. URLs from the gap are appended verbatim. |
max_retries |
10 |
Retries for the compaction LLM call (own retry layer on top of the model's internal retrier). Mirrors Claude Code's default budget. After exhaustion, falls back to head+tail token truncation rather than crashing the run. |
| Key | Default | Effect |
|---|---|---|
hf_token |
"" |
HuggingFace token. Only required when running the GAIA benchmark (run_gaia.py) which downloads the validation dataset. |
A pure-display list of {id, name, description} triples for the model picker in the web UI. Editing this only affects the UI. The actual model used is whatever default_model_id (or CLI --model-id) resolves to.
For Docker or when you'd rather not put secrets in JSON, copy .env.example to .env:
cp .env.example .env| Variable | Effect |
|---|---|
ENABLE_CONFIG_UI |
If true, exposes the server-side config editing endpoint in the UI. Defaults to false. |
CONFIG_ADMIN_PASSWORD |
Password gate for the server-side config UI. Required when ENABLE_CONFIG_UI=true. |
META_SOTA_API_KEY |
API key for MetaSo search. Used as fallback when search.providers[].key is empty. |
SERPAPI_API_KEY |
API key for SerpAPI search. Same fallback rule. |
BOCHA_API_KEY |
API key for Bocha AI (εζ₯) search. Same fallback rule. |
TAVILY_API_KEY |
API key for Tavily search. Same fallback rule. |
OPENAI_API_KEY |
OpenAI key. Used when model.providers[] openai entry has no api_key. |
ANTHROPIC_API_KEY |
Anthropic key. Same fallback rule. |
DEEPSEEK_API_KEY |
DeepSeek key. Same fallback rule. |
HF_TOKEN |
HuggingFace token. Same fallback for other_keys.hf_token. |
ODR_MAX_CONCURRENT |
Max research sessions running at once. Extra submissions are queued (FIFO) and auto-started as slots free. Defaults to 16; floor of 1, no upper limit β tune to your provider rate limits and memory. Enforced globally across all Gunicorn workers via SQLite. |
ODR_DB_PATH |
Path to the SQLite session database. Defaults to odr_sessions.db next to the app. |
DEBUG |
Enable debug logging (false by default). |
LOG_LEVEL |
Log verbosity β DEBUG, INFO, WARNING, ERROR (INFO by default). |
Note
Keys set in odr-config.json take precedence over .env values.
Supports OpenAI, Anthropic, DeepSeek, Ollama, and any OpenAI-compatible provider. Model routing is automatic from the model id prefix. Examples:
python run.py --model-id "gpt-4o" "Your question"
python run.py --model-id "o1" "Your question"
python run.py --model-id "claude-sonnet-4-6" "Your question"
python run.py --model-id "deepseek/deepseek-chat" "Your question"
python run.py --model-id "ollama/mistral" "Your question" # local modelmax_completion_tokens is automatically clamped to each model's published output ceiling (see scripts/model_routing.py for the full table). You don't need to lower the config when switching to a small-cap model.
Warning
The o1 model requires OpenAI tier-3 API access: https://help.openai.com/en/articles/10362446-api-access-to-o1-and-o3-mini
| Engine | Key Required | Notes |
|---|---|---|
DDGS |
No | DuckDuckGo, free, default. |
TAVILY |
Yes | Tavily, often the best result quality for English queries. |
META_SOTA |
Yes | MetaSo, optimized for Chinese queries. |
SERPAPI |
Yes | Google results via SerpAPI. |
BOCHA |
Yes | Bocha AI (εζ₯), Chinese-optimized web search. |
Multiple engines can be listed in search.providers[] β the agent tries them in order and falls through to the next on empty results or errors.
python web_app.py
# or with custom host/port:
python web_app.py --port 8000 --host 0.0.0.0Open http://localhost:5080 in your browser.
Run modes (available via the split-button in the UI):
| Mode | Behavior |
|---|---|
| Live | Stream output in real-time; session ends on disconnect |
| Background | Agent runs persistently; reconnect anytime to view results |
| Auto-kill | Agent runs, session is cleaned up after completion |
python run.py --model-id "gpt-4o" "What are the latest advances in quantum computing?"# Requires HF_TOKEN for dataset download
python run_gaia.py --model-id "o1" --run-name my-runPre-built images are available on GitHub Container Registry:
docker pull ghcr.io/extracurricular-ai/open-deep-research-with-web-ui:latest
docker run -d \
--env-file .env \
-v ./odr-config.json:/app/odr-config.json \
-p 5080:5080 \
--name open-deep-research \
ghcr.io/extracurricular-ai/open-deep-research-with-web-ui:latestDocker Compose (includes volume for downloaded files):
cp .env.example .env # configure API keys
cp odr-config.example.json odr-config.json # configure models
docker-compose up -d
docker-compose logs -f # follow logs
docker-compose down # stopBuild your own image:
docker build -t open-deep-research .
docker run -d --env-file .env -p 5080:5080 open-deep-researchWarning
Never commit .env or odr-config.json with real API keys to git. Always pass secrets at runtime.
pip install -e .
gunicorn -c gunicorn.conf.py web_app:appThe included gunicorn.conf.py is pre-configured with:
- Multi-worker process management
- 300s timeout for long-running agent tasks
- Proper logging and error handling
User Question
β
βΌ
Manager Agent (CodeAgent / ToolCallingAgent)
β Plans multi-step research strategy
ββββΆ Search Sub-Agent Γ N
β β Web search β browse β extract
β ββββΆ Tools: DuckDuckGo/SerpAPI/MetaSo, VisitWebpage,
β TextInspector, VisualQA, YoutubeTranscript
β
ββββΆ Final Answer synthesis
run.py (step_callbacks β JSON-lines on stdout)
β
βΌ
web_app.py (subprocess β Server-Sent Events)
β
βΌ
Browser (Preact components β DOM)
SSE event types:
| Event | Description |
|---|---|
planning_step |
Agent reasoning and plan |
code_running |
Code being executed |
action_step |
Tool call + observation |
final_answer |
Completed research result |
error |
Error with details |
#output
βββ step-container.plan-step (manager plan)
βββ step-container (manager step)
β βββ step-children
β βββ model-output (reasoning)
β βββ Agent Call (code, collapsed)
β βββ sub-agent-container
β βββ step-container.plan-step (sub-agent plan)
β βββ step-container (sub-agent steps)
β βββ sub-agent-result (preview + collapsible)
βββ final_answer (prominent result block)
The 55% pass@1 result on GAIA was obtained with augmented data:
- Single-page PDFs and XLS files were opened and screenshotted as
.png - The file loader checks for a
.pngversion of each attachment and prefers it
The augmented dataset is available at smolagents/GAIA-annotated (access granted instantly on request).
pip install -e ".[dev]" # includes testing, linting, type checking tools
python web_app.py # starts dev server with auto-reloadThe frontend is a dependency-free Preact app using htm for JSX-like templates β no build step required. Edit files in static/js/components/ and refresh.
Licensed under the Apache License 2.0 β the same license as smolagents.
See LICENSE for details.
Acknowledgments:
- Original research agent implementation by HuggingFace smolagents
- Web UI, session management, streaming architecture, and configuration system added in this fork
