凌晨两点,你部署的生产环境突然报警——RAG系统返回的答案全是乱码,日志里清一色是 ConnectionError: timeout after 30000ms。你检查了网络、换了代理、甚至重启了服务器,但问题依然存在。这是我在上个月为一个金融客户搭建智能客服时真实遇到的场景。最终的解决方案出乎意料——不是代码问题,而是 API 端点的选择。

这篇文章,我将完整记录如何从零构建一个生产级的 AI Agent 知识库系统,涵盖向量检索原理、Embedding 生成、RAG 流程实现,以及如何避开我踩过的那些坑。全文约 15 分钟,建议收藏。

一、为什么你的知识库问答总是"答非所问"?

很多开发者在接入大模型后发现,AI 的回答要么太泛、要么瞎编。根本原因在于:LLM 本身并不"知道"你的业务知识,它需要从外部知识库中检索相关内容作为上下文。这就是 RAG(Retrieval-Augmented Generation)的核心思想。

一个完整的 RAG 流程如下:

在这个链路中,Embedding 质量和向量检索精度直接决定了最终效果。我测试过多个模型后发现,text-embedding-3-large 在中文语义理解上表现稳定,且成本可控。

二、技术架构与工具选型

先看整体架构图(文字版):

用户问题 → [Embedding API] → 向量检索 → Top-K Chunks → [LLM API] → 最终答案
                                       ↑
                              向量数据库(Milvus/Chroma)
                                       ↑
文档切片 → [Embedding API] → 索引构建

关键组件说明:

三、实战:从零构建 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 生成