AI 不替代推荐排序,而是补上“为什么选它”

2026-07-09 27 预计阅读时间: 1 分钟
来源: my.oschina.net AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

推荐系统过去主要回答“推什么”:召回、粗排、精排、重排,把最可能点击或转化的内容放到前面。但当用户面对的是多款相似游戏时,单纯给出列表并不够。本文讨论的变化点是:不改排序链路,而是在排序之后补上一层“表达与决策”,让推荐结果变得可比较、可解释、可追溯。

这件事的关键不在于让大模型接管推荐系统,而是让它理解游戏、组织差异、生成理由,再用工程约束把输出收住,稳定进入生产。

排序之后,还有一个决策缺口

传统推荐排序擅长处理概率问题:用户会不会点、会不会下载、会不会付费。它通常输出一个有序列表,例如:

  • 游戏 A:0.91
  • 游戏 B:0.88
  • 游戏 C:0.84

但用户真正要做的是选择。尤其在游戏场景里,几个结果可能都属于“开放世界”“二次元”“卡牌养成”或“多人竞技”,它们的差异并不总能从标题、封面、标签里看出来。

排序解决的是系统侧的最优展示,决策辅助解决的是用户侧的理解成本。两者不应混在一起:

  • 排序层继续负责候选质量和个性化权重。
  • AI 表达层负责解释“为什么这几款值得一起看”。
  • 决策层负责把差异压缩成用户能快速判断的信息。

这样做的好处是风险边界清楚:即使 AI 生成失败,也不应该破坏主推荐链路,只需要降级为普通推荐列表。

让大模型“放开探索”,再用工程约束收住

大模型适合做游戏理解:总结玩法、抽取卖点、比较差异、生成自然语言解释。问题是,它也容易发散、幻觉、输出格式不稳定。

所以可落地的做法不是“把排序结果丢给模型,让它自由发挥”,而是分两步:

  1. 给模型足够上下文,让它探索游戏之间的可比维度。
  2. 用结构化输出、字段校验、证据引用、降级策略约束生产结果。

例如,模型可以探索这些维度:

  • 核心玩法:动作、策略、养成、解谜、射击。
  • 节奏强度:碎片化、长线投入、高操作压力。
  • 社交方式:单人、弱社交、公会、实时对抗。
  • 付费体验:外观、抽卡、通行证、买断。
  • 推荐理由:适合什么用户、不适合什么用户。

但进入生产时,输出最好是稳定 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_idevidence,避免生成一段看似合理但无法追溯的文案。

如果接入真实 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 帮用户理解差异,工程约束保证它可控地上线。真正的目标,是从“给用户一个结果”走向“帮用户做一个选择”。


相关推荐