上个月,一个做项目管理的朋友突然找我。他手里有三四百份历史项目文档,想搜一句话:“哪些项目延期过,风险点是什么”。他原来用的是一个全文搜索工具,结果输入“延期风险”,出来的全是“存在延期风险”这种原话。可真正描述问题的那几份文档,写的是“时间安排很不合理”“阶段进度落后了两个版本”“资源一直不够”。字面上没有一个词和“延期”相同,自然一条都搜不出来。
这事并不罕见。我们习惯把搜索理解成“精确匹配”,但在真实工作流里,很多时候用户其实在找“意思相近的内容”。这也是向量搜索引擎这几年从概念走向日常落地的主要原因。而用 Claude Code 这类 AI 编程助手来搭一个向量搜索引擎,恰好是一个值得拆透的场景:它不是一句“帮我写个搜索引擎”就完事,也不是要求你从底层 KNN 算法开始啃。真正的关键是搞清楚人和 AI 各自该负责哪一层。
1. 向量搜索引擎解决的不是“搜索慢”,而是“搜不到”
1.1 关键词搜索的边界在哪里
传统的关键词搜索,核心是字符匹配。系统把文档拆成词,建立倒排索引,用户输入什么词,就找回包含什么词的文档。这套机制非常成熟,BM25 直到今天仍然是很多搜索系统的基础召回策略。
但它的局限也很明显:只能处理“词面相同”的情况。同义词、近义表达、语义抽象,都会让结果变差。朋友那个需求就是一个典型例子:“进度落后两个版本”和“延期风险”之间没有共同词,但语义上是强相关的。
这引出向量搜索的第一个价值:它不是替代关键词搜索,而是补充关键词搜索覆盖不了的那部分需求。你要面对的不是“搜索变慢了”,而是“明明有数据,却搜不到”。
1.2 向量搜索底层发生了什么
向量搜索的工作方式可以这样理解:用一个 Embedding 模型,把文本转换成一串固定长度的数字向量。这个向量要尽量保留文本的语义信息,意思相近的文本,向量在空间里的位置也相近。
搜索时,查询文本也转换成向量,然后在向量库里找出距离最近的 K 条记录。距离度量的常见选择有内积、余弦相似度、欧氏距离。只要向量模型训练得足够好,这个“按向量距离找结果”的过程就能做到:即使没有相同关键词,也能召回意思相近的内容。
可以类比成给每一段文字定位一个“语义坐标”。关键词搜索是在书名里找字面匹配,向量搜索是按照内容坐标找临近区域。后者更适合处理用户说不准精确表达、但描述得出大致意图的场景。
1.3 为什么前几年不流行,现在才开始普遍
最直接的原因是基础设施成熟了。
以前想做语义搜索,缺一个效果好、能落地、中文支持比较好的 Embedding 模型。近几年开源社区出现了很多可选择的中文向量模型,本地就能跑,效果也能接受。与此同时,向量检索库也从一个相对小众的方向变成了常见组件。FAISS 适合追求性能的批量检索,Chroma 这类轻量级工具适合学习和中小规模项目,Qdrant、Milvus 则更偏服务化和大规模部署。
另一个推动力是 LLM 应用。很多知识库问答、RAG 系统,都需要先做文本召回,再把召回结果交给大模型生成回答。向量搜索成了这一类应用的地基。
1.4 适合谁、不适合谁
把适用边界想清楚,比知道它很强大更重要。
| 场景 | 适合程度 | 原因 |
|---|---|---|
| 个人知识库、笔记检索 | 很合适 | 内容表达多样,用户通常只关心主题接近 |
| RAG 知识库问答 | 很合适 | 需要从文档中快速召回候选片段 |
| FAQ 相似问题匹配 | 很合适 | 用户提问不会和标准问题逐字一致 |
| 金额、日期、编号精确查询 | 不合适 | 语义相近不代表数值相等 |
| 权限控制、审计过滤 | 不合适 | 需要结构化字段和确定性规则 |
| 代码符号、包名精确查找 | 不合适 | 一个字符不同就是另一个符号 |
所以更准确的说法是:向量搜索是搜索引擎里的“召回层”,不是完整搜索产品。它在非结构化文本、语义模糊的场景下优势明显,但在需要精确、可解释、可审计的场景里,仍然离不开数据库查询和关键词检索。
2. Claude Code真正的价值:把试错循环从小时级压缩到分钟级
2.1 它是AI聊天助手,但工作方式更接近“会动手的同事”
Claude Code 是 Anthropic 推出的终端 AI 编程助手。和常见的对话式 AI 不同,它能直接读取项目文件,在项目目录里执行命令,观察到报错后修改代码。也就是说,它不是一个只给建议的聊天框,而是一个能实际参与开发过程的 Agent。
这对搭建向量搜索项目很重要。向量搜索引擎不是一个“单文件能解决”的东西,它包含装依赖、选模型、写数据管线、调查询接口、处理异常等一连串任务。如果 AI 只能生成代码片段,你还要自己复制、保存、跑、看报错,再回来粘贴,效率提升有限。但 Claude Code 可以在项目上下文里直接推进任务,省掉大量“搬运代码”的过程。
2.2 安装和接入的基本路径
常见安装方式是先准备好 Node.js 环境,然后在终端执行:
npm install -g @anthropic-ai/claude-code安装完成后,在项目目录里运行claude,首次启动通常会引导你完成登录或配置访问凭证。如果你习惯在编辑器里工作,也可以接入 VS Code、PyCharm 之类的 IDE 插件,思路仍然是复用同一套 CLI 能力,只是多了一层客户端配置。
另外,社区里经常被问到的一个问题是:能不能把 Claude Code 接到别的模型服务上。这种需求很常见,落地时要确认两件事:一是配置文件里的接口地址和模型名是否正确,二是当前 Claude Code 版本是否识别这个模型名。如果启动时报类似xxx is not a model this version of Claude Code recognizes,先不要怀疑环境坏了,大概率是配置里的模型名写错或者版本不匹配,回到配置里改掉即可。
2.3 高效用法不是“一次生成”,而是“小步快跑”
很多人第一次用 AI 编程助手时,习惯让它一次性生成完整项目。这个预期本身就有问题。搜索系统的复杂点不在代码量,而在需求判断和边界验证。一次生成再多的代码,也很难保证符合你的数据结构、文件格式和业务预期。
我更建议把它当作一个“能快速执行方案的同事”。你的工作方式变成:描述这一步要做什么,让它生成并运行;如果报错,把报错信息交给它,让它修改;跑通后,再叠加下一个需求。每一步都看结果,确认符合预期之后再往下走。
这种“生成→运行→观察→修改”的循环,才是 Claude Code 这类工具的核心体验。传统开发里,从一个想法到一段可运行代码,中间要经历写代码、编译、查错、改环境,通常以小时计。有了 Agent 辅助之后,循环被压缩到分钟级,但前提是你仍然需要定义清楚“这一步做完了没有”。
2.4 使用边界:它是放大器,不是替代品
我的判断是,Claude Code 是熟练开发者的放大器,也是新手的学习工具,但它不能替代人的判断。
- 适合:原型验证、脚本编写、数据管道搭建、接口封装、排查报错。
- 不适合:完全不理解搜索需求,就让 AI 自己决定一切参数,然后直接上生产。
- 需要补位的地方:验收标准、数据边界、效果评估、运行策略。
用一句话总结:AI 能把“从想法到代码”的速度变得很快,但“这个想法对不对”“跑出来的结果算不算好”仍然需要你来判断。
3. 用Claude Code搭向量搜索引擎:一条可复现的最小路径
3.1 先做技术选型,不要一上来就上重型方案
搭建向量搜索引擎时,最容易犯的错误是选型过度。数据只有几千条,先部署一套分布式向量数据库,最后发现运维成本比功能开发还高。我建议按阶段选型:
| 方案 | 适合规模 | 持久化方式 | 部署复杂度 | 适合阶段 |
|---|---|---|---|---|
| Chroma | 小到中 | 使用 PersistentClient 可落盘 | 低 | 学习、原型、本地工具 |
| FAISS | 中到大 | 自己管理索引文件 | 中 | 批量处理、离线检索 |
| Qdrant / Milvus | 大 | 服务化存储 | 高 | 生产环境、多人共用 |
如果只是第一次把链路跑通,Chroma + SentenceTransformer 是最省事的组合。Chroma 不需要单独启动服务,写代码就能建库、写入、查询;SentenceTransformer 可以加载本地开源向量模型,也不需要走远端接口。
3.2 准备数据:先拿20条真实内容做验证
不要一开始就处理全量文档。先挑出 20 到 50 条有代表性的内容,覆盖各种表达方式,然后围绕这批数据做搜索验证。目的是验证两个东西:
- 数据能不能正常读取、切片、向量化。
- 搜索结果是否符合真实业务语义。
这一步的数据质量,决定后续所有调试能不能以“反馈”的方式推进。如果数据是乱码、切片切碎了、内容来源不明,后面的搜索结果再怎么调都很难解释。
3.3 最小代码骨架:文本向量化 + 入库 + 查询
下面是一个接近最小可运行的示例,使用开源中文向量模型和 Chroma:
from sentence_transformers import SentenceTransformer import chromadb # 1. 加载中文向量模型 model = SentenceTransformer("BAAI/bge-small-zh-v1.5") # 2. 创建向量库 # 注意:不同版本的 chromadb API 略有差异,以你实际安装的版本为准 client = chromadb.Client() collection = client.create_collection("docs") texts = [ "项目A的进度落后了两个版本", "测试环境经常出现资源不足", "客户对验收标准理解不一致", ] # 3. 写入向量 for i, t in enumerate(texts): collection.add( ids=[str(i)], embeddings=[model.encode(t).tolist()], documents=[t], ) # 4. 查询 query = "项目延期风险有哪些" result = collection.query( query_embeddings=[model.encode(query).tolist()], n_results=3, ) for doc in result["documents"][0]: print(doc)这段代码并不复杂,但已经具备了一个向量搜索引擎最核心的链路:文本向量化、数据入库、查询召回。先让它跑通,再谈优化。
3.4 用FAISS再走一遍,理解“距离”到底怎么算
Chroma 屏蔽了很多细节,但如果你想理解向量检索的底层逻辑,建议再用 FAISS 写一遍。这样你才会真正明白“相似度”是怎么来的。
import faiss import numpy as np from sentence_transformers import SentenceTransformer model = SentenceTransformer("BAAI/bge-small-zh-v1.5") texts = [ "项目A的进度落后了两个版本", "测试环境经常出现资源不足", ] # 得到向量并做归一化 vecs = np.array([model.encode(t) for t in texts]) vecs = vecs / np.linalg.norm(vecs, axis=1, keepdims=True) # 建立内积索引 index = faiss.IndexFlatIP(vecs.shape[1]) index.add(vecs) # 查询 query = "项目延期风险有哪些" qvec = model.encode([query]) qvec = qvec / np.linalg.norm(qvec, axis=1, keepdims=True) D, I = index.search(qvec, k=2) for i in I[0]: print(texts[i])为什么要归一化?因为IndexFlatIP计算的是内积。当向量都被归一化成单位长度时,内积的结果正好等于余弦相似度。值越接近 1,方向越一致,语义越相近。理解这一点之后,你后面设相似度阈值才会有方向感。
3.5 让Claude Code帮你叠加元数据、过滤和结果排序
第一版只返回文本,验证的是“能不能搜到”。确认能搜到之后,再逐步叠加需求。你可以用自然语言继续描述任务,例如:
- “给每条文档增加 title 和 source 字段,查询时返回这些元数据。”
- “增加一个 metadata 过滤条件,只返回 project_id 为 p_001 的记录。”
- “把 top_k 调整为 5,并输出相似度分数。”
- “增加一个阈值,分数低于 0.5 的直接不返回。”
重点是保持“每加一个功能,就跑一次验证”的节奏,不要一次性丢给它十个需求。
4. 最容易翻车的不是代码,而是数据、依赖和生产边界
4.1 文本切片:太长会稀释,太短会割裂
向量化之前,通常需要对长文档做切片。这是整个项目里最容易被低估的环节。
如果一段文本太长,整个 chunk 的语义会被稀释,查询时相似度普遍偏低。如果按固定长度硬切,又可能把一个完整句子从中间切断,导致 chunk 语义不完整。更合理的做法是:按自然段切分,或者使用带重叠窗口的滑动切片。比如每段 chunk 控制在 400 到 600 字,chunk 之间重叠 50 字左右,能降低语义被切断的风险。
还有一点容易被忽略:检索命中之后,下游需要的是“上下文原文”。所以每条 chunk 最好都带上文档标题、原始路径、章节位置等元数据,方便使用者回溯到原文,而不是只看到孤立片段。
4.2 中文环境:模型、编码、分词
中文环境下,向量模型的选择直接影响效果。优先选中文语料训练过的模型。英文模型虽然也能处理中文,但语义表达上往往会弱一些,需要通过小样本测试对比,不要想当然。
文件读取阶段的编码问题也很常见。尤其是从 Windows 环境读取文本文件时,经常出现UnicodeDecodeError或者乱码。统一使用 UTF-8 编码,并在读取时显式指定编码,可以省掉很多排查时间。
4.3 持久化和索引选择
Chroma 如果用临时 Client,进程一重启数据就没了。要持久化,需要使用 PersistentClient,并指定一个存储目录。这是新手最容易踩的坑:本地明明能搜到结果,第二天打开就空了。
FAISS 则需要自己管理索引文件。训练好的索引要保存到磁盘,启动时重新加载。索引类型的选择也有讲究:
IndexFlatIP:暴力扫描,结果最准确,适合数据量不大时使用。IndexIVFFlat:先聚类再检索,省内存、速度快,但需要训练,还有nlist和nprobe参数需要理解。HNSW:基于图索引,检索效率和召回率比较均衡,但索引构建耗时和内存占用偏高。
新手不要直接上 IVF。先 Flat 跑通,再评估是否需要换索引。
4.4 排查链路:从“没结果”到“结果不对”
搜索系统出问题时,往往表现为几种现象:报错、空结果、结果相似度普遍很低、结果顺序不对。按下面的链路排查,通常能快速定位。
第一层,看现象。是直接报错,还是没结果?报错信息是模型下载、网络请求、还是编码问题?先弄清楚卡在哪一步。
第二层,看输入。原始文件读取是否正常?切片后的文本是否包含正文?查询语句是不是太短或太空?很多“搜不到”的问题,本质是查询本身太模糊。
第三层,看环境和依赖。模型是否真的加载完成?向量库数据是否写入?用collection.count()或者index.ntotal确认一下数据量。很多时候不是代码写错了,而是数据根本没有入库。
第四层,看参数。n_results是不是太小?阈值是不是设得太高?模型输出维度是否和向量库索引维度匹配?比如模型改成大版本后,向量维度从 512 变成 768,索引里的旧数据维度对不上,就会报错。
4.5 529、配额和模型名问题
使用在线 Embedding API 时,遇到类似 529 的状态码,通常表示服务端过载或者当前配额受限。常见处理方式是退避重试、降低并发,或者检查账户配额。它不一定是你代码写错了。
如果 Claude Code 配置的是第三方模型服务,启动时报 “not a model this version of Claude Code recognizes”,要按这个顺序排查:先看配置文件里的模型名是否和当前版本支持列表一致;再看接口地址是否正确;然后看权限和配额是否有效;最后才是升级或调整版本。这类报错最怕直接归因于“环境坏了”,其实大部分时候只是配置名层面的问题。
建议:在本地项目里维护一个
README,把模型名、向量维度、索引类型、持久化路径写清楚。搜索项目换个人维护时,这些信息比代码本身更重要。
5. 从“能跑”到“能用”:向量搜索还差四块拼图
5.1 数据入库管线:批量、重试、增量
如果只是处理几十条文档,一次性写入没问题。但真实项目里,文档数量会增长,数据格式也会变。需要把管道拆成几个阶段:
- 文档解析与切片。
- 向量化。
- 写入向量库。
一个建议是:把前两步的中间结果保存成 JSONL 或 JSON 文件。这样向量化失败时,不需要重新解析原始文档,只需要从中间文件继续。批量向量化时,要控制 batch size,避免单次请求超时。
增量更新时,给每个 chunk 生成稳定 ID。稳定 ID 可以基于“来源文件路径 + 段落序号”生成。这样新增文件时可以直接追加,已有文件内容变化时可以按 ID 先删除再写入,避免重复数据堆积。
5.2 搜索体验:top-k、阈值、元数据过滤、Rerank
搜索不是“找出最相似的一条”,而是“找出最可能相关的一批,再排序”。
在实际项目里,我建议:
- 检索阶段取
n_results大一点,比如 20 或 50,给后续精排留空间。 - 设置相似度阈值,过滤掉明显不相关的结果。但阈值不能拍脑袋,要先跑一批真实查询,观察相似度分布。
- 元数据过滤能显著提升准确性。比如只搜某个项目、某个时间范围、某种文档类型。
- 如果效果还不够,可以引入 Rerank 阶段:先用向量检索召回候选,再用交叉编码器或更精准的模型重新排序。代价是耗时和成本上升,但收益通常很明显。
另外,不要排斥关键词检索。实际生产环境里,很多搜索系统采用“向量搜索 + BM25”混合召回,再把两条路的分数融合。这样能兼顾语义匹配和精确匹配。
5.3 效果评估:不要靠肉眼
“搜出来的结果看着还行”是最危险的评价标准。因为几条例子的主观感受,不能代表整体搜索质量。
更稳妥的做法是建一个小样本评测集:
- 准备 10 到 30 个真实查询。
- 对每个查询,标注出 2 到 5 条应该被命中的文档。
- 跑搜索后,看“有多少比例的真实相关文档出现在 top-k 结果里”。
这个指标能帮你判断:切片长度要不要改,向量模型要不要换,阈值怎么定。很多项目花大量时间调参数,却忽略了“判断好坏的标准”本身。
5.4 边界意识:向量搜索是召回层,不是完整产品
向量搜索解决的是非结构化文本的语义召回问题,但它不能替代权限管理、精确查询、审计需求。如果业务系统需要一个稳定的搜索功能,更完整的方案通常是:
- 用向量检索做语义召回。
- 用关键词检索做精确匹配。
- 用元数据和规则做硬过滤。
- 用 Rerank 做精排。
一套可复用的落地框架可以这样归纳:
- 定义场景和数据边界:明确搜什么、不搜什么、给谁用。
- 跑通最小闭环:用几十条数据把“切片→向量化→入库→查询”跑通。
- 建立小样本评估集:用数据判断每次改动是好是坏。
- 逐步补工程化能力:持久化、增量更新、日志、重试、混合检索。
如果现在让我重新搭一次向量搜索引擎,我不会一上来就追求完整系统。我会先拿 20 条真实文档跑通查询,再慢慢加数据量;先接受最朴素的代码,再让 Claude Code 帮忙加元数据、持久化和批量处理。原因很简单:搜索系统的难点不在“有没有向量检索的库”,而在数据质量、切片策略、效果评估和长期维护。
AI 编程助手能把代码部分做得很快,但“该切多大”“什么时候返回空”“阈值设多少”这种判断,仍然要由使用场景来决定。回到开头那个朋友的需求,我会先用一段脚本把文档切片向量化,建一个能搜“延期风险”的接口,然后让他拿真实问题来测试。能搜到什么,不能搜到什么,比代码本身更能说明下一步该往哪走。