金句:LLM 知道全世界的知识,但不知道你公司的数据。RAG 是一座桥,把你的私有知识和 AI 的通用能力连接起来。
一、为什么需要 RAG?
场景:你让 AI 帮你写代码,但 AI 不知道:
- 你们公司内部的 API 规范
- 你们的业务领域模型(DDD 术语)
- 你们的历史技术决策(ADR 文档)
- 你们的代码注释约定和工程实践
这时候,AI 只能给出"通用"的答案,而不是"符合你们具体情况"的答案。
RAG(Retrieval-Augmented Generation,检索增强生成) 解决了这个问题。
二、RAG 工作原理
传统 LLM 调用:
用户问题 → LLM → 基于训练数据回答
RAG 流程:
用户问题 → [检索层] → 相关文档 ↓
→ LLM → 基于文档+训练数据回答
具体步骤:
1. 知识库构建(离线)
文档 → 切分 → 向量化 → 存入向量数据库
2. 查询时(在线)
用户问题 → 向量化 → 相似度检索 → 找到相关文档片段
→ 构建提示词(问题 + 相关文档) → LLM 生成答案
三、构建代码知识库 RAG 系统
架构设计
# 技术选型
向量数据库: ChromaDB(本地部署,免费)
Embedding 模型: text-embedding-3-small(OpenAI)
LLM: GPT-4o / Claude-3.5-sonnet
文档处理: LangChain
完整实现
# code_knowledge_rag.py
import os
from pathlib import Path
from langchain_community.document_loaders import (
TextLoader,
DirectoryLoader,
MarkdownLoader
)
from langchain.text_splitter import (
RecursiveCharacterTextSplitter,
Language
)
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
class CodeKnowledgeRAG:
def __init__(self, knowledge_base_path: str, persist_dir: str = "./chroma_db"):
self.knowledge_base_path = knowledge_base_path
self.persist_dir = persist_dir
self.embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
self.vectorstore = None
def build_index(self):
"""构建知识库索引"""
print(f"📚 正在索引知识库:{self.knowledge_base_path}")
# 加载不同类型的文档
documents = []
# 加载 Markdown 文档
md_loader = DirectoryLoader(
self.knowledge_base_path,
glob="**/*.md",
loader_cls=TextLoader,
loader_kwargs={'encoding': 'utf-8'}
)
documents.extend(md_loader.load())
# 加载 TypeScript/JavaScript 代码
ts_loader = DirectoryLoader(
self.knowledge_base_path,
glob="**/*.ts",
loader_cls=TextLoader,
loader_kwargs={'encoding': 'utf-8'}
)
documents.extend(ts_loader.load())
# 加载 Python 代码
py_loader = DirectoryLoader(
self.knowledge_base_path,
glob="**/*.py",
loader_cls=TextLoader,
loader_kwargs={'encoding': 'utf-8'}
)
documents.extend(py_loader.load())
print(f"✅ 加载了 {len(documents)} 个文档")
# 智能切分:代码文件用代码感知切分器
code_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=1000,
chunk_overlap=200,
)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n## ", "\n### ", "\n\n", "\n", " "]
)
chunks = []
for doc in documents:
if doc.metadata.get('source', '').endswith('.py'):
chunks.extend(code_splitter.split_documents([doc]))
else:
chunks.extend(text_splitter.split_documents([doc]))
print(f"✅ 切分为 {len(chunks)} 个文本块")
# 创建向量索引
self.vectorstore = Chroma.from_documents(
documents=chunks,
embedding=self.embeddings,
persist_directory=self.persist_dir
)
print(f"✅ 向量索引已创建,保存至 {self.persist_dir}")
def load_index(self):
"""加载已有索引"""
if Path(self.persist_dir).exists():
self.vectorstore = Chroma(
persist_directory=self.persist_dir,
embedding_function=self.embeddings
)
print(f"✅ 已加载向量索引")
else:
raise FileNotFoundError(f"索引目录不存在:{self.persist_dir}")
def ask(self, question: str, k: int = 5) -> dict:
"""基于知识库回答问题"""
if not self.vectorstore:
raise RuntimeError("请先调用 build_index() 或 load_index()")
# 构建自定义提示词模板
template = """你是一个代码助手,专门回答关于项目代码库的问题。
请基于以下检索到的相关代码和文档来回答问题。
如果文档中没有相关信息,请明确说明"在提供的代码库中没有找到相关信息"。
不要编造代码或信息。
相关代码/文档:
{context}
问题:{question}
请用中文回答,并指出你引用的是哪个文件中的内容:"""
PROMPT = PromptTemplate(
template=template,
input_variables=["context", "question"]
)
llm = ChatOpenAI(model="gpt-4o", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=self.vectorstore.as_retriever(
search_type="mmr", # 使用 MMR 算法提高多样性
search_kwargs={"k": k, "fetch_k": 20}
),
chain_type_kwargs={"prompt": PROMPT},
return_source_documents=True
)
result = qa_chain({"query": question})
# 整理来源文件
sources = list(set([
doc.metadata.get('source', 'unknown')
for doc in result['source_documents']
]))
return {
"answer": result['result'],
"sources": sources
}
# 使用示例
if __name__ == '__main__':
rag = CodeKnowledgeRAG(
knowledge_base_path="./docs", # 文档目录
persist_dir="./chroma_db"
)
# 首次使用:构建索引
rag.build_index()
# 之后直接加载:
# rag.load_index()
# 提问
questions = [
"我们的用户认证是如何实现的?使用了什么技术?",
"如何添加一个新的 API 端点?有什么规范要求?",
"数据库查询应该遵循什么规范?",
"如何处理异步错误?"
]
for q in questions:
print(f"\n❓ 问题:{q}")
result = rag.ask(q)
print(f"💡 回答:{result['answer']}")
print(f"📄 来源:{', '.join(result['sources'])}")
print("-" * 60)
四、在 Cursor 中集成私有知识库
方法一:@Docs 功能
Cursor 内置了 @Docs 功能,可以添加自定义文档:
1. 在 Cursor 设置中找到 Docs
2. 添加你的内部文档 URL 或本地路径
3. 在对话中使用 @YourDocs 引用
方法二:MCP + RAG 集成
// rag-mcp-server.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
// 将 RAG 系统封装为 MCP 工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: 'search_knowledge_base',
description: '在公司内部知识库中搜索相关信息',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: '搜索查询' }
},
required: ['query']
}
}]
}));
五、RAG 质量优化技巧
技巧一:Chunk 策略优化
# 错误示例:按固定字符数切分代码
text_splitter = CharacterTextSplitter(chunk_size=500)
# 正确示例:代码感知切分,保持函数完整性
code_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=2000, # 代码块允许更大
chunk_overlap=400 # 更多重叠,保持上下文
)
技巧二:混合检索
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# 结合向量检索(语义)和 BM25(关键词)
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
bm25_retriever = BM25Retriever.from_documents(documents, k=5)
ensemble_retriever = EnsembleRetriever(
retrievers=[vector_retriever, bm25_retriever],
weights=[0.7, 0.3] # 语义搜索权重更高
)
技巧三:元数据过滤
# 只搜索后端相关文档
results = vectorstore.similarity_search(
query,
filter={"module": "backend"}
)
章节小结:RAG 是让 AI 理解你私有知识的核心技术。通过向量化存储代码文档、架构规范和历史决策,AI 在回答你的问题时不再是"泛泛而谈",而是基于你的具体项目给出精准建议。这是 Vibe Coding 从"工具辅助"升级为"智能协作"的关键技术。