推荐系统过去主要回答“推什么”:召回、粗排、精排、重排,把最可能点击或转化的内容放到前面。但当用户面对的是多款相似游戏时,单纯给出列表并不够。本文讨论的变化点是:不改排序链路,而是在排序之后补上一层“表达与决策”,让推荐结果变得可比较、可解释、可追溯。
这件事的关键不在于让大模型接管推荐系统,而是让它理解游戏、组织差异、生成理由,再用工程约束把输出收住,稳定进入生产。
排序之后,还有一个决策缺口
传统推荐排序擅长处理概率问题:用户会不会点、会不会下载、会不会付费。它通常输出一个有序列表,例如:
- 游戏 A:0.91
- 游戏 B:0.88
- 游戏 C:0.84
但用户真正要做的是选择。尤其在游戏场景里,几个结果可能都属于“开放世界”“二次元”“卡牌养成”或“多人竞技”,它们的差异并不总能从标题、封面、标签里看出来。
排序解决的是系统侧的最优展示,决策辅助解决的是用户侧的理解成本。两者不应混在一起:
- 排序层继续负责候选质量和个性化权重。
- AI 表达层负责解释“为什么这几款值得一起看”。
- 决策层负责把差异压缩成用户能快速判断的信息。
这样做的好处是风险边界清楚:即使 AI 生成失败,也不应该破坏主推荐链路,只需要降级为普通推荐列表。
让大模型“放开探索”,再用工程约束收住
大模型适合做游戏理解:总结玩法、抽取卖点、比较差异、生成自然语言解释。问题是,它也容易发散、幻觉、输出格式不稳定。
所以可落地的做法不是“把排序结果丢给模型,让它自由发挥”,而是分两步:
- 给模型足够上下文,让它探索游戏之间的可比维度。
- 用结构化输出、字段校验、证据引用、降级策略约束生产结果。
例如,模型可以探索这些维度:
- 核心玩法:动作、策略、养成、解谜、射击。
- 节奏强度:碎片化、长线投入、高操作压力。
- 社交方式:单人、弱社交、公会、实时对抗。
- 付费体验:外观、抽卡、通行证、买断。
- 推荐理由:适合什么用户、不适合什么用户。
但进入生产时,输出最好是稳定 JSON,而不是一段不可控长文。前端可以按字段渲染,后端可以记录版本,审核系统可以检查敏感词和事实来源。
可以这样实践:在排序结果后增加 AI 决策卡片
下面是一个最小可改造的 Python 示例。它不依赖真实大模型 API,而是模拟“排序结果 + 游戏元数据 + AI 决策层”的工程形态。实际接入时,可以把 mock_llm_compare 替换为自己的 LLM 调用。
运行前只需要 Python 3.10+。
from dataclasses import dataclass
from typing import list
import json
@dataclass
class Game:
id: str
name: str
rank_score: float
tags: list[str]
description: str
def mock_llm_compare(games: list[Game]) -> dict:
"""
假设这里是大模型输出。
生产环境应替换为真实 LLM 调用,并要求 JSON schema 输出。
"""
return {
"comparison_title": "三款高沉浸游戏怎么选",
"summary": "它们都适合喜欢长期投入的玩家,但侧重点不同:探索、策略和社交压力差异明显。",
"items": [
{
"game_id": "g1",
"best_for": "喜欢开放地图和自由探索的玩家",
"decision_reason": "任务路径更开放,适合慢节奏推进。",
"evidence": ["开放世界", "探索", "单人"]
},
{
"game_id": "g2",
"best_for": "喜欢阵容搭配和数值养成的玩家",
"decision_reason": "核心乐趣在角色组合、资源规划和长期培养。",
"evidence": ["卡牌", "养成", "策略"]
},
{
"game_id": "g3",
"best_for": "喜欢实时对抗和团队配合的玩家",
"decision_reason": "胜负反馈快,但对操作和协作要求更高。",
"evidence": ["多人", "竞技", "实时对战"]
}
]
}
def validate_decision_card(card: dict, games: list[Game]) -> None:
game_ids = {game.id for game in games}
required_top_fields = {"comparison_title", "summary", "items"}
missing = required_top_fields - set(card)
if missing:
raise ValueError(f"missing fields: {missing}")
if not isinstance(card["items"], list) or not card["items"]:
raise ValueError("items must be a non-empty list")
for item in card["items"]:
if item.get("game_id") not in game_ids:
raise ValueError(f"unknown game_id: {item.get('game_id')}")
if not item.get("decision_reason"):
raise ValueError("decision_reason is required")
if not item.get("evidence"):
raise ValueError("evidence is required for traceability")
def build_recommendation_response(games: list[Game]) -> dict:
ranked_games = sorted(games, key=lambda x: x.rank_score, reverse=True)
top_games = ranked_games[:3]
decision_card = mock_llm_compare(top_games)
validate_decision_card(decision_card, top_games)
return {
"ranked_results": [game.__dict__ for game in top_games],
"decision_card": decision_card,
"fallback": False
}
if __name__ == "__main__":
games = [
Game("g1", "星海漫游", 0.91, ["开放世界", "探索", "单人"], "在开放星球中探索遗迹和剧情。"),
Game("g2", "幻阵契约", 0.88, ["卡牌", "养成", "策略"], "通过角色组合和资源规划推进关卡。"),
Game("g3", "争锋前线", 0.84, ["多人", "竞技", "实时对战"], "强调团队配合和即时操作。"),
]
response = build_recommendation_response(games)
print(json.dumps(response, ensure_ascii=False, indent=2))
这个例子的重点不是推荐算法,而是边界设计:排序结果仍然来自原系统,AI 只生成“决策卡片”。同时,validate_decision_card 要求每条解释绑定 game_id 和 evidence,避免生成一段看似合理但无法追溯的文案。
如果接入真实 LLM,可以把提示词写得更工程化一些:
你是游戏推荐解释模块。请基于输入的游戏元数据,为用户生成可比较的决策卡片。
要求:
1. 不改变输入游戏顺序。
2. 不编造输入中没有出现的玩法、付费、平台信息。
3. 每个推荐理由必须引用 tags 或 description 中的证据。
4. 输出严格 JSON,不要 Markdown。
5. 如果信息不足,reason 中明确写“信息不足,无法判断”。
输出字段:
comparison_title: string
summary: string
items: array of {
game_id: string,
best_for: string,
decision_reason: string,
evidence: string[]
}
生产里真正要管的是稳定性
“让 AI 进推荐系统”最容易被误解成“让模型决定推荐什么”。从这篇思路看,更稳的切入点是排序之后的表达层。它不抢主链路的控制权,却能明显改善用户理解结果的方式。
落地时建议重点检查这些点:
- 是否保留原排序结果,AI 不直接重排。
- 是否有结构化 schema,而不是只接收自然语言。
- 每条解释是否能追溯到游戏标签、描述、用户偏好或排序上下文。
- 是否有超时、空结果、校验失败时的降级方案。
- 是否记录模型版本、提示词版本和输出结果,方便回放问题。
- 是否区分“事实解释”和“营销表达”,避免把生成文案当事实来源。
这类系统的价值不是让推荐变得更神秘,而是把推荐结果讲清楚。排序给出候选,AI 帮用户理解差异,工程约束保证它可控地上线。真正的目标,是从“给用户一个结果”走向“帮用户做一个选择”。