RAGAgent 项目知识点 & 面试题汇总
📋 项目概览
RAGAgent 是一个基于 FastAPI + LangChain + Qdrant + 通义千问 的 RAG(检索增强生成)知识库问答系统。核心功能:文档上传与向量化、语义检索、多轮对话、长期记忆存储。
技术栈
| 层 | 选型 |
|---|---|
| 后端框架 | FastAPI(Python 3.12+,async-first) |
| AI 框架 | LangChain(链式编排、Retriever 抽象、历史感知检索) |
| 向量数据库 | Qdrant(余弦相似度,支持 Payload 过滤) |
| Embedding 模型 | 通义千问 text-embedding-v3(DashScope,1536 维) |
| LLM | 通义千问 qwen-plus(ChatTongyi) |
| 会话存储 | Redis(RedisChatMessageHistory,7 天 TTL) |
| 持久日志 | MongoDB(motor 异步驱动) |
| 长期记忆 | Qdrant _longmem collection(跨会话 Q&A 向量记忆) |
🏗️ 一、RAG 核心原理
1.1 RAG 是什么?
RAG(Retrieval-Augmented Generation)检索增强生成,通过在生成答案前先从知识库检索相关文档片段,将其作为上下文注入 LLM Prompt,从而让模型回答私有知识库中的问题,解决 LLM 知识截止和幻觉问题。
1.2 RAG 完整流程(本项目实现)
- 文档处理:加载 → 清洗 → 分块(DocumentPipeline)
- 向量化:
TongyiEmbedder.embed_documents()→ 1536 维向量 - 存储:
QdrantStore.upsert()→ 写入向量数据库 - 查询:用户问题 →
embed_query()→ Qdrant 余弦相似度检索 - 增强生成:检索文档 + 对话历史 → LLM → 回答
1.3 多轮对话 RAG(本项目亮点)
使用 LangChain create_history_aware_retriever:在检索前,将"带历史的问题"改写为"独立完整问题",解决代词指代、上下文依赖导致的检索失败问题。
contextualize_prompt: 根据对话历史,将用户问题改写为独立完整问题(保持原意)
→ history_aware_retriever
→ create_retrieval_chain (RAG Chain)
→ RunnableWithMessageHistory (自动注入历史)📦 二、向量数据库 Qdrant
2.1 核心概念
| 概念 | 说明 |
|---|---|
| Collection | 类似关系库的 Table,存储同维度向量 |
| Point | 一条记录:id + vector + payload |
| Payload | 与向量关联的 JSON 元数据,支持过滤查询 |
| Distance | 相似度度量:Cosine(本项目)/ Dot / Euclid |
| Score Threshold | 相似度阈值(0.7),过滤低质量结果 |
2.2 本项目 Qdrant 使用模式
ensure_collection():幂等创建,余弦相似度,1536 维md5_exists():scroll + Filter 实现 MD5 去重,防重复入库upsert():PointStruct(id=uuid, vector, payload)批量写入query_points():替代旧版search(),score_threshold 过滤delete_by_md5():scroll 获取 ids → delete 删除文档
2.3 Qdrant vs 其他向量库
| 特性 | Qdrant | Pinecone | Weaviate | FAISS |
|---|---|---|---|---|
| 部署方式 | 自托管/云 | 云托管 | 自托管/云 | 本地库 |
| Payload 过滤 | ✅ 强大 | ✅ | ✅ | ❌ |
| 持久化 | ✅ | ✅ | ✅ | ❌ |
| 生产可用 | ✅ | ✅ | ✅ | 适合离线 |
🔤 三、Embedding(文本向量化)
3.1 Embedding 原理
将文本映射到高维向量空间,语义相近的文本在向量空间距离更近。本项目使用通义千问 text-embedding-v3,输出 1536 维向量。
3.2 相似度计算
余弦相似度(Cosine Similarity):衡量两向量夹角,值域 [-1, 1],越接近 1 越相似。不受向量长度影响,适合文本语义匹配。
3.3 指数退避重试策略
MAX_RETRIES = 3
delay: 1s → 2s → 4s(指数退避)
捕获所有异常,最终重抛最后一次异常这是生产环境中调用外部 API 的最佳实践,避免瞬时网络故障导致整体失败。
📄 四、文档处理 Pipeline
4.1 Pipeline 三阶段
| 阶段 | 组件 | 职责 |
|---|---|---|
| 加载(Load) | PDFLoader / WordLoader / MarkdownLoader / CSVLoader | 读取不同格式文件,提取纯文本 |
| 清洗(Clean) | TextCleaner | 去除控制字符、规范化空白、压缩空行 |
| 分块(Chunk) | SmartChunker | RecursiveCharacterTextSplitter,中文优先段落分割 |
4.2 SmartChunker 分块策略
使用 RecursiveCharacterTextSplitter,分割符优先级:段落(\n\n)→ 换行(\n)→ 句号(。)→ 空格 → 空字符串。chunk_size=1000,chunk_overlap=200(保留语境连续性)。
4.3 MD5 去重设计
上传文件时计算 MD5,先查询 Qdrant 是否已存在该 MD5 的 Point,防止重复入库。还用 in_flight_md5s(进程内 Set)防止并发上传同一文件的竞态条件。
💬 五、多轮对话系统
5.1 三层存储架构
| 层次 | 存储 | 生命周期 | 用途 |
|---|---|---|---|
| 短期记忆 | Redis | 7 天 TTL | 当前会话对话历史(LangChain 标准接口) |
| 持久日志 | MongoDB | 永久 | 完整对话记录,审计/分析用 |
| 长期记忆 | Qdrant _longmem | 永久 | Q&A 向量化存储,跨会话语义关联 |
5.2 异步日志设计
try:
loop = asyncio.get_running_loop()
loop.create_task(self._mongo_logger.log(...)) # 不阻塞响应
except RuntimeError:
pass # 没有事件循环(测试环境)MongoDB 日志采用 fire-and-forget 模式,不阻塞用户响应,使用 motor(async MongoDB 驱动)异步写入。
5.3 RunnableWithMessageHistory
LangChain 提供的链式包装器,自动从 get_session_history() 加载/存储对话历史,通过 configurable={"session_id": id} 区分不同会话。
⚙️ 六、FastAPI 工程实践
6.1 依赖注入 + @lru_cache 单例
@lru_cache
def get_chat_engine():
return ChatEngine(...)
# FastAPI Depends() → 进程级单例,避免重复创建通过 @lru_cache 实现进程级单例,结合 FastAPI Depends() 依赖注入,既保证单例又方便测试替换。
6.2 Pydantic Settings 配置管理
继承 BaseSettings,所有配置均可通过环境变量或 .env 文件覆盖,支持类型验证。生产部署只需设置环境变量,无需修改代码。
6.3 API 设计
| 端点 | 方法 | 功能 |
|---|---|---|
/documents/upload | POST | 文档上传、向量化入库 |
/documents/search | POST | 语义检索 |
/documents/{md5} | DELETE | 按 MD5 删除文档 |
/chat | POST | 多轮问答 |
/chat/{id}/history | GET | 查看会话历史 |
/chat/{id}/logs | GET | 查看 MongoDB 永久日志 |
/chat/{id} | DELETE | 清空会话历史 |
❓ 七、面试题精选
🔵 RAG 原理类
Q1:RAG 和 Fine-tuning 的区别是什么?各自适用场景?
RAG:无需训练,实时检索外部知识库,适合私有知识、实时更新、知识量大的场景,可解释性强。Fine-tuning:调整模型参数,适合固定领域风格/格式学习,但成本高、知识更新需重训。混合使用:Fine-tuning 学习领域风格 + RAG 注入最新知识。
Q2:RAG 中如何处理多轮对话的指代问题?
使用 create_history_aware_retriever:将"带历史的问题"改写为"独立完整问题"。例如 "它是谁?" + 历史(上文提到张三)→ 改写为 "张三是谁?",再去检索。
Q3:如何评估 RAG 系统的质量?
检索质量:Recall@K(检索到的相关文档比例)、MRR(平均倒数排名)。生成质量:RAGAS 框架(faithfulness 忠实度、answer_relevancy 答案相关性、context_precision 上下文精确率)。实际指标:用户满意度、无法回答率。
Q4:分块策略如何影响 RAG 效果?
chunk 太小:语境不足,答案不完整;chunk 太大:包含噪声,检索精度下降。overlap 保证边界语义连续。本项目 chunk_size=1000/overlap=200,中文优先段落分割,平衡了语义完整性和检索精度。
🟢 向量数据库类
Q5:余弦相似度 vs 点积 vs 欧式距离,如何选择?
余弦相似度:不受向量 norm 影响,只看方向,适合文本语义匹配(本项目选择)。点积:受 norm 影响,适合已归一化向量(如 OpenAI 官方推荐)。欧式距离:受 norm 影响大,适合几何空间,一般不用于文本。
Q6:向量检索中如何处理大规模数据的性能问题?
ANN(近似最近邻)算法:HNSW(Qdrant 默认)图结构,O(log n) 查询。量化压缩(PQ/SQ)减少内存。Payload 过滤先缩小候选集。分 Collection 隔离业务数据。
🟡 工程实践类
Q7:FastAPI 中如何实现依赖注入和单例模式?
@lru_cache 装饰 get_xxx() 函数,首次调用创建实例,后续返回缓存对象(进程级单例)。FastAPI Depends() 每次请求调用该函数但因 lru_cache 返回同一对象。优点:懒加载、线程安全、易于测试(替换 mock)。
Q8:如何设计异步日志不影响响应延迟?
asyncio.get_running_loop().create_task() 在同一事件循环中创建协程任务,不等待结果(fire-and-forget)。使用 motor 异步 MongoDB 驱动。注意:fire-and-forget 可能丢日志,生产中可考虑消息队列(Kafka/Redis Stream)作持久缓冲。
Q9:Pydantic Settings 和普通配置文件有什么优势?
类型安全(自动验证和转换)、环境变量自动映射、.env 文件支持、默认值管理、IDE 自动补全。12-factor App 规范:配置与代码分离,不同环境只改环境变量。
Q10:生产环境中如何保证文档不重复入库?
双重防护:① 进程内 in_flight_md5s(set)防并发竞态;② Qdrant Payload 过滤查询(md5_exists)防跨请求重复。MD5 是文件内容的指纹,同内容文件 MD5 相同,即使文件名不同也能去重。
🔴 系统设计类
Q11:如何设计一个支持百万文档的 RAG 系统?
存储分层:热数据 Qdrant(内存索引),冷数据对象存储(S3)。检索优化:ANN 索引 + Payload 前置过滤。计算分离:Embedding 服务独立部署,支持 GPU 加速。流式处理:大文件分批 Embedding,异步入库(Celery/Kafka)。多租户:Collection 隔离或 Payload tenant_id 过滤。
Q12:Redis 会话存储的 TTL 如何设计?
本项目 7 天 TTL,权衡用户体验(不频繁登出)和内存成本。RedisChatMessageHistory 每次访问可自动续期。生产考量:活跃用户滑动窗口续期,非活跃用户自然过期,避免历史消息无限增长(限制最大条数)。
📚 八、学习资源推荐
- LangChain 官方文档:RAG 和 Memory 模块
- Qdrant 官方文档:Collection、Payload 过滤、HNSW 参数调优
- RAGAS 框架:RAG 系统评估指标体系
- 论文:《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》(Lewis et al., 2020)
- 通义千问 DashScope API 文档:Embedding 和 LLM 接口