我给AI知识库加了"联想大脑":从关键词搜索到语义理解

知识库关键词搜索经常找不到明明存在的内容。本文用 Embedding + ChromaDB 实现语义搜索,让 wiki 从"能找到"升级为"能联想",附完整 Python 代码和踩坑记录。

我的 wiki 里有 27 个页面,涵盖 Hermes、Obsidian、MySQL 等十几个主题。

搜"AI怎么记住之前的对话"——0 结果。

明明写了,为什么搜不到?

因为那篇 wiki 叫"五层记忆架构",标题里没有"对话"“记住"这些词。

再来一个。搜"有没有能自动理解项目的工具”。

还是 0。wiki 里写的是"Understand Anything",英文名。

第三个。搜"知识库怎么同步"。

0。wiki 里用的是"knowledge-base-workflow"。

三次搜索,三次翻车。不是 wiki 没写,是我用的词跟标题对不上。

现有 wiki-scan.py 用正则匹配:ai|llm|模型 这类关键词去打标签。

这种方案有个硬伤:同义词、近义词、描述性语句一律匹配不到。

关键词搜索只认字面,不懂意思。你脑子里想的跟标题不一样,就搜不到。

这是关键词搜索的天花板:它能找字,但不能找意思。

Embedding:给每段话算一个"语义坐标"

Embedding 模型的能力就一件事:把文本变成一组数字(向量)。

意思相近的文本,向量距离就近。意思无关的,距离远。

不需要懂线性代数。想象每段话都有自己的"语义指纹"。指纹越像,内容越相关。

专业说法叫"余弦相似度"——两个向量的夹角越小,内容越像。

但你不关心数学,只关心结果:输入一个问题,返回最相关的几段文字。

“AI怎么记住之前的对话"和"五层记忆架构"这两个短语,字面完全不同。

但语义指纹很接近——都在说"AI的记忆机制”。

这就是 Embedding 解决的核心问题:跨越字面,匹配语义。

“有没有能自动理解项目的工具"和"Understand Anything"也一样。

一个中文描述,一个英文专有名词,但语义空间里它们是邻居。

我用的本地模型是 qwen3-embedding,4.7GB,跑在 Ollama 上。

不调云 API,数据不出本机。对于个人知识库来说完全够用。

技术选型还有 nomic-embed-textbge-m3,后面踩坑段会说差异。

动手:三步给 Wiki 接上向量引擎

向量数据库选 ChromaDB,轻量,Python 几行搞定。

版本是 1.5.8,pip install chromadb 直接装。

第一步:扫描 wiki 文件 + 分段切片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import glob

def load_wiki_chunks(wiki_dir, chunk_size=500):
    """扫描 wiki 目录,按固定字数切片"""
    chunks = []
    for f in glob.glob(f"{wiki_dir}/**/*.md", recursive=True):
        text = open(f, encoding="utf-8").read()
        # 按 chunk_size 字符切,保留来源信息
        for i in range(0, len(text), chunk_size):
            chunks.append({
                "text": text[i:i+chunk_size],
                "source": f,
            })
    return chunks

27 个 md 文件,500 字符切片,大约生成 80 个 chunk。

每个 chunk 都带着 source 元数据,搜索命中后可以直接跳到原文。

第二步:Embedding 向量化 + 存入 ChromaDB

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import chromadb
from chromadb.utils.embedding_functions import (
    OllamaEmbeddingFunction,
)

ef = OllamaEmbeddingFunction(
    model_name="qwen3-embedding",
    url="http://localhost:11434/api/embed",
)

client = chromadb.PersistentClient(path="./wiki_chroma")
col = client.get_or_create_collection(
    "wiki",
    embedding_function=ef,
)

chunks = load_wiki_chunks("/root/wiki")
col.add(
    documents=[c["text"] for c in chunks],
    metadatas=[{"source": c["source"]} for c in chunks],
    ids=[f"chunk_{i}" for i in range(len(chunks))],
)

PersistentClient 把索引存到本地目录,重启不丢数据。

每个 chunk 附带 metadata 记录来源文件,方便溯源。

整个入库过程在我的机器上跑完不到 30 秒。

第三步:语义查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
results = col.query(
    query_texts=["AI怎么记住之前的对话"],
    n_results=3,
)
for doc, meta in zip(
    results["documents"][0],
    results["metadatas"][0],
):
    print(meta["source"], "→", doc[:80])
# 命中 memory-layered-architecture.md ✅

三步搞定。从安装到跑通,不超过 10 分钟。

效果对比:搜同一个问题

三组实测,关键词搜索 vs 语义搜索:

查询关键词结果语义搜索结果
“怎么防止模型记不住东西”0 条命中 memory-layered-architecture
“大表加字段怎么办”命中 gh-ost同时命中 gh-ost + mysql-big-table-ddl
“类似 Notion 的本地工具”命中 obsidian额外关联 bidirectional-links

第一组最典型。关键词完全匹配不到,语义搜索直接命中。

因为"防止记不住"和"五层记忆架构"在语义空间里是近邻。

第二组说明语义搜索不会丢掉关键词能找到的内容。

gh-ost 关键词能找到,语义也能找到,还额外关联了同主题的另一个页面。

这是语义搜索的加分项:不只补齐关键词的盲区,还能发现你可能没想到的关联。

第三组体现了"联想"能力。

用户想找的不只是工具名称,还有工具背后的核心理念(双向链接)。

Obsidian 的核心卖点就是 bidirectional-links,语义搜索自动把这个关联挖了出来。

从"能找到"到"能联想”,这是质变,不是量变。

让索引自动更新

wiki 内容会增长,索引也得跟上。用 cronjob 定时增量更新:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
existing = set(
    m["source"] for m in col.get()["metadatas"]
)
new_chunks = [
    c for c in load_wiki_chunks("/root/wiki")
    if c["source"] not in existing
]
if new_chunks:
    col.add(
        documents=[c["text"] for c in new_chunks],
        metadatas=[{"source": c["source"]} for c in new_chunks],
        ids=[
            f"chunk_{len(existing)+i}"
            for i in range(len(new_chunks))
        ],
    )
    print(f"新增 {len(new_chunks)} 个 chunk")

新页面入库时自动切片 + 向量化,零手动操作。

和 Hermes 的联动更进一层:搜索结果注入对话上下文。

Agent 回答问题时自动从 wiki 检索相关知识,不用每次手动查。

这个联动让知识库从"被动存档"变成"主动辅助"。

踩坑记录

三个坑,踩过了才知道。

模型选择:通用 embedding 模型对中文效果差。

nomic-embed-text 是好模型,但主要针对英文。中文查询经常跑偏。

搜"怎么防止模型记不住东西",它返回的是跟"模型训练"相关的内容。

换成 qwen3-embedding 后,中文语义理解提升明显。

如果你的 wiki 主要是中文,选中文优化模型。这是效果差距最大的一个选择。

切片粒度:500 字符是经验值,不是万能解。

太粗(>1000)会丢细节。搜"五层记忆架构的第三层"可能匹配不到具体段落。

因为第三层的描述被淹没在一大段文字里,向量被平均掉了。

太细(<200)会丢上下文。返回的片段短到看不懂在说什么。

建议从 500 开始,根据实际搜索效果调整。

索引体积:27 个页面、80 个 chunk,ChromaDB 索引只有几 MB。

不需要百万级向量库的方案。个人知识库,ChromaDB 完全够用。

不要过度工程化,够用就好。

关键词搜索是"翻字典"——你得知道精确的词。

语义搜索是"问懂行的人"——描述个大概就能找到。

给知识库加上这层能力,从"能找到"到"能联想",区别很大。

而这个升级,只需要一个 embedding 模型 + 一个向量数据库,本地就能跑。