rw-integrate-characters
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseIntegrate Characters (GWM-1 Avatars)
集成Characters(GWM-1虚拟形象)
PREREQUISITES:
— Project must have a server-side component (API key must NEVER be exposed to the client)+rw-check-compatibility — Load the latest API reference from https://docs.dev.runwayml.com/api/ before integrating+rw-fetch-api-reference — API credentials must be configured+rw-setup-api-keyOPTIONAL DEPENDENCIES:
— Add a knowledge base to your character+rw-integrate-documents — Use the React SDK to embed the avatar call UI+rw-integrate-character-embed
Help users create Runway Characters — real-time conversational AI avatars powered by GWM-1.
Use this only when modifying a user's codebase. For direct avatar management or other one-off Runway account actions from the agent, use instead.
+use-runway-apiCharacters are generated from a single image (any visual style — photorealistic, animated, non-human) with full control over voice, personality, knowledge, and actions. No fine-tuning or training required.
前置要求:
— 项目必须包含服务端组件(API密钥绝对不能暴露给客户端)+rw-check-compatibility — 集成前请从https://docs.dev.runwayml.com/api/加载最新的API参考文档+rw-fetch-api-reference — 必须配置API凭证+rw-setup-api-key可选依赖:
— 为你的角色添加知识库+rw-integrate-documents — 使用React SDK嵌入虚拟形象通话UI+rw-integrate-character-embed
帮助用户创建Runway Characters——由GWM-1驱动的实时对话AI虚拟形象。
仅在修改用户代码库时使用此内容。如果要通过Agent直接管理虚拟形象或执行其他一次性Runway账户操作,请使用。
+use-runway-apiCharacters由单张图片生成(支持任何视觉风格——写实、动画、非人类),可完全控制声音、性格、知识和行为。无需微调或训练。
Key Concepts
核心概念
Avatars vs Sessions
虚拟形象 vs 会话
| Concept | Description |
|---|---|
| Avatar | A persistent persona with a defined appearance, voice, and personality. Created once, used many times. |
| Session | A live WebRTC connection for real-time conversation. Connects one user to one avatar. Max duration: 5 minutes. |
| 概念 | 描述 |
|---|---|
| Avatar | 具有确定外观、声音和性格的持久角色。创建一次,可多次使用。 |
| Session | 用于实时对话的WebRTC连接。将一个用户连接到一个虚拟形象。最长时长:5分钟。 |
Session Lifecycle
会话生命周期
┌───────────┐
┌──────────┤ NOT_READY ├──────────┐
│ └─────┬─────┘ │
│ │ │
▼ ▼ ▼
CANCELLED READY FAILED
┌──┴──┐
│ │
▼ ▼
RUNNING FAILED
┌──┴──┐
│ │
▼ ▼
COMPLETED CANCELLED| Status | Description |
|---|---|
| Session is being provisioned. Poll until ready. |
| Session is ready. The |
| WebRTC connection is active. Conversation in progress. |
| Session ended normally. |
| Error occurred. Check the |
| Explicitly cancelled before completion. |
Important: Session credentials can only be consumed once. If the WebRTC connection fails after credentials are consumed, you must create a new Session.
┌───────────┐
┌──────────┤ NOT_READY ├──────────┐
│ └─────┬─────┘ │
│ │ │
▼ ▼ ▼
CANCELLED READY FAILED
┌──┴──┐
│ │
▼ ▼
RUNNING FAILED
┌──┴──┐
│ │
▼ ▼
COMPLETED CANCELLED| 状态 | 描述 |
|---|---|
| 会话正在配置中。轮询直到就绪。 |
| 会话已就绪。 |
| WebRTC连接已激活。对话进行中。 |
| 会话正常结束。 |
| 发生错误。检查 |
| 在完成前被显式取消。 |
重要提示: 会话凭证只能使用一次。如果在凭证被使用后WebRTC连接失败,你必须创建一个新的会话。
Architecture
架构
The API key must stay server-side. The flow is:
Client (React) → Your Server → Runway API
↓
Client (React) ←─── WebRTC ───← Runway (realtime)- Client requests a session from your server
- Your server calls Runway API to create a session ()
POST /v1/realtime_sessions - Your server polls until session is (
READY)GET /v1/realtime_sessions/:id - Your server consumes credentials ()
POST /v1/realtime_sessions/:id/consume - Your server returns credentials to the client
- Client establishes a direct WebRTC connection to Runway
API密钥必须保存在服务端。流程如下:
Client (React) → 你的服务器 → Runway API
↓
Client (React) ←─── WebRTC ───← Runway (实时)- 客户端向你的服务器请求会话
- 你的服务器调用Runway API创建会话()
POST /v1/realtime_sessions - 你的服务器轮询直到会话变为状态(
READY)GET /v1/realtime_sessions/:id - 你的服务器使用凭证()
POST /v1/realtime_sessions/:id/consume - 你的服务器将凭证返回给客户端
- 客户端与Runway建立直接的WebRTC连接
Step 1: Install Dependencies
步骤1:安装依赖
bash
npm install @runwayml/sdk @runwayml/avatars-react- — Server-side SDK (session creation, avatar management)
@runwayml/sdk - — Client-side React components (WebRTC, UI)
@runwayml/avatars-react
bash
npm install @runwayml/sdk @runwayml/avatars-react- — 服务端SDK(会话创建、虚拟形象管理)
@runwayml/sdk - — 客户端React组件(WebRTC、UI)
@runwayml/avatars-react
Step 2: Create an Avatar
步骤2:创建虚拟形象
Avatars can be created via the Developer Portal (UI) or the API (programmatic).
虚拟形象可通过开发者门户(UI)或API(程序化)创建。
Option A: Developer Portal (Recommended for first time)
选项A:开发者门户(首次使用推荐)
- Go to https://dev.runwayml.com/ → Characters tab
- Click Create a Character
- Upload a reference image (tips below)
- Choose a voice preset
- Write personality instructions (e.g., "You are a helpful customer support agent for Acme Corp...")
- Optionally add a starting script (what the character says first)
- Optionally upload knowledge documents (files)
.txt - Click Create Character
- Copy the Avatar ID (a UUID like )
8be4df61-93ca-11d2-aa0d-00e098032b8c
- 访问https://dev.runwayml.com/ → Characters标签页
- 点击Create a Character
- 上传参考图片(提示见下文)
- 选择语音预设
- 编写性格说明(例如:"你是Acme公司的客服助手...")
- 可选:添加开场脚本(角色首次说的内容)
- 可选:上传知识文档(文件)
.txt - 点击Create Character
- 复制Avatar ID(类似的UUID)
8be4df61-93ca-11d2-aa0d-00e098032b8c
Option B: API (Programmatic)
选项B:API(程序化)
javascript
// 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
undefinedjavascript
// 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
undefinedPython
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)
**If the reference image is a local file**, upload it first using `+rw-integrate-uploads`:
```javascript
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...',
});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`上传:
```javascript
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...',
});Reference Image (Required)
参考图片(必填)
referenceImage| Format | Limit | When to use |
|---|---|---|
| 2048 chars | Image already hosted publicly |
| 5 MB (characters) | Small-to-medium local files (~3.5 MB raw max) |
| 5000 chars | Large files uploaded via |
<<<<<<< HEAD:skills/rw-integrate-characters/SKILL.md
For local files over ~3.5 MB, use the upload flow (+rw-integrate-uploads
) to get a runway://
URI instead of a data URI.
+rw-integrate-uploadsrunway://For local files over ~3.5 MB, use the upload flow () to get a URI instead of a data URI.
+integrate-uploadsrunway://810dd3a (Improve CLI error details, auth fallback, and skill docs from testing):skills/integrate-characters/SKILL.md
创建虚拟形象时是必填项。它支持三种格式:
referenceImage| 格式 | 限制 | 使用场景 |
|---|---|---|
| 2048字符 | 图片已公开托管 |
| 5 MB(字符数) | 中小型本地文件(原始最大约3.5 MB) |
| 5000字符 | 大文件需先通过 |
<<<<<<< HEAD:skills/rw-integrate-characters/SKILL.md
对于超过约3.5 MB的本地文件,请使用上传流程(+rw-integrate-uploads
)获取runway://
URI,而非data URI。
+rw-integrate-uploadsrunway://对于超过约3.5 MB的本地文件,请使用上传流程()获取 URI,而非data URI。
+integrate-uploadsrunway://810dd3a (Improve CLI error details, auth fallback, and skill docs from testing):skills/integrate-characters/SKILL.md
Reference Image Guidelines
参考图片指南
- Any visual style works: photorealistic humans, animated mascots, stylized brand characters
- Use high-quality images with good lighting
- Face must be clearly visible and centered — images without a recognizable face will fail processing
- Avoid images with multiple people or obstructions
- Recommended aspect ratio: 1088×704
- 支持任何视觉风格:写实人物、动画吉祥物、风格化品牌角色
- 使用高质量、光线良好的图片
- 面部必须清晰可见且居中 —— 无法识别面部的图片会处理失败
- 避免包含多个人物或遮挡物的图片
- 推荐宽高比:1088×704
Voice Presets
语音预设
| Preset ID | Name | Style |
|---|---|---|
| Clara | Soft, approachable |
| Victoria | Firm, professional |
| Vincent | Knowledgeable, authoritative |
Preview all voices in the Developer Portal.
| 预设ID | 名称 | 风格 |
|---|---|---|
| Clara | 柔和、亲切 |
| Victoria | 坚定、专业 |
| Vincent | 博学、权威 |
在开发者门户预览所有语音。
Step 3: Create a Session (Server-Side)
步骤3:创建会话(服务端)
This is the server-side API route that your client will call. It creates a session, polls until ready, consumes credentials, and returns them.
这是客户端将调用的服务端API路由。它负责创建会话、轮询直到就绪、使用凭证并返回凭证。
Next.js App Router
Next.js App Router
typescript
// 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,
});
}typescript
// 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. 创建会话
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'custom', avatarId },
});
// 2. 轮询直到就绪
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: '会话超时' }, { status: 504 });
}
// 3. 使用会话获取WebRTC凭证
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
Express.js
typescript
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' });
}
});typescript
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. 创建会话
const { id: sessionId } = await client.realtimeSessions.create({
model: 'gwm1_avatars',
avatar: { type: 'custom', avatarId },
});
// 2. 轮询直到就绪
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: '会话超时' });
}
// 3. 使用凭证
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('会话创建失败:', error);
res.status(500).json({ error: error instanceof Error ? error.message : '未知错误' });
}
});FastAPI (Python)
FastAPI (Python)
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"],
}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. 创建会话
result = client.realtime_sessions.create(
model='gwm1_avatars',
avatar={'type': 'custom', 'avatar_id': req.avatar_id},
)
session_id = result.id
# 2. 轮询直到就绪
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='会话超时')
# 3. 使用凭证
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"],
}Step 4: Connect from the Client
步骤4:从客户端连接
See for the React SDK components that handle WebRTC connection and rendering. The simplest approach:
+rw-integrate-character-embedtsx
'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)}
/>
);
}请查看获取处理WebRTC连接和渲染的React SDK组件。最简单的实现方式:
+rw-integrate-character-embedtsx
'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('通话结束')}
onError={(error) => console.error('错误:', error)}
/>
);
}Troubleshooting
故障排除
- API key errors: Key starts with followed by 128 hex chars. Ensure it's active.
key_ - No credits: Account must have prepaid credits before starting a call.
- Session timeout: The 60-iteration poll loop waits ~60 seconds. If sessions consistently time out, check your tier's concurrency limits.
- Credentials already consumed: Session credentials are one-time use. If WebRTC fails after consume, create a new session.
- API密钥错误: 密钥以开头,后跟128个十六进制字符。确保密钥处于激活状态。
key_ - 无可用额度: 开始通话前账户必须有预付费额度。
- 会话超时: 60次迭代的轮询循环等待约60秒。如果会话持续超时,请检查你的套餐并发限制。
- 凭证已被使用: 会话凭证仅可使用一次。如果在使用凭证后WebRTC连接失败,请创建新会话。
Debug logging
调试日志
tsx
<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);
}}
/>tsx
<AvatarCall
avatarId="your-avatar-id"
connectUrl="/api/avatar/session"
onError={(error) => {
console.error('虚拟形象错误:', error);
console.error('错误名称:', error.name);
console.error('错误信息:', error.message);
if (error.cause) console.error('原因:', error.cause);
}}
/>Monitor session state
监控会话状态
tsx
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>
);
}tsx
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>
);
}Test with minimal setup
极简配置测试
bash
npx degit runwayml/avatars-sdk-react/examples/nextjs-simple test-app
cd test-app
npm installbash
npx degit runwayml/avatars-sdk-react/examples/nextjs-simple test-app
cd test-app
npm installAdd your API key to .env.local
将你的API密钥添加到.env.local
npm run dev
undefinednpm run dev
undefinedBrowser Support
浏览器支持
| Browser | Minimum Version |
|---|---|
| Chrome | 74+ |
| Firefox | 78+ |
| Safari | 14.1+ |
| Edge | 79+ |
Users must grant microphone permissions. Camera permissions needed if user video is enabled.
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 74+ |
| Firefox | 78+ |
| Safari | 14.1+ |
| Edge | 79+ |
用户必须授予麦克风权限。如果启用用户视频,则需要摄像头权限。
Getting Help
获取帮助
| Resource | Description |
|---|---|
| Developer Portal | Manage avatars, view logs, access dashboard |
| SDK Repository | Report bugs, view examples, check releases |
When reporting issues, include: browser/version, SDK version (), error messages, session ID, and steps to reproduce.
npm list @runwayml/avatars-react