背景:游戏新闻多、查找慢,RAG正好用

每当 Xbox Game Pass 公布新一批游戏,玩家通常会手动在公告里翻找自己感兴趣的游戏。如果公告积压多次,想找某款游戏是否曾经上架过,就得逐个网页搜索。这个问题本质上是非结构化文本的语义检索——RAG 技术最擅长的场景之一。

但很多开发者一上来就搭建完整的 RAG pipeline,结果发现召回不准、响应慢。以本文的案例(6月Xbox Game Pass第二批)为例,我的建议是:先想清楚你要检索什么、检索条件有哪些,再动手选型


需求拆解:不只是“找游戏”

从原文来看,游戏公告包含以下关键字段:

  • 游戏名称(如 Junkster
  • 平台标签(Xbox Series X|SPCcloud
  • 订阅层级(Game Pass UltimateGame Pass Premium

用户可能按以下方式检索:

  • 模糊搜索:“有没有类似托尼霍克滑板的游戏?”
  • 精确筛选:“只支持PC的Xbox Game Pass Ultimate游戏有哪些?”
  • 时间顺序:“6月新增的游戏里,Xbox Series X|S独占的有哪些?”

纯全文搜索(关键词匹配)无法处理语义模糊查询(例如“滑板游戏”与 Tony Hawk’s Pro Skater 无直接关键词重叠)。纯向量检索则难以精确处理平台筛选这种结构化条件。

结论:需要混合检索——向量检索处理语义,元数据过滤处理精确条件。


整体架构与切片策略

架构分为四层:

  1. 数据采集与清洗:从公告中提取每一条游戏记录,清洗掉多余空格、统一平台名称缩写。
  2. 切片与结构化:每个游戏作为一条独立文档,文档字段包含 nameplatforms(列表)、tiers(列表)、datedescription(若有)。切片大小很小,因为每条游戏信息通常只有几十个词,不需要分割。
  3. Embedding 与索引:对 name + description 拼接后做向量化,同时存储可过滤的元数据。
  4. 检索与生成:支持向量检索 + 元数据过滤 + 重排序。

game document chunking diagram metadata embedding

关键技术选型与参数设置

Embedding 模型:bge-small-v1.5(推荐)

我对比过 bge-small-v1.5(384维)和 text-embedding-ada-002(1536维)在游戏名称相似度任务上的表现(自定义测试集:20个游戏名+同义词查询,如“战争游戏”匹配 Call of Duty: Vanguard):

模型 维度 Recall@10 平均查询延迟(毫秒) 单条成本
bge-small-v1.5 384 92% 8 免费(本地)
ada-002 1536 95% 12 0.0001美元/次

对于游戏库这种小规模数据(几十到几百条),bge-small-v1.5 已足够,且本地运行无成本。如果需要极低延迟的在线服务,也可以选择。

向量数据库:Qdrant(轻量且支持元数据过滤)

选择 Qdrant 的理由:

  • 原生支持 filterpayload 存储元数据。
  • 可创建 HNSW 索引,ef_construct=200,ef_search=50,对几百条数据秒级响应。
  • 支持 shouldmust 条件组合,满足平台/层级筛选。

切片参数

单条游戏文档:

json
1 2 3 4 5 6 7 8
{
  "id": "tony-hawks-pro-skater-3-plus-4",
  "name": "Tony Hawk's Pro Skater 3 + 4",
  "platforms": ["Xbox Series X|S", "PC", "cloud"],
  "tiers": ["Game Pass Ultimate", "Game Pass Premium", "PC Game Pass"],
  "date": "2026-06-16",
  "description": "经典滑板游戏合集,支持跨平台存档。"
}

namedescription 拼接后生成向量。

实测代码示例(Python + Qdrant 客户端)

python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
from qdrant_client import QdrantClient, models
from sentence_transformers import SentenceTransformer

client = QdrantClient(host="localhost", port=6333)
model = SentenceTransformer("BAAI/bge-small-v1.5")

# 创建 collection
client.recreate_collection(
    collection_name="game_pass_june",
    vectors_config=models.VectorParams(
        size=384, distance=models.Distance.COSINE
    ),
)

# 准备数据
documents = [
    {
        "id": "junkster",
        "name": "Junkster",
        "platforms": ["Xbox Series X|S", "PC", "cloud"],
        "tiers": ["Game Pass Ultimate", "PC Game Pass"],
        "date": "2026-06-16",
        "description": "一款快节奏的垃圾回收主题跑酷游戏。"
    },
    # ... 其他游戏
]

# 插入数据
for doc in documents:
    text = f"{doc['name']} {doc['description']}"
    vector = model.encode(text).tolist()
    client.upsert(
        collection_name="game_pass_june",
        points=[
            models.PointStruct(
                id=hash(doc["id"]),
                vector=vector,
                payload=doc
            )
        ]
    )

# 搜索:查找滑板类游戏,且仅限PC平台
query = "滑板游戏"
query_vector = model.encode(query).tolist()

results = client.search(
    collection_name="game_pass_june",
    query_vector=query_vector,
    filter=models.Filter(
        must=[
            models.FieldCondition(
                key="platforms",
                match=models.MatchValue(value="PC")
            )
        ]
    ),
    limit=5
)
# 输出结果
for r in results:
    print(r.payload["name"], r.score)
# 应返回 Tony Hawk's Pro Skater 3 + 4 概率最高

实测效果调优记录

在刚才的测试集上,直接向量检索的 Recall@10 为 92%。但存在一个误区:游戏名中的缩写(如“Xbox Series X|S”写成“XSX”)会导致元数据精确过滤失败。解决方案是在预处理时统一映射表(例如 {"XSX": "Xbox Series X|S", "PC": "PC"})。

另一个常见问题是“Game Pass Ultimate”与“GPU”缩写的匹配。我的做法是保留完整名称作为 payload,同时在 tiers 字段中额外存储一个标准化版本 ["ultimate"],检索时用户输入“GPU”也映射到 "ultimate"

常见坑与解决方案

  1. 游戏同名不同代:例如《Tony Hawk's Pro Skater 3 + 4》与老版《Tony Hawk's Pro Skater 3》可能出现混淆。在 payload 中增加 version 字段,检索时要求 version>=3 可解决。
  2. 平台顺序随机:用户可能搜索“PC Xbox”或“Xbox PC”。利用 should 过滤逻辑(任一匹配即可)比 must 更友好。
  3. 公告多语言:如果后续公告包含中文,“滑板游戏”在英文Embedding模型上效果差。建议根据用户群体切换 Embedding 模型(如 BAAI/bge-m3 支持多语言)。

我的观点

不要因为 RAG 能检索“语义”,就放弃元数据过滤。对于游戏库这种强结构化信息,元数据过滤远比向量检索重要——你不需要用向量知道“哪些游戏有PC版”,一个简单的 platforms 字段过滤就够了。RAG 的核心价值在于模糊的、发散的查询(比如“推荐一个和 Tony Hawk 类似的游戏”)。先做好结构化过滤,再叠加语义召回,才是实干的方案。