
上个月一个做项目管理的朋友突然找我。他手里有三四百份历史项目文档想搜一句话“哪些项目延期过风险点是什么”。他原来用的是一个全文搜索工具结果输入“延期风险”出来的全是“存在延期风险”这种原话。可真正描述问题的那几份文档写的是“时间安排很不合理”“阶段进度落后了两个版本”“资源一直不够”。字面上没有一个词和“延期”相同自然一条都搜不出来。这事并不罕见。我们习惯把搜索理解成“精确匹配”但在真实工作流里很多时候用户其实在找“意思相近的内容”。这也是向量搜索引擎这几年从概念走向日常落地的主要原因。而用 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 最小代码骨架文本向量化 入库 查询下面是一个接近最小可运行的示例使用开源中文向量模型和 Chromafrom 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_results3, ) 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, axis1, keepdimsTrue) # 建立内积索引 index faiss.IndexFlatIP(vecs.shape[1]) index.add(vecs) # 查询 query 项目延期风险有哪些 qvec model.encode([query]) qvec qvec / np.linalg.norm(qvec, axis1, keepdimsTrue) D, I index.search(qvec, k2) 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 编程助手能把代码部分做得很快但“该切多大”“什么时候返回空”“阈值设多少”这种判断仍然要由使用场景来决定。回到开头那个朋友的需求我会先用一段脚本把文档切片向量化建一个能搜“延期风险”的接口然后让他拿真实问题来测试。能搜到什么不能搜到什么比代码本身更能说明下一步该往哪走。