运行时底座:加密库 · 设置快照 · LLM 提供方 · Web 编排 · API
30 秒导读: 前面几章讲「研究引擎怎么想、搜索引擎怎么找、报告怎么写」。这一章讲让它们真正跑起来的平台层——多用户、隐私优先、可配置。核心有五块:每个用户一份 SQLCipher 加密数据库(密码就是解密密钥,服务器从不存密码);研究在 后台线程里只读一份冻结的设置快照而不碰数据库;LLM 按 provider 名从注册表里造出来(内置 provider 靠自动发现);Flask 应用工厂把请求接进来、丢给后台守护线程跑研究、用 WebSocket 实时推进度;还有一个自动处理 CSRF/登录的 HTTP 客户端给脚本用。安全出站管制(egress)是另一套,单独放在 06。
本章隶属 Local Deep Research 的架构解剖。研究主线看 01,搜索层看 02,LangGraph 智能体看 03,引用合成看 04。总览见 index。
1. 这层是什么(零基础也能懂)
一句话定义: 运行时底座 = 研究引擎之外、支撑「多用户 + 隐私 + 可配置 + 能远程调用」的所有基础设施。
它要解决的现实问题,可以拆成四个独立诉求(不要挤成一句):
| 诉求 | 平台层怎么答 |
|---|---|
| 多个用户共用一台部署,数据不能互相看见 | 每人一份独立的 加密数据库文件 |
| 用户的 API key、研究历史是隐私,连服务器管理员也不该随手读到 | 用户密码派生密钥,SQLCipher 全盘 AES-256 加密;密码从不落库 |
| 研究在后台跑几分钟,期间用户可能改设置,不能让它读到「跑一半变了的配置」 | 开跑前抓一份只读快照,整轮研究只认这份快照 |
| 既要能在浏览器里点,也要能被 Python 脚本 / CI 调用 | 一套 Flask Web + 一套 HTTP 客户端,同一后端 |
用起来什么样(直观感受)。 一个最小的编程式调用:
# 示意,基于 api/client.py 的真实签名
from local_deep_research.api.client import quick_query
# 用户名 + 密码登录(密码用来解密 该用户的数据库),问一个问题,拿回摘要
summary = quick_query("alice", "her-password", "什么是量子纠错?")
print(summary)
一句话直觉/类比。 把每个用户的数据库想成一个上了密码锁的保险箱:密码不是存在门口的名册上(那样管理员偷看名册就能开箱),而是密码本身就是钥匙的形状——你报对密码,箱子才打得开;报错了,箱子纹丝不动,且从外面看不出你是「密码错」还是「箱子根本不存在」。
2. 顶层全景(它大概怎么转)
先看一次「浏览器发起研究」从进门 到落库、再到实时推送的全链路。怎么读这张图:从左到右是请求 → 编排 → 后台线程 → 回推;竖线上方是主请求线程(能碰 DB),下方是后台守护线程(只认快照)。
浏览器 Flask 请求线程(有 DB 会话) 后台守护线程(daemon)
────── ──────────────────────────── ──────────────────────
POST /research/ start_research() run_research_process()
api/start_research ─► ①抓设置快照 get_all_settings ──┐ │ ⑤set_search_context(密码/快照)
②写 ResearchHistory 到用户库 │ │ ⑥SnapshotSettingsContext +
③start_research_process ────────┼──spawn─►│ set_settings_context(线程本地)
④返回 research_id (200) │ 快照 │ ⑦跑研究引擎(01-04 章)
│ +密码 │ └► get_llm() 造模型
WebSocket ◄──────── SocketIOService.emit_to_ │ │ └► 搜索/token 度量落用户库
progress_<id> subscribers(增量进度) ◄───────────┼─────────┤ ⑧cleanup_research_resources
│ │ 发终态消息 + 清订阅
▼ ▼
db_manager: 每用户 SQLCipher Engine
主要部件、职责、落在哪个文件:
| 部件 | 干什么 | 主文件 |
|---|---|---|
DatabaseManager | 每用户加密库的开/建/关、连接池、改密 | src/local_deep_research/database/encrypted_db.py |
| SQLCipher 工具 | 密钥派生、PRAGMA 序列、salt 管理 | src/local_deep_research/database/sqlcipher_utils.py |
SettingsManager / 快照 | 读写设置、生成冻结快照 | src/local_deep_research/settings/manager.py |
| 线程设置上下文 | 后台线程只从快照/线程本地取设置 | src/local_deep_research/config/thread_settings.py |
get_llm + 注册表 + 自动发现 | 按 provider 造 chat model | src/local_deep_research/config/llm_config.py、llm/ |
| Flask 应用工厂 + 路由 + 后台线程 | Web 编排全链路 | src/local_deep_research/web/ |
SocketIOService | WebSocket 实时进度 | src/local_deep_research/web/services/socket_service.py |
LDRClient / quick_query | 编程式 HTTP 访问 | src/local_deep_research/api/client.py |
| 度量追踪 | token/搜索/成本落进用户库 | src/local_deep_research/metrics/ |
3. 每用户加密数据库(密码即密钥,从不落库)
3.1 它要解决的小问题
多用户 + 隐私优先意味着:每个人的研究历史、API key、设置都不能被别人(包括拿到磁盘文件的人、甚至管理员)读到。传统做法是「存密码哈希 + 应用层做权限判断」,但那样数据本身仍是明文躺在盘上。LDR 走得更狠:数据整盘加密,密码就是唯一的解密钥匙,服务器不保存密码的任何形式。