**苏格拉底学习法,深度学习任何一本教材**
**中文** · [English](README.en.md)




这一节和下一节的界面图都是真跑出来的,没有摆拍。界面语言跟随 Obsidian,图里是中文界面。
状态行上那句「开发回退 DEEPSEEK_API_KEY」是从源码树跑才有的字样——你正常安装之后,
那里显示的是你在设置页填的那个节点。
**它不是对任意笔记都一样好用。** 主对话对任何 Markdown 都能跑;但后台深挖要在书里下锚,
需要教材按八拍体例写,每一关得有「第三拍 · 出身」「第七拍 · 实操代码」这样的小节。少了
那些小节,五条深挖轴会直接死掉一条,其余四条照常出题。
[`docs/demo/从零手写DQN.md`](docs/demo/从零手写DQN.md) 是一份可以照抄的样板,为什么非得是
这个体例,见 [4.9 教材无关](#49--教材无关)。
---
## 3 · 核心功能逐个上例子
**场景**:你在读《从零手写 DQN》的 Level 3,划中了第四拍的**决策① 「为什么非要两个网络——
一个冻着、一个在更新」**(第 954–964 行)。这一段把答案拆成了三层:数学、动力学、工程。
以下所有回答都来自 **`deepseek/deepseek-v4-flash`**(经 OpenRouter 节点),
跑在一个隔离的 **sidecar**(插件在你本机拉起的那个 Python 小服务)上。
换任何一个 OpenAI 兼容的 Chat Completions 端点都能跑——**但除这一个组合之外我没实测过**。
这一章每一段输出都是真跑出来的:原始请求体和 SSE 事件流全存在
[`docs/demo/transcripts/`](docs/demo/transcripts/) 里,每处引文下面都挂着对应的 JSON。
落盘之后只动过一件事——把采集环境的临时绝对路径换成 `/Users/you/…`,除此之外一个字符
没改([说明](docs/demo/transcripts/README.md))。唯一没随仓公开的是
[3.7](#37--两本书之间) 那两段,它们出自作者本机的旧会话,里面全是私人 vault 路径,
那一节的落款自己写着这件事。
### 3.1 · 先别揭晓 `socratic`
**默认那一枚。** 它读你划的那段,然后**不回答**——它去找那段话里你还没意识到的裂缝。
| **你**:(划中决策①,点「先别揭晓」,一个字没打) |
| **苏格拉底**: > 好,你刚读完了决策①的三层拆法。那一块其实已经把“为什么冻住”讲透了——但有一个裂缝你可能注意到了,也可能没注意到。 > > 手册说冻住目标之后,这就变回了一个“标准的监督回归问题”。好,那我问你: > > **监督回归里,标签得是 ground truth 吧?可 Q_target 还是自己估计出来的——它又不是上帝给的正确答案。那“变回标准监督回归”这个说法,是不是藏了一笔账没算?这笔账谁来还?** |
| **你**: > 我在学 DQN,不太理解为什么有一个 Q-network 要固定、另一个在更新。 > 凭什么这样算法就会收敛而不发散?这个 idea 最早是从哪来的、出自哪篇 paper?数学上怎么证明它不会发散? |
| **苏格拉底**: > 好问题,三个问题串得很顺。我先不急着全倒给你——你把它们拆开想清楚,比听我念答案值钱得多。 > > 先说你能自己翻到的部分: > > 手册第三拍「出身:这两个技巧分别是谁的」专门讲了 target network 和 replay buffer 各自从哪篇 paper 来的。你翻到那一段,看看它俩是不是同一篇论文提出来的——先搞清楚这个。 > > 然后你回来看决策④。它已经写得很清楚了:**没有人证明过 DQN 会收敛。** 而且它把“能证明什么”和“不能证明什么”分得清清楚楚。你读完全段之后,可以试着用你自己的话回答我下面这个追问: > > **决策④说 DQN 和 FQI 之间隔着一层——DQN 每步只做一次梯度更新、目标每 C 步才同步。那如果 FQI 能证收敛,DQN 只是“靠近”它,这种靠近到底多近才算够近?你自己觉得,C 取多大,你才愿意说“嗯,这差不多就是 FQI 了”?** |
真跑那一次池子里攒了三条,第三条是这样的:
|
◆ **深挖**
> 你在 Level 2 学到三个东西凑齐就发散,DQN 砍掉了自举的实时性——那剩下的两样在这个结构下还会不会联手炸给你看?
`axis: altitude` · `depth: 5` · 锚点跨两关:**Level 2(740–747)+ Level 3(954–964)** (deadly triad:自举 + 离策略 + 函数逼近,三个凑齐就可能发散。) |
| **它为什么抛这一条**(`why` 字段,模型自己填的): > 他刚读完决策①的三层拆法,手里正好捏着 Level 2 的 deadly triad。让他把两关的知识挂上钩——DQN 没有杀死发散,只是把那个环从每步都通改成每 C 步通一瞬。读者需要补上这个抽象跳跃。 |
```
GET /v1/handbooks/dqn-550d425c/snapshots
→ {"can_undo": true, "can_redo": false, "undo_n": 1, "redo_n": 0}
POST /v1/writeback/rollback
→ {"ok": true, "restored_from": ".../snapshots/20260822T015739533075Z-pre-edit.md"}
```
**它按整篇还原,不是只撤你划的那一段。** 因为模型提议的编辑可能同时动了好几处,
按选区回退会留下一个半新半旧的文件。
undo + redo **一共只留 20 版**(`pen/config.py:57` `SNAPSHOT_KEEP = 20`)——
这不是一个无限的历史,是一个能兜住「刚才那下点错了」的深度。
### 3.7 · 两本书之间
书架上不止一本书的时候,苏格拉底会拿到一份**其他教材的摘要 + 路径**,于是他能翻到另一本。
这件事的价值,看一对**同题对照**最清楚。同一个问题——「另一本讲什么」——问了两次。
**书架接上之前**,他老实认账:
> 好问题,但我得先给你交个底:**另一本《通关手册》我没读到,不能瞎编。**
>
> 我能确认的是手上这本。它名字就写明了——`writeback-fixture.md`,fixture,测试夹具。
> 意思是:这本书的真正用途不是「内容」,是**测试「写回」这条链路**。
>
> 至于那本《通关手册》具体讲什么——你把它的路径给我,我去读一眼再回答你。
> 不读就猜,那是江湖骗子干的事。
**书架接上之后**,同一个问题,他先去读:
> 我先去翻一眼那本《通关手册》的开头,别凭标题瞎说。
>
> `read_file` → `1 # 手搓 SWE Agent 通关手册 v2 · 教材级(全册:开篇 + Level 0~6 + Capstone)` …
>
> 看完了,两本册子不是一套书,是两种东西。
> **《通关手册》是正课。** 目标一句话:让你从零手搓出一个轻量 SWE Agent……
> 整本一个比喻:你是师傅,Agent 是记性为零、胆子极大的实习生。
⚠️ 两段都出自作者本机的真实会话,**没随仓公开**(里面是私人 vault 路径),
所以这是全文**唯一**你没法自己核对的引文。其余每一处引文下面都挂着可点开的 JSON 原文。
**注意「之前」那一版并不是失败。** 他答「我没读到那本书」是**对的**——那本来就是实情。
这就是为什么书架那一整段在只有一本书的时候会**整段不出现**,而不是写一个「(无)」:
写「(无)」等于替模型确认过「没有别的书」,那是替它撒谎。
还有一条:给书名**必须同时给路径**。
> 光给书名,苏格拉底只会去猜文件名。
这句话现在是 `pen/tests/test_tutor.py` 里一条断言的失败提示。
### 3.8 · 账单
```
GET /v1/usage
```
```json
{
"spend": {
"chat": {"calls": 14, "in_tokens": 189275, "out_tokens": 9872,
"cached_tokens": 125440, "reasoning_tokens": 4906},
"probe": {"calls": 4, "in_tokens": 15892, "out_tokens": 6923,
"cached_tokens": 3328, "reasoning_tokens": 5674},
"fold": {"calls": 0, "in_tokens": 0, "out_tokens": 0}
},
"total": 221962, "sessions": 2, "skipped": 0
}
```
原文:[`10-usage.json`](docs/demo/transcripts/10-usage.json)——上面 3.1 到 3.6 全部跑完之后的真实账单
三笔分开记:`chat` 是你正在聊的那条线,`probe` 是后台深挖,`fold` 是写回时的折叠生成。
**分开记是为了能分开限**——后台超支不该掐掉你正在读的那一轮。
**它只数 token,不折算成钱。** 因为汇率是你的:你填的是哪个节点、哪个模型、
有没有折扣、缓存命中算不算钱,只有你知道。
不过你大概想知道一个量级。上面那 22 万 token,按我跑这一轮时那个节点对
`deepseek/deepseek-v4-flash` 的挂牌价($0.077/M 入 + $0.154/M 出)算:
```
入 205167 × $0.077/M + 出 16795 × $0.154/M ≈ $0.018
```
**不到两美分**,而且这是**上限**——它把 12.9 万个缓存命中的 token 当全价算了。
换前沿模型会贵一到两个数量级。
**这个累计数会往下掉**,别以为是 bug——会话是有保质期的([见 4.8](#48--会话会过期账也跟着走)),
被清掉的那些,它们的账也跟着走了。
### 3.9 · 自己定一枚泡泡
上面那几枚芯片,底下其实都是同一件事:**点一下,往 `[意图]` 段里塞一句话**。
`socratic` 塞的是「别揭晓,先反问」,`explain_zero` 塞的是「当我零基础」。
就这么简单——所以没有理由只让作者写这句话。
设置页 → **自定义泡泡** → 从模板挑一个新建。三个起手模板分别是
「把这段出成一道题」「把解释折进原文」「补一段 LaTeX 风格伪代码」,
复制过来照你自己那本书的体例改:改小节名、改编号规则、改折叠块长什么样。
改完侧栏那排就多一枚按钮,虚线描边,和固定芯片分得开。
最常见的用法是**按你自己的体例往笔记里插内容**。比如让它把问题写进你的题目小节,
并且必须是 `
### 隐私与网络
**它不做什么。** 不索引、不遍历、不上传,它不会扫你的库,也不会把笔记发到任何地方。
实际发生的读取只有两种:你划词的那一篇,和你放上书架的那几本。插件默认只访问
`http://127.0.0.1:8765`,不向作者汇报任何东西;会话、快照、深挖账本全在你自己机器上的
`~/.socrates-pen/` 下。
**但那是行为,不是沙箱保证。** 只读到那两种,是因为提示词里只告诉了它这些。沙箱真正的
读边界是**整个库根**(`.git` / `.obsidian` / `.env*` 除外),库根由插件在登记教材时一并
发过去(`src/views/PenView.ts:849` 把 `vaultRoot(app)` 发给 `POST /handbooks/import`),
不需要你手动放宽。你在对话里直接报出一个路径,它就能读到。写的边界严格得多,见
[4.4 沙箱有两套根](#44--沙箱有两套根读和写不是一回事)。
**写盘那一侧才是真闸。** 任何编辑都要你先批准,不批准就是一个字都不落盘,只问不写是
完全正常的用法。兜住这件事的是审批闸,不是「你没点那枚写回芯片」——在 `free` 里直说
「帮我在那段后面补一句」,模型同样会走到 `edit_file`(`pen/session.py:64`),侧栏照样
弹审批,[3.5](#35--写回原文和那道审批闸) 那段拒绝演示用的正是 `free` 芯片
([`08b-approve-deny.json`](docs/demo/transcripts/08b-approve-deny.json))。真正的闸在
`pen/agent/permissions.py:18-19`:`edit_file` 恒为 `ask`,对每一枚芯片都成立。
**剩下三件要你自己留意。** 一,API Key 只存本机 sidecar 家目录的 `~/.socrates-pen/llm.json`
(权限 0600),**不写进这个库**——Sync / iCloud / git 带不走它;从旧版本升级时会把钥匙从
`data.json` 自动迁过去并抹掉。若这个库在旧版本时代被同步或提交过 git,历史里的那把钥匙
清不掉,请去服务商**轮换一把**。开发场景也可以不用设置页:给 sidecar 留 `OPENAI_API_KEY`
或 `DEEPSEEK_API_KEY` 环境变量(或源码树里的 `.env`)同样有效。二,网络有四处:装的时候从
GitHub 和 PyPI 拉 sidecar 和依赖到 `~/.socrates-pen`,插件升级后重新拉起服务时会再取一
次;模型调用由这个本机进程发到你填的那个节点;模型调用 `search` 时会把关键词发给
DuckDuckGo / Bing / Wikipedia;模型调用 `fetch` 时会向那个 URL 发 GET
(只接受公网 http/https,内网和本机一律拒绝)。三,禁用插件默认不会停掉
sidecar,那个 Python 进程还在跑,下次启用能立刻用,多个库也共用它;不想让它常驻的,
设置里有「退出后保持本机服务运行」,关掉之后退出 Obsidian 会停掉由本插件自己拉起的
那只(别人拉起的从不碰)。设置页那个「停止」会停掉配置端口上正在听的进程——包括
上次留下的、别的库共用的、旧版残留——并告诉你停的是哪一种。health 里带着 pen 版本,
和插件对齐才标「运行中」;旧服务占着端口时要先停再启动升级。
### 装不上 / 起不来
第一次启用要建 venv 再 pip 安装,这一步最容易出事。按顺序查:
1. **设置页最上面那一行说什么。** 显示「未运行」就先点**启动**,错误会直接印在那里。
2. **本机 Python 够不够新。** 终端里 `python3 --version`,要 3.11 或更高;macOS 自带的
可能还是 3.9,去 [python.org](https://www.python.org/downloads/) 装一个。
3. **venv 建坏了就整个删掉重来。** `rm -rf ~/.socrates-pen/venv`,回设置页点启动它会重建;
`~/.socrates-pen/` 下还存着你的会话和快照,**别删整个目录**。
4. **端口被占。** 默认 `127.0.0.1:8765`,`lsof -nP -iTCP:8765 -sTCP:LISTEN` 看是谁占着。
5. **还是不行。** 开 Obsidian 的开发者控制台(`Ctrl/Cmd + Shift + I`)看报错,连报错一起
提到 [Issues](https://github.com/xesws/socrates-pen/issues)。
---
## 6 · 开发 · 测试 · 许可
```bash
npm install
npm test # 十三道闸,见「测试闸门」那张表
npm run build # tsc --noEmit && npm test && esbuild
```
对着一个真库热更新:
```bash
export VAULT_PLUGIN_DIR=/path/to/vault/.obsidian/plugins/socrates-pen
npm run dev
```
没设 `VAULT_PLUGIN_DIR` 时 `npm run dev` 会**直接退出**——防止你把一份陈旧的
副本装进库里。
后端:
```bash
python -m pytest pen/tests -q # 1224 passed
python -m pen.index --check 你的笔记.md
```
想自己复现 [3 章](#3--核心功能逐个上例子)里的所有例子:教材在
[`docs/demo/从零手写DQN.md`](docs/demo/从零手写DQN.md),
每一步的原始请求体和 SSE 事件流在
[`docs/demo/transcripts/`](docs/demo/transcripts/)。
**许可:MIT**,见 [LICENSE](LICENSE)。
---
## 7 · Future Works
现在还做不到、或做得不好的几件事:
**超长划选第一包不再带全文。** 邻域 4000 字、目录 4500 字本来就有上限;划选超过
4000 字时,第一包只留开头和行号,其余按需 `read_file`。长会话可以把旧回合折进
带行号的滚动摘要(命令面板「把这场对话折进摘要」,或到了设置里那道窗口阈值
自动折)——侧栏旧气泡还在,下次请求不再带全文。
**深挖更适合按固定体例写的教材。** 后台追问会去「第三拍 · 出身」「第七拍 · 实操」这类小节
下锚。普通随笔、或没有这些小节的英文教材,主对话不受影响,跨章节的深挖会变弱。可以对照
[`docs/demo/从零手写DQN.md`](docs/demo/从零手写DQN.md)。
**实测过的模型组合还很少。** 这份文档里的例子都跑在 `deepseek/deepseek-v4-flash` 上。别的
OpenAI 兼容节点按协议能接,但没有逐家测过。
---
## 8 · 最近的版本
开发中新增 [固定 MQ Schema](docs/mq-schema-v1.md)、[作答图片测评](evals/answer_images_v2/REPORT.md) 和 [80 题强化学习教材测评](evals/rl_textbook_v1/REPORT.md)。本轮 80 题完整导入;rubric 单题重试后全部通过复核;96 次评分有 1 次多给 10 分。
**开发中,尚未发布 · 练习系统(实验功能,默认关闭)。** 从已有 Meta Questions 导入单选、填空、简答,并补充知识点与评分标准,按 rubric 评分并保留作答日志。独立本地 Analysis 和 Scheduling 服务提供正式作答分析、个性化模型训练与主动领取的推荐队列。[使用与验证](docs/v0.29.0-练习系统.md)
每个小版本都有一份设计说明在 [`docs/`](docs/):读者看到了什么、病根在哪、改了什么、哪道闸守着。
这里只列 0.19 以来的大版本,补丁版顺带一句。
**0.28.0 · 2026-09-21 · Big Bang(实验功能)。** 右上角「+ Agent」在同一窗口打开最多四场独立对话,按面板实际宽高自动分屏;空间不足时纵向滚动。读取和审批绑定文件版本,提交时短锁串行化,过期提案必须重读并重新审批。每个 Agent 可独立停止。[设计与验证](docs/v0.28.0-Big-Bang.md)
**0.27.0 · 2026-09-05 · 不知道链接先搜。** 新工具 `search`,不用任何钥匙、不接 Firecrawl / Tavily 之类的搜索 API:sidecar 自己 GET DuckDuckGo 的纯 HTML 结果页,倒了换 Bing,Wikipedia 总是补一份;三家结果去广告、去内网、按规范化 URL 合并,Reciprocal Rank Fusion 加关键词覆盖排好,一次默认 5 条、最多 10 条,尾注写共几条、还剩几条、下一段的 offset,同一个 query 十分钟内翻页从缓存切。`fetch` 改成按段落编号、带 offset / limit,切片和尾注与 `read_file` 共用同一个定义点,续读同一页不重新下载。[设计说明](docs/v0.27.0-不知道链接先搜.md)
**0.26.0 · 2026-09-03 · 窗口撞了退一批。** 给小窗口模型(64k / 128k)的两道工具层保险。节点回「上下文太长」不再当普通拒绝报给读者:认出六家节点的溢出报文,把这一枪多少 token、模型上限多少连同「改成分段读」一起退回给模型,同一枪重打;退不了的换基座或折一次历史,一轮最多退三次。`read_file` 的截断改成按整行切并写明下一段的 offset,读到一半会说文件共几行,offset / limit 写错是工具错误不再炸掉整轮。[设计说明](docs/v0.26.0-窗口撞了退一批.md)
**0.25.0 · 2026-09-03 · 学习画像。** 侧栏第六枚按钮打开一个独立页签:一张随轴数增长的雷达、
十分制规则分与 BKT 掌握概率并列、每一分怎么来的逐条列出、这个库里每本书的书架。逐轮编码一律主模型,
第一次全量要读者点一下,之后打开面板增量补编,旧日志只进频率不进分。发版前用 Codex(gpt-5.6-sol)
审了一遍整段 diff,修了 16 条。0.25.1:只点芯片没打字的轮次是操作不是证据,不再硬归轴重试三次;
编码器每一枪的原始回复落日志。[设计说明](docs/v0.25.0-学习画像.md)
**0.24.0 · 2026-09-03 · 日志要够看。** 画像的原料。轨迹补上本地时间、读者原话与导师回复全文、
真锚点(阅读视图下对不上行号不再假装在封面)、抛出的追问点没点、写回批准之后那半截、这一轮走的
快模型还是主模型。[设计说明](docs/v0.24.0-日志要够看.md)
**0.23.0 · 2026-09-02 · 厂商方言表与真形状体检。** 推理档 off / low / medium / high 按型号名自动
映射成各家的写法:DeepSeek、Google Gemini、OpenAI、GLM、Kimi、Meta,设置页多一个厂商选择兜底。
配置体检按主对话那一枪的真实形状打,不再猜。0.23.1:Gemini 3 的思考签名要带回去。
[设计说明](docs/v0.23.0-厂商方言表与真形状体检.md)
**0.22.0 · 2026-09-02 · Fast Mode。** 顶栏一枚闪电开关:只读轮次走快模型,写回类轮次仍走基座。
快模型窗口小,压缩策略层按 stub → slim → fold 三档压,压完不污染会话,关掉开关就拿回完整上下文。
0.22.1–0.22.5:降级不再无声、配置体检、体检不许猜、思考正文带回去、Gemini 的推理档。
[设计说明](docs/v0.22.0-Fast%20Mode%20与压缩策略层.md)
**0.21.0 · 2026-09-01 · 自定义泡泡。** 底座那排芯片开放出来:每枚就是读者自己写的一段 prompt,
外加一个「会改写原文」开关;存在 vault 的 data.json 里随库同步,后端全程无状态。预置三个起手模板。
[设计说明](docs/v0.21.0-自定义泡泡.md)
**0.20.0 · 2026-08-30 · 对话框可贴图。** 设置里加「图像理解」开关,开了就能往对话框粘贴或拖入图片
(最多 4 张、每张 2MB),走 OpenAI 兼容的 image_url。落盘时丢掉像素,会话文件不会被撑爆。
[设计说明](docs/v0.20.0-对话框贴图.md)
**0.19.0 · 2026-08-25 · 主对话滚动摘要。** 旧回合折成带行号锚点的抽取式摘要,不加第二次 LLM 调用;
划选超过 4000 字第一包只留开头。命令面板手动折,或到了 `compact_chat_tokens` 阈值自动折。
[设计说明](docs/v0.19.0-主对话compact.md)
---