基于 FastAPI+ChromaDB+Ollama 的外卖评价 RAG 系统实现详解
本文将详细拆解一个面向外卖评价场景的 RAG(检索增强生成)系统,从核心模块设计、数据流转流程到部署运维,完整呈现后端技术架构与实现细节,适合需要搭建垂直领域 RAG 应用的开发者参考。
一、系统核心架构概览
系统采用模块化设计,基于 Python 生态构建,核心围绕 “数据持久化 - 文档处理 - 向量检索 - LLM 生成” 四大环节,通过 FastAPI 提供对外服务。
1.1 核心模块分工
| 模块路径 | 核心职责 | 关键技术依赖 |
|---|---|---|
| models.py | 关系型数据库表结构定义 | SQLite + SQLAlchemy |
| services/document_processor.py | 文本清洗、分块、元数据提取 | LangChain(RecursiveCharacterTextSplitter) |
| services/vector_store.py | 向量库管理与相似检索 | ChromaDB + SentenceTransformer |
| services/llm_service.py | 大模型调用封装 | langchain_ollama |
| services/rag_service.py | RAG 流程编排(检索 + 生成) | 自定义服务编排 |
| main.py | API 入口与生命周期管理 | FastAPI + Uvicorn |
1.2 关键技术选型
- 嵌入模型:SentenceTransformer(本地生成向量,避免网络依赖)
- 向量数据库:ChromaDB(持久化集合名:food_reviews)
- 关系型数据库:SQLite(开发环境),支持通过配置切换其他 SQL 数据库
- API 框架:FastAPI(高性能、自动生成接口文档)
- 日志组件:loguru(多端输出、结构化日志)
二、数据模型与持久化设计
系统采用 “关系库 + 向量库” 双存储架构:关系库存储结构化数据与文档分块元信息,向量库存储文本嵌入向量用于检索。
2.1 核心数据模型(models.py)
1. FoodReview(评价主表)
存储原始外卖评价数据,字段覆盖业务核心信息:
- 基础信息:restaurant_name(餐厅名)、food_name(菜品名)、review_text(评价文本)
- 业务指标:rating(评分)、price(价格)、delivery_time(配送时间)、tags(标签)
- 状态与追踪:is_processed(是否已处理分块)、created_at/updated_at(时间戳)
2. DocumentChunk(文档分块表)
存储文本分块结果,关联原始文档与向量库:
- 核心标识:chunk_id(MD5 唯一标识)、vector_id(关联 ChromaDB 的向量 ID)
- 分块信息:source_document(来源文档标识)、chunk_text(分块文本)、chunk_index(分块序号)
- 元数据:chunk_metadata(JSON 字符串,存储分块元信息)
3. QueryLog(查询日志表)
记录用户查询与系统响应,用于审计与优化:
- 查询信息:query_text(用户查询)、retrieved_chunks(检索到的分块 ID 列表)
- 响应信息:response_text(系统回答)、response_time(响应耗时)、model_used(使用的 LLM 模型)
- 追踪信息:user_ip(用户 IP)、created_at(查询时间)
2.2 数据初始化脚本
提供两种初始化脚本,适配不同环境准备需求:
| 脚本名称 | 功能 | 依赖 | 适用场景 |
|---|---|---|---|
| init_data_simple.py | 仅插入示例数据到 FoodReview | 仅关系库 | 快速搭建基础数据,不依赖 LLM / 向量库 |
| init_data.py | 插入数据 + 分块处理 + 向量写入 | 关系库 + 向量库 + LLM | 完整环境就绪后,初始化带向量的知识库 |
使用建议:初次部署先执行init_data_simple.py,待 LLM 与向量库环境就绪后,再执行init_data.py完成向量化。
三、核心服务实现细节
3.1 文档处理服务(document_processor.py)
负责将原始文本转化为可用于检索的分块,核心流程包含 “清洗→分块→元数据提取→持久化” 四步:
1. 文本清洗(clean_text())
- 功能:去除特殊字符、多余空格、换行符,统一文本格式
- 应用场景:处理评价文本中的噪声数据,提升后续分块与嵌入质量
2. 文本分块(split_document())
基于 LangChain 的RecursiveCharacterTextSplitter实现,针对中文优化:
- 核心参数:chunk_size=500(分块长度)、chunk_overlap=50(分块重叠度)
- 中文适配:支持中文分隔符(。!?;,)作为分块边界,避免语义断裂
- 输入输出:接收原始文本,返回分块列表(含分块文本与元数据)
3. 元数据提取(extract_metadata())
自动提取分块的关键信息,支持业务规则扩展:
- 基础元数据:source(来源)、length(文本长度)、word_count(词数)、created_at(创建时间)
- 业务元数据:通过规则抽取餐厅名、评分、价格等(从评价文本或原始数据中提取)
4. 分块持久化(save_chunks_to_db())
将分块结果写入DocumentChunk表,关键处理:
- 生成唯一chunk_id:基于「来源 + 分块文本 + 分块索引」的 MD5 值
- 元数据序列化:将分块元数据字典转为 JSON 字符串,存储到chunk_metadata字段
3.2 向量存储服务(vector_store.py)
封装 ChromaDB 的核心操作,统一向量生成与检索逻辑,避免直接依赖向量库 API:
1. 初始化流程(_initialize_chroma())
def _initialize_chroma(self):
# 1. 创建持久化客户端
self.client = chromadb.PersistentClient(path=CHROMA_PERSIST_DIRECTORY)
# 2. 获取或创建集合(带描述元数据)
self.collection = self.client.get_or_create_collection(
name="food_reviews",
metadata={"description": "外卖评价知识库"}
)
2. 向量生成(generate_embeddings())
- 统一使用 SentenceTransformer 生成向量,避免依赖 ChromaDB 的embedding_function
- 输入:文本列表;输出:浮点数组列表(每个文本对应一个向量)
- 优势:便于切换嵌入模型,且支持本地生成,降低网络依赖
3. 相似检索(search_similar())
支持带过滤条件的精准检索,核心代码片段:
def search_similar(self, query: str, top_k: int = TOP_K_RESULTS,
filter_metadata: Optional[Dict] = None) -> List[Dict[str, Any]]:
try:
# 1. 生成查询向量
query_embedding = self.generate_embeddings([query])[0]
# 2. 构建查询参数(支持过滤条件)
query_params = {
"query_embeddings": [query_embedding],
"n_results": top_k
}
if filter_metadata:
query_params["where"] = filter_metadata # 如:{"restaurant_name": "麦当劳"}
# 3. 执行检索并格式化结果
results = self.collection.query(**query_params)
# 4. 计算相似度(1 - distance)并过滤阈值
formatted_results = []
for i in range(len(results["ids"][0])):
similarity = 1 - results["distances"][0][i]
if similarity >= SIMILARITY_THRESHOLD:
formatted_results.append({
"chunk_id": results["ids"][0][i],
"text": results["documents"][0][i],
"score": round(similarity, 4),
"metadata": results["metadatas"][0][i]
})
return formatted_results
except Exception as e:
self.logger.error(f"检索失败: {str(e)}")
raise
4. 数据库同步(sync_from_database())
解决分块与向量库的一致性问题:
- 读取关系库中vector_id为None的分块(未写入向量库的分块)
- 反序列化chunk_metadata(JSON 字符串→字典),避免误用 SQLAlchemy 的MetaData对象
- 写入向量库后,回填vector_id(与chunk_id保持一致)并提交事务
3.3 RAG 服务编排(rag_service.py)
作为系统核心大脑,整合向量检索、LLM 生成与日志记录,实现端到端 RAG 流程:
1. 服务依赖注入
class RAGService:
def __init__(self):
self.vector_store = VectorStore() # 向量检索服务
self.llm_service = LLMService() # LLM调用服务
self.document_processor = DocumentProcessor() # 文档处理服务
self.db = SessionLocal() # 数据库会话
2. 查询主流程(query())
包含 “检索→生成→日志→响应” 四步,支持业务路由:
- 检索阶段:调用vector_store.search_similar()获取高相似度分块,支持按元数据过滤
- 生成阶段:根据查询类型(推荐 / 对比 / 问答)路由生成逻辑,构建 Prompt 调用 LLM
- 日志阶段:将查询、响应、耗时等信息写入QueryLog
- 响应阶段:封装QueryResponse返回(含回答、检索片段、响应时间)
3. 数据写入流程
支持两种数据写入方式,覆盖不同场景:
| 功能 | 流程 | 适用场景 |
|---|---|---|
| 新增评价 | add_food_review() → 分块处理 → 写入 DocumentChunk → 同步向量库 | 单个外卖评价录入 |
| 上传文档 | add_document() → 分块处理 → 写入 DocumentChunk → 同步向量库 | 批量评价文档导入 |
四、API 层与系统生命周期
4.1 API 设计(main.py)
基于 FastAPI 实现 RESTful API,自动生成 Swagger 文档(访问/docs查看):
| 接口路径 | 请求方法 | 功能 | 输入参数 | 输出参数 |
|---|---|---|---|---|
| / | GET | 根路径状态 | - | 系统版本、服务状态 |
| /health | GET | 健康检查 | - | 关系库、向量库、LLM 的可用性 |
| /query | POST | RAG 查询 | QueryRequest(query、top_k 等) | QueryResponse(回答、检索片段等) |
| /reviews | POST | 新增外卖评价 | FoodReviewCreate(餐厅名、菜品名等) | 分块创建数量、同步状态 |
| /documents | POST | 上传文档 | DocumentUploadRequest(content、filename) | 分块创建数量、同步耗时 |
| /stats | GET | 系统统计 | - | 评价数、分块数、向量数、查询次数 |
| /search | GET | 仅检索(无生成) | query、top_k、filter_metadata | 检索到的分块列表 |
4.2 系统生命周期管理
通过 FastAPI 的lifespan机制管理服务启停资源:
@app.lifespan("startup")
async def startup_event():
# 1. 创建数据库表
create_tables()
# 2. 初始化RAG服务(含向量库、LLM服务)
global rag_service
rag_service = RAGService()
# 3. 同步关系库中未入向量库的分块
rag_service.vector_store.sync_from_database()
logger.info("系统启动完成,服务就绪")
@app.lifespan("shutdown")
async def shutdown_event():
# 关闭数据库会话
rag_service.db.close()
logger.info("系统关闭,资源已释放")
五、配置与运维实践
5.1 配置管理(config.py)
集中式配置便于环境切换,核心配置项分类:
| 配置类别 | 关键参数 | 说明 |
|---|---|---|
| API 参数 | API_HOST、API_PORT、API_DEBUG | 服务监听地址、端口、调试模式 |
| 嵌入 / 向量库 | EMBEDDING_MODEL、CHROMA_PERSIST_DIRECTORY、TOP_K_RESULTS | 嵌入模型、向量库路径、检索返回数量 |
| 数据库 | DATABASE_URL(如sqlite:///./food_reviews.db) | 关系库连接地址 |
| 日志 | LOG_LEVEL、LOG_FILE | 日志级别、日志文件路径 |
5.2 关键运维点
- 数据一致性:定期执行vector_store.sync_from_database()确保分块与向量同步
- 性能优化:调整chunk_size(避免语义模糊 / 丢失上下文)、更换高维度嵌入模型提升检索精度
- 错误排查:通过logs/rag_service.log查看详细错误堆栈,用/health快速定位故障组件
六、常见问题与解决方案
6.1 环境部署类
Q1:初次运行向量库无数据?
A1:① 先执行init_data_simple.py插入基础数据;② 启动 Ollama 服务并拉取模型;③ 执行init_data.py完成向量化。
Q2:Windows 中文输出乱码?
A2:① PowerShell 执行chcp 65001切换 UTF-8 编码;② 或在 Python 中添加:
import sys
sys.stdout.reconfigure(encoding='utf-8')
6.2 功能异常类
Q3:检索结果不相关?
A3:① 确认嵌入模型一致性;② 调整SIMILARITY_THRESHOLD阈值;③ 优化chunk_size分块长度。
Q4:LLM 调用失败?
A4:① 检查 Ollama 服务状态(curl http://localhost:11434/api/version);② 确认模型已拉取(ollama list);③ 查看日志定位错误。
七、核心链路总结
7.1 数据写入链路
评价/文档 → DocumentProcessor(清洗/分块/提元数据) → DocumentChunk表(关系库) → VectorStore.sync_from_database() → ChromaDB(向量库)
7.2 数据查询链路
POST /query → VectorStore.search_similar()(ChromaDB检索) → LLMService生成回答 → QueryLog记录日志 → 返回QueryResponse