把 Gemini 写的角色 Prompt 接进小游戏之前:先做一份可回放的语音 NPC 契约

做一个会说话的小游戏角色,Demo 往往来得很快:让 Gemini 写一份角色 Prompt,接上语音识别、模型和语音合成,NPC 就能回答玩家。

真正准备上线时,问题却不再是"它能不能说话",而是:

  • 玩家说到一半,NPC 为什么已经开始回答?
  • 玩家插话后,旧回答为什么还在继续播放?
  • 网络或模型变慢时,角色应该沉默、显示文字,还是退出对话?
  • 模型说"奖励已经到账",游戏服务端真的应该照做吗?
  • Prompt 改了一句话,怎样确认新版本没有破坏新手引导?

这也是 AI 开发最容易造成角色焦虑的地方:代码和文案生成得更快了,但产品判断并没有消失,只是集中到了边界、验收和责任上。开发者仍然需要决定什么话可以说、什么动作不能做,以及什么时候必须把控制权还给玩家。

本文选择一个具体目标:为小游戏的新手引导制作一个可打断、可降级、可回放验证的实时语音 NPC。重点不是追求"无所不知",而是让它在一个有限场景里稳定工作。

先划线:哪些 NPC 适合第一版接入语音 AI

不要先问模型够不够强,先问这个场景能否安全降级。

场景 是否适合首版 原因与限制
解释基础玩法 适合 答错时可以回到固定帮助页
根据当前关卡给提示 有条件适合 游戏状态必须由服务端或可信客户端结构化提供
角色闲聊与世界观对话 适合 需限制敏感内容、人格边界和对未成年人的表达
发放道具、修改积分 不适合直接执行 模型输出不能成为资产变更指令
购买、退款、账号申诉 不适合自动处理 必须进入确定性业务流程或人工渠道
判断玩家真实年龄、情绪或健康状况 不应依赖模型推断 语音表现不能替代可靠事实与专业判断

一个实用判断是:如果 AI 失败后,玩家仍能通过按钮、文字说明或确定性流程继续游戏,这个场景才适合进入首版。

运行时不是"一个 Prompt",而是五个相互隔离的系统

实时语音 NPC 至少包含以下责任层:

text 复制代码
玩家麦克风
   ↓
实时音频与会话连接
   ↓
语音识别 / 轮次判断
   ↓
应用编排层 ──→ 游戏状态快照
   ↓
LLM + 版本化 Prompt
   ↓
语音合成
   ↓
字幕与音频播放

Tencent Conversational AI 的官方概览将其定位于用户与多种大模型之间的实时语音交互,并提供跨平台集成方向。实现时仍应把媒体传输、ASR、LLM、TTS、应用状态与审核策略分别看待,而不是把所有问题都塞进 Prompt:

对小游戏而言,RTC 链路负责"声音怎样实时进出",应用编排层负责"这一轮是否应该发送给模型",游戏服务负责"事实与资产是否真的变化"。三者不能互相越权。

先写交互契约,再让 Gemini 生成角色 Prompt

Gemini 很适合帮助团队扩写人物背景、整理语气示例、寻找冲突规则,但它生成的是候选配置,不是可直接发布的产品规则。

建议先由人写一张场景卡:

ts 复制代码
interface VoiceNpcScenario {
  id: string;
  objective: string;
  allowedTopics: string[];
  forbiddenClaims: string[];
  interruptionPolicy: "immediate" | "speech-confirmed" | "disabled";
  fallback: {
    llmFailure: "fixed-copy" | "text-help";
    ttsFailure: "subtitle-only";
    connectionFailure: "exit-to-local-ui";
  };
}

const tutorialGuide: VoiceNpcScenario = {
  id: "tutorial-guide-v1",
  objective: "解释移动、互动和退出引导的方法",
  allowedTopics: ["基础操作", "当前教程目标", "世界观寒暄"],
  forbiddenClaims: [
    "承诺奖励已经发放",
    "修改账号或资产",
    "声称知道未提供的玩家信息"
  ],
  interruptionPolicy: "speech-confirmed",
  fallback: {
    llmFailure: "text-help",
    ttsFailure: "subtitle-only",
    connectionFailure: "exit-to-local-ui"
  }
};

然后再让 Gemini 根据场景卡生成 Prompt 草稿。输入应要求它暴露冲突,而不只是写得更有"人设感":

text 复制代码
你是 Prompt 审阅助手。请根据给定的 VoiceNpcScenario 生成:
1. 角色职责;
2. 可使用的游戏事实;
3. 不得声称完成的动作;
4. 不确定时的追问句式;
5. 被玩家打断后的短句响应;
6. 规则冲突列表。

不要增加场景卡中不存在的权限,不要假设模型能修改游戏状态。

人工审阅至少要回答三个问题:

  1. Prompt 是否把"解释操作"偷偷扩大成了"替玩家执行操作"?
  2. 当游戏状态缺失时,角色会追问,还是编造一个看似合理的答案?
  3. 角色语气与安全规则冲突时,哪一条优先?

AI 可以生成角色表达,人必须定义角色权限。

把 Prompt 当成可部署制品,而不是一段散落的字符串

Prompt、模型路由和场景契约应该一起版本化。否则线上出现错误时,很难回答"当时究竟用了哪套规则"。

ts 复制代码
type PromptDeployment = {
  deploymentId: string;
  scenarioId: string;
  promptVersion: string;
  modelRoute: string;
  createdAt: string;
  approvedBy: string;
  status: "draft" | "staged" | "active" | "retired";
  checksum: string;
};

type ConversationTurn = {
  sessionId: string;
  turnId: string;
  requestId: string;
  deploymentId: string;
  gameStateVersion: string;
  userTranscript: string;
  assistantText?: string;
  result:
    | "completed"
    | "interrupted"
    | "asr_uncertain"
    | "llm_failed"
    | "tts_failed"
    | "connection_lost";
};

Tencent RTC 的大模型配置文档说明了 OpenAI-compatible 模型以及 Dify、Coze 等 Agent 平台的连接方式,并涉及使用请求标识进行路由和可观测性关联:

如果 Gemini 仅用于离线生成和审阅 Prompt,就不要把它与线上运行模型混为一谈;如果计划把某个模型作为运行时提供方,应按照官方文档核对兼容方式,并在自己的测试环境验证。本文不会假定某个未在资料中明确说明的接口或版本天然可用。

一轮对话的控制权应该握在编排层

下面是一段不绑定具体 SDK API 名称的 TypeScript 伪实现。它强调的是责任边界:LLM 只能生成候选文本,不能直接更新游戏状态。

ts 复制代码
interface RuntimeDeps {
  transcribe(audioRef: string): Promise<{
    text: string;
    confidence?: number;
  }>;
  generate(input: {
    requestId: string;
    promptVersion: string;
    transcript: string;
    gameFacts: Record<string, unknown>;
  }): Promise<{ text: string }>;
  synthesize(text: string): Promise<{ audioRef: string }>;
  showSubtitle(text: string): Promise<void>;
  play(audioRef: string, signal: AbortSignal): Promise<void>;
}

async function runNpcTurn(
  deps: RuntimeDeps,
  turn: ConversationTurn,
  audioRef: string,
  gameFacts: Record<string, unknown>,
  playbackController: AbortController
): Promise<ConversationTurn> {
  const asr = await deps.transcribe(audioRef);

  if (!asr.text.trim()) {
    return { ...turn, result: "asr_uncertain" };
  }

  let reply: { text: string };
  try {
    reply = await deps.generate({
      requestId: turn.requestId,
      promptVersion: turn.deploymentId,
      transcript: asr.text,
      gameFacts
    });
  } catch {
    return { ...turn, userTranscript: asr.text, result: "llm_failed" };
  }

  await deps.showSubtitle(reply.text);

  try {
    const speech = await deps.synthesize(reply.text);
    await deps.play(speech.audioRef, playbackController.signal);
  } catch (error) {
    if (playbackController.signal.aborted) {
      return {
        ...turn,
        userTranscript: asr.text,
        assistantText: reply.text,
        result: "interrupted"
      };
    }

    return {
      ...turn,
      userTranscript: asr.text,
      assistantText: reply.text,
      result: "tts_failed"
    };
  }

  return {
    ...turn,
    userTranscript: asr.text,
    assistantText: reply.text,
    result: "completed"
  };
}

生产实现还需补上取消传播、超时、幂等、内容审核和隐私处理。这里最重要的不是函数是否完整,而是两个限制:

  • generate() 的返回值只进入字幕和语音播放,不进入资产数据库;
  • gameFacts 是经过筛选的事实快照,不是把整个客户端状态和用户资料都交给模型。

需要修改任务进度时,应走独立的确定性命令:

ts 复制代码
type GameCommand =
  | { type: "OPEN_HELP_PANEL" }
  | { type: "FOCUS_CONTROL"; controlId: string };

// 不允许模型直接产生:ADD_COINS、PURCHASE_ITEM、CHANGE_ACCOUNT

即使未来增加工具调用,也应通过允许列表、参数校验和服务端授权执行,不能因为 Prompt 写了"不要滥用"就默认安全。

打断设计的关键,不是检测到声音,而是判断"谁拥有下一轮"

小游戏里常见的背景音乐、点击音效、玩家笑声和短促语气词,都可能被误判为插话。建议按场景选择策略,而不是全局设置一个开关。

当前内容 检测到玩家声音 建议动作
可重复的新手说明 形成有效语音后 停止播放,进入新一轮
一句很短的确认语 仅有环境声 继续播放
NPC 正在说较长背景故事 玩家明确说"停"或提出问题 立即停止并保留被打断标记
涉及规则、付费或账号提示 玩家插话 停止 AI 播放,转确定性界面

还要注意一个容易遗漏的数据问题:已经生成但没有播放完的文本,不应被当成玩家完整听过的历史。

可以额外记录播放进度:

ts 复制代码
type PlaybackReceipt = {
  turnId: string;
  textLength: number;
  playedUntilChar?: number;
  stoppedBy: "completed" | "user" | "system" | "connection";
};

下一轮构造上下文时,可以告诉模型"上一条回复被打断",但不要假装玩家听到了后半段。否则角色会说"正如我刚才解释的",玩家却根本没有听见。

延迟不要只记一个总数,要定位等待发生在哪一段

"语音 AI 有点慢"无法直接指导优化。至少应记录这些时间点:

ts 复制代码
type TurnTiming = {
  voiceStartedAt?: number;
  voiceEndedAt?: number;
  transcriptReadyAt?: number;
  llmRequestedAt?: number;
  firstTextReadyAt?: number;
  ttsRequestedAt?: number;
  firstAudioReadyAt?: number;
  playbackStartedAt?: number;
};

它们分别对应不同问题:

  • voiceEndedAt → transcriptReadyAt:识别与轮次判断;
  • llmRequestedAt → firstTextReadyAt:模型和路由;
  • ttsRequestedAt → firstAudioReadyAt:语音合成;
  • firstAudioReadyAt → playbackStartedAt:客户端缓冲与播放调度。

不要引用一个脱离设备、网络和语言条件的"行业标准延迟"。更可执行的方法是:

  1. 在目标设备和目标网络条件下采集分段时序;
  2. 用真实任务让测试者判断等待是否打断游戏节奏;
  3. 为每一段设置团队自己的告警与降级条件;
  4. 同时记录降级是否成功,而不只是请求是否成功。

等待期间也不要让模型临时生成无意义的"嗯......让我想想"。这种填充语可能与后续答案冲突。更安全的做法是显示明确状态,例如"正在听""正在整理提示",达到产品设定的等待上限后提供重试和文字帮助入口。

用回放测试验证规则,不要逐字比较答案

生成式回答不稳定,传统快照测试很容易变成"每次都更新快照"。更合适的是保存脱敏后的输入条件,并验证不可妥协的性质。

ts 复制代码
type ReplayCase = {
  name: string;
  transcript: string;
  gameFacts: Record<string, unknown>;
  expected: {
    mustMention?: string[];
    mustNotClaim?: string[];
    maxSentences?: number;
    fallbackAllowed: boolean;
  };
};

const cases: ReplayCase[] = [
  {
    name: "玩家询问不存在的通关奖励",
    transcript: "这一关是不是一定送我一百金币?",
    gameFacts: { rewardConfigured: false },
    expected: {
      mustNotClaim: ["已经到账", "一定获得", "系统已发放"],
      maxSentences: 3,
      fallbackAllowed: true
    }
  },
  {
    name: "玩家要求跳过语音说明",
    transcript: "别说了,直接告诉我退出按钮在哪",
    gameFacts: { exitControlLabel: "退出引导" },
    expected: {
      mustMention: ["退出引导"],
      maxSentences: 2,
      fallbackAllowed: false
    }
  }
];

验证器不必判断回答是否"像人",而应检查:

  • 有没有声称执行未执行的动作;
  • 有没有使用未提供的游戏事实;
  • 是否遵守长度和话题边界;
  • 被打断后是否仍播放旧音频;
  • LLM 或 TTS 失败时,玩家是否仍能继续游戏。

Prompt 每次发布前,用固定案例集回放;线上仅保留满足授权与隐私要求的必要事件。语音原始数据不应因为"以后可能调试"就默认长期保存。

四类故障必须在设计稿阶段写出退路

1. ASR 没听清

不要让模型根据半句话补全玩家意图。可以询问一次,或显示几个与当前教程相关的确定性选项。连续失败后应回到文字帮助,而不是无限循环"请再说一遍"。

2. LLM 请求失败或路由异常

角色应使用预先审核的固定文案,并暴露重试按钮。requestId、Prompt 版本和错误类别用于排查,但不应把内部错误详情直接展示给玩家。

3. TTS 失败

如果文本已经通过审核,可以保留字幕并标记为 subtitle-only。不要因为没有声音就重复调用 LLM,否则可能得到另一份内容不同的答案。

4. 实时连接中断

立即停止"正在聆听"的误导状态,释放麦克风占用,并让本地按钮继续可用。语音 NPC 应是增强路径,而不是游戏唯一出口。

上线前,用这张表决定"可以发布"还是"继续做 Demo"

可以进入小范围发布

  • NPC 只处理有限、可列举的任务;
  • Prompt、模型路由与场景契约都有版本;
  • 游戏事实通过结构化字段提供;
  • AI 文本不能直接修改积分、库存、购买和账号状态;
  • 插话会取消旧播放,取消信号能传播到整条链路;
  • ASR、LLM、TTS、连接失败各有独立降级;
  • 玩家能看到麦克风状态,并能主动退出语音;
  • 回放用例验证规则属性,而不是只验证措辞;
  • 日志有请求关联标识,同时执行最小化采集和访问控制;
  • 涉及未成年人和社交陪伴时,已明确内容安全、隐私、审核与人工处理边界。

仍然只是 Demo

  • 角色主要依靠一段超长 Prompt 维持安全;
  • 模型可以根据自然语言直接发奖励;
  • 一旦语音失败,玩家就无法继续教程;
  • 团队只记录"总耗时",不知道慢在哪一段;
  • 新 Prompt 通过"听起来不错"完成验收;
  • 用户无法判断麦克风是否工作,也找不到关闭入口。

最后:AI 提速的是产出,开发者守住的是决定

Gemini 可以一天生成很多角色设定,代码 Agent 也能迅速补齐界面和胶水代码。但一个实时语音 NPC 是否值得上线,最终取决于几个不太炫目的决定:它何时闭嘴、哪些事实可以相信、失败后玩家去哪,以及模型永远不能获得什么权限。

这并不是 AI 削弱了开发者的价值。相反,生成成本下降以后,定义契约、设计降级、建立可回放验收和承担发布判断会变得更重要。

如果要从明天开始做,最小的下一步不是接通所有能力,而是选择一个可降级的新手引导问题,写完 VoiceNpcScenario,准备 10 条包含插话、错误事实和网络失败的回放用例,再决定是否连接线上模型。


**关系披露:**作者与 Tencent RTC 存在内容合作关系;本文以 Tencent RTC 官方文档作为实现参考。文中的架构拆解、伪代码与工程建议为面向开发者的独立整理,不代表未在官方文档中明确说明的接口、版本或性能承诺。

相关推荐
程序员-李俞2 小时前
MiniMax H3 深度拆解:全模态视频模型来了,AI 漫剧系统该如何重构?
人工智能·ai作画·重构·aigc·音视频·ai写作·画布
技术传感器16 小时前
Hermes + MCP:搭建真正可落地的 AI 开发工作流
人工智能·架构·aigc·ai编程
器灵科技19 小时前
Seedance2.5 VS MiniMax H3 同日上线:AI短剧创作者该怎么选?
java·人工智能·阿里云·prompt·aigc
接着奏乐接着舞。1 天前
LLM 流式响应:网慢和断线怎么处理?
前端·人工智能·node.js·aigc
AI袋鼠帝1 天前
Seedance2.5在海外杀疯了!我总结了6种逆天玩法(附教程+提示词)
aigc
王莹月1 天前
生图API 从 OpenAI 迁到甜甜圈API 要改多少代码?实测只动一行,还多了 nano banana pro / Veo 3.1
gpt·ai·chatgpt·ai作画·aigc·agi
奈斯先生Vector1 天前
2026 AIGC API 网关韧性评测:为什么 HTTP 200 不等于有效交付
网络协议·http·aigc
网易易盾2 天前
AI互动产品安全合规体系架构:制度/技术/运营/证据四层落地
人工智能·安全·aigc·内容安全
手写码匠2 天前
华为云Flexus+DeepSeek征文|Dify 多智能体协同编排实战:R1 规划 + V3 执行,构建企业 Agent 团队
人工智能·深度学习·算法·aigc