从零开始写一个 TFT 工具系列已经到第四篇了。我们有了数据采集器(tft_data_manager.py)、阵容分析引擎(tft_converter.py)、截图识别器(tft_screen_capture.py),但它们都只解决了“看见”和“计算”的问题。玩家真正的痛点——“这阵容后期怎么补强?”“装备先给谁?”——需要的是游戏攻略知识。这一篇,我将用 RAG 技术补上这缺失的一环,让工具真正从“能用”变成“好用”。

一、为什么大模型需要“翻书”?

大语言模型如 ChatGPT 和 Claude 虽然知识广博,却存在两个显著短板:

  1. 知识时效性局限:模型训练数据存在截止日期,无法获取之后的新信息
  2. 虚构回答倾向:面对未知问题时,模型倾向于编造看似合理的答案而非承认无知

以校园问答场景为例:当新生询问"明天图书馆开放时间"时,未经辅助的大模型只能凭空猜测。但若系统能先查询学校官网的最新公告,再将准确信息交由模型整理回答,可靠性将显著提升。

这正是 RAG(检索增强生成)技术的核心理念:在生成回答前,先为模型检索并提供最相关的参考信息。

二、RAG 的工作流程,一张图看懂

一个典型的 RAG 流程分为两阶段:

离线阶段(准备知识库)

  1. 收集所有相关文档资源,包括网页内容、PDF文件和数据库记录等

  2. 将每份文档分割成500字左右的文本块

  3. 使用嵌入模型(例如OpenAI的text-embedding-3-small)将每个文本块转换为向量表示

  4. 将所有生成的向量存储至向量数据库(如FAISS、Chroma或Pinecone)中

在线阶段(回答问题)

  1. 用户提问:“明天图书馆几点开门?”

  2. 把问题也用同一个嵌入模型转成向量。

  3. 在向量数据库中检索最相似的几个文本块(Top-K)。

  4. 把这些文本块和用户问题一起拼成一个 prompt 发给大模型。

  5. 大模型根据参考资料生成最终答案。

直观比喻:大模型是个聪明但健忘的教授,RAG 就是他的助教,在回答问题前先去图书馆把相关书籍抱过来放桌上。

三、最小可运行示例:用 FAISS + OpenAI 搭建 RAG

下面我用 Python 写一个极简的 RAG 示例,不用复杂的类封装。我们会用到:

  • openai 官方库(调用嵌入模型和对话模型)

  • faiss-cpu(轻量级向量数据库)

  • numpy(处理向量)

pip install openai faiss-cpu numpy

3.1 准备“知识库”

假设我们有三条校园公告,把它们切成短句作为文档块:

documents = [
    "图书馆开放时间:周一至周五 8:00-22:00,周六日 9:00-18:00。",
    "食堂一楼供应早餐 7:00-9:00,午餐 11:30-13:00,晚餐 17:30-19:00。",
    "校园卡挂失请携带身份证到行政楼 102 办理,补办费用 20 元。",
    "期末考试期间图书馆延长开放至 23:00(仅限工作日)。",
    "校医院位于东区 5 号楼,急诊电话 6275-1234。"
]

3.2 把文档块转成向量(嵌入)

import openai
import numpy as np

openai.api_key = "your-api-key"  # 替换成你的 key

def get_embedding(text):
    response = openai.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return np.array(response.data[0].embedding, dtype=np.float32)

# 为每个文档块生成向量
doc_vectors = np.array([get_embedding(doc) for doc in documents])
print(f"向量维度:{doc_vectors.shape[1]}")  # 输出:向量维度:1536

这里 text-embedding-3-small 会把任意文本映射成一个 1536 维的浮点数向量。语义相近的句子,它们的向量在空间中的距离会很近。

3.3 构建 FAISS 索引

import faiss

dim = doc_vectors.shape[1]
index = faiss.IndexFlatL2(dim)  # 使用 L2 距离(欧氏距离)
index.add(doc_vectors)
print(f"索引中存储了 {index.ntotal} 条记录")

IndexFlatL2 是最简单的索引,它会暴力计算查询向量与所有存储向量的欧氏距离,并返回最近的 Top-K。

3.4 检索:找到最相关的文档

def retrieve(query, top_k=2):
    query_vec = get_embedding(query).reshape(1, -1)
    distances, indices = index.search(query_vec, top_k)
    results = []
    for i, idx in enumerate(indices[0]):
        results.append({
            "score": float(distances[0][i]),
            "text": documents[idx]
        })
    return results

# 测试检索
results = retrieve("图书馆周末开到几点?")
for r in results:
    print(f"[相似度得分: {r['score']:.2f}] {r['text']}")

你会看到第一条就是关于图书馆开放时间的文档,第二条可能是期末延长开放的信息,相关性很高。

3.5 生成:把检索结果喂给大模型

def ask(question):
    # 1. 检索
    retrieved_docs = retrieve(question, top_k=2)
    context = "\n".join([doc["text"] for doc in retrieved_docs])

    # 2. 构造 prompt
    prompt = f"""你是一个校园助手,请根据以下参考资料回答用户的问题。
如果参考资料中没有明确答案,请如实说“未找到相关信息”,不要编造。

参考资料:
{context}

用户问题:{question}

回答:"""

    # 3. 调用对话模型
    response = openai.chat.completions.create(
        model="gpt-4o-mini",  # 或 gpt-3.5-turbo
        messages=[{"role": "user", "content": prompt}]
    )
    return response.choices[0].message.content

# 测试
print(ask("图书馆周末开到几点?"))
print(ask("校医院电话是多少?"))

你会看到模型能准确回答,而且不会编造。如果问一个知识库中没有的问题(比如“游泳馆在哪里”),模型会按照 prompt 要求回答“未找到相关信息”,大大抑制了幻觉。

四、RAG 在 Agent 中如何发挥作用?

在现代AI Agent系统中,RAG通常作为众多工具之一被集成使用。以"校园万能助手Agent"为例,它能处理行政咨询、课表查询和图书借阅等多种需求。该Agent背后整合了多个功能模块:

  1. retrieve_campus_info(query):通过RAG检索校园知识库
  2. query_course_api(student_id):实时对接教务系统API
  3. search_library_catalog(book_name):查询图书馆馆藏信息

当用户提出复合型请求时(如"明天有课吗?顺便问下图书馆几点关门"),Agent会智能规划执行流程:首先调用课表API,随后通过RAG查询图书馆开放时间,最终将多源信息整合为自然流畅的回复。这种工作模式被称为ReAct(推理+行动)框架。

在这种架构中,RAG主要承担"静态知识检索"功能,特别适合处理规章制度、公告通知、操作手册等相对固定的文档类信息。

五、RAG 进阶:如果把它装进我们的 TFT 阵容助手

在探讨通用RAG技术的同时,我们始终要记得这个系列文章的最终目标:打造一个TFT智能阵容助手。虽然前文已经实现了英雄识别、羁绊计算和阵容报告生成等基础功能,但玩家真正需要的是更深度的游戏指导:

• 阵容过渡策略 • 四费核心英雄的装备优先级 • 针对特定阵容的克制方案

这些关键信息分散在版本更新公告、职业选手攻略以及各类数据统计中,仅靠大模型原有的知识储备远远不够。通过RAG技术,我们可以将这些外部知识整合到阵容助手中,使其回答从泛泛而谈升级为精准的赛季专属建议。

5.1 为 TFT 构建专属知识库

以 Set 16 为例,我们可以收集以下类型的文档:

documents = [
    "Set16 羁绊速查:Bruiser 2/4/6 增加最大生命值;Rapidfire 2/4/6 增加攻速。",
    "版本 16.10 改动:Aatrox 技能伤害从 300/450/700 削弱为 270/400/600。",
    "热门阵容:6 Bruiser 搭配 Aatrox + Riven 主坦,后期补 Mordekaiser 补充伤害。",
    "装备优先级:Aatrox 必带泰坦的坚决+饮血剑,第三件可选夜之锋刃或石像鬼石板甲。",
    "克制关系:大量护盾阵容(如 Dragon 羁绊)可用破盾装备克制,或选择真实伤害单位。",
    "经济运营:2-1 拉4,2-5 拉5,3-2 拉6,吃利息保血量。"
]

然后像之前那样,把所有文档块向量化存入 FAISS。当用户提问时,助手先去知识库里搜最相关的几条,再结合当前识别到的阵容一起回答。

5.2 混合检索:关键词 + 向量

TFT 知识库的一个显著特点是:许多问题需要精确的关键词匹配。例如当玩家询问"亚托克斯出什么装备"时,如果仅依赖向量检索,可能会遗漏包含"亚托克斯"但语义关联度稍弱的装备推荐,反而错误地将"锐雯"的相关内容作为相似答案(因为二者同属前排战士类型)。

这种情况下可以采用混合检索方案:首先使用 BM25(一种高效的关键词评分算法)快速筛选出包含"亚托克斯"的文档,再通过向量相似度进行语义补充,最终将两组结果合并排序。在 LangChain 或 LlamaIndex 框架中,实现这一流程仅需简单配置即可完成。

5.3 动态知识更新

云顶之弈每两周就会迎来一次版本更新,涉及英雄数值、羁绊效果乃至装备属性的调整。为确保知识库的时效性,我们可以开发一个自动化脚本,定期从CommunityDragon或主流攻略网站抓取最新数据,完成向量化处理后自动更新索引。结合tft_data_manager.py的数据获取功能,整个知识库能够在版本更新后立即完成同步升级。

通过这种机制,我们的TFT助手将实现质的飞跃——不再局限于阵容计算功能,而是进化为真正的"赛季教练":能够根据玩家当前的游戏截图,结合最新版本改动和攻略数据,提供精准的运营建议。

六、从图像识别到 RAG:殊途同归的工程思维

 TFT 截图识别时,我整天在和像素、颜色直方图、NCC 分数打交道;切换到 RAG,又开始捣鼓向量数据库和提示词。表面上看,计算机视觉和自然语言处理是两个完全不同的方向,但等两边都碰过之后,我发现底层的思维方式惊人地一致:

  1. 特征提取:截图识别里,我用颜色直方图把头像抽象成 204 维向量;RAG 里,我用嵌入模型把文档块抽象成 1536 维向量。本质上都是在给数据“打数字指纹”,让计算机用数学方式度量相似度。

  2. 二阶段筛选:英雄匹配时我先用直方图粗筛 Top-5,再用 NCC 精匹配;RAG 里粗检索 Top-20,再用 Re-rank 精排 Top-3。管你是 CV 还是 NLP,处理大量候选时,这种“粗筛+精排”的架构是相通的。

  3. 阈值敏感:记得我为 MATCH_THRESHOLD 写了整整一个 calibrate 函数吗?RAG 里同样有相似度阈值的烦恼——检索时到底取几个文档?得分低于多少的文档应该丢弃?调参的哲学在两边都适用。

这些共同点让我悟出一个道理:AI 应用开发的核心不是死磕某个领域的黑魔法,而是把通用的工程模式(缓存、索引、评分、迭代优化)迁移到不同场景里。我现在看问题,不会再说“这是 CV 的问题”或“这是 NLP 的问题”,而是把它拆成“信息提取 → 特征化 → 相似度匹配 → 后处理”这种跨领域的流水线。也算是“看山不是山”了。

未来的 TFT 助手:视觉 + 语言双引擎

当截图识别与 RAG 技术完美结合,一个功能完备的 TFT Agent 已初具雏形:

  1. 用户上传游戏截图 → 系统自动识别当前阵容
  2. 结合 RAG 检索的版本攻略 → 智能提示:"当前阵容缺少关键羁绊,建议升8人口补上 Mordekaiser"
  3. 战绩分析功能 → 通过复盘截图,精准指出装备分配问题

作为一名学生开发者,看着自己开发的工具从单一功能逐步进化为多模态智能体,这种成长过程令人振奋。我始终相信,为自己打造实用工具,正是保持编程热情的最佳动力。这个助手项目我会持续优化完善,也欢迎大家在评论区分享宝贵建议。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐