1. 从每月三百块会员费说起:为什么我决定自己搭 AI 知识库
打开账单,那个 AI 知识库会员又扣了三百多块。不多,但每次看到这笔钱,我心里都会冒出一个念头:这个月我打开过它几次?答案是:不超过三次。而且其中两次搜出来的东西,说实话,不太对。
这就是我退订所有商业 AI 知识库的开始。不是一开始就觉得它们不好,第一年用的时候确实方便——把笔记、文档、网页一股脑扔进去,想用的时候搜一下,AI 帮你整理好答案。但用久了,问题一个个冒出来:单个产品月费 200 到 400 块不等,一年下来两三千;内容不可控,想改一篇已上传的文章只能删了重传,批量导出格式乱七八糟、链接全断;检索质量完全没法干预,问客服分块策略是什么,等了三天回复"我们的 AI 会自动选择最优策略";最让我下决心的是数据出境问题,我有一批笔记涉及企业内部架构文档,虽然脱敏了,但上传到海外服务器之后会不会被用于模型训练、会不会被第三方访问,这些问题他们不会正面回答。
对一个做了十几年后端的工程师来说,这种"相信我就行了"的回答,比直接说"我们还没做到"更让人焦虑。我要的东西其实很简单:把我写了 5 年的技术笔记和收藏的文章存起来,需要的时候能搜出来,搜出来的东西靠谱,数据留在自己机器上,不用给别人交月费。这不应该是天大的需求,它其实是一个工程师最基本的权利——掌控自己的知识。
而且说实话,现在有了 Claude Code 配合 MCP 协议,搭这个真的不是难事。向量数据库的概念十年前就有了,embedding 这两年已经成熟到可以本地跑。真正的阻力不是技术,是那种"反正有现成的,干嘛自己折腾"的惯性。想明白这一点之后,动手就很快了。
这篇文章我会把整套方案掰开揉碎讲清楚:用 Claude Code 作为交互入口,通过 MCP 协议接入本地 ChromaDB 向量库,embedding 模型跑在本地,模型调用走 TaoToken 统一 Key 通道。全程零元,不需要 GPU,M1 MacBook Air 上就能跑。适合那些技术笔记已经积累了一定规模、对内容质量有要求、对数据隐私敏感、愿意花半天时间做初始搭建的开发者。如果你属于这一类,往下看,每一步都有可复制的配置和命令。
2. TaoToken 统一 Key 通道:Claude Code 接入的前置准备
在开始搭知识库之前,得先把 Claude Code 的模型调用通道打通。这一步很多人会卡住,因为 Claude Code 默认走的是 Anthropic 官方接口,国内直连不稳定,而且计费方式对个人开发者不太友好。我的做法是用 TaoToken 作为统一 Key 通道,一个 Key 管所有模型调用,省得在多个平台之间来回切换。
TaoToken 是什么?简单说,它是一个统一的 API 网关,把 Claude、GPT 这些主流模型的调用接口聚合到一起,你只需要一个 Key、一个 Base URL,就能在 Claude Code、Cline、Codex 这些工具里调用不同模型。对个人开发者来说,最大的好处是省心:不用每个工具配一套 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 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 创建一个新的 Key。创建的时候注意两点:一是 Key 只在创建时显示一次,复制下来存好;二是可以给 Key 起个名字,比如 "claude-code-kb",方便后面区分用途。
拿到 Key 之后,Claude Code 的配置有两种方式。第一种是环境变量,适合临时测试:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_API_KEY="sk-你的TaoToken密钥"第二种是写进配置文件,适合长期使用。Claude Code 的配置文件在~/.claude/settings.json,如果没有这个文件就手动创建:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoToken密钥" } }这里有个坑要注意:Base URL 末尾不要加/v1,TaoToken 的接口路径已经处理好了,加了反而会 404。另外 Key 的权限要确认一下,在控制台里看看这个 Key 有没有开启 Claude 系列模型的调用权限,有些 Key 默认只开了部分模型。
配置完之后,验证一下是否生效。在终端里跑:
claude --version然后进入 Claude Code 交互模式,随便问一句"你好",看能不能正常返回。如果返回了内容,说明通道打通了。如果报 401,大概率是 Key 复制错了或者没生效,重新检查一下settings.json的格式,JSON 里不能有注释和多余逗号。
这一步做完,Claude Code 就能正常调模型了。接下来才是重头戏:搭知识库本体。整个方案由三个东西拼起来——ChromaDB 负责存向量,本地 embedding 模型负责把文字转成向量,MCP 协议负责把知识库接进 Claude Code。三者各司其职,下面逐个拆解。
3. 可复制配置:ChromaDB 集合初始化与 embedding 写入脚本
这一节是整篇文章的核心,我会给出完整的目录结构、依赖安装、ChromaDB 集合初始化代码、embedding 写入脚本,以及 MCP server 的配置。你照着复制粘贴就能跑通。
先说目录结构。我做了双 repo 分离:一个纯内容仓库awesome-ai-kb,只管 Markdown 文章,按主题分类(Redis、Kafka、JVM、MySQL、算法、AI、架构等 25+ 分类);一个工具仓库kb-builder,管索引管道和 MCP server。这样分离的好处是:文章永远是干净的纯文本,工具出问题不影响内容,换个工具也不影响内容。
awesome-ai-kb/ ├── redis/ │ ├── 持久化机制.md │ └── 集群方案.md ├── kafka/ │ └── 消息可靠性.md └── ... kb-builder/ ├── requirements.txt ├── build_index.py ├── mcp_server.py └── config.yaml依赖安装很简单,Python 3.10+ 环境:
pip install chromadb sentence-transformers mcp pyyamlembedding 模型我用的是paraphrase-multilingual-MiniLM-L12-v2,384 维,支持 50 多种语言,中英文混查毫无压力,跑在本地不需要 GPU。第一次运行会自动下载模型,大概 400MB 左右。
接下来是 ChromaDB 集合初始化和 embedding 写入脚本。分块策略我做了三个版本,现在稳定在 v3.0:结构感知分块(800 字)给技术教程和长文用,按 H2/H3 标题自然切分;模板段落分块(600 字)给设计模式、最佳实践这类结构化内容用;Q&A 对分块给 FAQ 和踩坑笔记用,一个问答一个块。
# build_index.py import os import re import yaml import chromadb from sentence_transformers import SentenceTransformer with open("config.yaml", "r", encoding="utf-8") as f: config = yaml.safe_load(f) KB_ROOT = config["kb_root"] DB_PATH = config["db_path"] COLLECTION = config["collection"] model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") client = chromadb.PersistentClient(path=DB_PATH) collection = client.get_or_create_collection( name=COLLECTION, metadata={"hnsw:space": "cosine"} ) def split_by_structure(text, max_len=800): parts = re.split(r"(?=^## )", text, flags=re.MULTILINE) chunks = [] for part in parts: part = part.strip() if not part: continue if len(part) <= max_len: chunks.append(part) else: for i in range(0, len(part), max_len): chunks.append(part[i:i + max_len]) return chunks def iter_docs(root): for dirpath, _, filenames in os.walk(root): for name in filenames: if name.endswith(".md"): full = os.path.join(dirpath, name) topic = os.path.basename(dirpath) with open(full, "r", encoding="utf-8") as f: yield full, topic, f.read() def main(): ids, docs, metas = [], [], [] for path, topic, content in iter_docs(KB_ROOT): for idx, chunk in enumerate(split_by_structure(content)): ids.append(f"{path}::{idx}") docs.append(chunk) metas.append({"source": path, "topic": topic, "chunk": idx}) if not docs: print("没有找到 Markdown 文件,检查 kb_root 配置") return embeddings = model.encode(docs, batch_size=32, show_progress_bar=True).tolist() collection.add(ids=ids, documents=docs, embeddings=embeddings, metadatas=metas) print(f"写入完成:{len(docs)} 个语义块") if __name__ == "__main__": main()配套的config.yaml:
kb_root: "/Users/yourname/awesome-ai-kb" db_path: "/Users/yourname/kb-builder/chroma_db" collection: "personal_kb"跑python build_index.py,240 篇文章经过三种分块策略处理后,会产生 5877 个语义块,平均每篇拆成 24 个左右的检索单元。写入过程大概两三分钟,取决于文章数量。
然后是 MCP server,这是把知识库接进 Claude Code 的关键。MCP 协议你可以理解为 AI 工具和外部世界之间的"通用插头",我用它把知识库接入了 Claude Code,写着代码、聊着天,直接问一句"我之前的笔记里怎么说的",它就帮你去向量库里搜回来。
# mcp_server.py import chromadb from sentence_transformers import SentenceTransformer from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2") client = chromadb.PersistentClient(path="./chroma_db") collection = client.get_collection("personal_kb") app = Server("kb-server") @app.list_tools() async def list_tools(): return [ Tool( name="search_kb", description="语义搜索个人知识库,输入自然语言返回最相关的片段", inputSchema={ "type": "object", "properties": { "query": {"type": "string"}, "top_k": {"type": "integer", "default": 3} }, "required": ["query"] } ), Tool( name="list_kb_topics", description="列出知识库覆盖的所有主题", inputSchema={"type": "object", "properties": {}} ), Tool( name="get_kb_stats", description="获取知识库索引统计信息", inputSchema={"type": "object", "properties": {}} ) ] @app.call_tool() async def call_tool(name, arguments): if name == "search_kb": query = arguments["query"] top_k = arguments.get("top_k", 3) emb = model.encode([query]).tolist() res = collection.query(query_embeddings=emb, n_results=top_k) out = [] for doc, meta in zip(res["documents"][0], res["metadatas"][0]): out.append(f"[{meta['topic']}] {meta['source']}\n{doc}") return [TextContent(type="text", text="\n\n---\n\n".join(out))] if name == "list_kb_topics": data = collection.get(include=["metadatas"]) topics = sorted({m["topic"] for m in data["metadatas"]}) return [TextContent(type="text", text="\n".join(topics))] if name == "get_kb_stats": data = collection.get(include=["metadatas"]) return [TextContent(type="text", text=f"总块数:{len(data['ids'])}")] return [TextContent(type="text", text="未知工具")] async def main(): async with stdio_server() as (r, w): await app.run(r, w, app.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main())最后是 Claude Code 的 MCP 配置。在~/.claude/settings.json里加上 MCP server 的声明:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-你的TaoToken密钥" }, "mcpServers": { "personal-kb": { "command": "python", "args": ["/Users/yourname/kb-builder/mcp_server.py"] } } }这里三件套要写全:Base URL 是https://taotoken.net/api,Key 是你在 TaoToken 控制台生成的sk-开头的密钥,Model ID 在 Claude Code 里默认走 Claude 系列,不用额外指定。如果你用 Cline 或者 Codex,配置逻辑一样,只是配置文件位置不同——Cline 在 VS Code 设置里,Codex 在~/.codex/auth.json。
配置完重启 Claude Code,输入/mcp命令,应该能看到personal-kb这个 server 已经连上,三个工具search_kb、list_kb_topics、get_kb_stats都列出来了。到这一步,知识库就算接进去了。
4. 验证请求与成功结果:一次检索命中对比
配置写完不算完,得实际跑一次验证,看看检索效果到底怎么样。这一节我会演示一次完整的检索请求,对比自建知识库和商业产品的命中差异,并给出成功结果的判断标准。
先做基础验证。在 Claude Code 里输入:
用 search_kb 工具查一下"Redis 持久化 RDB 和 AOF 的区别"Claude Code 会调用 MCP server,把 query 转成 embedding,去 ChromaDB 里查最相近的 3 个块。返回结果大概长这样:
[redis] /Users/yourname/awesome-ai-kb/redis/持久化机制.md ## RDB 与 AOF 的取舍 RDB 是快照式持久化,触发时机分为 save 和 bgsave... AOF 是追加写日志,每条写命令都记录... 生产环境通常两者混用,RDB 做冷备,AOF 做实时恢复... --- [redis] /Users/yourname/awesome-ai-kb/redis/持久化机制.md ## AOF 重写机制 AOF 文件会随着写入不断膨胀,需要重写...判断命中质量的标准有三条:第一,返回的块里有没有直接回答问题的内容,比如上面第一块就明确讲了 RDB 和 AOF 的区别;第二,来源路径对不对,是不是你预期的那篇文章;第三,top 3 里至少两条相关。我自己做了 50 次随机查询测试,75%+ 的情况下前三条结果里至少有两条和我想找的内容高度相关。剩下的 25% 里,大部分是问题本身就比较模糊,不是检索的问题。
再做一个对比测试。同样的问题"Redis 持久化 RDB 和 AOF 的区别",我之前用的那个按月付费产品搜出来的结果,前三条里有一条是讲 Redis 集群的,一条是讲内存淘汰策略的,只有一条沾边。为什么?因为它的分块策略是黑盒,可能按固定 token 数切,把一篇讲持久化的文章切得七零八落,检索的时候语义不完整。而我的结构感知分块是按 H2/H3 标题切的,每个块都是一个完整的语义单元,检索命中率自然高。
延迟方面,单次查询从输入到返回结果,本地跑 800ms 以内。比商业 SaaS 产品快吗?不一定——它们有 CDN 加速。但 800ms 够用了,而且没有网络抖动,不担心服务宕机。
再验证一下另外两个工具。输入:
用 list_kb_topics 看看我的知识库覆盖了哪些主题返回:
ai algorithm architecture distributed go java jvm kafka microservice mysql redis ...输入:
用 get_kb_stats 看看索引统计返回:
总块数:5877到这里,三个工具都验证通过了。你可以在 Claude Code 里直接问"我之前的笔记里怎么说的关于 Kafka 消息可靠性的内容",它会自动调search_kb,把相关片段拉回来,然后基于这些片段回答你。整个过程不用切窗口,不用登录网页,一句话就查回来了。
如果你想让检索结果更可控,可以在search_kb里加一个topic参数,限定只在某个主题下搜。比如查 Redis 相关的问题时,传topic="redis",ChromaDB 的where条件会过滤掉其他主题的块,命中率还能再提一截。这个改动很小,在collection.query里加where={"topic": topic}就行。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
搭这套东西的过程中,我踩过的坑不少。这一节把最常见的几类报错和排查动作列出来,你遇到的时候可以对照着查。
401 Unauthorized。这个最常见,基本是 Key 的问题。先检查~/.claude/settings.json里的ANTHROPIC_API_KEY是不是复制完整了,sk-开头后面那一长串不能少字符。然后确认 Base URL 是https://taotoken.net/api,末尾不要加/v1,加了会 404 或者 401。如果 Key 和 URL 都没问题,去 TaoToken 控制台看看这个 Key 有没有开启 Claude 系列模型的权限,有些 Key 默认只开了部分模型。还有一种情况是 Key 被禁用或者额度用完了,控制台里能看到状态。
local proxy failed。这个报错通常出现在 Claude Code 启动的时候,意思是本地代理连接失败。先确认你的网络环境能正常访问https://taotoken.net/api,在终端里跑curl -I https://taotoken.net/api看看返回码。如果返回 200 或者 401,说明网络通,问题在配置;如果超时,说明网络本身有问题。另外检查一下settings.json的 JSON 格式,多余逗号、注释、中文引号都会导致解析失败,Claude Code 读不到配置就会报代理失败。
reading choices 相关报错。这个一般出现在 MCP server 返回结果的时候,报错信息里带reading 'choices'或者choices is undefined。原因是 MCP server 返回的数据结构不符合 Claude Code 的预期。检查mcp_server.py里call_tool的返回值,必须是[TextContent(type="text", text="...")]这种格式,不能直接返回字符串或者 dict。另外确认mcp库的版本,pip install mcp --upgrade升到最新版,老版本的类型定义和新版 Claude Code 不兼容。
OAuth 相关报错。如果你在 Claude Code 里看到 OAuth 认证失败的提示,说明它还在尝试走 Anthropic 官方的 OAuth 流程,没走你配的 Base URL。检查两点:一是settings.json里env字段的 Key 名必须是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY,拼错了就不生效;二是环境变量有没有覆盖配置文件,如果你在 shell 里export过ANTHROPIC_API_KEY,它会优先用环境变量的值,把环境变量清掉再试。
ChromaDB 查询返回空。MCP server 能连上,但search_kb返回空结果。先确认build_index.py跑成功了,chroma_db目录下有数据文件。然后检查mcp_server.py里client.get_collection("personal_kb")的集合名和build_index.py里get_or_create_collection(name=COLLECTION)的COLLECTION值是不是一致,不一致就查不到。还有一种情况是 embedding 模型不一致,建索引用的模型和查询用的模型必须是同一个,换了模型要重新建索引。
中文检索效果差。如果搜出来的结果明显不相关,先确认用的是paraphrase-multilingual-MiniLM-L12-v2这个多语言模型,不是纯英文模型。然后检查分块策略,如果一篇文章被切得太碎,语义不完整,检索效果也会差。可以把max_len从 800 调到 1000 试试,或者针对技术教程用结构感知分块,针对 FAQ 用 Q&A 对分块。如果追求极致效果可以上更大的模型,但 384 维在这个场景下够用了。
排查的时候有个通用思路:先确认网络通不通,再确认配置对不对,最后确认数据在不在。三步走下来,大部分问题都能定位。如果还是搞不定,去 TaoToken 的接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 看看有没有对应的说明,或者在控制台里提工单。
6. 从检索到长期编码:把知识库接进日常工作流
知识库搭好之后,真正的价值在于把它接进日常工作流。我现在的工作方式是:写着代码、聊着天,遇到不确定的技术方案,直接在 Claude Code 里问一句"我之前的笔记里怎么说的",它自动调search_kb把相关片段拉回来,基于我的笔记回答。不用切窗口,不用登录网页,不用等加载。
如果你长期用 Claude Code 做编码和 Agent 任务,可以考虑 TaoToken 的 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=,一个入口切换多个模型,省得来回配 Key。
维护方面,文章就是 Markdown 文件,放在文件夹里。新增一篇文章就是写一个.md,然后跑一条索引更新命令——这个过程可以做成 git hook 自动触发。内容仓库和工具仓库分离的好处就在这里:你的文章永远是干净的纯文本,工具出问题不影响内容,换个工具也不影响内容。我现在的习惯是每周日晚上跑一次全量索引,把这一周新增的笔记和收藏的文章都写进去,几分钟的事。
硬件配置方面,ChromaDB 加本地 embedding 模型对资源要求很低。我在 M1 MacBook Air 上跑,内存占用不到 500MB,CPU 占用毫秒级,不需要 GPU。如果你用的是 Windows 或者 Linux,流程一样,只是路径要改成对应的格式。
最后说一句实在话:这件事最难的不是技术,是下决心开始。向量数据库、embedding 模型、分块策略、检索管道——光这些名词列出来,就够你折腾一个周末。但真正动手之后你会发现,核心代码就那么几百行,配置也就那么几段。搭完之后,你对自己的知识有了完全的控制权:用什么模型、怎么分块、怎么检索,全由你决定。代价是你得自己搭,但换来的是数据留在自己机器上,不用给别人交月费,搜出来的东西靠谱。这笔账,我觉得划算。