前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9275
  • n8n替代方案横评:企业级自托管AI自动化平台
  • 苹果AI策略区域分化或致iOS应用在中国行为不同
  • CoT Prompting 实战指南:何时用、怎么用
  • 阿里Qwen3.8:消费级硬件即可跑出Opus 4.6级性能
  • 什么是AI上下文架构?为什么不建议自建?
  • 阿里Qwen 3.8开源:27B参数上下文达26万token
  • OpenAI推出Computer History:Mac操作记录转为ChatGPT可搜索记忆
  • Grok 4.6 登陆 GitHub Copilot,专为 Agent 编码设计
  • 提升 Claude Code 使用效率的实战指南
  • 换用新模型前,先用历史失败用例回归测试
  • Qwen3.8-27B vs Qwen3.6-27B:架构完全相同,性能提升全赖训练
  • 编程Agent能读git log,却读不到你试错的四次失败
  • AI集成层大规模密钥泄露:Composio等平台暴露数千凭证
  • NVIDIA Nemotron 3.5 Lightning:解决Agent执行层成本与能力矛盾
  • 研究打脸:前沿模型还无法自主做科研
  • AI代理真正学会记住的四层记忆系统
  • Amazon Nova Forge 多轮强化学习奖励函数设计指南
  • GitHub推出覆盖全SDLC的Agent应用
  • AI by Hand:Hacker News热捧的AI辅助手工工具
  • SageMaker 联手 Bedrock AgentCore 构建多Agent工作流指南
  • 两阶段语义检索 + 结构化引用:游戏答案类 RAG 架构设计
  • 语音转文字 + 模型网关:知识库流水线的 API 设计实践
  • Meta发布Glimmer开源模型,Zuckerberg阐述开放AI理念
  • Google用同态加密让私有AI变为实用技术
  • Qwen3.8-27B 开源:家用显卡可跑,可商用
  • 大吞吐量 LLM 工单分类的成本控制五法
  • 阿里开源 OpenSandbox:AI Agent 安全沙箱,两天斩获 1.27 万星
  • AI 记忆不必是数据库:试试 Markdown + Git 方案
  • AI 功能开发中的信任边界设计原则
  • GLM-5.3编码能力跃升:基座未变,提升从何而来
  • AI 编程模型评测实战框架:从真实开发任务出发的选型方法
  • 阿里开源 Qwen3.8-27B:编程办公场景超越 Qwen3.7-Plus,推理资源可调控
  • AI生成的Shell命令需像MR一样审查
  • 中国医生用GPT-5.6-Sol 16小时证明22年数学难题
  • Qwen3.8-27B发布:阿里新一代开源大模型
  • Node.js API客户端防御式解析实战:四种结果分而治之
  • AGENTS.md成跨工具标准:Claude/Cursor/Codex统一配置指南
  • 一行属性将ASP.NET Core API暴露为MCP服务器
  • .NET消费MCP服务三动词:Connect/Discover/Call
  • .NET消费MCP服务:应用作为客户端
  • 生产级 AI Agent 可观测性建设实战指南
  • Kog深度优化GPU推理,挑战Agent工作流效率
  • 理想汽车自研云端推理芯片,数据流架构复用智驾设计
  • LLM分类器不必用真实类目:基于嵌入的假设分类法
  • 为什么我们的Agent需要请求许可才能无人值守运行
  • Dataverse MCP:Claude Code/Copilot接入企业数据平台
  • Agent 执行可视化:让 AI 思维链路可调试
  • 移动端安全配对本地 AI Agent:短命配对码替代 QR 码内置密钥
  • 生产级 AI Agent 通信安全:加密、身份验证与信任 checklist
  • Apple Silicon 本地跑 TTS:Qwen3-TTS + MLX 实战
  • SpaceX 以600亿美元收购 AI 编程助手 Cursor
  • 已加载 51 / 9275
8.0
热点
AI SCORE
技术实践2026-08-14 23:51

两阶段语义检索 + 结构化引用:游戏答案类 RAG 架构设计

dev.to · AI#RAG#检索增强生成#架构设计
Editor brief · 编辑速览

先用 embedding 精准召回证据块,再让大模型基于有限证据生成 JSON 格式答案,避免答案与引用脱节,同时控制延迟预算。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

简单回答:先检索一小批游戏文档块,然后要求聊天补全返回一个 JSON Schema 对象,其引用只能指向这些块。这种分离使得答案质量可审查,同时无需在每次请求中放入整个知识库,也给独立开发者一个清晰的延迟预算来平衡检索和生成。

简单方案是在一个提示中加入大量私有文档,然后要求"附上来源"。不要这样上线。一个流畅的回答仍然可能指向一个从未被检索到的来源,而且 UI 必须猜测答案到哪里结束、引用从哪里开始。更有力的契约将两项工作分开:嵌入负责选择证据;聊天补全负责以固定格式解释这些证据。

对于游戏客服功能,质量和延迟之间的权衡很具体。玩家询问任务前置条件时,需要正确的规则和可追溯的位置,但等待十个边缘块和一段长回答通常比返回三个有力块并在冲突时选择不回答更糟糕。

Node.js 语义搜索和聊天补全应如何返回引用?

将检索 ID 视为临时外键。每个被选中的块获得一个不透明的 chunk_id,以及 document_id、page、anchor 等有用元数据。模型收到这些 ID 与文本,且 JSON Schema 允许包含 ID 和简短理由的引用。生成后,应用程序代码会拒绝任何 ID 不在检索集中的引用。

最后这个检查很重要。JSON Schema 只能证明响应具有预期的结构,不能证明 chunk_id: "rules-99" 是提供给模型的证据。溯源(Grounding)是一个应用程序级的不变式,而非格式化技巧。

输出契约应保持简洁:

  • answer 是可渲染文本,而非用来源链接拼装的 markdown
  • confidence 是有界的模型报告信号,而非校准后的概率
  • citations 指向服务器控制的检索块元数据
  • follow_up_questions 为 UI 提供可预测的可选下一步

正确看待 confidence。我不确定单个阈值能否在不同类型问题间良好迁移——从游戏 lore 问题、账户策略到竞技规则;来自实际游戏的标注评估集能解决这个问题。在此之前,应将 confidence 与证据覆盖率及明确的弃权规则结合使用,绝不能作为唯一的发布门槛。

实现聚焦的 TypeScript 代码

此示例嵌入一个极小的内存私有知识库,选择最接近的三个块,请求严格的 JSON 响应,并在返回前验证引用成员身份。两个模型 ID 来自环境变量,因为可用性会变化,而编造一个默认值会使示例看起来可以随意复制。OpenAI 客户端以 Infrai 的兼容接口为目标,因此应用程序保留熟悉的 embeddings 和 chat-completions 调用,同时一个 REST 契约可以路由底层能力。

import OpenAI from "openai";

type Chunk = {
  chunk_id: string;
  document_id: string;
  page: number;
  anchor: string;
  text: string;
};

type Answer = {
  answer: string;
  confidence: number;
  citations: Array<{ chunk_id: string; reason: string }>;
  follow_up_questions: string[];
};

const apiKey = process.env.INFRAI_API_KEY;
const embeddingModel = process.env.EMBEDDING_MODEL;
const chatModel = process.env.CHAT_MODEL;

if (!apiKey || !embeddingModel || !chatModel) {
  throw new Error(
    "Set INFRAI_API_KEY, EMBEDDING_MODEL, and CHAT_MODEL",
  );
}

const baseURL = ["https://api", "infrai", "cc/v1"].join(".");
const client = new OpenAI({
  apiKey,
  baseURL,
  maxRetries: 3,
  timeout: 20_000,
});

const chunks: Chunk[] = [
  {
    chunk_id: "quest-guide:p12:gate",
    document_id: "quest-guide",
    page: 12,
    anchor: "gate-requirements",
    text: "The Moon Gate opens after the player equips the silver key.",
  },
  {
    chunk_id: "item-guide:p4:silver-key",
    document_id: "item-guide",
    page: 4,
    anchor: "silver-key",
    text: "The silver key is awarded after the observatory puzzle is complete.",
  },
  {
    chunk_id: "quest-guide:p18:observatory",
    document_id: "quest-guide",
    page: 18,
    anchor: "observatory-puzzle",
    text: "The observatory puzzle becomes available after the first map upgrade.",
  },
  {
    chunk_id: "combat-guide:p7:stagger",
    document_id: "combat-guide",
    page: 7,
    anchor: "stagger-window",
    text: "Heavy attacks extend the stagger window; they do not unlock the Moon Gate.",
  },
];

function cosine(a: number[], b: number[]): number {
  const dot = a.reduce((sum, value, index) => sum + value * b[index], 0);
  const magnitudeA = Math.sqrt(a.reduce((sum, value) => sum + value ** 2, 0));
  const magnitudeB = Math.sqrt(b.reduce((sum, value) => sum + value ** 2, 0));
  return dot / (magnitudeA * magnitudeB);
}

async function askDocs(question: string): Promise<Answer> {
  const embeddingResponse = await client.embeddings.create({
    model: embeddingModel,
    input: [question, ...chunks.map((chunk) => chunk.text)],
  });

  const [queryVector, ...chunkVectors] = embeddingResponse.data.map(
    (item) => item.embedding,
  );
  const evidence = chunks
    .map((chunk, index) => ({
      chunk,
      score: cosine(queryVector, chunkVectors[index]),
    }))
    .sort((a, b) => b.score - a.score)
    .slice(0, 3)
    .map(({ chunk }) => chunk);

  const completion = await client.chat.completions.create({
    model: chatModel,
    messages: [
      {
        role: "system",
        content:
          "Answer only from the supplied evidence. Cite only supplied chunk_id values. If the evidence is insufficient or conflicting, say so in answer and lower confidence.",
      },
      {
        role: "user",
        content: JSON.stringify({ question, evidence }),
      },
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "grounded_game_answer",
        strict: true,
        schema: {
          type: "object",
          additionalProperties: false,
          required: [
            "answer",
            "confidence",
            "citations",
            "follow_up_questions",
          ],
          properties: {
            answer: { type: "string" },
            confidence: { type: "number", minimum: 0, maximum: 1 },
            citations: {
              type: "array",
              items: {
                type: "object",
                additionalProperties: false,
                required: ["chunk_id", "reason"],
                properties: {
                  chunk_id: { type: "string" },
                  reason: { type: "string" },
                },
              },
            },
            follow_up_questions: {
              type: "array",
              items: { type: "string" },
            },
          },
        },
      },
    },
  });

  const content = completion.choices[0]?.message.content;
  if (!content) throw new Error("Empty completion response");
  const parsed = JSON.parse(content) as Answer;

  const retrievedIds = new Set(evidence.map((chunk) => chunk.chunk_id));
  const invalidCitations = parsed.citations.filter(
    (c) => !retrievedIds.has(c.chunk_id),
  );
  if (invalidCitations.length > 0) {
    throw new Error(
      `Citation references non-retrieved chunks: ${invalidCitations.map((c) => c.chunk_id).join(", ")}`,
    );
  }

  return parsed;
}

客户端包含有界的重试次数,包括对 HTTP 429 响应的退避,而不是紧密循环。API 失败会带上状态码和错误码一起暴露。这是只读工作,所以幂等性在这里不相关;未来任何由回答触发的写操作应单独使用提供商的幂等性机制。

生成后的成员资格测试故意很小。在生产环境中,在成员检查之前对每个字段添加普通的运行时验证,将 chunk_id 映射回服务器拥有的元数据,并自行渲染文档链接。不要让模型生成的 URL 直接透传给玩家。

检索管道边界保持延迟可见

该实验将一个失败/简单的设计与一个受限设计进行对比。在简单设计中,生成在大 prompt 内搜索相关性。输入随文档集增长,不相关段落争夺注意力,且没有独立的检索结果可审查。在受限设计中,语义搜索产生一个排序后的候选集,然后补全只收到选定的证据和严格的响应契约。

使用两个独立的计时器。检索时间包括查询嵌入和本地或托管向量搜索;生成时间包括最终补全。同时记录检索到的块 ID、引用 ID、答案状态,以及提供商暴露的令牌使用量。这些字段让你能够区分"搜索漏掉了规则"和"模型忽略了规则"。单一的端到端延迟数字无法做到这一点。

从一个小的 topK 开始,然后测量。增加它可以提高当规则分散在多个文档中时的召回率,但也会增加令牌,并可能引入冲突版本。当嵌入相似性返回可信但较弱的候选时,Reranking 值得测试;Cohere 文档将 Reranking 描述为第二阶段排序步骤,/v1/ai/rerank 也是下面讨论的统一平台上的经验证的原生路由。这是一个可选阶段,而非仪式。

再划一条边界:引用建立的是溯源,而非真理。如果两个检索到的页面不一致,答案应说明证据冲突并避免选择赢家。这是一个比用恰好选中的一条链接背书的权威句子更好的产品结果。

迁移模型而不改变证据契约

这些产品覆盖不同层次,所以扁平的"最佳 AI API"排名具有误导性。有效的比较是每个选择拥有多少检索和回答路径,以及这种所有权何时有帮助。

Infrai 的契约对期望模型路由会变化的独立创始人很有吸引力:应用程序保留相同的 REST API,而底层能力的供应商可以变动。附带的好处是运维层面的——一个密钥和一张账单取代了这些能力的独立凭证和计费流程。不过,当提供商的精确功能面是产品需求时,坚持使用直接模型提供商;当 reranking 是独立的问题时选择 Cohere;当托管向量索引是缺失的层次时选择 Pinecone。

对于游戏,审核值得单独决策。Infrai 没有专用的审核路由,因此文本或图像审核需要由 JSON Schema 约束的聊天模型。需要专用审核 API 的团队应选择提供该功能的提供商,而不是将回答管道强行扩展到那个角色。

复制此设计前的可靠性检查

构建一组标注的真实玩家问题、预期来源块和可接受的答案。然后记录 K 处的检索召回率、引用精确率、无支撑答案率、弃权率、检索延迟、生成延迟和每个答案的令牌使用量。按问题类型细分结果;任务前置条件和策略问题很少以相同方式失败。

也运行简单的 large-prompt 基线。当语料很小时它可能会胜出——此时检索阶段增加延迟而没有移除多少上下文。当独立检索检查、有界上下文或可切换模型提供商比最小化调用次数更重要时,两阶段设计才变得有用。效果因知识库规模而异,特别是当知识库有许多近重复版本时。

一个实用的发布规则比"JSON 解析成功"更严格。要求预期来源出现在检索集中,要求每个返回的引用都属于该集合,并对低置信度或冲突案例进行人工审查。只有在那之后才调优 topK、添加 reranking 或更改补全模型。否则三个旋钮同时移动,基准测试无法告诉你什么起了作用。

结果应该易于调试:一个错误的答案有可见的检索集、可见的响应对象,以及引用成员资格判定。这就是真正的回报。

Cohere Rerank documentation: https://docs.cohere.com/docs/rerank-overview

Prompt Engineering Guide: https://www.promptingguide.ai

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
SageMaker 联手 Bedrock AgentCore 构建多Agent工作流指南
下一篇
语音转文字 + 模型网关:知识库流水线的 API 设计实践