Skip to content

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 完整流程(本项目实现)

  1. 文档处理:加载 → 清洗 → 分块(DocumentPipeline)
  2. 向量化TongyiEmbedder.embed_documents() → 1536 维向量
  3. 存储QdrantStore.upsert() → 写入向量数据库
  4. 查询:用户问题 → embed_query() → Qdrant 余弦相似度检索
  5. 增强生成:检索文档 + 对话历史 → 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 其他向量库

特性QdrantPineconeWeaviateFAISS
部署方式自托管/云云托管自托管/云本地库
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)SmartChunkerRecursiveCharacterTextSplitter,中文优先段落分割

4.2 SmartChunker 分块策略

使用 RecursiveCharacterTextSplitter,分割符优先级:段落(\n\n)→ 换行(\n)→ 句号()→ 空格 → 空字符串。chunk_size=1000chunk_overlap=200(保留语境连续性)。

4.3 MD5 去重设计

上传文件时计算 MD5,先查询 Qdrant 是否已存在该 MD5 的 Point,防止重复入库。还用 in_flight_md5s(进程内 Set)防止并发上传同一文件的竞态条件。

💬 五、多轮对话系统

5.1 三层存储架构

层次存储生命周期用途
短期记忆Redis7 天 TTL当前会话对话历史(LangChain 标准接口)
持久日志MongoDB永久完整对话记录,审计/分析用
长期记忆Qdrant _longmem永久Q&A 向量化存储,跨会话语义关联

5.2 异步日志设计

python
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 单例

python
@lru_cache
def get_chat_engine():
    return ChatEngine(...)
# FastAPI Depends() → 进程级单例,避免重复创建

通过 @lru_cache 实现进程级单例,结合 FastAPI Depends() 依赖注入,既保证单例又方便测试替换。

6.2 Pydantic Settings 配置管理

继承 BaseSettings,所有配置均可通过环境变量或 .env 文件覆盖,支持类型验证。生产部署只需设置环境变量,无需修改代码。

6.3 API 设计

端点方法功能
/documents/uploadPOST文档上传、向量化入库
/documents/searchPOST语义检索
/documents/{md5}DELETE按 MD5 删除文档
/chatPOST多轮问答
/chat/{id}/historyGET查看会话历史
/chat/{id}/logsGET查看 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 接口