
前几天刷到条新闻,美国 AI 公司 Anthropic 为了给 Claude 凑训练数据,搞了个代号"巴拿马计划"的活儿:花几百万美元买下几百万本实体书,一刀切掉书脊,高速扫描仪扫完,原书直接销毁回收。两百万本书就这么没了,简直就是人间之屑 人类文明的破坏者,妄图将一切人类共享文明成果转为私有资产
顺便,扫描纸质书存进向量库这个点子其实不赖,我就想,这事儿能不能文明点复现?不切书脊、不烧书,书原样放回书架,内容却规规矩矩进了我自己的知识库,于是想着刚好做了一个RAG知识库的练手项目,顺手扩展一下相关能力。
于是有了这篇:用 WorkBuddy 装上腾讯云的 OCR Skill(通用文字识别高精度版),把一张纸质文档的照片识别成文字;文字切块、向量化;存进我自己一直在维护的嵌入式数据库 TriviumDB;最后用中文提问,直接命中英文论文里的原句。全程本地跑通,代码都贴在下面。
下面每个数字都是今天实机跑出来的,不是 PPT 配图。

三个环节,各选了一个我认为最合适的组件:
.tdb 文件就是一个知识库,不用起服务。在workbuddy的技能市场搜 tencentcloud-ocr,一键安装(v1.0.4),或者你直接跟ai说一句让ai一键配好。
先在腾讯云控制台开通通用文字识别服务,再去 CAM 访问管理创建 API 密钥。密钥走环境变量注入,不写进代码和文件:
export TENCENTCLOUD_SECRET_ID="你的SecretId"
export TENCENTCLOUD_SECRET_KEY="你的SecretKey"文章要真实,素材就得像回事,因为我实在懒得下楼去买纸笔手写模拟,干脆让ai给我模拟一个纸质环境文档。我手头正好有一篇从网上下载的 ANN 检索论文(QuIVer,讲二进制量化建图),把它做成两种形态:


生成脚本(make_test_images.py,核心代码):
# 1) PyMuPDF 渲染页面 → 2) 灰度 → 3) 旋转 0.5° → 4) 光照蒙版 → 5) 噪点 → 6) JPEG
img = ImageOps.grayscale(img.rotate(angle, expand=True, fillcolor=235))
img = ImageChops.multiply(img, light_mask(img)) # 环境光不均匀
img = img.point(lambda i: clamp(i + randint(-6, 6))) # 传感器噪点
img.save(out, "JPEG", quality=85)这样生成的图既有真实文字,又带拍照常见的退化,OCR 识别出来才有说服力。两张图都是 2564×3310 像素。
用 WorkBuddy 调 Skill,对两张图分别识别,输出存成 JSON:
python .../tencentcloud-ocr/scripts/main.py \
--image-base64 "QuIVer_..._p1_paper.jpg"返回的核心字段是 raw_text。第 1 页识别出 5502 字符,第 2 页 6292 字符。摘录第 1 页开头(双栏论文,识别顺序按栏流式输出):
QuIVer: Rethinking ANN Graph Topology via Training-Free
Binary Quantization
Wenxuan Xiao Peidong Zhu Chengcheng Li
...
Approximate nearest neighbor (ANN) graph indices such as HNSW
Modern retrieval-augmented generation (RAG) pipelines [1], se-
and Vamana construct their edge topology in full-precision or high-
...这里有个真实观察:双栏论文的 OCR 输出是按栏交错的。上面 HNSW 那句和 RAG 那句,原文里分属左右两栏,识别结果里却挨在一起。这个现象直接影响切块策略,下面会讲。
OCR 结果是"行"的序列。我的切法:按行聚合到 ~400 字符/块,记录页码。2 页共切出 32 块。
function chunkText(text, size = 400) {
const lines = text.split("\n").map(s => s.trim()).filter(Boolean);
const chunks = []; let cur = "";
for (const line of lines) {
if (cur && cur.length + line.length > size) { chunks.push(cur); cur = line; }
else cur = cur ? `${cur} ${line}` : line;
}
if (cur) chunks.push(cur);
return chunks;
}模型用 Xenova/multilingual-e5-small(384 维,q8 量化版,约 100MB,首次运行自动下载;国内访问慢的话设 HF_ENDPOINT=https://hf-mirror.com)。e5 系列官方推荐:文档侧加 passage: 前缀,查询侧加 query: 前缀。我照做了,但实测下来发现前缀的影响跟直觉不太一样,这点在第 7 节单独说。
我维护 TriviumDB 有一阵子了(开源,Rust 实现,Apache-2.0,向量+图+文档三位一体)。选它当向量库的理由:
.tdb 文件就是整个库,本地文件即知识库,不用起服务new TriviumDB(file, dim, "f32", "normal") 一行打开search() 返回 0入库代码(简化自我落地项目 RAG_libraries 里 triviumdb-store.ts 的封装模式):
const { TriviumDB } = require("triviumdb");
const db = new TriviumDB("quiver_ocr_demo.tdb", 384, "f32", "normal");
// 32 个 chunk → 32 条向量记录,payload 里存原文
for (let i = 0; i < chunks.length; i++) {
db.insert(vectors[i], { chunkId, page, text, source: "quiver-paper-ocr" });
}三个问题,两个中文一个英文,全部命中英文论文原文:
Q: QuIVer 论文用二值量化做了什么创新?
#1 [score=0.8383] (page 2) tests [13]) but violated by Euclidean-native features...
#2 [score=0.8336] (page 2) XOR and Popcount in O(D/64) word operations...
#3 [score=0.8330] (page 2) The parameter α ≥ 1 controls the trade-off between graph den-...Q: Why does RAG need approximate nearest neighbor search?
#1 [score=0.8831] (page 1) Modern retrieval-augmented generation (RAG) pipelines [1]...
#2 [score=0.8467] (page 2) The parameter α ≥ 1 controls the trade-off between graph den-...
#3 [score=0.8395] (page 2) u, v ∈ R^D with θ = arccos(⟨u, v⟩)...Q: HNSW 和 Vamana 有什么不同?
#1 [score=0.8096] (page 2) ogy. Evaluation on twelve million-scale datasets... (Vamana 段落)
#2 [score=0.7968] (page 1) QuIVer: Rethinking ANN Graph Topology... (标题块)
#3 [score=0.7823] (page 1) peting objectives: recall (search accuracy), throughput...① 双栏论文的切块边界是个真问题。 看 Q3 的命中:第二名是"标题块",而不是真正讲 HNSW/Vamana 对比的段落。原因很直接:标题块恰好同时含「QuIVer」「HNSW」「Vamana」这些词,向量相似度被拉高了。OCR 按栏流式输出让这个问题更明显(我另一版不带 query: 前缀的检索里,标题块直接排到了第一)。我的解法思路:识别结果来自双栏排版时,切块前先按栏重排(TextDetections 里有文字框坐标,可按 x 坐标分栏);或者检索时把阈值调高。这属于"把 OCR 结果当文档解析来做",我落地项目的下一步就是它。
② e5 的 query:/passage: 前缀,实测影响和直觉相反。 官方推荐这么写,我干脆做了个对照实验:同一批文档用 passage: 前缀入库,查询分别用 query: / passage: / 无前缀,各问 3 题。结果:
Q1 二值量化创新 query: 0.8383 passage: 0.8293 无前缀 0.8338
Q2 RAG 与 ANN query: 0.8831 passage: 0.8741 无前缀 0.8822
Q3 HNSW vs Vamana query: 0.8096 passage: 0.8211 无前缀 0.8220三个发现:分数绝对值差异不到 1 个百分点,prefix 不是玄学但也没那么神;query: 前缀改变了排序,Q3 里它把真正讲 Vamana 的段落排到第一,无前缀时反而是标题块第一;但 Q3 的 query: 分数(0.8096)反而比无前缀(0.8220)低,因为它把高分但跑题的标题块压下去了。我的结论:前缀的价值在"排序质量"不在"分数",检索场景照官方推荐写,不能指望它能弥补切块或数据质量问题。
③ 维度一致是硬约束。 换 embedding 模型 = 维度变 = 已入库向量全部作废。TriviumDB 的维度在建库时固定,换了模型忘了清理 .tdb,检索结果就是垃圾。我在落地项目里把它写成了运行时校验:模型返回维度 ≠ 配置维度,直接报错。
TENCENTCLOUD_SECRET_ID/KEY 环境变量tencentcloud-ocr(v1.0.4)python make_test_images.py <your.pdf> ocr-demo 1(或直接用你的扫描件/照片)python .../main.py --image-base64 <img>npm i triviumdb @huggingface/transformers(或复用 monorepo 的 node_modules)node pipeline-demo.mjs(首次会自动下载 e5-small 模型)脚本和输出都已归档:make_test_images.py(图片生成)、ocr_output_p1/p2_paper.json(OCR 真实输出)、pipeline-demo.mjs(切块→向量→入库→问答)、test-prefix.mjs(前缀对照实验)、data/quiver_ocr_demo.tdb(库文件)。
最实在的感受是:AI 太强了。越来越觉得人的价值在往“定方向、做判断、帮 QA 兜底背锅”上挪——甚至方向都能让 AI 先提一版。我全篇基本上就是在已有项目的基础上,让 AI 去推进实现的,程序员在AI时代最大的价值可能就是以前积累下来的判断力了。
文中所有输出均为 2026-08-16 本机实测;TriviumDB 0.7.4、tencentcloud-ocr v1.0.4、multilingual-e5-small (q8)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。