关于 rw-integrate-characters
帮助开发者在代码库中集成 Runway Characters,构建由单张图像生成、支持实时对话的 AI 虚拟形象。提供服务端会话流程,并可选配知识文档和 React 通话界面。
主要功能
- 01从单张图像创建虚拟形象
- 02设置语音、个性与知识内容
- 03通过服务端 API 创建实时会话
- 04使用 WebRTC 建立对话连接
- 05可选集成文档与 React 界面
适用场景
- 01在应用中加入实时对话虚拟形象
- 02创建具有品牌个性和指定语音的角色
- 03为角色添加知识文档并嵌入 React 通话界面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ runwayml/ skills --skill rw-integrate-characters安装后在 Claude Code 中输入/rw-integrate-characters确认已加载。
详细介绍
集成 Character(GWM-1 Avatar)
前置条件:
+rw-check-compatibility— 项目必须包含服务端组件(绝不能向客户端暴露 API 密钥)+rw-fetch-api-reference— 集成前,从 https://docs.dev.runwayml.com/api/ 加载最新 API 参考+rw-setup-api-key— 必须配置 API 凭据可选依赖项:
+rw-integrate-documents— 为你的 Character 添加知识库+rw-integrate-character-embed— 使用 React SDK 嵌入 Avatar 通话 UI
帮助用户创建 Runway Character — 由 GWM-1 驱动的实时对话式 AI Avatar。
仅在修改用户代码库时使用此功能。若要直接管理 Avatar,或由 Agent 执行其他一次性 Runway 账户操作,请改用 +use-runway-api。
Character 根据单张图片生成(支持任何视觉风格,包括照片级真实、动画、非人类),并可全面控制语音、个性、知识和操作。无需微调或训练。
核心概念
Avatar 与会话
| 概念 | 说明 |
|---|---|
| Avatar | 具有既定外观、语音和个性的持久 Persona。创建一次,可多次使用。 |
| 会话 | 用于实时对话的 WebRTC 实时连接。将一位用户连接到一个 Avatar。最长时长:5 分钟。 |
会话生命周期
┌───────────┐
┌──────────┤ NOT_READY ├──────────┐
│ └─────┬─────┘ │
│ │ │
▼ ▼ ▼
CANCELLED READY FAILED
┌──┴──┐
│ │
▼ ▼
RUNNING FAILED
┌──┴──┐
│ │
▼ ▼
COMPLETED CANCELLED
| 状态 | 说明 |
|---|---|
NOT_READY | 正在配置会话。轮询直到就绪。 |
READY | 会话已就绪。sessionKey 已可用。 |
RUNNING | WebRTC 连接处于活动状态。对话正在进行。 |
COMPLETED | 会话正常结束。 |
FAILED | 发生错误。请检查 failure 字段。 |
CANCELLED | 会话在完成前被明确取消。 |
重要: 会话凭据只能使用一次。如果凭据使用后 WebRTC 连接失败,你必须创建一个新会话。
架构
API 密钥必须保留在服务端。流程如下:
Client (React) → Your Server → Runway API
↓
Client (React) ←─── WebRTC ───← Runway (realtime)
- 客户端向你的服务器请求会话
- 你的服务器调用 Runway API 创建会话(
POST /v1/realtime_sessions) - 你的服务器轮询,直到会话状态变为
READY(GET /v1/realtime_sessions/:id) - 你的服务器使用凭据(
POST /v1/realtime_sessions/:id/consume) - 你的服务器将凭据返回给客户端
- 客户端与 Runway 建立直接的 WebRTC 连接
步骤 1:安装依赖项
npm install @runwayml/sdk @runwayml/avatars-react
@runwayml/sdk— 服务端 SDK(创建会话、管理 Avatar)@runwayml/avatars-react— 客户端 React 组件(WebRTC、UI)
步骤 2:创建 Avatar
可以通过开发者门户(UI)或 API(编程方式)创建 Avatar。
选项 A:开发者门户(首次使用推荐)
- 前往 https://dev.runwayml.com/ → Characters 标签页
- 点击 Create a Character
- 上传参考图片(提示见下文)
- 选择语音预设
- 编写个性指令(例如:“你是 Acme Corp 的一名乐于助人的客户支持 Agent……”)
- 可选:添加开场脚本(Character 首先说的话)
- 可选:上传知识文档(
.txt文件) - 点击 Create Character
- 复制 Avatar ID(UUID,例如
8be4df61-93ca-11d2-aa0d-00e098032b8c)
选项 B:API(编程方式)
// Node.js
import RunwayML from '@runwayml/sdk';
const client = new RunwayML();
const avatar = await client.avatars.create({
name: 'Support Agent',
referenceImage: 'https://example.com/avatar.png',
voice: {
type: 'runway-live-preset',
presetId: 'clara',
},
personality: 'You are a helpful customer support agent for Acme Corp. You help users with billing questions and technical issues.',
});
console.log('Avatar ID:', avatar.id);
# Python
from runwayml import RunwayML
client = RunwayML()
avatar = client.avatars.create(
name='Support Agent',
reference_image='https://example.com/avatar.png',
voice={
'type': 'runway-live-preset',
'preset_id': 'clara',
},
personality='You are a helpful customer support agent for Acme Corp.',
)
print('Avatar ID:', avatar.id)
如果参考图片是本地文件,请先使用 +rw-integrate-uploads 上传:
import fs from 'fs';
const upload = await client.uploads.createEphemeral(
fs.createReadStream('/path/to/avatar-image.png')
);
const avatar = await client.avatars.create({
name: 'Support Agent',
referenceImage: upload.runwayUri,
voice: { type: 'runway-live-preset', presetId: 'clara' },
personality: 'You are a helpful customer support agent...',
});
参考图像(必需)
创建头像时,必须提供 referenceImage。它支持以下三种格式:
| 格式 | 限制 | 使用场景 |
|---|---|---|
https://… URL | 2048 个字符 | 图像已公开托管 |
data:image/…;base64,… | 5 MB(字符) | 中小型本地文件(原始文件最大约 3.5 MB) |
runway://… URI | 5000 个字符 | 先通过 /v1/uploads 上传的大型文件 |
<<<<<<< HEAD:skills/rw-integrate-characters/SKILL.md
对于超过约 3.5 MB 的本地文件,请使用上传流程(+rw-integrate-uploads)获取 runway:// URI,而不要使用 data URI。
对于超过约 3.5 MB 的本地文件,请使用上传流程(+integrate-uploads)获取 runway:// URI,而不要使用 data URI。
810dd3a (Improve CLI error details, auth fallback, and skill docs from testing):skills/integrate-characters/SKILL.md
参考图像指南
- 任何视觉风格均可:写实人物、动画吉祥物、风格化品牌角色
- 使用光线良好的高质量图像
- 面部必须清晰可见且位于画面中央——无法识别出面部的图像将无法通过处理
- 避免使用包含多人的图像或有遮挡的图像
- 建议宽高比:1088×704
语音预设
| 预设 ID | 名称 | 风格 |
|---|---|---|
clara | Clara | 柔和、亲切 |
victoria | Victoria | 坚定、专业 |
vincent | Vincent | 知识渊博、权威 |
在 Developer Portal 中预览所有语音。
步骤 3:创建会话(服务端)
这是客户端将调用的服务端 API 路由。它会创建会话、轮询直至会话就绪、使用凭据并返回这些凭据。
Next.js App Router
// app/api/avatar/session/route.ts
import RunwayML from '@runwayml/sdk';
const client = new RunwayML();
export async function POST(request: Request) {
const { avatarId } = await request.json();
// 1. Create session
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'custom', avatarId },
});
// 2. Poll until ready
let sessionKey: string | undefined;
for (let i = 0; i < 60; i++) {
const session = await client.realtimeSessions.retrieve(sessionId);
if (session.status === 'READY') {
sessionKey = session.sessionKey;
break;
}
if (session.status === 'FAILED') {
return Response.json({ error: session.failure }, { status: 500 });
}
await new Promise(r => setTimeout(r, 1000));
}
if (!sessionKey) {
return Response.json({ error: 'Session timed out' }, { status: 504 });
}
// 3. Consume session to get WebRTC credentials
const consumeResponse = await fetch(
`${client.baseURL}/v1/realtime_sessions/${sessionId}/consume`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${sessionKey}`,
'X-Runway-Version': '2024-11-06',
},
}
);
const credentials = await consumeResponse.json();
return Response.json({
sessionId,
serverUrl: credentials.url,
token: credentials.token,
roomName: credentials.roomName,
});
}
Express.js
import RunwayML from '@runwayml/sdk';
import express from 'express';
const client = new RunwayML();
const app = express();
app.use(express.json());
app.post('/api/avatar/session', async (req, res) => {
const { avatarId } = req.body;
try {
// 1. Create session
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'custom', avatarId },
});
// 2. Poll until ready
let sessionKey: string | undefined;
for (let i = 0; i < 60; i++) {
const session = await client.realtimeSessions.retrieve(sessionId);
if (session.status === 'READY') {
sessionKey = session.sessionKey;
break;
}
if (session.status === 'FAILED') {
return res.status(500).json({ error: session.failure });
}
await new Promise(r => setTimeout(r, 1000));
}
if (!sessionKey) {
return res.status(504).json({ error: 'Session timed out' });
}
// 3. Consume credentials
const consumeResponse = await fetch(
`${client.baseURL}/v1/realtime_sessions/${sessionId}/consume`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${sessionKey}`,
'X-Runway-Version': '2024-11-06',
},
}
);
const credentials = await consumeResponse.json();
res.json({
sessionId,
serverUrl: credentials.url,
token: credentials.token,
roomName: credentials.roomName,
});
} catch (error) {
console.error('Session creation failed:', error);
res.status(500).json({ error: error instanceof Error ? error.message : 'Unknown error' });
}
});
FastAPI (Python)
import time
import httpx
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from runwayml import RunwayML
app = FastAPI()
client = RunwayML()
class SessionRequest(BaseModel):
avatar_id: str
@app.post("/api/avatar/session")
async def create_session(req: SessionRequest):
# 1. Create session
result = client.realtime_sessions.create(
model='gwm1_avatars',
avatar={'type': 'custom', 'avatar_id': req.avatar_id},
)
session_id = result.id
# 2. Poll until ready
session_key = None
for _ in range(60):
session = client.realtime_sessions.retrieve(session_id)
if session.status == 'READY':
session_key = session.session_key
break
if session.status == 'FAILED':
raise HTTPException(status_code=500, detail=str(session.failure))
time.sleep(1)
if not session_key:
raise HTTPException(status_code=504, detail='Session timed out')
# 3. Consume credentials
async with httpx.AsyncClient() as http:
resp = await http.post(
f"{client.base_url}/v1/realtime_sessions/{session_id}/consume",
headers={
"Authorization": f"Bearer {session_key}",
"X-Runway-Version": "2024-11-06",
},
)
credentials = resp.json()
return {
"session_id": session_id,
"server_url": credentials["url"],
"token": credentials["token"],
"room_name": credentials["roomName"],
}
步骤 4:从客户端连接
请参阅 +rw-integrate-character-embed,了解用于处理 WebRTC 连接和渲染的 React SDK 组件。最简单的方法如下:
'use client';
import { AvatarCall } from '@runwayml/avatars-react';
import '@runwayml/avatars-react/styles.css';
export default function CharacterPage() {
return (
<AvatarCall
avatarId="your-avatar-id"
connectUrl="/api/avatar/session"
onEnd={() => console.log('Call ended')}
onError={(error) => console.error('Error:', error)}
/>
);
}
故障排查
- **API 密钥错误:**密钥以
key_开头,后跟 128 个十六进制字符。请确保密钥处于有效状态。 - **没有额度:**账户必须有预付额度才能开始通话。
- **会话超时:**60 次迭代的轮询循环会等待约 60 秒。如果会话持续超时,请检查你所在套餐的并发限制。
- **凭据已被使用:**会话凭据只能使用一次。如果使用凭据后 WebRTC 连接失败,请创建新会话。
调试日志
<AvatarCall
avatarId="your-avatar-id"
connectUrl="/api/avatar/session"
onError={(error) => {
console.error('Avatar error:', error);
console.error('Error name:', error.name);
console.error('Error message:', error.message);
if (error.cause) console.error('Cause:', error.cause);
}}
/>
监控会话状态
import { useAvatarSession } from '@runwayml/avatars-react';
function DebugInfo() {
const { state, sessionId, error } = useAvatarSession();
return (
<pre>
{JSON.stringify({ state, sessionId, error: error?.message }, null, 2)}
</pre>
);
}
使用最简配置测试
npx degit runwayml/avatars-sdk-react/examples/nextjs-simple test-app
cd test-app
npm install
# Add your API key to .env.local
npm run dev
浏览器支持
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 74+ |
| Firefox | 78+ |
| Safari | 14.1+ |
| Edge | 79+ |
用户必须授予麦克风权限。如果启用了用户视频,还需要摄像头权限。
获取帮助
| 资源 | 说明 |
|---|---|
| Developer Portal | 管理头像、查看日志、访问仪表板 |
| SDK Repository | 报告 Bug、查看示例、检查版本发布 |
报告问题时,请提供:浏览器及版本、SDK 版本(npm list @runwayml/avatars-react)、错误消息、会话 ID 和重现步骤。