Pouchy 陪伴接入指南 — 德州扑克(Texas Hold'em)
把 Pouchy 的 AI 陪伴接进你的德州扑克游戏:玩家在牌桌旁能和自己的陪伴聊牌、 让它语音陪打、它还能"看见"牌局实时反应。本文是给集成方(你们的工程团队)的 落地说明,只需几十行代码。
你不需要写任何提示词(prompt)。陪伴的人格、记忆、语气全部由 Pouchy 服务端 组装,你只负责:把牌桌上发生的事喂进来 + 转发玩家的文字/语音。这是设计上的 安全边界,能保证陪伴在任何接入方里都保持一致、可控。
0. 三句话理解
- 运行时:游戏用一个
pchy_…密钥调用 Pouchy 的 REST/SSE 接口,陪伴就能在 你的牌桌里说话。今天就能跑,不依赖任何审核或上架。 - 密钥来自玩家自己:每个玩家登录 pouchy.ai 后调用
POST /api/companion/keys自助生成(应用内暂无"伴侣接入密钥"面板),粘进你的游戏即可。默认权限就够德州 扑克用,敏感权限(扣费/技能/社交)默认关闭。 - 被 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 里操作,不是写代码:
- Pouchy → 钱包 → 开发者:我的应用 → 注册,填写清单:
- name:游戏名(≤60 字)
- launchUrl:游戏入口 URL(必须 https)
- categories:选
game - shortDescription:一句话简介(≤140 字)
- goodForHint:让 Agent 匹配意图的关键词,如"打德州扑克 / 陪打牌 / 牌局分析"(≤200 字)
- iconUrl(可选,https)、requestedScopes(你会用到的权限)
- 拿到
app_…后,按页面提示给launchUrl域名加一条 DNS TXT 记录:_pouchy-verify.<你的域名>值为pouchy-verify=<验证 token>,回页面点"验证域名"。 - 验证通过 → 进入全局发现(陌生用户也可能被推荐到)。不验证也能用,只是仅在 "已连接"列表里出现。
玩家把密钥绑定到这个 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.execute、
wallet.spend、social.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)。