从零开始搭建RAG系统系列(八):RAG系统部署流程

353 阅读11分钟

将开发好的RAG系统部署到实际应用环境中,是使其发挥价值的关键一步。部署方案的选择通常取决于应用的规模、预期的并发量、对可用性和成本的考量等因素。本节将介绍两种常见的部署思路:本地部署(适用于快速验证和小型应用)和云平台部署(适用于生产环境)。

本地部署方案

适用场景:

  • 快速原型验证和功能演示。
  • 个人项目或小团队内部使用。
  • 对数据隐私有极高要求,不希望数据离开本地环境的场景。
  • 计算资源受限,但仍希望运行一个小型RAG应用的场景。

关键步骤:

1. 将RAG Pipeline封装为API接口:

为了方便其他应用或前端界面调用RAG系统的能力,通常会将其核心逻辑封装成一个HTTP API接口。Python的`FastAPI`框架因其高性能、易用性以及对异步操作的良好支持,是构建此类API的优秀选择。

下面是一个使用FastAPI封装前面构建的`rag_chain_with_sources`的简单示例:

创建一个名为 `main_api.py` 的文件:

# main_api.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Dict, Any
import uvicorn # 用于运行FastAPI应用

# --- 假设以下RAG组件和链已在别处定义并可导入 ---
# --- 或者,为了独立运行,可以将之前步骤的代码整合到这里或一个初始化函数中 ---

# 伪代码: 导入或重新定义之前步骤的 RAG 组件和链
# from your_rag_module import rag_chain_with_sources, Document # 假设你把RAG逻辑放到了一个模块

# ---- START: 临时的RAG组件定义 (实际项目中应从模块导入或正确初始化) ----
# 这是一个非常简化的模拟,实际你需要完整的RAG链
class MockDocument:
    def __init__(self, page_content: str, metadata: Dict[str, Any]):
        self.page_content = page_content
        self.metadata = metadata

# 模拟 rag_chain_with_sources 的行为
def pseudo_rag_invoke(query: str) -> Dict[str, Any]:
    if "RAGFlow功能" in query:
        return {
            "answer": "RAGFlow的主要功能包括数据接入、智能分块、向量化索引等。",
            "context_docs": [
                MockDocument("RAGFlow的主要功能包括:1. 数据接入...", {"source": "features.txt"}),
                MockDocument("RAGFlow项目由ABC Lab发起...", {"source": "project_intro.txt"})
            ],
            "user_query": query
        }
    return {
        "answer": "抱歉,根据我目前掌握的信息,我无法回答您的问题。",
        "context_docs": [],
        "user_query": query
    }
# rag_chain_with_sources = pseudo_rag_invoke # 替换为实际的链
# ---- END: 临时的RAG组件定义 ----

# ---------------------------------------------------------------------------
# !! 重点:在实际应用中,你需要确保下面这行能够正确加载或构建你的RAG链 !!
# !! 例如,通过一个初始化函数在应用启动时加载模型和数据库。      !!
# ---------------------------------------------------------------------------
# from rag_pipeline_setup import get_rag_chain # 假设这是你的设置脚本
# rag_chain_with_sources = get_rag_chain() # 这是理想的加载方式

# 为了这个示例能直接运行,我们继续用上面的pseudo_rag_invoke
# 如果您已在Jupyter Notebook或其他脚本中成功运行了步骤五的代码,
# 可以将 rag_chain_with_sources 的定义部分复制并粘贴到此文件的全局作用域
# 或者封装到一个初始化函数中,在FastAPI启动时调用。
# 确保所有依赖 (ChromaDB, Embedding模型, LLM) 都能被FastAPI进程访问。

app = FastAPI(
    title="RAG System API",
    description="一个简单的API,用于与RAG系统进行问答交互。",
    version="0.1.0"
)

# 定义请求体模型
class QueryRequest(BaseModel):
    query: str
    top_k_retrieval: int = 3 # 可选参数,指定检索数量

# 定义响应体模型
class SourceDocumentModel(BaseModel):
    source: str
    content_preview: str

class RAGResponse(BaseModel):
    answer: str
    sources: List[SourceDocumentModel] # 返回源文档信息

@app.post("/ask", response_model=RAGResponse)
async def ask_rag_endpoint(request: QueryRequest):
    """
    接收用户查询,通过RAG系统生成答案并返回,同时附带参考的源文档信息。
    """
    if not request.query:
        raise HTTPException(status_code=400, detail="查询内容不能为空")

    try:
        # 注意:实际调用时,如果你的RAG链(如retriever部分)支持动态调整k,可以在这里传递
        # result = rag_chain_with_sources.invoke(request.query, config={"configurable": {"retriever_k": request.top_k_retrieval}})
        # 为简化,这里我们假设k是固定的或在链定义时已设定。
        # 我们将使用上面定义的伪函数以便示例运行
        
        # 【【【 替换这里为你实际的RAG链调用 】】】
        # 假设你的 rag_chain_with_sources 是一个可调用的对象 (如LCEL链)
        # 在真实场景中,rag_chain_with_sources 要么在全局初始化,要么通过依赖注入获取
        # 例如:
        # from my_rag_implementation import rag_chain_with_sources # 假设你的链在这里
        # result = rag_chain_with_sources.invoke(request.query)
        
        # 使用伪函数进行演示
        result = pseudo_rag_invoke(request.query) # 替换此行

        if not result or "answer" not in result or "context_docs" not in result:
             raise HTTPException(status_code=500, detail="RAG系统未能生成有效结果。")

        response_sources = []
        for doc in result.get("context_docs", []):
            response_sources.append(SourceDocumentModel(
                source=doc.metadata.get("source", "未知来源"),
                content_preview=doc.page_content[:150] + "..." # 截取预览
            ))
        
        return RAGResponse(
            answer=result["answer"],
            sources=response_sources
        )
    except Exception as e:
        # 更细致的错误处理,例如区分是检索错误还是LLM调用错误
        print(f"API请求处理错误: {e}") # 打印到服务器日志
        raise HTTPException(status_code=500, detail=f"处理请求时发生内部错误: {str(e)}")

# (可选) 添加一个根路径用于健康检查或API文档说明
@app.get("/")
async def root():
    return {"message": "欢迎使用 RAG System API! 访问 /docs 查看API文档。"}

# 如果希望直接运行此文件 (例如 python main_api.py)
if __name__ == "__main__":
    # 警告:直接用 uvicorn.run() 启动在 __main__ 中可能不总是最佳实践,
    # 特别是对于需要更复杂配置(如重载、多worker)的生产场景。
    # 通常推荐在命令行使用 `uvicorn main_api:app --reload`。
    print("启动API服务,访问 http://127.0.0.1:8000")
    print("API文档位于 http://127.0.0.1:8000/docs")
    uvicorn.run("main_api:app", host="127.0.0.1", port=8000, reload=True)


# --- 要使此API能与步骤五的RAG链一起工作,您需要: ---
# 1. 将步骤一到五的代码组织成可导入的模块或一个初始化函数。
#    例如,创建一个 rag_pipeline_setup.py 文件,包含所有RAG组件的加载和链的构建,
#    并提供一个函数如 get_rag_chain() 来返回构建好的 rag_chain_with_sources。
# 2. 在 main_api.py 中导入并调用这个初始化函数,如:
#    from rag_pipeline_setup import get_rag_chain
#    rag_chain_with_sources = get_rag_chain()
# 3. 确保所有依赖的模型文件(如BGE模型)、向量数据库文件(ChromaDB持久化目录)
#    对于FastAPI运行的Python进程是可访问的,路径正确。
# 4. 如果使用OpenAI或本地Ollama,确保API Key或服务连接在API环境中也有效。

2.运行Web服务:

在终端中,导航到包含`main_api.py`文件的目录,然后使用`uvicorn`命令启动应用:

```
uvicorn main_api:app --host 0.0.0.0 --port 8000 --reload
```

其中:

-   `main_api`: 指的是Python文件名 (`main_api.py`)。
-   `app`: 指的是在`main_api.py`中创建的FastAPI实例 (`app = FastAPI()`)。
-   `--host 0.0.0.0`: 使API服务可以从本地网络中的其他设备访问(如果需要)。如果仅本地访问,可用`127.0.0.1`-   `--port 8000`: 指定服务监听的端口。
-   `--reload`: 开发时使用,当代码文件发生变化时,服务会自动重启。生产环境不应使用此选项。

服务启动后,你可以通过浏览器访问 `http://127.0.0.1:8000/docs` 查看FastAPI自动生成的交互式API文档(基于Swagger UI),并直接在文档页面测试`/ask`接口。

工具推荐:

  • Docker:  为了确保运行环境的一致性并方便分发,强烈建议使用Docker将RAG应用及其所有依赖(包括Python环境、系统库、甚至本地模型文件和向量数据库)打包成一个容器镜像。你需要编写一个Dockerfile来定义构建镜像的步骤,包括复制应用代码、安装依赖、设置启动命令等。
  • Streamlit / Gradio:  如果你除了API外,还想快速搭建一个简单的Web UI界面进行交互式演示或测试,这两个Python库非常方便。它们可以让你用很少的代码就创建一个用户友好的前端界面,直接调用你的RAG逻辑。

云平台部署方案 (以 AWS 为例,其他云平台类似)

适用场景:

  • 生产环境的RAG应用,需要处理大量并发请求。
  • 对系统的高可用性、弹性伸缩有较高要求。
  • 希望与其他云服务(如数据库、存储、监控、安全服务)进行深度集成。
  • 希望利用云平台提供的托管式AI服务(如托管向量数据库、LLM API)。

核心云服务选择 (AWS示例):  

  • 计算服务 (承载RAG API后端逻辑):

    • AWS Lambda: Serverless计算服务。适合事件驱动、无状态的API。如果RAG链的冷启动时间和执行时间可控,且依赖可以打包在Lambda的限制内,这是一个成本效益高且易于扩展的选择。
    • Amazon EC2: 虚拟服务器。提供完全的控制权,可以部署任何自定义环境和应用。适合对环境有特殊要求或长时间运行的任务。
    • Amazon ECS (Elastic Container Service) / EKS (Elastic Kubernetes Service): 容器编排服务。如果你的RAG应用已经Docker化,ECS或EKS可以帮助你管理、扩展和部署这些容器。
  • 向量数据库服务:

    • Amazon OpenSearch Service: 完全托管的OpenSearch(Elasticsearch的分支)服务,支持k-NN向量搜索插件。
    • Amazon Kendra: 企业级智能搜索服务,内置了部分RAG能力,可以直接连接数据源并提供问答接口,但定制化程度可能不如自建RAG灵活。
    • 在EC2或ECS/EKS上自建开源向量数据库(如Milvus, Qdrant, Weaviate)。
  • LLM服务 (模型推理):

    • Amazon Bedrock: 提供对多种来自AI21 Labs, Anthropic, Stability AI, Amazon等的基础模型(Foundational Models, FMs)的API访问,简化了LLM的集成。
    • Amazon SageMaker: 综合的机器学习平台,可以用于部署自定义训练的LLM,或从Hugging Face Hub等地方获取开源LLM并托管为推理端点。
  • API网关:

    • Amazon API Gateway: 创建、发布、维护、监控和保护API。它可以将HTTP请求路由到Lambda函数、EC2实例、ECS/EKS服务或其他后端。
  • 存储服务:

    • Amazon S3 (Simple Storage Service): 用于存储原始文档、处理后的文本块、Embedding模型文件(如果本地加载)、日志等。通常是RAG数据处理流程的起点。

image.png

上图描绘了一个在AWS云平台上部署RAG系统的高级架构。用户请求通过API Gateway到达后端计算服务(如Lambda),该服务负责执行RAG的核心逻辑:与向量数据库(如Amazon OpenSearch)交互进行检索,并调用LLM服务(如Amazon Bedrock)生成答案。知识库的构建通常是一个独立的离线流程,涉及S3存储、数据处理服务(如Glue或Lambda)以及向量数据库的更新。

简要部署步骤(概览,以Lambda + API Gateway + OpenSearch + Bedrock为例):

  1. 知识库处理与索引(离线):

    • 将原始文档上传到Amazon S3存储桶。

    • 设置一个AWS Lambda函数或AWS Glue ETL作业,当S3中有新文档上传或需要更新时触发。此函数/作业负责:

      • 从S3加载文档。
      • 进行文本分割和向量化(可以使用Bedrock提供的Embedding模型或在Lambda/Glue环境中运行自定义Embedding模型)。
      • 将生成的向量和元数据写入Amazon OpenSearch Service集群中预先配置好的k-NN索引。
  2. RAG API后端(在线):

    • 编写一个AWS Lambda函数,该函数包含RAG Pipeline的在线查询逻辑。它会:

      • 接收来自API Gateway的请求(包含用户查询)。
      • 使用与索引时相同的Embedding模型(例如通过Bedrock调用)对用户查询进行向量化。
      • 使用AWS SDK连接到OpenSearch Service集群,执行向量相似度搜索,获取相关文档块。
      • (可选)执行上下文重排逻辑。
      • 构建Prompt,将查询和检索到的上下文整合。
      • 通过AWS SDK调用Amazon Bedrock API,选择合适的LLM模型进行答案生成。
      • 将生成的答案格式化并返回。
    • 为这个Lambda函数创建一个IAM角色,授予其访问OpenSearch、Bedrock、S3(如果需要直接读取某些配置或数据)等服务的必要权限。

  3. API网关配置:

    • 在Amazon API Gateway中创建一个REST API或HTTP API。
    • 配置一个资源和方法(例如 /ask POST方法),将其与上述Lambda函数集成。
    • 配置请求/响应模型、授权(如IAM授权、Cognito用户池、API密钥)和限流等。
  4. 安全与监控:

    • 严格管理IAM角色和策略,遵循最小权限原则。
    • 使用Amazon CloudWatch进行日志记录(Lambda函数日志、API Gateway访问日志)、指标监控(Lambda调用次数、延迟、错误率,API Gateway指标等)和告警设置。
    • 考虑使用AWS WAF保护API Gateway免受常见Web攻击。

云平台部署提供了强大的可扩展性、可靠性和丰富的配套服务,但也需要对所选云平台的服务有一定了解,并考虑其成本。选择合适的云服务组合是成功的关键