最近我用 Claude Code 把一个小型向量搜索引擎从零搭了出来。整个过程没有手写多少代码,大部分逻辑由 Claude Code 在项目目录里直接生成,我只负责提需求、跑测试、看日志、让它改到跑通为止。今天就把这个案例拆开,从环境准备到批量索引再到常见报错,按实际落地顺序说一遍。
这个主题适合三类人看:想学 Claude Code 但不知道怎么下手的开发者,想了解向量搜索引擎到底怎么落地的产品和技术同学,以及已经装了 Claude Code 但只拿来写单段代码、没有跑过完整项目的人。最值得关注的点不是“AI 能写代码”,而是“你怎么描述需求、怎么让它改代码、怎么判断改出来的东西能不能用”。
先说清楚一个判断:向量搜索引擎不等于 Elasticsearch、Milvus 这种完整分布式系统。我这次用 Claude Code 搭的,是一个完整可用的小型检索方案,数据量在几千条文本以内完全够用。它的核心流程是:把原始文本切块,用 Embedding 模型转成向量,把向量存成本地索引文件,查询时用相似度检索 Top-N 结果。整个过程可以直接在命令行里跑,也可以包装成接口。
1. 为什么第一个实战案例选向量搜索引擎
1.1 Claude Code 适合做什么类型的事
Claude Code 不是一个“打开就能写代码”的网页 IDE,它是跑在命令行里的 AI 编程助手。和普通聊天式 AI 不一样,它可以直接读取你项目目录里的文件,修改代码,运行命令,然后根据运行结果继续调整。这意味着它更适合“多轮、可验证、围绕真实文件系统展开”的开发任务。
我个人的经验是,Claude Code 最适合做三种事:
- 一个项目从零到一的第一版代码,尤其是工具、脚本、CLI 程序。
- 已有项目里“改一个功能、修复报错、补测试”这类闭环任务。
- 需要反复运行、观察输出、调整参数的技术验证任务。
向量搜索引擎恰好属于第三种。它不是一个一次性生成就结束的代码,也不是完全没有标准答案的开放需求。它有明确的判断标准:能不能启动、能不能建立索引、查询返回的结果是否合理。这种任务让 Claude Code 来做,比让它写一段逻辑无关的代码更有价值。
1.2 向量搜索引擎这个案例的价值
很多人在学习 Claude Code 时容易走两个极端。一种是把 AI 当成高级搜索框,每次问一句“写一个冒泡排序”,这只用到了最表层能力。另一种是一上来就让它生成二十个文件的完整项目,结果框架太多,报错后连 AI 自己都搞不清文件之间的关系。
向量搜索引擎是一个规模适中的案例。它的技术链路完整,但实现可以很简单;它的核心逻辑只有几段,但踩坑点并不少;它可以跑在纯命令行环境,不需要额外部署数据库服务。
从技术角度看,这个案例覆盖了几个关键点:
- 原始数据如何加载和清洗。
- 文本如何切块,切块大小对检索效果的影响。
- 文本向量化用什么模型,本地模型和 API 模型怎么切换。
- 向量索引怎么存储,内存索引和磁盘索引的取舍。
- 查询时用什么相似度计算方式,Top-K 怎么取。
- 批量构建索引时如何控制内存和日志输出。
这些点不光是向量搜索引擎的问题,也是做 RAG、本地知识库问答、语义搜索、推荐系统都可能遇到的。所以即使你以后不继续做向量搜索,这个案例里的思路也可以迁移到其他场景。
2. 开始之前:先把环境准备好
2.1 安装 Claude Code 与模型确认
Claude Code 是通过 npm 分发的命令行工具,安装前保证本机已经有 Node.js 环境。安装命令是:
npm install -g @anthropic-ai/claude-code安装完成之后执行:
claude --version能输出版本号,说明基本环境没问题。如果你用的是 VSCode,也可以在终端里启动,它并不依赖特定编辑器。
首次启动时可能会要求登录或配置 API Key。这一步每家网络条件、密钥获取方式都不同,我就不过多展开了,只提醒一件事:启动后先确认当前会话用的是哪个模型。如果你配置了多个模型服务商或代理地址,最好先跑一个最简单的任务,比如让它读一下当前目录结构。
注意:很多“启动失败”“请求报错”不是 Claude Code 本身的问题,而是模型名配置错误、密钥失效或网络不通。启动之后先看会话列表里的模型名,再开始正式任务。
还有一个常见坑是模型名不被当前版本识别。比如你在配置里写了一个新模型名,但客户端版本还不支持,就会看到类似“xx is not a model this version of claude code recognizes”的提示。这种情况不是代码写错了,而是模型名和版本不匹配。解决顺序是:先确认你使用的 Claude Code 版本支持哪些模型名,再比对配置里的名称是否完全一致。不要直接换个模型名,要看清报错里提示的是配置问题还是网络问题。
2.2 准备数据与项目目录
这次案例的数据不要用太复杂的格式。我的建议是先用少量 Markdown 文件或 txt 文件跑通流程,数据量控制在几十条以内。比如在项目目录下建一个docs文件夹,放几篇技术笔记,每篇几百字。
项目目录结构建议这样组织:
vector-search/ ├── docs/ │ ├── claude-code-intro.md │ ├── embedding-basics.md │ └── vector-index.md ├── data/ │ └── chunks.json ├── src/ │ ├── index.py │ └── query.py └── output/为什么要提前建好目录?因为 Claude Code 在生成代码时,如果没有明确目录约束,它会自己随便创建文件,后续管理容易乱。我先在需求里把目录结构写清楚,生成出来的代码就会按这个结构落地。
原始材料里没有明确指定向量化模型。我的建议是:如果你本地已经装了sentence-transformers,可以直接用里面的通用文本向量模型;如果不想装本地模型,也可以换成任意平台的 Embedding API。关键不是选哪个模型,而是先把“文本到向量”和“向量到相似度结果”这个链路跑通。
3. 关键:怎么向 Claude Code 描述你的需求
3.1 从一句话需求到可执行计划
同样一件事,用不同的描述方式让 Claude Code 去做,结果差别很大。如果只说“帮我写一个向量搜索引擎”,它大概率会生成一个功能齐全但过于复杂的代码。如果只让它写一个“读取文档并搜索”,它可能又忽略了很多关键细节。
我更建议用结构化方式提需求,把输入、处理、输出、验证方式都写清楚。下面是我实际使用的提示词:
请帮我用 Python 搭建一个本地向量搜索引擎。 输入: - 读取 docs 目录下的 Markdown 文件 - 按标题和段落切块,每块尽量控制在 200 到 300 字 处理: - 用 sentence-transformers 的文本向量模型把每个文本块向量化 - 如果本地没有该模型,给出一个可替换的 Embedding API 调用示例 - 把所有向量保存到 data/chunks.json,要包含文本内容、来源文件、块编号、向量列表 查询: - 提供一个 search.py,命令行传入一句话,返回 Top-5 结果 - 每个结果包含相似度分数、来源文件、文本块内容 要求: - 先生成源文件,不要一次性创建多余文件 - 运行前先打印字典,确认模型加载成功 - 查询结果里相似度从高到低排序这个提示词有五个特点:
- 先说输入,再说处理,再说输出。
- 明确告诉它按多少字切块。
- 明确要求保存索引文件。
- 明确查询方式。
- 明确代码运行之后怎么判断结果。
Claude Code 拿到这样的需求,不会直接堆一个大型框架,而是会从最小路径开始实现。这也是我一直强调的:AI 编程工具不是帮你跳过思考,而是帮你把已经想清楚的需求快速落地。你需求里的边界越清楚,生成出来的代码越能直接跑。
3.2 完整代码实现长什么样
Claude Code 生成的索引构建代码基本是这个逻辑:
import os import re import json import uuid from sentence_transformers import SentenceTransformer model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2") def load_docs(doc_dir): files = [f for f in os.listdir(doc_dir) if f.endswith(".md")] docs = [] for f in files: with open(os.path.join(doc_dir, f), "r", encoding="utf-8") as fh: docs.append({"file": f, "content": fh.read()}) return docs def chunk_text(text, chunk_size=250): parts = [] for para in re.split(r"\n+", text): para = para.strip() if not para: continue while len(para) > chunk_size: parts.append(para[:chunk_size]) para = para[chunk_size:] if para: parts.append(para) return parts def build_index(doc_dir, output_path): docs = load_docs(doc_dir) records = [] for doc in docs: chunks = chunk_text(doc["content"]) for i, chunk in enumerate(chunks): emb = model.encode(chunk).tolist() records.append({ "id": str(uuid.uuid4()), "file": doc["file"], "chunk_id": i, "text": chunk, "vector": emb }) with open(output_path, "w", encoding="utf-8") as fh: json.dump(records, fh, ensure_ascii=False, indent=2) print(f"索引完成,共 {len(records)} 个文本块")这段代码不复杂,但它有一个很重要的点:把切块和向量化分开了。切块是纯文本处理,向量化是模型推理,分开写的好处是后续你想换模型或改切块逻辑,不用动整体结构。
查询部分的逻辑很简单,核心就是向量相似度计算:
import json from sentence_transformers import SentenceTransformer def search(query, top_k=5, index_path="data/chunks.json"): model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2") with open(index_path, "r", encoding="utf-8") as fh: records = json.load(fh) q_vec = model.encode([query])[0] scored = [] for r in records: v = r["vector"] dot = sum(a * b for a, b in zip(q_vec, v)) norm_q = sum(a * a for a in q_vec) ** 0.5 norm_v = sum(a * a for a in v) ** 0.5 score = dot / (norm_q * norm_v) if norm_q and norm_v else 0 scored.append((score, r)) scored.sort(key=lambda x: x[0], reverse=True) for score, r in scored[:top_k]: print(f"{score:.4f} | {r['file']}#{r['chunk_id']} | {r['text'][:80]}")这里用的是余弦相似度,适合大多数文本检索场景。如果你要更快的计算速度,可以用向量数据库或 torch 矩阵计算。但小型项目里,普通 Python 列表遍历加余弦相似度完全够用。
需要注意的是,这段代码是我根据常见实践补的示例,不是 Claude Code 必然生成的唯一版本,不同模型生成的代码在细节上会有差异。重点不是照抄,而是看懂“切块、向量化、保存、检索”这条主链路。
4. 从单条检索到批量索引落地
4.1 先跑通最小样例
不要一上来就处理几百个文件。我的做法是先在docs目录里放 3 个 Markdown 文件,每个文件 1000 字左右,先把最小链路跑通。
先运行索引构建脚本:
python src/index.py成功的话,会看到类似输出:
模型加载成功 索引完成,共 24 个文本块然后运行查询:
python src/query.py "什么是向量检索"如果能看到相似度排序结果,说明主链路是通的。这时候要检查几个点:
- 文本块数量是不是合理范围,3 个文件如果切出来只有 2 块,要考虑切块逻辑是否正确。
- 相似度分数是否合理,如果是满分 1.0 或全部是 0,说明向量化环节有问题。
- 打印出来的文本片段是否完整,有没有出现乱码、截断、空块。
这里最容易忽略的是路径和编码问题。Windows 环境容易遇到GBK编码问题,Markdown 文件如果是 UTF-8,读取时要显式指定encoding="utf-8",否则中文内容容易报错。
4.2 批量索引与命令行搜索
单条链路跑通后,就可以做批量扩展了。第一批文件可以放到 50 到 100 个,这时候要关注的不只是“能不能跑”,还有几个容易出问题的指标:
- 向量化耗时:本地模型对短文本的向量化一般在毫秒到几百毫秒之间,如果几十个文件跑了几分钟,要检查是不是加载了过大的模型。
- 内存占用:所有向量都保存在内存里,再一次性 dump 到 JSON,文件数量多时内存会明显上升。如果几千条数据,普通电脑可以扛住;如果几万条,建议改成边算边写,或者用 SQLite 存向量。
- 输出命名:批量生成索引时,不要把临时结果全部打到终端。日志里只需要显示进度和最终统计,比如每处理 10 个文件打印一次。
批量构建时我一般会加一个进度提示:
for idx, doc in enumerate(docs): chunks = chunk_text(doc["content"]) for i, chunk in enumerate(chunks): # 向量化并写入 records pass if (idx + 1) % 10 == 0: print(f"已处理 {idx + 1}/{len(docs)} 个文件")搜索部分可以扩展成带参数的命令行工具:
python src/search.py --query "如何安装 Claude Code" --top-k 3 --index data/chunks.json这样看起来才像一个真正可用的检索工具,而不是一段写死的测试代码。
注意:批量任务不能只看“能不能跑完”,还要看输出一致性。同样的数据跑两次,索引文件里文本块数量和向量维度应该完全一致,否则说明切块逻辑里有随机性或编码问题。
5. 常见报错与排查顺序
5.1 启动、模型名和密钥相关问题
很多人在 Claude Code 环境里遇到的第一个问题是“启动报错”或“请求失败”。我的排查顺序比较固定:先看错误类型,再分方向处理。
| 现象 | 优先排查方向 | 常见原因 |
|---|---|---|
| 启动后提示模型名不识别 | Claude Code 版本与模型名 | 模型名拼写错误或版本不支持 |
| 请求超时 | 网络和模型服务地址 | 网络不通、代理配置异常 |
| API Key 报错 | 密钥是否有效、权限 | 密钥过期或没有对应模型权限 |
| 命令找不到 | Node.js 环境 | npm 全局 bin 目录没有加入 PATH |
| 跑了但没反应 | 当前目录是否为空 | Claude Code 找不到项目上下文 |
排查时先不要改参数,先用小任务验证会话是否正常。比如让它读当前目录文件列表,如果能正常执行,说明工具环境没问题,问题在后面的具体任务上。
模型名不识别这个问题值得单独说。热词里出现了deepseek-v4-pro、deepseek-v4-flash这类模型名,对应的报错是模型名不被 Claude Code 识别。现实中类似问题很常见,尤其是在配置第三方模型服务时。遇到这种报错,不要急着删配置,先确认三件事:
- 你用的 Claude Code 版本是否支持当前模型命名规则。
- 配置里的模型名是否和模型服务商提供的名称完全一致。
- 会话里是否加载了旧配置。
模型名与版本匹配的问题,通常升级客户端或修正配置名就能解决。如果升级后仍然报错,就去看模型服务商的文档,确认它是不是走 OpenAI 兼容接口,以及接口地址、模型名、密钥三个字段是否都正确。
5.2 搜索质量差时先查数据而不是查代码
索引构建成功、查询也能返回结果,不代表搜索质量就好。经常出现的情况是:输入一个很明显的问题,返回的结果却完全不相关。这时候不要急着改相似度算法,先按这个顺序排查:
第一,看切块结果。如果一块文本超过 500 字,里面包含多个主题,向量是多个主题的混合,搜索结果自然不精准。建议先把切块长度调小,观察结果是否有改善。
第二,看文本块内容是否保留了必要信息。有些 Markdown 文档里的标题、列表符号、代码块会被切块逻辑破坏,导致向量化效果差。可以在保存索引时把“原文片段”和“向量化输入”分开,检索展示时用原文片段,向量化时用清洗后的纯文本。
第三,看查询词和文档语言是否一致。如果文档是中文,查询英文,模型跨语言效果不好时会出现搜索质量明显下降。这时候要么选多语言模型,要么确保查询词和文档语言一致。
第四,看 Top-K 结果里的分数分布。如果前 5 个结果的相似度分数都在 0.8 以上,说明候选集本身区分度不够;如果前 5 个结果分数从 0.95 直接掉到 0.2,说明可能只有一条数据真正相关,需要检查数据覆盖范围。
我自己遇到最多的不是相似度算法问题,而是原始文档本身太短、太杂,导致切块后没有任何一块能完整表达一个语义单元。先处理数据,再调参数,这个顺序不能反。
6. 向量搜索引擎的下一步扩展
6.1 换模型、加接口、做网页端
向量搜索引擎跑通之后,扩展方向有很多。最简单的是切换向量化模型。如果本地 sentence-transformers 模型效果不好,可以换成其他开源文本向量模型,也可以换成 Embedding API。切换时主要改两个地方:模型加载和向量生成方式。索引文件里保存的是向量,查询时用同一个模型生成查询向量,所以只要保证“索引时和查询时用同一个模型”,切换模型后就重建一次索引即可。
第二个扩展方向是把命令行工具包装成接口。用 FastAPI 或 Flask 写一个本地服务,接受 POST 请求,返回 JSON 格式的搜索结果。这样就不只是自己用,也可以给其他程序调用。
# 示意代码 from fastapi import FastAPI from pydantic import BaseModel app = FastAPI() class QueryRequest(BaseModel): query: str top_k: int = 5 @app.post("/search") def search_api(req: QueryRequest): results = run_search(req.query, req.top_k) return {"results": results}第三个方向是加一个简单的网页端。把搜索结果渲染成列表页,支持点击查看原文。到这一步,这个项目就已经从一个命令行脚本变成一个小型应用了。
6.2 项目边界和更合理的规模化路线
这个方案适合什么场景?适合个人知识库、团队内部文档检索、几百到几万条文本的语义搜索实验。它不适合做高并发在线服务,也不适合几十亿级别的全量检索。如果数据量继续增长,更合理的路线是:
- 把向量存进专门的向量数据库,比如 Chroma、Qdrant、Milvus。
- 把索引构建过程做成定时任务,而不是每次手动跑。
- 加入增量更新逻辑,只对新增或修改的文档重新向量化。
- 加评估集,固定一批查询问题,每次改动后对比检索结果变化。
这个案例真正落地时,最该盯住的不是功能列表,而是输入格式、资源占用和失败重试。AI 编程工具能帮你生成代码,但不能替你做数据清洗,也不能替你判断搜索结果是否满足业务需求。代码生成只是起点,后面还有验证、调优和维护,这些仍然需要你理解整个检索链路。
如果只是学习,默认流程跑通就够了。如果要长期使用,就要把日志、输出目录和任务队列提前整理好,避免项目做到一半,连当时用什么模型生成索引都忘了。踩过几次之后我发现,很多问题不是工具能力不够,而是前置环境和输入材料没有处理干净。