1. 为什么你的 claude code 需要一个本地知识库
用 claude code 写代码久了,你会发现一个尴尬的事实:模型对通用框架了如指掌,但对你项目里的业务规则、历史决策、踩过的坑一无所知。你问它「这个订单接口为什么必须幂等」,它只能给你一段教科书式的通用回答,而不是你团队三年前那次事故换来的教训。
这就是 claude code 知识库要解决的问题。简单说,知识库就是给 claude code 外挂一套「项目记忆」,让它在回答前先去你的本地文档里检索相关片段,再结合这些片段生成答案。适合谁?适合所有已经用 claude code 做日常开发、但反复被「模型不懂我项目」折磨的个人开发者和团队。
落地路径有两条。一条是传统 RAG:把文档切片、向量化、存进向量库,查询时做相似度召回。另一条是 File-Based 知识库:用 Markdown/YAML 文件加目录结构做弱结构化记忆,靠 INDEX 路由按需加载。前者擅长「帮你找资料」,后者擅长「让 AI 记住你的项目」。这篇教程把两条路都跑通,并且用 TaoToken 统一 Key 把模型调用链路收口,避免你在多个平台之间来回切换 Key。
我试过把两种方式混用:向量检索负责大范围召回,文件索引负责精确命中项目决策。下面从目录结构开始,一步步搭出最小可用知识库。
2. TaoToken 前置准备:统一 Key 打通模型调用链路
在动手写检索脚本之前,先把模型调用这一层理顺。claude code 知识库的检索问答环节需要调用大模型,如果你直接用官方渠道,会面临两个问题:一是 Key 分散在不同平台,二是网络和计费不透明。TaoToken 的作用就是提供一个统一的 API 入口,你只需要一个 Key,就能调用包括 Claude 系列在内的多种模型。
先注册并拿到 Key。访问官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 完成账号注册,然后进入控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在「API Keys」页面点击新建,复制生成的 Key 保存好。API 基础地址统一用 https://taotoken.net/api ,注意这个地址不带任何查询参数。
如果你用的是 Claude Code 这类命令行工具,需要配置三件套:Base URL、API Key、Model ID。Base URL 填 https://taotoken.net/api ,Key 填你刚创建的,Model ID 根据你需要的模型填写,比如 claude-sonnet-4-20250514 这类标识。配置方式可以写进环境变量,也可以写进工具的配置文件。
对于长期做编码和 Agent 任务的场景,可以了解 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它更适合高频调用。如果你只是想先验证模型能不能正常对话,可以用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 快速测试。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,遇到参数问题可以对照查阅。
这里要强调一点:TaoToken 是合规的 API 聚合入口,不是让你绕过任何限制的工具。你拿到的 Key 就是正常调用模型的凭证,所有请求都走标准 HTTP 接口。把 Key 配好之后,后面的检索脚本才能顺利调用模型做问答。
3. 可复制配置:目录结构、切片脚本与检索配置
这一节给出可以直接复制的配置。先建目录结构,再写切片脚本,最后配检索参数。
目录结构建议这样组织,兼顾向量检索和文件索引:
knowledge-base/ ├── raw/ # 原始文档,放 Markdown、txt │ ├── api/ │ ├── decisions/ │ └── bugs/ ├── chunks/ # 切片后的 JSON 文件 ├── index/ # 向量索引持久化目录 ├── knowledge/ # File-Based 知识库 │ ├── INDEX.md # 路由入口 │ ├── concepts/ │ ├── patterns/ │ ├── decisions/ │ └── bugs/ ├── config/ │ └── settings.json # 检索与模型配置 └── scripts/ ├── chunk.py # 文档切片 ├── embed.py # 向量化 └── query.py # 检索问答配置文件config/settings.json内容如下,把 Key 换成你自己的:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-your-taotoken-key", "model_id": "claude-sonnet-4-20250514", "embedding_model": "text-embedding-3-small", "chunk_size": 500, "chunk_overlap": 80, "top_k": 5, "index_path": "./index/faiss.index", "chunks_path": "./chunks/chunks.json" }切片脚本scripts/chunk.py,按段落和固定长度切分,保留重叠避免语义断裂:
import json import os from pathlib import Path def chunk_text(text, size=500, overlap=80): chunks = [] start = 0 while start < len(text): end = start + size chunks.append(text[start:end]) start = end - overlap return chunks def process_dir(raw_dir, out_file): all_chunks = [] for path in Path(raw_dir).rglob("*.md"): text = path.read_text(encoding="utf-8") for i, c in enumerate(chunk_text(text)): all_chunks.append({ "id": f"{path.stem}-{i}", "source": str(path), "text": c }) os.makedirs(os.path.dirname(out_file), exist_ok=True) with open(out_file, "w", encoding="utf-8") as f: json.dump(all_chunks, f, ensure_ascii=False, indent=2) print(f"生成 {len(all_chunks)} 个切片") if __name__ == "__main__": process_dir("./raw", "./chunks/chunks.json")向量化脚本scripts/embed.py,调用 TaoToken 的 embedding 接口:
import json import requests import numpy as np import faiss with open("./config/settings.json", encoding="utf-8") as f: cfg = json.load(f) with open(cfg["chunks_path"], encoding="utf-8") as f: chunks = json.load(f) texts = [c["text"] for c in chunks] resp = requests.post( f"{cfg['base_url']}/embeddings", headers={"Authorization": f"Bearer {cfg['api_key']}"}, json={"model": cfg["embedding_model"], "input": texts} ) vectors = np.array([d["embedding"] for d in resp.json()["data"]], dtype="float32") faiss.normalize_L2(vectors) index = faiss.IndexFlatIP(vectors.shape[1]) index.add(vectors) faiss.write_index(index, cfg["index_path"]) print(f"索引写入完成,共 {index.ntotal} 条")File-Based 知识库的knowledge/INDEX.md充当路由器,内容示例:
# 项目知识索引 ## concepts - [订单幂等](./concepts/order-idempotent.md) - [库存扣减](./concepts/stock-deduct.md) ## decisions - [为什么用 Redis 做锁](./decisions/redis-lock.md) ## bugs - [超卖问题复盘](./bugs/oversell.md)claude code 在回答前先读 INDEX.md,再按需深入对应文件。这样既省 token,又能精确命中项目记忆。
4. 验证请求:从提问到命中的完整跑通
配置写完了,现在跑一次完整验证。先执行切片和向量化:
cd knowledge-base python scripts/chunk.py python scripts/embed.py预期输出类似:
生成 128 个切片 索引写入完成,共 128 条接着写检索问答脚本scripts/query.py:
import json import requests import numpy as np import faiss with open("./config/settings.json", encoding="utf-8") as f: cfg = json.load(f) with open(cfg["chunks_path"], encoding="utf-8") as f: chunks = json.load(f) index = faiss.read_index(cfg["index_path"]) def search(question): resp = requests.post( f"{cfg['base_url']}/embeddings", headers={"Authorization": f"Bearer {cfg['api_key']}"}, json={"model": cfg["embedding_model"], "input": [question]} ) qv = np.array([resp.json()["data"][0]["embedding"]], dtype="float32") faiss.normalize_L2(qv) scores, ids = index.search(qv, cfg["top_k"]) return [chunks[i] for i in ids[0]] def ask(question): contexts = search(question) context_text = "\n\n".join([c["text"] for c in contexts]) prompt = f"根据以下项目资料回答问题:\n{context_text}\n\n问题:{question}" resp = requests.post( f"{cfg['base_url']}/chat/completions", headers={"Authorization": f"Bearer {cfg['api_key']}"}, json={ "model": cfg["model_id"], "messages": [{"role": "user", "content": prompt}] } ) return resp.json()["choices"][0]["message"]["content"] if __name__ == "__main__": print(ask("订单接口为什么必须幂等?"))运行:
python scripts/query.py成功时你会看到模型基于你raw/目录里的文档给出回答,而不是泛泛而谈。如果raw/decisions/里有一篇讲幂等的文档,回答里会带上你项目特有的细节,比如「因为支付回调会重复触发」。这就是命中的标志。
再验证 File-Based 路径:在 claude code 里让它先读knowledge/INDEX.md,然后问「之前 Redis 锁那个决策是怎么定的」。它会顺着索引找到decisions/redis-lock.md,回答里带上当时的取舍理由。两条链路都跑通,最小可用知识库就成立了。
5. 本篇常见错排查:401、local proxy failed 与 reading choices
搭建过程中最容易卡在几个报错上,逐个说清楚。
第一个是 401 Unauthorized。这个几乎都是 Key 没配对。检查config/settings.json里的api_key是不是完整的sk-开头字符串,有没有多余空格。如果你把 Key 写进了环境变量,确认脚本读取的是同一个变量名。还有一种情况是 Key 被复制时带了换行,用echo $TAOTOKEN_KEY | tr -d '\n'清理一下。401 不会因为模型选错而出现,所以先查 Key。
第二个是 local proxy failed。这个报错通常出现在你本地配了某些网络转发工具,导致请求发不出去。TaoToken 的 API 地址是标准 HTTPS,不需要任何额外转发。检查你的环境变量里有没有HTTP_PROXY、HTTPS_PROXY这类设置,有的话临时清掉再跑。命令是unset HTTP_PROXY HTTPS_PROXY,然后重新执行脚本。如果用的是 Claude Code 命令行,检查它的配置文件里有没有残留的代理字段。
第三个是 reading choices 相关报错,典型信息是Cannot read properties of undefined (reading 'choices')。这说明接口返回的结构和你预期的不一样,通常是请求体格式错了。检查chat/completions请求里messages是不是数组、model字段有没有拼错。还有一种可能是返回了错误对象而不是正常响应,打印resp.json()看完整内容,里面会有error.message告诉你具体原因。
第四个是 OAuth 相关报错。如果你在 Claude Code 里看到 OAuth 认证失败,说明工具还在走它默认的登录流程,没有用你配的 Base URL 和 Key。这时候要确认三件套是否写全:Base URL 填https://taotoken.net/api,Key 填你的 TaoToken Key,Model ID 填对应模型标识。三者缺一不可,只填 Key 不填 Base URL 会继续走默认端点。
排障时建议打开详细日志,把请求 URL、请求体、响应体都打印出来。大部分问题看一眼原始响应就能定位。如果确认配置无误还是报错,对照接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 检查参数,或者到 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 重新生成一个 Key 试试,排除 Key 本身失效的可能。
6. 把知识库接进日常编码流
最小可用知识库跑通之后,下一步是让它融入日常。我的做法是在项目根目录放一个knowledge/文件夹,每次做完一个决策、修完一个 bug,就顺手往对应目录写一个 Markdown 文件,并在INDEX.md里加一行链接。这样知识库会随着项目一起生长,越用越准。
向量检索那条链路适合处理大量历史文档,比如把接口文档、设计稿说明批量切片入库。文件索引那条链路适合沉淀高频决策和踩坑记录,因为它的命中更精确,token 消耗也更低。两者不冲突,可以同时用。
如果你用 Claude Code 做长期编码任务,建议把 Coding Plan 用起来,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,高频调用下更划算。想快速验证某个模型对知识库问答的效果,用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 直接试。
最后给一个实用技巧:在INDEX.md顶部写一段「使用说明」,告诉 claude code 先读索引再回答,并且优先引用decisions/和bugs/里的内容。这样它每次都会走你设计的检索路径,而不是自由发挥。知识库的价值不在于文件多少,而在于每次提问都能命中那条你真正需要的项目记忆。