local-deep-research

LearningCircuitMIT活跃维护

本地优先的深度研究问答系统,接 llama.cpp、Ollama 等本地或云端模型,聚合 arXiv、PubMed 及私有文档等十余个检索源,全程加密。

~95% on SimpleQA (e.g. Qwen3.6-27B on a 3090). Supports all local and cloud LLMs (llama.cpp, Ollama, Google, ...). 10+ search engines - arXiv, PubMed, your private documents. Everything Local & Encrypted.

v2.0.0发布于 2026-11-07 20:10 UTC9,556star +40 / 7 天911fork同步于 2026-11-10 11:30 UTC

怎么上手

由 README 提炼,执行前请确认
pip install local-deep-research
python -m local_deep_research.web.app
硬件要求按 README 说明

README 称可在单张 RTX 3090 上以 Qwen3.6-27B 全本地运行;也提供 CPU-only 的 Docker Compose 方案;CPU 需支持 AVX(Intel Sandy Bridge / AMD Bulldozer 及更新)。

纯 CPU 可运行有 Docker 镜像
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

本地可跑的深度研究助手,用多 LLM 与多搜索引擎做带引用的 agentic 检索

本地优先的深度研究问答系统,接 llama.cpp、Ollama 等本地或云端模型,聚合 arXiv、PubMed 及私有文档等十余个检索源,全程加密。

核心能力

  • 多 LLM 与 10+ 搜索引擎组合做带引用的深度研究
  • 把检索到的论文网页下载进加密库并索引供后续检索
  • 每用户独立 SQLCipher 数据库,密钥由密码派生
  • 支持 llama.cpp、Ollama 及 OpenAI 兼容端点等本地与云端模型
  • 提供 Docker、Docker Compose 与 pip 三种部署方式

容易踩的坑

pip 安装后仍需自行准备 Ollama(或 OpenAI 兼容端点)与 SearXNG;Docker 的 --network host 仅在原生 Linux 可用,Mac/Windows/WSL2 需改用 Compose;自 v1.10.3 起私有/localhost 搜索引擎地址默认被拦截,需通过环境变量显式放行;Windows 导出 PDF 需额外安装 Pango。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v2.0.0
  1. v2.0.0Release 2.0.0最新正式版2026-11-07 · 2026-11-07 20:10 UTC

    LDR 2.0

    LDR 2.0 is our biggest architectural change yet. We rebuilt the web layer on FastAPI and Uvicorn, hardened security across the whole app, added new search engines, and made Stop actually stop research mid-generation. It took 1,010 commits in ten weeks, from 40 contributors, 31 of them new to the project. Thank you to everyone who built, tested and reported issues.

    Several defaults and API contracts changed. Read the upgrade notes before deploying, especially if you run LDR behind a reverse proxy or call its HTTP API from your own clients.

    ⚠️ Before you upgrade

    • Take a verified offline backup first. 2.0 changes your databases, and v1.10.7 can't use them afterwards. LDR's built-in backups aren't a substitute (see the next point). Never start v1.10.7 on upgraded data, because doing so can delete your remaining built-in backups. Follow the upgrade and rollback guide, which also covers failed upgrades.
    • Pre-migration backups now live in their own folder. Each user gets encrypted_databases/backups/<user hash>/pre-migration/. Copy that folder out of the data directory after each user's first sign-in. With the default retention (1 backup, 7 days), the first login backup after the upgrade removes the regular backups made before it. Raise Max Backups and Backup Retention (days) first if you want to keep them.
    • Check your settings and environment overrides. Invalid configured values now raise an error instead of silently falling back to a default. This includes bad LDR_* booleans (only true/1/yes/on/enabled and false/0/no/off/disabled are accepted), llm.request_timeout below 1 second, and llm.max_retries outside 0–5. The settings page, research form and API errors name the offending key.
    • Everyone must sign in again after upgrading.

    Breaking changes for operators

    • Reverse proxies:
      • If a trusted proxy terminates TLS, set TRUST_PROXY_HEADERS=true and block direct access to the backend.
      • Rate limiting now trusts forwarded headers only from private or loopback peers.
      • A proxy on a public or CGNAT address (cloud load balancers, Tailscale) needs LDR_SERVER_FORWARDED_ALLOW_IPS set to its address. Otherwise all clients behind it share one rate-limit bucket and one login-lockout counter.
      • A single proxy must overwrite X-Forwarded-For and X-Real-IP, not append to them.
      • Proxy chains (for example CDN → nginx) can use the new LDR_SERVER_TRUSTED_PROXY_COUNT. See "Proxy chains" in docs/deployment/reverse-proxy.md.
    • Socket.IO moved from /socket.io to /ws/socket.io. Update any explicit proxy location rules and custom clients.
    • Direct (non-container) launches bind to 127.0.0.1. Set LDR_WEB_HOST=0.0.0.0 if you want remote access. Docker images already set this.
    • Research-library storage path is operator-managed. Users can no longer change it in Settings. Use LDR_RESEARCH_LIBRARY_STORAGE_PATH instead. The legacy shared-directory PDF read fallback has been removed.
    • LAN notification targets (self-hosted Apprise plugins on private hosts) now need LDR_NOTIFICATIONS_ALLOW_PRIVATE_IPS=true, plus an egress scope that permits the host.
    • Linux servers are now non-dumpable. They write no core dumps, and same-user py-spy, gdb and lsof no longer work against them. Startup fails if a seccomp profile denies prctl.
    • Non-Docker installs must install NLTK data once. Uploaded Office, HTML and email documents are now parsed in bounded child processes, which need the punkt_tab and averaged_perceptron_tagger_eng packages. The Docker image ships them.
    • FAISS indexes convert on their next write. No re-embedding is needed. Older LDR versions must rebuild a converted index before they can read it.

    Breaking changes for API clients

    • CSRF tokens are now required for session-authent
  2. v1.10.7Release 1.10.72026-08-28 · 2026-08-28 21:43 UTC
    查看更新说明

    Security hardening for notification URLs and the news subscription API

    This is a security release. It closes several server-side request forgery (SSRF) and local-file-read holes in outbound notifications and the news subscription API. If you have notifications enabled or use news subscriptions with a custom LLM endpoint, update promptly.

    Notification URL validation

    Three related fixes tighten what a notification URL is allowed to do:

    • Unsafe Apprise query options are now rejected before any hostname parsing or DNS lookup (#5085). Options like template, redirect, and mail-specific SMTP/PGP/WKD parameters could select a second, unvalidated destination or resource — for example, discord://id/token?template=/etc/passwd previously made the server read a local file and send it to the webhook. The dependency floor is now apprise>=1.12.0,<2, with a test that fails CI if Apprise's query-parsing semantics ever drift from what the validator mirrors.
    • The test-notification endpoint now validates every URL in a multi-URL string individually (#5120, via #5132). Commas are legal URL characters, so previously only the leading entry was checked while Apprise still notified the rest — letting an unvetted destination (such as a cloud-metadata address) receive the test notification. Target counts are now capped, and the test is refused if Apprise registers more services than were validated.
    • Legacy numeric IPv4 host forms are rejected before DNS resolution (#5086). Decimal, octal, hex, and mixed-base forms like 2130706433, 0x7f000001, or 0177.0.0.1 — including percent-encoded and trailing-dot variants — could previously resolve to loopback or metadata addresses depending on the platform. They are now blocked deterministically on every platform, in both the notification validator and the shared SSRF validator.

    News subscription API

    An SSRF guard for the custom_endpoint field (a user-supplied LLM base URL the server fetches) existed only on a blueprint nothing routes to; it is now applied on the live /news/api blueprint for both subscription creation and update (#5603). The guard blocks cloud-metadata and link-local addresses and non-HTTP schemes, while deliberately keeping loopback and private-network addresses allowed so local backends like Ollama, LM Studio, and vLLM keep working. A wrong-typed custom_endpoint now returns a 400 instead of a 500. Feed and history endpoints additionally reject non-UUID subscription ids as defence in depth (the underlying queries were already escaped and not exploitable).

    🔒 Security

    • Notification URL validation now rejects Apprise query options that can select an unvalidated secondary destination or resource (template, redirect, and mail-specific SMTP/PGP/WKD options) before any hostname parsing or DNS lookup. A template option is an unrestricted Apprise attachment, so discord://id/token?template=/etc/passwd previously made the server read a local file and ship it to the user's webhook. Query-key parsing mirrors Apprise's own parse_qsd rules (& and ; separators, +-as-space, percent-decoding, duplicate keys) so encoded or semicolon-separated names cannot bypass the policy, and a malformed percent escape in a key is rejected outright. The published dependency now declares apprise>=1.12.0,<2 — 1.12.0 is the reviewed parser floor — and a test diffs LDR's canonical keys against the installed Apprise parser so the contract cannot drift silently. Notification security documentation and compatibility tests now cover mode-dependent destinations and the current direct scheme-prefix gaps. (#5085)
    • The notification test endpoint (/api/notifications/test-url) now validates every URL in a comma/space-separated multi-URL string individually. Previously the input was validated as a single URL — commas are legal URL characters, so only the leading entry was checked while Appri
  3. v1.10.6Release 1.10.62026-08-28 · 2026-08-28 14:38 UTC
    查看更新说明

    Cross-user isolation fixes and a reworked citation pipeline

    This release is strongly recommended for multi-user deployments: it closes several ways one signed-in user could touch another user's benchmark runs, research runs, and live progress events. It also contains one small breaking API change for external programmatic users of SearchResultsCollector — see the first section below.

    Breaking change: SearchResultsCollector.add_results() return type

    If you use LDR's search collector from your own code, add_results() now returns (start, indexed) instead of just the start index. Migrate start = collector.add_results(results) to start, indexed = collector.add_results(results) and pass indexed to _format_results() so deduped [N] citation markers stay aligned with the collector. All internal call sites were already updated. (#5381)

    Security: per-user isolation and hardening

    • Benchmark runs were keyed by the per-user integer run id alone, so two users owning the same id (both "run 1") could cancel each other's runs, leak each other's results and credentials, and receive each other's live progress events in the browser. The run registry and Socket.IO subscriptions are now keyed by (username, run id). Ordinary research is unaffected — those ids are UUIDs. (#5600, #5520)
    • Research ownership: starting a follow-up and terminating a research run now verify the caller owns the parent/target research before any shared state is touched. (#5600)
    • Notification URL secrecy: notifications.service_url embeds credentials (e.g. discord://webhook/token) but was returned in plaintext by the settings API. It's now redacted like API keys, the UI handles the redaction sentinel correctly, and Test Notification falls back to the stored URL. (#5602)
    • Other hardening: /library/document/... resolution rejects guessable filenames at the regex (#5496); relevance-filter parser diagnostics are scrubbed of credential-shaped content (#5213); the LDR_SECURITY_ALLOW_NAT64 opt-in no longer widens the private-IP policy and decodes wrapped addresses at the correct RFC 6052 positions (#5594).

    Citations: dedup that keeps unique evidence

    On the LangGraph agent strategy, repeated hits of one source reuse their existing [N] — but a genuinely different excerpt of that source now gets its own citation instead of being silently discarded (#5381, #5894). Library/RAG citations are chunk-targeted (/library/document/<id>/chunks#chunk-<n>), so clicking a citation scrolls to the cited chunk, and all views of one document collapse to a single bibliography line with every citation number it owns. fetch_content can read those chunk citations again, sources_count now agrees with the rendered ## Sources block, and BibTeX/Quarto/LaTeX exports keep documents cited from several chunks (RIS export is still pending, tracked in #5687). (#5685, #5894)

    Notifications: Telegram scheme change

    Telegram notification URLs must use Apprise's canonical tgram:// scheme; telegram:// is now rejected with a dedicated migration error pointing at the right form. Multi-target URLs like tgram://...?to=id1,id2 are no longer split into partial notifications, and Apprise log output that could leak notification credentials is suppressed. (#5576)

    SearXNG

    • New engines setting lets you restrict LDR's queries to a specific set of engines on your instance — useful when premium-API engines (Kagi, Brave) should only be spent on demand. Empty keeps the current all-engines behavior. (#5622)
    • The rate limiter no longer evicts an in-use lock at capacity or loses its timestamp, so delay_between_requests keeps applying under pressure. (#5607, #5688)

    Settings fixes

    • The settings lock now also guards delete, import, and reset-to-defaults — and reset can no longer silently clear the lock. Locked operations return 403. (#5659)
    • Importing settings validates values against the current defaults schema; invalid entries a
  4. v1.10.5Release 1.10.52026-08-16 · 2026-08-16 17:47 UTC
    查看更新说明

    Making the SearXNG private-URL guard livable

    This release smooths out the private-URL protection introduced in v1.10.3, which blocked self-hosted SearXNG instances on localhost/LAN by default but made the failure hard to diagnose and the fix hard to apply.

    If your self-hosted SearXNG stopped returning results

    The research form now shows a dismissible warning banner when SearXNG is active but its private/localhost instance URL hasn't been approved by the server operator — the situation that previously caused the engine to silently self-disable and return no results. The banner includes the exact environment line needed to fix it. The runtime log message was also upgraded from WARNING to ERROR and now names all three remedies, as does the rejection message when saving such a URL in settings.

    To approve a self-hosted instance, the server operator picks one of these in the server environment, then restarts:

    • New in this release (recommended): LDR_SEARCH_PRIVATE_ENGINE_URL_ALLOWLIST=http://localhost:8080 — a comma-separated list of exact URL origins that may be private. Finer-grained than the blanket flag: only listed origins get private access. Matching is exact scheme + host + port (no wildcards); cloud-metadata addresses and non-http(s) schemes stay blocked even if listed, and the setting is env-only so it can't be changed through the web UI.
    • Env-lock the URL itself via LDR_SEARCH_ENGINE_WEB_SEARXNG_DEFAULT_PARAMS_INSTANCE_URL (what the bundled docker-compose already does — Docker Compose users are unaffected).
    • LDR_SEARCH_ALLOW_PRIVATE_ENGINE_URLS=true to allow all private engine URLs (broadest).

    Mirror-image warning for local document engines

    A companion banner now appears when a local document engine (Paperless-ngx, Elasticsearch) points at a host that doesn't look private — since search queries about your private documents would then leave the machine, and a Private-only egress scope would silently exclude the engine. It's advisory and dismissible, for cases like a deliberate Elastic Cloud endpoint.

    Docs

    The README quick start, installation guides, FAQ, troubleshooting, and .env.template now document the v1.10.3 guard next to every localhost:8080 example, instead of describing the old allow-by-default behavior — so fresh installs following the docs won't hit the silent-breakage case.

    ✨ New Features

    • The research form now shows a dismissible warning when SearXNG would silently return no results because its private/localhost instance URL has not been approved by the server operator — including the exact environment line to fix it. A mirror-image warning appears when a local document engine (Paperless-ngx, Elasticsearch) points at a host that does not look private, since document-search queries would then leave the machine. (#5604)
    • New env-only operator setting LDR_SEARCH_PRIVATE_ENGINE_URL_ALLOWLIST: a comma-separated list of exact URL origins (e.g. http://localhost:8080) that public search-engine instance URLs (SearXNG) may use even though they are private/loopback/link-local. This is a finer-grained alternative to LDR_SEARCH_ALLOW_PRIVATE_ENGINE_URLS=true — private fetching is enabled only when the engine's configured URL matches a listed origin, instead of for any private address. Consulted at both save time and runtime; cloud-metadata addresses and non-http(s) schemes remain blocked even if listed, and the setting cannot be edited through the web UI. (#5604)

    📝 Other Changes

    • Clearer guidance when a self-hosted SearXNG URL is blocked by the private-URL guard: the runtime self-disable now logs at ERROR (was WARNING) and both it and the settings-save rejection name all three operator remedies; docs, installation guides, and .env.template now document the v1.10.3 guard next to every localhost:8080 example instead of desc
  5. v1.10.4Release 1.10.42026-08-16 · 2026-08-16 13:20 UTC
    查看更新说明

    Critical login security fix and notification URL parsing

    This release fixes a serious authentication bypass — upgrade promptly. While a user's encrypted database was open (the normal state whenever that user is signed in anywhere, and for up to 30 days afterward with "remember me"), the login route accepted any password for that account: knowing only the username was enough to sign in from another session, browser, or IP and read or write their data. The attempt also counted as a successful login, so it never tripped account lockout or rate limiting. The fix (#5596) records which password opened each cached connection and re-verifies it on every login, falling back to a real key-validating open on mismatch. Accounts that had never been opened were not affected, and the cold-open path always validated passwords correctly — but any currently-open account was exposed, so updating is strongly recommended.

    Also in this release, notification service URLs are parsed more carefully (#5394): commas inside a single Apprise URL — such as multi-recipient mailto: destinations or webhook query parameters — are now preserved instead of being split into bogus entries, while configurations containing scheme-less URL fragments are refused outright rather than partially dispatched. If a notification configuration that previously failed validation now works (or vice versa), this change is why.

    🔒 Security

    • Login now verifies your password even when your encrypted database is already open. The connection cache is keyed by username alone, so a cache hit proved only that someone had already opened that user's database — and login's sole credential check is whether that open succeeds.

      This only affected an account that already existed and was currently open — the normal state while that user is signed in anywhere. It could not be used to sign in to an account that does not exist or that has never been opened, and the attacker needed no account of their own. The exposure also outlives an active browser session: the database stays open after the user closes the browser (until the idle timeout — up to 30 days with "remember me") and while a research job is running. For any account meeting that condition, knowing only the username — not the password — was enough to sign in as that user from a different session, browser, or IP and read (and write) their data. The attempt also counted as a successful login, so it never tripped the account lockout or the rate limiter and left nothing in the logs that looked like an attack.

      The cache now records which password opened the connection and re-checks it on every login, falling back to a real key-validating open when it does not match.

    🐛 Bug Fixes

    • Preserve commas inside Apprise service URLs while refusing configurations that contain scheme-less URL fragments. (#5394)

    What's Changed

    🔒 Security Updates

    📚 Documentation

    🔧 CI/CD & Maintenance

    🧪 Tests

    Full Changelog: https://github.c

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Local Deep Research

    GitHub stars Docker Pulls PyPI Downloads

    Trendshift

    Commits Last Commit

    SimpleQA Accuracy SQLCipher

    OpenSSF Scorecard CodeQL Semgrep

    🔧 Pre-commit

    🐳 Docker Publish 📦 PyPI Publish

    Discord Reddit YouTube

    AI-powered research assistant for deep, agentic research

    Performs deep, agentic research using multiple LLMs and search engines with proper citations

    🧪 First open-source project — fully-local on a single RTX 3090 (Qwen3.6-27B) — to report ~95% SimpleQA (n=500) and 77% xbench-DeepSearch (n=100) on local hardware. See the r/LocalLLaMA announcement and the benchmark dataset.

    🚀 What is Local Deep Research?

    AI research assistant you control. Run locally for privacy, use any LLM and build your own searchable knowledge base. You own your data and see exactly how it works.

    ⚡ Quick Start

    Option 1: Docker Run (Linux)

    # Step 1: Pull and run Ollama
    docker run -d -p 11434:11434 --name ollama ollama/ollama
    docker exec ollama ollama pull gpt-oss:20b
    
    # Step 2: Pull and run SearXNG for optimal search results
    docker run -d -p 8080:8080 --name searxng searxng/searxng
    
    # Step 3: Pull and run Local Deep Research
    # (the URL line pins SearXNG's address AND marks it operator-approved —
    #  private/localhost engine URLs are otherwise blocked by default since
    #  v1.10.3. The URL becomes read-only in the web UI; docs/SearXNG-Setup.md
    #  lists the alternatives, e.g. an origin allowlist.)
    docker run -d --network host \
      --name local-deep-research \
      --volume "deep-research:/data" \
      -e LDR_DATA_DIR=/data \
      -e LDR_SEARCH_ENGINE_WEB_SEARXNG_DEFAULT_PARAMS_INSTANCE_URL=http://localhost:8080 \
      localdeepresearch/local-deep-research
    

    Mac / Windows / WSL2 users: --network host only works on native Linux. On Docker Desktop it silently fails to publish port 5000 and leaves localhost pointing at the LDR container itself (so it can't reach Ollama/SearXNG). Use Option 2 below, or see the Windows/WSL2 FAQ entry for a working docker run recipe.

    Option 2: Docker Compose

    CPU-only (all platforms):

    curl -O https://raw.githubusercontent.com/LearningCircuit/local-deep-research/main/docker-compose.yml && docker compose up -d
    

    With NVIDIA GPU (Linux):

    curl -O https://raw.githubusercontent.com/LearningCircuit/local-deep-research/main/docker-compose.yml && \
    curl -O https://raw.githubusercontent.com/LearningCircuit/local-deep-research/main/docker-compose.gpu.override.yml && \
    docker compose -f docker-compose.yml -f docker-compose.gpu.override.yml up -d
    

    Open http://localhost:5000 after ~30 seconds. For GPU setup, environment variables, and more, see the Docker Compose Guide.

    Option 3: pip install

    pip install local-deep-research
    python -m local_deep_research.web.app   # starts the web UI on http://localhost:5000
    

    You'll also need Ollama (or any OpenAI-compatible LLM endpoint) and SearXNG running — see the pip install guide for the full recipe. Works on Windows, macOS, and Linux. SQLCipher encryption is included via pre-built wheels — no compilation needed. PDF export on Windows requires Pango (setup guide). Development fallback: LDR_BOOTSTRAP_ALLOW_UNENCRYPTED=true permits plaintext SQLite only when SQLCipher is unavailable or nonfunctional; working SQLCipher still takes priority. The fallback does not verify account passwords and is unsuitable for shared deployments. See fallback limitations.

    Detailed install guides: Docker · Docker Compose · pip · Unraid · full install reference

    Older CPU (x86-64)? LDR needs an AVX-capable CPU — Intel Sandy Bridge / AMD Bulldozer (2011) or newer. Several scientific Python dependencies (pandas, scikit-learn) ship wheels that crash with Illegal instruction on older CPUs. ARM64 (aarch64) is fully supported. Every release is smoke-tested against this floor, including AVX-without-AVX2 CPUs (#4480).

    🏗️ How It Works

    Research

    You ask a complex question. Local Deep Research (LDR):

    • Does the research for you automatically
    • Searches across web, academic papers, and your own documents
    • Synthesizes everything into a report with proper citations

    Choose the research strategy that fits: quick pipeline modes for fast facts, or fully agentic deep research for complex analysis and academic work.

    LangGraph Agent Strategy — An autonomous agentic research mode where the LLM decides what to search, which specialized engines to use (arXiv, PubMed, Semantic Scholar, etc.), and when to synthesize. It adaptively switches between search engines based on what it finds and collects significantly more sources than pipeline-based strategies — this is the strategy behind the ~95% SimpleQA result above. Select langgraph-agent in Settings.

    Build Your Knowledge Base

    flowchart LR
        R[Research] --> D[Download Sources]
        D --> L[(Library)]
        L --> I[Index & Embed]
        I --> S[Search Your Docs]
        S -.-> R
    

    Every research session finds valuable sources. Download them directly into your encrypted library — academic papers from ArXiv, PubMed articles, web pages. LDR extracts text, indexes everything, and makes it searchable. Next time you research, ask questions across your own documents and the live web together. Your knowledge compounds over time.

    🛡️ Security

    DevSkim Bearer

    OSV-Scanner npm-audit Retire.js

    Container Security Dockle Hadolint Checkov

    Zizmor OWASP ZAP Security Tests

    flowchart LR
        U1[User A] --> D1[(Encrypted DB)]
        U2[User B] --> D2[(Encrypted DB)]
    

    Your data stays yours. Each user gets their own isolated SQLCipher database encrypted with AES-256, with the key derived from your password. Your password is never stored — login works by attempting to decrypt your database, so the database files on their own are unusable to anyone who obtains them. Per-user LLM API keys live encrypted inside the same personal database rather than in a shared server-level store.

    The Docker setup ships with cap_drop: ALL, no-new-privileges, and a non-root runtime, with the bundled Ollama and SearXNG images pinned by digest. With a model running in the bundled Ollama, inference runs on your own hardware. LDR judges locality mainly by provider and endpoint address, not by where the model runs, so a local endpoint that relays to a hosted model (an Ollama cloud model, or a local LiteLLM or other OpenAI-compatible proxy in front of a hosted API) can still send your prompts off the machine without being detected. A self-hosted SearXNG still forwards your search queries to its upstream engines on the internet. To keep a run on private sources, search only local sources (collections, your library) under the Private only egress scope described below. That scope is a best-effort guard rail, not an air gap: see the Egress control section below and SECURITY.md.

    In-memory credentials: Credentials are held in process memory during active sessions. LDR limits their lifetime and applies process dump controls at web and MCP startup on POSIX, with additional dumpability controls on Linux. Other platforms and applications embedding LDR require operating-system configuration. See the Security Policy for scope and platform details.

    Supply Chain Security: Docker images are signed with Cosign using GitHub's keyless OIDC flow, include SLSA provenance attestations, and ship with attested SPDX SBOMs. See Verifying images and SBOMs for the step-by-step verification commands.

    Security Transparency: Scanner suppressions are documented with justifications in Security Alerts Assessment, Scorecard Compliance, Container CVE Suppressions, and SAST Rule Rationale. Some alerts (Dependabot, code scanning) can only be dismissed or are very difficult to suppress outside the GitHub Security tab, so the files above do not cover every dismissed finding.

    Detailed Architecture → | Security Policy → | Security Review Process →

    🌐 Egress control

    Every research run has an egress scope that restricts, on a best-effort basis, where its traffic may go: which search engines run, whether the LLM and embeddings may be cloud services, and which result URLs may be fetched. Set it under Settings → Egress Scope or in the Privacy & Egress panel on the research form; choosing a scope in the panel also saves it as your default setting, so later runs use it too.

    ModeSearch sourcesInference
    Adaptive (default)follows your primary engine: a public engine behaves like Public only, a private collection or your library like Private only; a locally stored collection you marked public as primary allows any classified engine, private collections includedforced local only when the run is private
    Public onlypublic web and academic engines; private collections and local document stores classified as private are excluded (a document store whose URL resolves to a public host, or does not resolve, is normally treated as public)your configured providers; a document-store primary with a cloud model is normally refused at start for research run through the web research form (not for entry points that call the research functions directly, such as /api/v1/quick_summary or MCP)
    Private onlylocal sources only (collections, library, local document stores such as Paperless)forced local; cloud providers are normally refused
    Strictyour single primary engine, no expansionnot forced local, but cloud models usually do not work under Strict (see the Strict section of Egress modes)
    Unprotectedanything; unavailable unless the operator sets LDR_POLICY_ALLOW_UNPROTECTED_EGRESS=truenormally anything (an operator's environment lock on the require-local settings still applies)

    Strict and Private only (including an Adaptive run that resolves to it) also arm a best-effort socket-level guard on the research run that normally refuses connections to public addresses. It is a backstop with known gaps, not a guarantee; the Strict section of Egress modes lists some of them, such as a forward proxy or a relay on a private address. Under Strict, public engines, public result fetches and cloud model calls therefore usually fail, so pair it with a local engine and local models.

    A locally-hosted SearXNG counts as a public engine — it proxies searches to the internet — so it is not available under Private only. Under Strict it may still run as your primary engine, and it then forwards your queries to its upstream engines on the internet.

    LDR's guarded HTTP layer, used for most search-result and document fetches, also checks destinations against SSRF rules (including known cloud-metadata addresses such as 169.254.169.254) and re-checks redirects. With JavaScript rendering enabled (web.enable_javascript_rendering, off by default), pages that need it are loaded in headless Chromium, which resolves addresses itself. See the egress module README for the details.

    Whether a signed-in user can point a search engine at a private address depends on the kind of engine:

    • Public engines with a configurable address (currently SearXNG): a private or loopback URL is normally refused unless the operator opts in through the environment (LDR_SEARCH_ALLOW_PRIVATE_ENGINE_URLS=true, an exact origin in LDR_SEARCH_PRIVATE_ENGINE_URL_ALLOWLIST, or pinning the URL itself with LDR_SEARCH_ENGINE_WEB_SEARXNG_DEFAULT_PARAMS_INSTANCE_URL). A SearXNG on localhost therefore normally needs one of these.
    • Local document-store engines (Paperless-ngx and Elasticsearch): their address settings, search.engine.web.paperless.default_params.api_url and search.engine.web.elasticsearch.default_params.hosts, are meant to point at private hosts, so currently no opt-in applies: any signed-in user can set them to an internal address and the server will connect to it. If that is not acceptable, pin these settings with their environment variables (LDR_ plus the key in upper case with dots replaced by underscores, for example LDR_SEARCH_ENGINE_WEB_PAPERLESS_DEFAULT_PARAMS_API_URL), or lock settings changes with LDR_APP_LOCK_SETTINGS=true.

    The egress policy is an in-process guard rail against misconfiguration and prompt-injected fetches, not an air gap or a hard security boundary: for example, hostnames are still resolved through the configured DNS resolver before a fetch is refused, and it cannot reliably constrain code that runs inside the LDR process. For a hard boundary, add egress filtering at the container or firewall level as well.

    Egress modes → | Threat model and caveats → | Enforcement points →

    🔒 Privacy & Data

    Local Deep Research contains no telemetry, no analytics, and no tracking. We do not collect, transmit, or store any data about you or your usage. No analytics SDKs, no phone-home calls, no crash reporting, no external scripts. Usage metrics stay in your local encrypted database.

    The only network calls LDR makes are ones you initiate: search queries (to engines you configure), LLM API calls (to your chosen provider), and notifications (only if you set up Apprise).

    Since we don't collect any usage data, we rely on you to tell us what works, what's broken, and what you'd like to see next — bug reports, feature ideas, and even which features you love or never use all help us improve LDR.

    📊 Benchmarks

    Headline results from the community benchmarks using the langgraph-agent strategy with Serper (cloud) search and the models running locally in Ollama:

    ModelSimpleQAxbench-DeepSearch
    Qwen3.6-27B95.7% (287/300)77.0% (77/100)
    Qwen3.5-9B91.2% (182/200)59.0% (59/100)
    gpt-oss-20B85.4% (295/346)–

    Caveats: small samples, LLM-grader noise, and SimpleQA contamination risk on newer base models.

    Picking a local model? The same community-maintained dataset tracks accuracy across models, search engines, and research strategies — the fastest way to see which Ollama / LM Studio / llama.cpp models actually work well for deep research before you download multi-GB weights. Browse the full leaderboard on Hugging Face →

    Submit your own results → (contributors are listed in CONTRIBUTORS.md), or run benchmarks locally →.

    ✨ Key Features

    🔍 Research Modes

    • Quick Summary - Get answers in 30 seconds to 3 minutes with citations
    • Detailed Research - Comprehensive analysis with structured findings
    • Report Generation - Professional reports with sections and table of contents
    • Document Analysis - Search your private documents with AI

    🛠️ Advanced Capabilities

    • LangChain Integration - Use any vector store as a search engine
    • REST API - Authenticated HTTP access with per-user databases
    • Benchmarking - Test and optimize your configuration
    • Analytics Dashboard - Track costs, performance, and usage metrics
    • Journal Quality System - Automatic journal reputation scoring with 212K+ indexed sources, predatory detection, and quality dashboard. Powered by OpenAlex (CC0), DOAJ (CC0), and Stop Predatory Journals (MIT). See the v1.6.0 announcement.
    • Real-time Updates - WebSocket support for live research progress
    • Chat Mode - Multi-turn research conversations with streaming progress and accumulated context across turns
    • Export Options - Download results as PDF or Markdown
    • Research History - Save, search, and revisit past research
    • Adaptive Rate Limiting - Intelligent retry system that learns optimal wait times
    • Keyboard Shortcuts - Navigate efficiently (ESC, Ctrl+Shift+1-4)

    📰 News & Research Subscriptions

    • Automated Research Digests - Subscribe to topics or specific queries; AI filters and summarizes only the most relevant developments
    • Customizable Delivery - Daily, weekly, or custom schedules, as markdown reports or structured summaries

    🌐 Search Sources

    Free Search Engines
    • Academic: arXiv, PubMed, Semantic Scholar
    • General: Wikipedia, SearXNG
    • Technical: GitHub, Elasticsearch
    • Archived pages: Wayback Machine URL fetching
    • News: The Guardian, Wikinews
    Premium Search Engines
    • Tavily - AI-powered search
    • Google - Via SerpAPI or Programmable Search Engine
    • Brave Search - Privacy-focused web search
    Custom Sources
    • Local Documents - Search your files with AI
    • LangChain Retrievers - Any vector store or database
    • Meta Search - Combine multiple engines intelligently

    LDR respects robots.txt and identifies itself honestly when fetching web pages — no stealth or anti-detection techniques. In rare cases this means a page that blocks automated access won't be fetched, which we consider the right trade-off.

    Full Search Engines Guide →

    💻 Usage Examples

    Python API

    from local_deep_research.api import LDRClient, quick_query
    
    # Option 1: Simplest - one line research
    summary = quick_query("username", "password", "What is quantum computing?")
    print(summary)
    
    # Option 2: Client for multiple operations
    client = LDRClient()
    client.login("username", "password")
    result = client.quick_research("What are the latest advances in quantum computing?")
    print(result["summary"])
    

    HTTP API

    The code example below shows the basic API structure - for working examples, see the link below

    import requests
    from bs4 import BeautifulSoup
    
    # Create session and authenticate
    session = requests.Session()
    login_page = session.get("http://localhost:5000/auth/login")
    soup = BeautifulSoup(login_page.text, "html.parser")
    login_csrf = soup.find("input", {"name": "csrf_token"}).get("value")
    
    # Login and get API CSRF token
    session.post("http://localhost:5000/auth/login",
                data={"username": "user", "password": "pass", "csrf_token": login_csrf})
    csrf = session.get("http://localhost:5000/auth/csrf-token").json()["csrf_token"]
    
    # Make API request
    response = session.post("http://localhost:5000/api/start_research",
                           json={"query": "Your research question"},
                           headers={"X-CSRF-Token": csrf})
    

    🚀 Ready-to-use HTTP API Examples → examples/api_usage/http/

    • ✅ Automatic user creation - works out of the box
    • ✅ Complete authentication with CSRF handling
    • ✅ Result retry logic - waits until research completes
    • ✅ Progress monitoring and error handling

    Command Line Tools

    # Run benchmarks from CLI
    python -m local_deep_research.benchmarks.cli.benchmark_commands simpleqa --examples 50
    

    See the Command Line Tools guide for the full reference.

    🔗 Bring Your Own Knowledge Base

    Connect LDR to your existing knowledge base. Unlike the HTTP client above, quick_summary() runs LDR in-process — no server needed — so you can pass it live Python objects such as LangChain retrievers:

    from local_deep_research.api import quick_summary
    
    # Use your existing LangChain retriever
    result = quick_summary(
        query="What are our deployment procedures?",
        retrievers={"company_kb": your_retriever},
        search_tool="company_kb"
    )
    

    Works with: FAISS, Chroma, Pinecone, Weaviate, Elasticsearch, and any LangChain-compatible retriever.

    Integration Guide →

    🔌 MCP Server (Claude Integration)

    LDR provides an MCP (Model Context Protocol) server that allows AI assistants like Claude Desktop and Claude Code to perform deep research. Full setup details in the MCP Server guide.

    ⚠️ Security Note: This MCP server is designed for local use only via STDIO transport (e.g., Claude Desktop). It has no built-in authentication or rate limiting. Do not expose over a network without implementing proper security controls. See the MCP Security Best Practices for network deployment requirements.

    Installation

    # Install with MCP extras
    pip install "local-deep-research[mcp]"
    

    Claude Desktop Configuration

    Add to your claude_desktop_config.json:

    {
      "mcpServers": {
        "local-deep-research": {
          "command": "ldr-mcp",
          "env": {
            "LDR_LLM_PROVIDER": "openai",
            "LDR_LLM_OPENAI_API_KEY": "sk-..."
          }
        }
      }
    }
    

    Claude Code Configuration

    Add to your .mcp.json (project-level) or ~/.claude/mcp.json (global):

    {
      "mcpServers": {
        "local-deep-research": {
          "command": "ldr-mcp",
          "env": {
            "LDR_LLM_PROVIDER": "ollama",
            "LDR_LLM_OLLAMA_URL": "http://localhost:11434"
          }
        }
      }
    }
    

    Any LDR setting can be passed in env as an LDR_* variable — see the auto-generated Full Configuration Reference for the complete list.

    Available Tools

    ToolDescriptionDurationLLM Cost
    searchRaw results from a specific engine (arxiv, pubmed, wikipedia, ...)5-30sNone
    quick_researchFast research summary1-5 minYes
    detailed_researchComprehensive analysis5-15 minYes
    generate_reportFull markdown report10-30 minYes
    analyze_documentsSearch local collections30s-2 minYes
    list_search_enginesList available search enginesinstantNone
    list_strategiesList research strategiesinstantNone
    get_configurationGet current configinstantNone

    Individual Search Engines

    The search tool lets you query specific search engines directly and get raw results (title, link, snippet) — no LLM processing, no cost, fast. This is especially useful for monitoring and subscriptions where you want to check for new content regularly without burning LLM tokens.

    # Search arXiv for recent papers
    search(query="transformer architecture improvements", engine="arxiv")
    
    # Search PubMed for medical literature
    search(query="CRISPR clinical trials 2024", engine="pubmed")
    
    # Search Wikipedia for quick facts
    search(query="quantum error correction", engine="wikipedia")
    
    # Search GitHub for code and repositories
    search(query="agentic research frameworks", engine="github")
    
    # Use list_search_engines() to see all available engines
    

    Example Usage

    "Use quick_research to find information about quantum computing applications"
    "Search arxiv for recent papers on diffusion models"
    "Generate a detailed research report on renewable energy trends"
    

    🤖 Supported LLMs

    Local Models

    • Ollama — connect to its native API (default http://localhost:11434)
    • LM Studio — connect to its OpenAI-compatible server (default http://localhost:1234/v1)
    • llama.cpp — connect to llama-server's OpenAI-compatible endpoint (default http://localhost:8080/v1); start with llama-server -m <model.gguf>
    • Common models: Llama, Mistral, Gemma, DeepSeek, Qwen
    • LLM processing runs on your own hardware when the model itself runs locally (search queries still go to the web). A local endpoint that relays to a hosted model, such as an Ollama cloud model or a local LiteLLM proxy, is not detected as remote. No API costs for locally-run models.

    💡 Which local model should I pick? See the community benchmarks — community-submitted accuracy numbers across local and cloud models, so you can compare before downloading.

    Cloud Models

    • OpenAI
    • Anthropic Claude
    • Google Gemini
    • 100+ models via OpenRouter
    • 100+ models via Requesty

    Custom Endpoints

    • OpenAI-Compatible Endpoint — any service speaking the OpenAI chat-completions API (vLLM, llama.cpp, gateways)
    • Anthropic-Compatible Endpoint — a self-hosted service speaking the Anthropic Messages API (/v1/messages); set llm.anthropic_endpoint.url

    Model Setup →

    🔄 Upgrading from Earlier Versions

    For the first FastAPI-based major release, read the upgrade and rollback guide before updating. Back up the data first: the release's new database revisions prevent a direct rollback to v1.10.7 against the upgraded databases. Copy each user's automatic pre-migration backup from encrypted_databases/backups/<user hash>/pre-migration/ to storage outside the data directory immediately after that user's first sign-in on the new release, and in any case before that user's next password change, which can remove it, and before any downgrade attempt. Never start v1.10.7 against the upgraded databases: each sign-in there first makes a backup, keeps only the newest backup directly in the user's backup directory (that new copy of the upgraded database), and then refuses the login. After any rollback to a release before 2.0, move every user's pre-migration/ folder out of the data directory (see the upgrade guide).

    • llm.model no longer has a default. Pre-1.6.3 installs auto-filled gemma3:12b (Ollama) when no model was configured, which silently downloaded a multi-GB binary. The field is now empty by default — pick a model in Settings → LLM, or research will fail loudly with a clear error.

    • The auto and parallel meta search engines were removed. The default langgraph-agent strategy selects engines dynamically per query, which replaces them. Stored settings are migrated automatically (removed values become searxng); update any explicit search_tool="auto" API calls or LDR_SEARCH_TOOL=auto env overrides to a concrete engine such as searxng.

    • Behind a reverse proxy: the rate limiter reads the client address differently. Up to v1.10.7 the login and registration rate limits keyed on the left-most X-Forwarded-For entry, or else X-Real-IP, sent by any peer, so any client could choose its own bucket. What happens now depends on your configuration:

      • TRUST_PROXY_HEADERS unset (the default): those headers are read only when the direct peer is a private, loopback or link-local address (6to4, Teredo and NAT64 tunnel addresses do not count), or, once you set LDR_SERVER_FORWARDED_ALLOW_IPS to your proxy's address(es), only when it is listed there. The key is the right-most X-Forwarded-For entry, or X-Real-IP when no X-Forwarded-For is present.
      • TRUST_PROXY_HEADERS=true with LDR_SERVER_FORWARDED_ALLOW_IPS set to your proxy's address(es): forwarded headers count only from a listed peer. uvicorn derives the client address from a listed peer's X-Forwarded-For, and X-Real-IP (used when no X-Forwarded-For is present) is read only from a listed peer. Any other peer is keyed on its own address.
      • TRUST_PROXY_HEADERS=true without LDR_SERVER_FORWARDED_ALLOW_IPS: the key is the right-most X-Forwarded-For entry sent by any peer, public addresses included. That stops a client behind one appending proxy from choosing its bucket with a forged prefix, but a client that connects straight to LDR writes the whole header and still chooses its own key. X-Real-IP is read only from a private, loopback or link-local peer. LDR logs a startup warning for this configuration: set LDR_SERVER_FORWARDED_ALLOW_IPS.

      What it means for an existing proxy setup:

      • Proxy reaching LDR from a public or carrier-grade-NAT address (a cloud load balancer, a Tailscale 100.64.x.x peer, a public nginx host): without TRUST_PROXY_HEADERS its forwarded headers are now ignored; with TRUST_PROXY_HEADERS=true and no allow-list its X-Real-IP is ignored, while its X-Forwarded-For still counts. When the header your proxy sends is ignored, every client shares the proxy's one rate-limit bucket and a few failed logins lock everyone out. Set LDR_SERVER_FORWARDED_ALLOW_IPS to the proxy's IP address(es).
      • Proxy on a private or Docker network: (if you set a restricted LDR_SERVER_FORWARDED_ALLOW_IPS, only listed peers are trusted, so list this private proxy too.) With a single proxy, clients keep separate buckets, provided the proxy overwrites X-Forwarded-For with the address it observed (nginx: $remote_addr) and overwrites X-Real-IP the same way. A proxy that passes a client-sent header through unchanged still lets that client choose its key; a proxy that sets only X-Real-IP must also clear X-Forwarded-For, because LDR prefers it. Appending ($proxy_add_x_forwarded_for) gives the right rate-limit key, but under TRUST_PROXY_HEADERS=true without LDR_SERVER_FORWARDED_ALLOW_IPS uvicorn reports the left-most, client-written entry as the client address.
      • Two or more proxies in a chain (CDN → nginx): by default every client is keyed on the outer proxy's address, so all of them share one rate-limit bucket and a few failed logins lock everyone out. Either have the inner proxy resolve the real client itself (nginx realip) and overwrite with that address, or make every inner proxy append and set a multi-hop setting: LDR_SERVER_TRUSTED_PROXY_COUNT, or, with TRUST_PROXY_HEADERS=true, every hop listed in LDR_SERVER_FORWARDED_ALLOW_IPS. See Proxy chains.

      With TRUST_PROXY_HEADERS=true but no LDR_SERVER_FORWARDED_ALLOW_IPS, LDR logs a startup warning naming the variable (or refuses to start if LDR_SERVER_TRUSTED_PROXY_COUNT is also set to a positive value, or if the allow-list is set explicitly to * or another value that trusts every peer). See Reverse proxy configuration.

    • The llamacpp provider now uses HTTP instead of in-process loading. If you previously set llm.llamacpp_model_path to a local .gguf file, that setting is no longer read. Instead, run llama-server -m <your-model.gguf> (it ships with every modern llama.cpp build) and the default llm.llamacpp.url of http://localhost:8080/v1 will pick it up. Optional API key support is available via llm.llamacpp.api_key if you put llama-server behind an auth proxy.

    📚 Documentation

    Getting Started

    Core Features

    Advanced Features

    Development

    Examples & Tutorials

    📰 Featured In

    "Local Deep Research deserves special mention for those who prioritize privacy... tuned to use open-source LLMs that can run on consumer GPUs or even CPUs. Journalists, researchers, or companies with sensitive topics can investigate information without queries ever hitting an external server."

    — Medium: Open-Source Deep Research AI Assistants

    News & Articles

    Community Discussions

    International Coverage

    🇨🇳 Chinese
    🇯🇵 Japanese
    🇰🇷 Korean

    Reviews & Analysis

    Related Projects

    Note: Third-party projects and articles are independently maintained. We link to them as useful resources but cannot guarantee their code quality or security.

    🤝 Community & Support

    🧑‍💻 Contributing

    We welcome contributions of all sizes — from typo fixes to new features. The key rule: keep PRs small and atomic (one change per PR). You don't need to be assigned to an issue: comment that you're working on it and open an early (draft) PR so progress is visible. For hard or architectural changes, and for anything that could affect research results or benchmarks (LLM prompts, the advanced search system, search engines), outline your approach in the issue first, so your effort leads to a successful merge rather than a misaligned PR. See our Contributing Guide to get started.

    Acknowledgements

    Local Deep Research is built on the work of many open-access initiatives, academic databases, and open-source projects. We are grateful to:

    Academic & Research Data

    SourceWhat It ProvidesLicense
    OpenAlexAcademic metadata for ~280K sources and ~120K institutions, including DOAJ statusCC0
    DOAJDirectory of Open Access Journals — open-access verification (via OpenAlex)CC0
    arXivPreprints in physics, mathematics, CS, and moreVarious (see arXiv license)
    PubMed / NCBIBiomedical and life sciences literaturePublic domain (US Gov)
    Semantic ScholarCross-discipline academic search with citation dataTerms
    NASA ADSAstrophysics, physics, and astronomy papersTerms
    ZenodoOpen research data, datasets, and softwareVarious per record
    PubChemChemistry and biochemistry databasePublic domain (US Gov)
    Stop Predatory JournalsPredatory journal/publisher blacklistMIT
    JabRefJournal abbreviation databaseCC0

    Knowledge & Content Sources

    Wikipedia • OpenLibrary • Project Gutenberg • GitHub • Stack Exchange • The Guardian • Wayback Machine

    Infrastructure & Frameworks

    LangChain • Ollama • SearXNG • FAISS

    Support Open Access

    These projects run on donations and grants, not paywalls. If Local Deep Research is useful to you, consider giving back to the open-access ecosystem that makes it possible:

    📄 License

    MIT License - see LICENSE file.

    Dependencies: All third-party packages use permissive licenses (MIT, Apache-2.0, BSD, etc.) - see allowlist