RAG with Milvus


title: “使用 Milvus 的 RAG” weight: 400


在本模块中,我们将使用检索增强生成(Retrieval-Augmented Generation,RAG)将 agent 连接到产品目录。Milvus 已经部署在我们的环境中。连接完成后,agent 会用有依据的结果来回答产品问题(例如"我们们有 100 美元以下的无线耳机吗?"),而不是靠猜测。

什么是 Milvus?

Milvus 是一个开源向量数据库,专为大规模相似性搜索而构建。我们将数据存储为高维向量(embeddings),然后通过"找出离这个向量最近的向量"来进行查询。它支持多种索引类型(IVF、HNSW、DiskANN)、混合搜索(向量 + 标量过滤器),并且可以从单 pod 独立模式扩展到分布式集群。

为什么在本次研讨会中使用 Milvus?

  • 完全运行在 EKS 上:Helm chart 将 Milvus standalone、etcd 和 MinIO 作为 pod 部署在 milvus 命名空间中。无需外部向量数据库服务。
  • 为 agent 提供 RAG 支持:产品描述和常见问题(FAQs)以 embeddings 形式存储。当客户询问产品问题时,agent 会将查询嵌入化,在 Milvus 中搜索最接近的匹配项,并将其作为上下文提供给 LLM。LLM 根据真实的目录数据来回答,而不是靠猜测。
  • 轻量级嵌入:我们使用带有 all-MiniLM-L6-v2fastembed (ONNX runtime,约 20 MB),而不是完整的 PyTorch。相同的向量,镜像却小了约 80%。
  • 与 AgentCore Memory 互补:Milvus 和 AgentCore Memory 解决的是不同的问题。Milvus 从外部来源(如产品目录和 FAQs)检索知识。AgentCore Memory 存储对话历史,以便 agent 能够回忆起之前的会话。两者都作为记忆的一种形式,但各自扮演着不同的角色。

工作原理

  1. 产品描述以向量 embeddings 的形式存储在 Milvus 中
  2. 客户询问产品问题 → agent 调用 search_products
  3. 该工具使用 fastembed (ONNX runtime,相同的 all-MiniLM-L6-v2 权重)将查询嵌入化,并在 Milvus 中搜索相似的条目
  4. LLM 使用这些匹配项来撰写有帮助的答案

步骤 1:验证 Milvus

kubectl get pods -n milvus

我们应该看到 milvus-standaloneetcdminio pod 正在运行。

步骤 2:代码演练

cd ~/environment/modules/20-self-managed/400-rag-milvus/customer-agent

使用 Langfuse 的可观测性实验 相比,本模块引入了两个新文件:rag_tools.py,它定义了产品搜索工具;以及 seed_products.py,它作为一次性设置步骤将目录加载到 Milvus 中。除了这些新增内容外,改动很小。agent.py 添加了一个 import,而 Dockerfile 切换到了多阶段构建,以便将嵌入模型权重打包到最终镜像中。

_embedder = TextEmbedding(
    model_name="sentence-transformers/all-MiniLM-L6-v2",
    cache_dir="/app/.fastembed_cache",
)
_client = MilvusClient(uri=MILVUS_URI)

@tool
def search_products(query: str, limit: int = 5) -> list:
    """Search the AnyCompany Shop product catalog and FAQs.

    Use this when a customer asks about products, pricing, shipping, returns, or warranties.
    """
    embeddings = [v.tolist() for v in _embedder.embed([query])]
    results = _client.search(COLLECTION, data=embeddings, ...)
  • fastembed 是来自 Qdrant 的轻量级嵌入库,它通过 ONNX runtime 运行 all-MiniLM-L6-v2 模型。镜像中没有 Python ML 框架。
  • Embedder 和 Milvus 客户端是模块级的,在 import 时加载一次,在各次调用中重复使用。Milvus 客户端不是线程安全的;对于多线程 agent,请对其进行池化。
  • docstring 的第一行告诉 LLM 何时 选择这个工具。这就是选择的启发式方法。

FROM public.ecr.aws/docker/library/python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
RUN python -c "from fastembed import TextEmbedding; TextEmbedding(model_name='sentence-transformers/all-MiniLM-L6-v2', cache_dir='/app/.fastembed_cache')"

FROM public.ecr.aws/docker/library/python:3.12-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY --from=builder /app/.fastembed_cache /app/.fastembed_cache

Builder 预先将 ONNX 模型下载到 /app/.fastembed_cache 中,运行时阶段只复制这些内容。pod 启动时无需访问 HuggingFace Hub。

步骤 3:填充产品目录

seed_products.py 定义了约 13 个产品和 FAQs,将它们嵌入化,并插入到 product_catalog 集合中。与其为了一次性填充而在我们的 IDE 上安装 fastembed + pymilvus,我们将该脚本打包在 customer-agent 镜像中,并从集群中的一个 pod 运行它。

customer-agent:milvus 镜像在配置期间已经预先构建并推送到 ECR,因此直接运行 seeder:

IMG=$ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/customer-agent:milvus
kubectl run milvus-seed --rm -i --restart=Never \
  --image=$IMG \
  --image-pull-policy=Always \
  --env="MILVUS_URI=http://milvus.milvus.svc.cluster.local:19530" \
  --command -- python seed_products.py

我们应该看到 “Inserted 13 items” 以及针对 “wireless headphones” 的测试搜索返回了匹配项。脚本退出时该 pod 会消失。

步骤 4:部署 agent

envsubst < k8s.yaml | kubectl apply -f -
kubectl rollout status deployment/customer-agent --timeout=180s

步骤 5:聊天

打开聊天 UI,选择 Customer Agent (Self-managed GenAI),并尝试产品问题:

What's your return policy?
Do you have any noise cancelling headphones?
Is the Laptop Pro under warranty?

在 Langfuse 中,注意新的 search_products span,嵌入 + 搜索通常需要 50-200ms。

我们构建了什么

agent 现在有两个工具:lookup_order 用于特定订单,search_products 用于目录/FAQ 问题。产品知识由真实的 embeddings 支持,而不是硬编码的 switch 语句。

下一步

订单仍然在 agent 进程内部被模拟。接下来我们将把工具迁移到运行在 EKS 上的真实 MCP server 上。