本文最后更新于 2026-05-22,文章内容可能已经过时。

用AI API搭一个真正能用的个人知识库:一份带血的实操复盘

你有多少个收藏夹,从来没打开过第二次?

我数了一下:Chrome里有14个文件夹,最老的一个建于2021年,里面躺着47个链接,我已经记不得为什么要收藏它们。Notion里有三套"第二大脑"模板,最近一次打开是为了新建一个从未填满的数据库。微信收藏里有200多条消息,90%是我发给自己的"以后要看"。

然后有一天,我在写一份方案,明明记得半年前看过一篇讲"用户决策路径"的文章,写得很好,有一个框架我想引用。我找了四十分钟,没找到。最后重新搜索、重新阅读、重新整理,花了两个小时。

那一刻我意识到:我不是在积累知识,我是在假装积累知识。

这篇文章不是概念科普,不是"什么是RAG"的入门介绍。它是我在一个周末把整个流程跑通之后,把所有踩过的坑、走过的弯路、最终可运行的代码,整理成的一份实操复盘。如果你也被"找不到之前看过的东西"折磨过,这篇文章就是为你写的。

---

第一章:我踩过的三个错误方向

在给出"正确答案"之前,我必须先讲讲那些让我浪费了将近两周的弯路。

错误一:直接用ChatGPT对话当知识库

最直觉的想法:把文章贴进去,然后问问题。

这个方案在单次对话里体验不错。但你一旦关掉窗口,一切归零。ChatGPT没有跨会话记忆,每次都要重新粘贴文档,而且单次上下文有长度限制,超过一定字数就开始"忘记"前面的内容。

失败原因:无法持久化,无法扩展,本质上是在用一个有遗忘症的助手。 正确替代:把知识存在你控制的地方,只在需要回答问题时才调用AI。

错误二:本地向量数据库 + 开源模型,全部自托管

第二个想法是"我要完全掌控":在本地跑一个Embedding模型,用FAISS做向量检索,再跑一个本地LLM来回答问题。

结果是:光配置Python环境就花了半天,CUDA版本不对,torch装了卸、卸了装,Embedding模型下载了7GB,跑起来内存不够,换小模型效果又差。一行业务代码没写,我已经精疲力竭。

失败原因:环境配置地狱。对于个人项目,这个复杂度完全不值得。 正确替代:用API调用Embedding,用轻量级向量库,把精力放在业务逻辑上。

错误三:上来就设计微服务架构

第三次,我学聪明了,要"设计好再动手"。于是我画了一个架构图:FastAPI后端、Redis缓存、PostgreSQL存元数据、向量库单独一个服务、前端用React……

设计了三天,一行代码没写,项目就死了。

失败原因:过度设计是个人项目的第一杀手。在你验证核心价值之前,架构是负担,不是资产。 正确替代:最小可行方案,先跑通,再优化。

---

三种方案横向对比

| 方案 | 复杂度 | 效果 | 成本 | 结论 | | ChatGPT直接对话 | 极低 | 差(无持久化) | 低 | ❌ 不可用 | | 全本地自托管 | 极高 | 中(模型效果一般) | 低(但时间成本高) | ❌ 不推荐个人用 | | 微服务架构 | 高 | 未知(没跑通) | 中 | ❌ 过度设计 | | Embedding API + ChromaDB + Chat API | | | | ✅ 最终方案 |

最终选定的架构极其简洁:

文档 → 切片 → Embedding API生成向量 → 存入ChromaDB

用户提问 → 向量检索相关片段 → 拼装Prompt → Chat API → 返回答案

这就是RAG(检索增强生成)的核心思路,不神秘,用Python写完整流程不超过150行。

---

第二章:完整代码流程——从文档到能问答

我们用Python实现,依赖极少:openaichromadbtiktoken

pip install openai chromadb tiktoken

步骤一:文档切片

文档不能整体塞进去,要切成小块。每块太大,检索精度下降;每块太小,上下文丢失。我测试下来,400-600 token、重叠50 token是个不错的起点。

import tiktoken

def split_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:

"""

将长文本切分为固定token大小的片段

Args:

text: 原始文本

chunk_size: 每个片段的最大token数(建议400-600)

overlap: 相邻片段的重叠token数(建议40-80,保留上下文连贯性)

Returns:

切分后的文本片段列表

"""

enc = tiktoken.get_encoding("cl100k_base") # GPT-4/Embedding模型使用的编码器

tokens = enc.encode(text)

chunks = []

start = 0

while start < len(tokens):

end = min(start + chunk_size, len(tokens))

chunk_tokens = tokens[start:end]

chunk_text = enc.decode(chunk_tokens)

chunks.append(chunk_text)

# 下一个片段从(end - overlap)开始,制造重叠

start = end - overlap

if start >= len(tokens):

break

return chunks

步骤二:调用Embedding接口生成向量

💡 本文代码使用兼容OpenAI格式的API接口。
如果你还没有稳定的API访问渠道,可以直接用 [api.884819.xyz](https://api.884819.xyz)——支持Embedding模型和Chat模型,格式完全一致,把代码里的 base_url 换成它就能跑,不需要改其他任何东西。新用户注册即送体验token,国产模型完全免费,没有月租。
from openai import OpenAI

client = OpenAI(

api_key="your_api_key",

base_url="https://api.884819.xyz/v1" # 换成你的API地址

)

def get_embedding(text: str) -> list[float]:

"""

调用Embedding API,将文本转换为向量

注意:text-embedding-3-small 是性价比最高的选择

输出维度1536,足够个人知识库使用

"""

response = client.embeddings.create(

model="text-embedding-3-small",

input=text

)

return response.data[0].embedding

def embed_chunks(chunks: list[str]) -> list[list[float]]:

"""批量处理,加入简单的错误重试"""

embeddings = []

for i, chunk in enumerate(chunks):

try:

emb = get_embedding(chunk)

embeddings.append(emb)

if i % 10 == 0:

print(f"已处理 {i}/{len(chunks)} 个片段")

except Exception as e:

print(f"第{i}个片段出错: {e},跳过")

embeddings.append(None)

return embeddings

步骤三:存入ChromaDB

ChromaDB是一个轻量级向量数据库,数据直接存在本地文件夹,不需要额外启动服务。

import chromadb

def init_db(db_path: str = "./knowledge_db"):

"""初始化本地向量数据库"""

client = chromadb.PersistentClient(path=db_path)

# get_or_create: 已存在就复用,不存在就新建

collection = client.get_or_create_collection(

name="my_knowledge",

metadata={"hnsw:space": "cosine"} # 使用余弦相似度

)

return collection

def add_documents(collection, chunks: list[str], embeddings: list, source: str):

"""将切片和向量存入数据库"""

valid_items = [

(i, chunk, emb)

for i, (chunk, emb) in enumerate(zip(chunks, embeddings))

if emb is not None

]

collection.add(

ids=[f"{source}_{i}" for i, _, _ in valid_items],

documents=[chunk for _, chunk, _ in valid_items],

embeddings=[emb for _, _, emb in valid_items],

metadatas=[{"source": source, "chunk_index": i} for i, _, _ in valid_items]

)

print(f"成功存入 {len(valid_items)} 个片段,来源:{source}")

步骤四:检索 + 问答(完整可运行脚本)

def search(collection, query: str, n_results: int = 5) -> list[str]:

"""根据问题检索最相关的片段"""

query_embedding = get_embedding(query)

results = collection.query(

query_embeddings=[query_embedding],

n_results=n_results,

include=["documents", "distances"]

)

# distances越小越相关(余弦距离)

# 过滤掉相关性太低的结果(距离>0.7通常意味着不相关)

filtered = [

doc for doc, dist in zip(

results["documents"][0],

results["distances"][0]

)

if dist < 0.7 # 置信度过滤阈值,可调整

]

return filtered

def ask(collection, question: str) -> str:

"""完整的问答流程"""

# 1. 检索相关片段

relevant_chunks = search(collection, question)

if not relevant_chunks:

return "在知识库中没有找到与这个问题相关的内容。"

# 2. 拼装Prompt

context = "\n\n---\n\n".join(relevant_chunks)

prompt = f"""你是一个知识库助手。请严格根据以下参考资料回答问题。

如果参考资料中没有相关信息,请明确说"知识库中没有这方面的记录",不要凭空编造。

参考资料:

{context}

问题:{question}

回答:"""

# 3. 调用Chat API

response = client.chat.completions.create(

model="gpt-4o-mini", # 日常问答用mini,成本低

messages=[{"role": "user", "content": prompt}],

temperature=0.1 # 低温度,让回答更忠实于原文

)

return response.choices[0].message.content

===== 主流程 =====

if __name__ == "__main__":

# 初始化

collection = init_db()

# 导入一篇文档(示例)

with open("my_article.txt", "r", encoding="utf-8") as f:

text = f.read()

chunks = split_text(text)

embeddings = embed_chunks(chunks)

add_documents(collection, chunks, embeddings, source="my_article")

# 开始问答

while True:

question = input("\n请输入问题(输入q退出):")

if question == "q":

break

answer = ask(collection, question)

print(f"\n回答:{answer}")

跑通这个脚本之后,你就有了一个真实可用的知识库。我第一次成功运行时,问了一个两个月前存入的文章里的问题,它准确地引用了原文的框架——那一刻确实有点上头。

---

第三章:让它"真的能用"——三个质变细节

跑通Demo和"好用"之间,隔着这三个细节。

细节一:切片策略决定召回质量

按固定token切片是最简单的方案,但不是最好的。三种主流策略对比:

  • 按句切片:语义完整,但片段长度不均,短句信息量太低
  • 按段切片:符合人类阅读习惯,但段落长度差异极大,长段会超出token限制
  • 滑动窗口(推荐):固定大小 + 重叠区域,兼顾长度控制和上下文连贯

上面代码里的 overlap=50 就是滑动窗口的核心参数。如果你发现回答经常"断章取义",把overlap调大到80-100。如果检索结果总是不够精准,把chunk_size调小到300。

细节二:Prompt模板是知识库的"护栏"

最容易被忽视的问题:如果你不在Prompt里明确约束AI,它会用自己的训练知识来"补充"答案,而不是老实告诉你"知识库里没有"。

关键约束句(必须加):
如果参考资料中没有相关信息,请明确说"知识库中没有这方面的记录",不要凭空编造。

另外,temperature=0.1 这个参数很重要。温度越高,AI越"有创意",对知识库场景是灾难;温度越低,AI越忠实于给定的上下文。

细节三:置信度过滤,拒绝强行作答

向量检索会返回"最相关的N个结果",但这个"最相关"是相对的。如果你问的问题根本不在知识库里,它依然会返回距离最近的片段——只是这个片段可能完全不相关。

代码里的 if dist < 0.7 就是置信度过滤。余弦距离小于0.7,说明相似度还可以;大于0.7,说明这个检索结果很可能是"凑数"的,直接过滤掉,返回"知识库中没有相关内容"。

这个阈值需要根据你的数据调整,但有它和没它,用户体验差距非常明显。

---

第四章:部署与日常维护

向量库备份

ChromaDB的数据存在 ./knowledge_db 文件夹,直接压缩备份即可。建议每周备份一次,存到云盘。

# 备份

tar -czf knowledge_db_backup_$(date +%Y%m%d).tar.gz ./knowledge_db

恢复

tar -xzf knowledge_db_backup_20240101.tar.gz

增量更新

新文档不需要重建整个库,直接调用 add_documents 即可。ChromaDB支持增量写入,ID用文件名+时间戳避免冲突。

API费用估算

很多人担心"用API会不会账单爆炸"。以1000篇笔记为例(每篇平均800字,约1000 token):

| 操作 | 模型 | 用量 | 预估费用(参考) | | 初始Embedding(一次性) | text-embedding-3-small | ~100万token | 极低,个位数人民币量级 | | 日常问答(每次检索5片段+回答) | gpt-4o-mini | 每次约2000token | 非常低,日常使用几乎可忽略 | | 月度总费用(假设每天问10个问题) | — | — | 通常在十几元以内 |
⚠️ 以上为量级参考,实际费用取决于具体API定价,请以你使用的平台实时价格为准。

关键结论:对于个人知识库,API费用基本不是问题。 如果用[api.884819.xyz](https://api.884819.xyz),国产模型(Deepseek、通义千问等)完全免费,用来做日常问答完全够用,Embedding用小模型,整体成本极低。

---

写在最后

现在我的知识库里有340篇文档,从技术文章到产品方法论,从读书笔记到会议记录。上周它帮我找到了一年前记的一个关于"用户心智模型"的框架,我只用了30秒——而不是之前的两小时。

这件事真正让我意识到的不是技术有多厉害,而是:知识的价值在于能被检索到的那一刻。 存而不用,和没存没有区别。

整个流程从零到跑通,我花了一个周末。你如果跟着这篇文章做,应该更快。

---

但这套知识库现在还有一个致命缺陷——

它不知道"我"是谁。

同样是问"这个项目下一步怎么推进",我和你的最优答案可能完全不同,因为我们的背景、偏好、当前处境都不一样。现在的知识库只会检索文档,不会理解提问者。

下一篇,我会尝试给知识库加上"用户画像层":让它记住你的角色、偏好和历史提问,让回答开始有记忆、有偏好、有个性。

如果你想第一时间看到,记得关注。

---

本文由8848AI原创,转载请注明出处。关注8848AI,带你从零开始学AI。 新用户注册即送体验token。 访问 [api.884819.xyz](https://api.884819.xyz) 注册,用户名+密码即可,无需邮箱验证,国产模型完全免费,没有月租。

#AI教程 #个人知识库 #RAG #Python #8848AI #向量数据库 #ChatGPT #AI实战