本地语义搜索入门:不用记住文件原词,也能找相关笔记

本篇目录 6 节
  1. 一、下载专门的嵌入模型
  2. 二、准备三条能人工判断的笔记
  3. 三、运行与理解分数
  4. 四、扩展到文件夹时保留来源
  5. 五、与关键词搜索配合
  6. 六、常见问题与排错

本地语义搜索适合“记得大意,却忘了当时用哪个词”的场景。与按字面查找不同,它把查询和笔记转成向量,再比较语义接近程度。本文只处理三条演示笔记,不搭建复杂数据库,也不生成答案,先把检索过程看清楚。需要运行中的 Ollama、Python 和一个可用的嵌入模型。

本地 AI系列主题示意图
系列主题示意图;操作步骤以正文与官方界面为准。

一、下载专门的嵌入模型

嵌入模型不是聊天模型。本文使用官方示例中的 embeddinggemma,实际所需 Ollama 版本和许可条件请看模型页。首次下载需要网络。

ollama pull embeddinggemma
ollama list

选中文资料时也要用自己的样本检验效果,不能只凭“支持多语言”就断言能准确找到所有专业术语。

二、准备三条能人工判断的笔记

下面的样本分别讲备份、字幕和模型目录。查询“照片丢了怎么恢复”时,重点看检索结果是否把备份笔记排在合理位置,再打开原文确认,而不是把最高分当作正确答案。

保存代码为 search_notes.py。示例每次运行重新生成向量,便于入门检查;大规模资料应保存索引并记录模型版本。

import json
import math
import urllib.request

notes = [
    {"id": "N01", "text": "照片应保留独立备份,并定期恢复到测试目录验证。"},
    {"id": "N02", "text": "字幕校对需要检查时间码、人名和中文断句。"},
    {"id": "N03", "text": "模型目录可以迁到其他磁盘,修改后重启模型服务。"},
]
query = input("Search: ").strip()
if not query:
    raise SystemExit("Query is empty")
payload = {"model": "embeddinggemma", "truncate": False,
           "input": [item["text"] for item in notes] + [query]}
request = urllib.request.Request(
    "http://127.0.0.1:11434/api/embed",
    data=json.dumps(payload).encode("utf-8"),
    headers={"Content-Type": "application/json"})
with urllib.request.urlopen(request, timeout=180) as response:
    vectors = json.load(response)["embeddings"]
if len(vectors) != len(notes) + 1:
    raise SystemExit("Unexpected vector count")

def cosine(a, b):
    if len(a) != len(b):
        raise ValueError("Vector dimensions differ")
    denominator = math.sqrt(sum(x*x for x in a) * sum(x*x for x in b))
    return sum(x*y for x, y in zip(a, b)) / denominator if denominator else 0.0

scores = [(cosine(vector, vectors[-1]), note)
          for vector, note in zip(vectors[:-1], notes)]
for score, note in sorted(scores, key=lambda item: item[0], reverse=True):
    print(f'{score:.4f} {note["id"]}: {note["text"]}')

三、运行与理解分数

py search_notes.py

分别搜索照片恢复、字幕校对和模型换盘,查看原文是否相关。这里显示的是相似度排序,不是模型判断“答案正确”的概率。0.8 不能直接解释为 80% 正确率,分数阈值需要用自己的检索任务校准。

四、扩展到文件夹时保留来源

每个文本块至少保存文件名、章节、块编号和内容哈希。长文件要按小节分块,别把整个文档硬塞进单次输入。示例设置 truncate 为 False,是为了让超长输入明确失败,而不是悄悄丢掉后文。

建立索引和执行查询必须使用同一嵌入模型与兼容维度。更换模型后重建向量,不要将两种模型的向量直接混在一起比较。

五、与关键词搜索配合

设备型号、错误码、订单编号等精确字符串,关键词搜索通常更直接;语义搜索更适合描述性问题。实际资料库可以先找关键词,再用向量补充候选,始终展示来源而不是只给一个神秘分数。

需要基于检索内容生成答案时,再增加引用与事实检查。不要从“找到了相关段落”直接跳到“系统已经理解所有资料”。

六、常见问题与排错

为什么返回了不相关的最高分?

只要强行排序,总有第一名。增加无答案查询,检查资料里根本没有相关内容时系统能否停止,而不是硬给结论。

用聊天模型为什么调用失败?

确认下载的是嵌入模型,并调用 /api/embed。聊天生成与嵌入的输入输出结构不同。

更换模型后旧索引还能用吗?

通常需要重新生成,至少核对模型、维度和预处理规则。不要只改查询端的模型名。

原理与接口见 Ollama Embeddings及嵌入接口说明。管理笔记摘要可配合本地批量摘要。

滚动至顶部