源码级实战!本地搭建外卖评价知识库RAG系统也能这么简单

145 阅读9分钟

基于 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.pyRAG 流程编排(检索 + 生成)自定义服务编排
main.pyAPI 入口与生命周期管理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())

包含 “检索→生成→日志→响应” 四步,支持业务路由:

  1. 检索阶段:调用vector_store.search_similar()获取高相似度分块,支持按元数据过滤
  1. 生成阶段:根据查询类型(推荐 / 对比 / 问答)路由生成逻辑,构建 Prompt 调用 LLM
  1. 日志阶段:将查询、响应、耗时等信息写入QueryLog
  1. 响应阶段:封装QueryResponse返回(含回答、检索片段、响应时间)
3. 数据写入流程

支持两种数据写入方式,覆盖不同场景:

功能流程适用场景
新增评价add_food_review() → 分块处理 → 写入 DocumentChunk → 同步向量库单个外卖评价录入
上传文档add_document() → 分块处理 → 写入 DocumentChunk → 同步向量库批量评价文档导入

四、API 层与系统生命周期

4.1 API 设计(main.py

基于 FastAPI 实现 RESTful API,自动生成 Swagger 文档(访问/docs查看):

接口路径请求方法功能输入参数输出参数
/GET根路径状态-系统版本、服务状态
/healthGET健康检查-关系库、向量库、LLM 的可用性
/queryPOSTRAG 查询QueryRequest(query、top_k 等)QueryResponse(回答、检索片段等)
/reviewsPOST新增外卖评价FoodReviewCreate(餐厅名、菜品名等)分块创建数量、同步状态
/documentsPOST上传文档DocumentUploadRequest(content、filename)分块创建数量、同步耗时
/statsGET系统统计-评价数、分块数、向量数、查询次数
/searchGET仅检索(无生成)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 关键运维点

  1. 数据一致性:定期执行vector_store.sync_from_database()确保分块与向量同步
  1. 性能优化:调整chunk_size(避免语义模糊 / 丢失上下文)、更换高维度嵌入模型提升检索精度
  1. 错误排查:通过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