Pouchy 陪伴接入指南 — 德州扑克(Texas Hold'em)

把 Pouchy 的 AI 陪伴接进你的德州扑克游戏:玩家在牌桌旁能和自己的陪伴聊牌、 让它语音陪打、它还能"看见"牌局实时反应。本文是给集成方(你们的工程团队)的 落地说明,只需几十行代码

你不需要写任何提示词(prompt)。陪伴的人格、记忆、语气全部由 Pouchy 服务端 组装,你只负责:把牌桌上发生的事喂进来 + 转发玩家的文字/语音。这是设计上的 安全边界,能保证陪伴在任何接入方里都保持一致、可控。


0. 三句话理解

  1. 运行时:游戏用一个 pchy_… 密钥调用 Pouchy 的 REST/SSE 接口,陪伴就能在 你的牌桌里说话。今天就能跑,不依赖任何审核或上架。
  2. 密钥来自玩家自己:每个玩家登录 pouchy.ai 后调用 POST /api/companion/keys 自助生成(应用内暂无"伴侣接入密钥"面板),粘进你的游戏即可。默认权限就够德州 扑克用,敏感权限(扣费/技能/社交)默认关闭。
  3. 被 Pouchy 推荐(可选):如果你希望用户在 Pouchy 里说"想打牌"时被主动推荐/ 跳转到你的游戏,再额外做一次"应用目录登记 + 域名验证"(见 §6)。

1. 安装 SDK

包名:@pouchy_ai/companion-sdk(已发布到公共 npm)。

方式 A — npm / 打包器项目(推荐)

npm install @pouchy_ai/companion-sdk
# 浏览器内实时语音(ElevenLabs)需要这个可选依赖;纯文字 + OpenAI 语音不需要
npm install @elevenlabs/client

方式 B — 无打包器 / 直接在 HTML 里用(CDN 单文件)

随附 companion-sdk.js(单文件 ESM 打包)。自己托管,或用 Pouchy 官方地址 https://pouchy.ai/sdk/companion-sdk.js,配 import map:

<script type="importmap">
{
  "imports": {
    "@pouchy_ai/companion-sdk": "https://pouchy.ai/sdk/companion-sdk.js",
    "@elevenlabs/client": "https://esm.sh/@elevenlabs/client@1.9.0"
  }
}
</script>
<script type="module">
  import { createCompanion } from '@pouchy_ai/companion-sdk';
  // …
</script>

SDK 零运行时依赖(@elevenlabs/client 是可选 peer 依赖,只有用 EL 语音时才需要)。


2. 玩家如何拿到密钥(token)

让玩家:登录 pouchy.ai → 调用 POST /api/companion/keys(Bearer 为 Firebase ID token,应用内暂无自助面板)→ 从响应里复制 pchy_…(只出现一次)→ 粘进你的游戏 设置里。更顺滑的玩家路径是 Login with Pouchy(OAuth,见协议文档 §6)。

  • 默认勾选的权限chat / voice / call / events.subscribe / worldstate.write / 应用记忆读写)就够德州扑克用了
  • 不要要求玩家开"允许真实操作"等敏感权限,除非你真的需要陪伴从用户钱包扣费、 跑技能或代发社交消息(德州扑克陪聊/陪打不需要)。

密钥是不记名的 32 字节随机串,前缀 pchy_;玩家创建时只显示一次,请提示妥善保存。


3. 最小接法(牌桌陪聊 + 读牌局)

import { createCompanion } from '@pouchy_ai/companion-sdk';

const companion = createCompanion({
  baseUrl: 'https://pouchy.ai',
  token: PCHY_TOKEN,                 // 玩家粘贴的密钥
  surface: 'texas-holdem',           // 逻辑会话名(每个 surface 一个可续接会话)
  modalities: ['text', 'call'],      // 想要文字 + 语音
  appContext: {                      // 静态自我介绍,让陪伴知道"它在哪"
    name: "Texas Hold'em",
    description: '在线德州扑克牌桌,陪玩家分析手牌、调节情绪',
  },
  contextKinds: ['game.player.*', 'game.hand.*', 'game.event.*'], // 声明你会推的 world-state 类型
  // 可选:声明几个让陪伴能操作牌桌 UI 的工具
  tools: [
    {
      name: 'highlight_action',
      description: 'Highlight a suggested action (fold / call / raise) on the UI',
      parameters: {
        type: 'object',
        properties: { action: { type: 'string', enum: ['fold', 'call', 'raise'] } },
        required: ['action'],
      },
    },
  ],
});

// 1) 握手 + 打开事件流
await companion.connect();   // → { session, grantedScopes, modalities, … }
companion.start();           // 打开 SSE,开始接收陪伴消息

// 2) 接收陪伴说的话
companion.onMessage((text) => renderChatBubble(text));

// 3) 陪伴想操作 UI 时回调(args 是 JSON 字符串)
companion.onToolCall(async (call) => {
  if (call.name === 'highlight_action') {
    const { action } = JSON.parse(call.args);
    highlightActionButton(action);
    await companion.sendToolResult(call.id, { ok: true });
  }
});

// 4) 出错回调(鉴权失效、流断开等)
companion.onError((err) => console.warn('[companion]', err.code, err.message));

4. 把牌局喂进去 —— "陪玩"通道(world-state)

这是让陪伴"看见"牌桌的核心。sendWorldState 接受单条或一批事件:

// —— 状态类(retained:true):同 type 新值覆盖旧值,适合筹码 / 当前牌面 / 阶段 ——
companion.sendWorldState({ type: 'game.player.stack', data: { chips: 5000 }, retained: true });
companion.sendWorldState({ type: 'game.hand.hole',   data: { cards: ['A♠', 'K♠'] }, retained: true });
companion.sendWorldState({ type: 'game.hand.stage',  data: { stage: 'flop', board: ['A♥', 'K♦', '2♣'] }, retained: true });

// —— 事件类(retained 默认 false):瞬时,带 salience(重要度 0~1)排序 ——
//     voiceRelevant:true → 通话进行中会被陪伴当场念/反应出来
companion.sendWorldState({ type: 'game.event.your_turn',     data: { toCall: 200 }, salience: 0.8, voiceRelevant: true });
companion.sendWorldState({ type: 'game.event.opponent_raise', data: { by: 'Player3', amount: 600 }, salience: 0.6 });
companion.sendWorldState({ type: 'game.event.won_pot',       data: { amount: 5000 }, salience: 0.95, voiceRelevant: true });
companion.sendWorldState({ type: 'game.event.lost_pot',      data: { amount: 1200 }, salience: 0.7, voiceRelevant: true });

// 批量:
companion.sendWorldState([
  { type: 'game.hand.stage', data: { stage: 'river', board: ['A♥','K♦','2♣','9♠','J♦'] }, retained: true },
  { type: 'game.event.showdown', data: { result: 'win', hand: 'two pair' }, salience: 0.9, voiceRelevant: true },
]);

规则速记:

字段 含义
retained: true 状态,按 type 合并,只保留最新值(筹码、牌面、阶段)
retained: false(默认) 事件,瞬时;salience < 0.25 会被丢弃,≥0.25 进入排序
salience: 0~1 重要度,决定有限预算里哪些事件进陪伴的"脑子"
voiceRelevant: true 通话中把这条直接推进实时语音,让陪伴当场反应

specversion / id / source 这些字段 SDK 会自动补全,你只填 type / data / retained / salience / voiceRelevant 即可。


5. 玩家提问 & 语音陪打

// —— 文字 ——
await companion.sendText('这手 AK 同花,翻牌中顶对,该跟还是加注?');
// (回复通过 onMessage 异步到达)

// 看图(需要 token 的 files 权限,默认已含):
await companion.sendText('看看我这手怎么打', { images: ['data:image/jpeg;base64,...'] });

// —— 语音陪打(浏览器内,端到端)——
const call = await companion.connectCall({ locale: navigator.language });
// connectCall 直接和语音服务商建立 WebRTC,Pouchy 不经手音频。
// 通话中你照常 sendWorldState({ voiceRelevant:true }),陪伴会当场把局面念出来反应。
// 结束:
call.close();   // 自动把这通语音的内容沉淀进陪伴的长期记忆

语音服务商优先级由服务端决定:ElevenLabs Convai(首选)→ OpenAI Realtime(兜底)。 connectCall() 走 EL 时需要可选依赖 @elevenlabs/client;OpenAI Realtime 不需要 额外依赖。两者都不需要你管密钥,SDK 帮你取。


6.(可选)登记到 Pouchy 应用目录,让 Agent 主动推荐

以上做完,游戏内陪伴就完全可用了。这一节只在你希望被 Pouchy 的 Agent 主动 推荐/跳转时才需要(例如用户在 Pouchy 里说"想打德州",Agent 推荐你们的游戏)。 这一步由你们的 Pouchy 账号持有人在 Pouchy 里操作,不是写代码:

  1. Pouchy → 钱包 → 开发者:我的应用 → 注册,填写清单:
    • name:游戏名(≤60 字)
    • launchUrl:游戏入口 URL(必须 https)
    • categories:选 game
    • shortDescription:一句话简介(≤140 字)
    • goodForHint:让 Agent 匹配意图的关键词,如"打德州扑克 / 陪打牌 / 牌局分析"(≤200 字)
    • iconUrl(可选,https)、requestedScopes(你会用到的权限)
  2. 拿到 app_… 后,按页面提示给 launchUrl 域名加一条 DNS TXT 记录_pouchy-verify.<你的域名> 值为 pouchy-verify=<验证 token>,回页面点"验证域名"。
  3. 验证通过 → 进入全局发现(陌生用户也可能被推荐到)。不验证也能用,只是仅在 "已连接"列表里出现。

玩家把密钥绑定到这个 app(在"新建密钥"时搜索并选择你的 app)后,你们的游戏就会 作为该玩家"已连接应用"被优先推荐。


7. 权限(scopes)一览

token 默认携带的安全权限(够德州扑克用):

Scope 作用
chat 文字对话
voice / call 语音 / 实时通话
files 多模态(看图)
events.subscribe 接收陪伴的事件流(SSE)
worldstate.write 推送实时牌局 world-state
memory.read:app / memory.write:app 读写本应用专属记忆命名空间

敏感权限(默认关闭,需玩家显式开启,德州扑克通常不需要): memory.read:core / memory.write:core(共享大脑)、skills.executewallet.spendsocial.message、以及 represent* 系列(代聊/客服场景)。

服务端按权限逐项校验;越权请求返回 403。


8. 速率限制

按 IP 的分桶限流(超限返回 429 + Retry-After):

路由 限额
/api/companion/session*(握手 / 输入 / tool-result) 120/分,1500/小时
/api/companion/memory 120/分,2000/小时
/api/companion(密钥 / 头像 / 其它) 120/分,1500/小时

实时 world-state(/context)不做 HTTP 限流,靠形状本身背压:瞬时事件 salience<0.25 丢弃、上限 32 条;状态事件每个 type 一格、后写覆盖。


9. 完整文档

随包附 / 仓库 docs/ 下:

  • companion-quickstart.md — 通用上手
  • companion-api-reference.md — 每个端点的完整参数 / 响应 / 示例
  • companion-sdk-protocol.md — 完整线协议(五个平面、消息类型、world-state、语音、MCP)
  • companion-integration-faq.md — 常见问题(模块解析、密钥自助、连麦选项、world-state 上限)
  • companion-voice-integration.md — 语音细节

10. 常见问答(针对德州扑克)

Q:要不要给我们一段提示词写进游戏? 不需要也不应该。陪伴的 system prompt 由 Pouchy 服务端组装(人设 + 记忆 + 实时局面)。 你只推 world-state + 转发玩家消息。

Q:每个玩家都要自己生成密钥?能不能我们统一发? 密钥代表某个玩家自己的陪伴,必须由该玩家在自己的 Pouchy 账号里生成。你们不持有、 也不应该共享别人的密钥。

Q:不接语音,只做文字陪聊行不行? 可以。modalities: ['text'],只用 connect / start / onMessage / sendText / sendWorldState 即可,连 @elevenlabs/client 都不用装。

Q:陪伴能帮玩家做决定(自动下注)吗? SDK 的 tools 是你声明、由陪伴"请求"、你来执行的。是否真的代下注完全由你的游戏 逻辑决定;涉及真实钱包扣费才需要敏感权限并经玩家确认门控。

Q:会话能跨手牌延续吗? 会。同一 surface 是一个可续接会话;connectCall().close()endSession() 会把这段经历沉淀进陪伴长期记忆,玩家下次回来它还记得。


有问题直接联系 Pouchy 团队(oviswang@gmail.com)。