首页 / 文章 / 在单个EC2上实现仅VPC接入的RAG:Ollama、Qdrant与LangChain

在单个EC2上实现仅VPC接入的RAG:Ollama、Qdrant与LangChain

用带有引用功能的操作手册助手取代传统部落知识,该助手能确保文档始终不会离开您的 AWS 账户——由 GPU EC2 上的三个容器构成。

1723 词

非官方知识查询问题

每个工程团队都有掌握答案的人:部署流程是怎样的、休假政策规定是什么、为何Kafka能取代SQS。但一旦这些人请假、转岗,或者在新故障发生时深夜11点有新人需要答案,问题就出现了。

更好的方式是:任何人只要输入“我可以在周五部署吗?”,几秒钟内就能得到有据可查的答案——基于真实的操作手册,并附上引用来源。既不是猜测,也不是凭空编造的谎言。

设计时有一个核心约束:任何内部文档都不得离开AWS账户。不能使用OpenAI密钥,也不能使用托管的向量数据库。所有功能都必须在单个EC2实例的VPC内运行。

你能获得什么

一个简单的 Streamlit 聊天界面。提出问题后,可查看相关来源,了解回复所依据的文件和章节。示例:支付服务的依赖信息来自 deployment-runbook.md,而非编造的主机名。

架构——三个容器

执行一次 docker compose up 即可启动:

version: "3.9"

services:
  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    ports:
      - "11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    restart: unless-stopped

  qdrant:
    image: qdrant/qdrant:v1.18.2
    container_name: qdrant
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - qdrant_data:/qdrant/storage
    environment:
      - QDRANT__SERVICE__ENABLE_CORS=true
    restart: unless-stopped

  app:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: rag-app
    ports:
      - "8501:8501"
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434
      - QDRANT_URL=http://qdrant:6333
      - COLLECTION=company_docs
      - EMBED_MODEL=nomic-embed-text
      - LLM_MODEL=llama3.1:8b
      - S3_CORPUS_BUCKET=${S3_CORPUS_BUCKET}
      - S3_CORPUS_PREFIX=${S3_CORPUS_PREFIX:-corpus/}
      - AWS_REGION=${AWS_REGION:-us-east-1}
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-}
    volumes:
      - corpus_data:/app/corpus
      - ./scripts:/app/scripts
    depends_on:
      - ollama
      - qdrant
    restart: unless-stopped

volumes:
  ollama_data:
  qdrant_data:
  corpus_data:
  • Ollama — 本地嵌入模型以及聊天模型(例如 llama3.1)。无需 API 密钥,也不会按令牌计费。
  • Qdrant — 用于存储分块向量并快速检索最近邻数据。
  • LangChain — 负责检索、上下文整合以及生成内容,并附带引用信息。

所有组件都运行在 VPC 内的 GPU EC2 实例上,因此数据集永远不会跨出边界。

问题处理流程

采用 LCEL 风格的链式结构来检索、格式化数据,并向本地聊天模型发送提示词:

from operator import itemgetter
from langchain_core.runnables import RunnableLambda, RunnableParallel

chain = RunnableParallel(
    question=itemgetter("question"),           # pass question through
    docs=itemgetter("question") | retriever,   # embed + search Qdrant
).assign(
    answer=(
        {"context": itemgetter("docs") | RunnableLambda(format_docs),
         "question": itemgetter("question")}
        | prompt_template    # inject into system/human message
        | llm                # send to Ollama
        | str_parser         # extract text from response
    )
)

检索部分会保持您配置的词汇密集型特性,生成则由 Ollama 承担。引用信息来自 Qdrant 返回的数据块中的元数据。

数据导入与操作注意事项

将 Markdown 文档或运行手册放入数据导入路径,将其分割成数据块,使用本地嵌入模型进行处理,然后插入 Qdrant。文档有变动时需重新执行该流程。注意监控模型占用的磁盘空间、并发请求占用的 GPU 内存,以及安全组设置,确保只有 VPN 或堡垒机能够访问 Streamlit 和 Qdrant。

为何这种模式适合内部知识管理

您以略低的模型峰值质量为代价,换取了数据集中管理与成本控制的优势。对于“运行手册上是怎么规定的?”这类问题,这种权衡是合理的。即使 Aaron 不在,运行手册依然能提供答案,同时引用信息会指示验证位置。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

让部署过程尽可能简单:使用 Compose 构建,加入健康检查功能,并提供一个统一的位置来切换模型。正是这种简单性让内部工具能够在面对真实的值班安排时依然正常运行。

限制条件:数据绝不能离开 VPC

这一不可妥协的规则决定了所有供应商的选择。托管型、兼容 OpenAI 的 API 被排除在外,托管式向量数据库也被排除,甚至“临时”的预发布上传也不被允许。最终只剩下一个 EC2 GPU 实例、一个 compose 文件以及 IAM/安全组的相关配置。

如果您的法务团队无法为 EC2 ENI 设定明确边界,那么这种架构就不适合您。如果他们能做到,即便不将 PDF 发送给第三方,也能确保合规性。

数据摄取流程详情

操作手册和异常处理方案会以 markdown 格式存储在指定目录中。任务会将这些文件拆分成多个重叠的片段,通过 Ollama 使用本地嵌入模型进行处理,并将包含路径和章节元数据的记录插入 Qdrant。出现故障时应中止该文件的插入操作,而非默默丢弃半份政策文档。

当文件哈希值发生变化时需重新嵌入内容。如果能够通过路径差异进行区分,就无需在每次微小修改时都重新嵌入整个语料库。

查询时的行为表现

Streamlit 应用会将用户问题发送给配置了较小 k 值的检索系统。检索到的文本会被格式化为提示语,要求聊天模型仅依据上下文作答,且在上下文不足时拒绝回答。引用信息会从元数据中生成,以便用户能够直接查看对应的运行手册章节。

这种在上下文不足时拒绝作答的机制比温度控制参数更为重要。充满流畅却无意义的文字加上虚假引用,远不如“我在运行手册中找不到相关内容”那样可靠。

需要考虑的故障模式

当有人加载更大的聊天模型却未增加实例规模时,会导致GPU内存不足。如果导入二进制垃圾数据,Qdrant的磁盘容量也会不断增长。流程变更后若有旧答案未被重新导入,就会失效。将端口绑定到公共子网上的0.0.0.0的Compose堆栈应设置为私有模式。

值班人员需掌握如何重启Ollama、从源Markdown文件重新构建Qdrant数据卷,以及确认关键问题仍引用正确的文件。

“完成”的标准

新员工入职第二天就能提出部署相关问题,而无需联系Aaron。答案中会包含文件路径。VPC边界处于持续审查状态。成本仅为一个EC2实例加上存储费用——十分可预测。对于内部知识助手而言,这已算是成功的标准;只有在引用内容可信之后,才需追求更先进的模型质量。