凌晨两点,你部署的生产环境突然报警——RAG系统返回的答案全是乱码,日志里清一色是 ConnectionError: timeout after 30000ms。你检查了网络、换了代理、甚至重启了服务器,但问题依然存在。这是我在上个月为一个金融客户搭建智能客服时真实遇到的场景。最终的解决方案出乎意料——不是代码问题,而是 API 端点的选择。
这篇文章,我将完整记录如何从零构建一个生产级的 AI Agent 知识库系统,涵盖向量检索原理、Embedding 生成、RAG 流程实现,以及如何避开我踩过的那些坑。全文约 15 分钟,建议收藏。
一、为什么你的知识库问答总是"答非所问"?
很多开发者在接入大模型后发现,AI 的回答要么太泛、要么瞎编。根本原因在于:LLM 本身并不"知道"你的业务知识,它需要从外部知识库中检索相关内容作为上下文。这就是 RAG(Retrieval-Augmented Generation)的核心思想。
一个完整的 RAG 流程如下:
- 文档处理:将 PDF、Word、网页等非结构化文本切分成 chunks
- 向量化:用 Embedding 模型将每个 chunk 转换为高维向量
- 存储:将向量存入向量数据库(如 Milvus、Pinecone、Chroma)
- 检索:将用户问题向量化,在向量库中做相似度搜索
- 生成:将检索到的 top-k chunks 作为上下文,调用 LLM 生成答案
在这个链路中,Embedding 质量和向量检索精度直接决定了最终效果。我测试过多个模型后发现,text-embedding-3-large 在中文语义理解上表现稳定,且成本可控。
二、技术架构与工具选型
先看整体架构图(文字版):
用户问题 → [Embedding API] → 向量检索 → Top-K Chunks → [LLM API] → 最终答案
↑
向量数据库(Milvus/Chroma)
↑
文档切片 → [Embedding API] → 索引构建
关键组件说明:
- Embedding 模型:text-embedding-3-large(1536维,中文支持好)
- 向量数据库:Chroma(轻量级,适合中小规模),Milvus(生产级)
- LLM:Claude 3.5 Sonnet 或 GPT-4o(复杂推理),DeepSeek V3.2(简单问答,省成本)
- API 中转:HolySheep AI(国内直连 <50ms,汇率 1:1,无抽成)
三、实战:从零构建 RAG 知识库系统
3.1 环境准备与依赖安装
pip install openai chromadb langchain-community python-dotenv requests tiktoken
注意:我踩过的第一个坑是版本兼容问题。建议锁定以下版本:
openai==1.12.0
chromadb==0.4.22
langchain-community==0.0.20
3.2 配置 API 客户端(核心代码)
这是最容易出错的地方。很多人直接复制官方文档的代码,结果出现 401 Unauthorized。问题出在 base_url 配置上。
import os
from openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
✅ 正确配置:使用 HolySheep API 中转
client = OpenAI(
api_key=os.getenv("HOLYSHEEP_API_KEY"), # 格式:sk-xxxxx
base_url="https://api.holysheep.ai/v1" # ❌ 不是 api.openai.com
)
测试连通性
def test_connection():
try:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "ping"}],
max_tokens=5
)
print(f"✅ 连接成功: {response.choices[0].message.content}")
return True
except Exception as e:
print(f"❌ 连接失败: {e}")
return False
3.3 文档处理与 Embedding 生成
import chromadb
from chromadb.config import Settings
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
class KnowledgeBaseBuilder:
def __init__(self, collection_name="product_kb"):
# 初始化向量数据库
self.client = chromadb.Client(Settings(
chroma_db_impl="duckdb+parquet",
persist_directory="./chroma_db"
))
self.collection = self.client.get_or_create_collection(name=collection_name)
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
length_function=len
)
def load_and_index(self, file_path: str, metadata: dict = None):
"""加载文档并生成向量索引"""
loader = TextLoader(file_path, encoding='utf-8')
documents = loader.load()
# 文档切分
texts = self.text_splitter.split_documents(documents)
# 批量生成 Embedding(优化:减少 API 调用次数)
batch_size = 100
for i in range(0, len(texts), batch_size):
batch = texts[i:i+batch_size]
batch_texts = [t.page_content for t in batch]
# 调用 Embedding API
response = client.embeddings.create(
model="text-embedding-3-large",
input=batch_texts
)
# 批量存入向量库
self.collection.add(
embeddings=[item.embedding for item in response.data],
documents=batch_texts,
ids=[f"doc_{i+j}" for j in range(len(batch_texts))],
metadatas=[{"source": file_path} for _ in batch_texts]
)
print(f"✅ 已索引 {min(i+batch_size, len(texts))}/{len(texts)} 个文本块")
print(f"🎉 索引完成,共 {len(texts)} 个文档块")
使用示例
builder = KnowledgeBaseBuilder()
builder.load_and_index("./docs/faq.txt")
3.4 语义检索与答案生成
def retrieve_and_answer(query: str, top_k: int = 3):
"""检索相关文档 + 生成答案"""
# Step 1: 将问题向量化
query_embedding = client.embeddings.create(
model="text-embedding-3-large",
input=query
).data[0].embedding
# Step 2: 向量相似度检索
results = builder.collection.query(
query_embeddings=[query_embedding],
n_results=top_k
)
# Step 3: 构建上下文
context = "\n\n".join([
f"[文档{i+1}] {doc}"
for i, doc in enumerate(results['documents'][0])
])
# Step 4: 调用 LLM 生成