多模态:视频输入

实时 Agent 可以。除了音频流,您还可以将来自摄像头(或屏幕)的图像帧推送到模型,这样用户就可以向 Agent 展示一些东西——例如用于退货的损坏产品、屏幕上的错误、一份文档——而不是描述它。

API

Runner 上的一个方法,与 send_audio 类似:

runner.send_video_frame("image/jpeg", &base64_jpeg).await?;

它也可以直接在较低级别的 RealtimeRunnerRealtimeSession 上使用。默认的 trait 实现是空操作,因此不接受视觉输入的提供商/模型会简单地忽略帧。

各提供商如何处理帧

两个后端对视觉的处理方式不同,这应该会影响您发送帧的频率:

提供者机制节奏
Gemini Live持续的 realtimeInput 媒体块流式传输它们 — ~1–2 fps 是自然的
OpenAI Realtime一个 input_image 部分在对话项上快照 — 节流到每隔几秒约 1 次

Gemini 专为连续视频而构建:帧像音频一样流动,模型对动态画面进行推理。OpenAI 的实时视觉是图像上下文:每帧都成为一个对话项,因此每秒发送多帧会淹没上下文并增加成本。在有用时(例如用户说“看看这个”)向 OpenAI 发送一帧,而不是每个时间点都发送。

示例 UI 对此进行了编码:

const intervalMs = provider === 'gemini' ? 700 : 2500;  // ~1.4 fps vs ~0.4 fps

在浏览器中捕获帧

获取摄像头,将 <video> 绘制到画布上,并通过您的 WebSocket 发送 JPEG 帧(服务器端桥接 将它们转发到 send_video_frame):

const stream = await navigator.mediaDevices.getUserMedia({ video: { width: 640, height: 480 } });
video.srcObject = stream;

const canvas = document.createElement('canvas');
setInterval(() => {
  if (ws.readyState !== WebSocket.OPEN) return;
  canvas.width = 640; canvas.height = 480;
  canvas.getContext('2d').drawImage(video, 0, 0, 640, 480);
  const data = canvas.toDataURL('image/jpeg', 0.6).split(',')[1];  // strip data: prefix
  ws.send(JSON.stringify({ type: 'video_frame', mime: 'image/jpeg', data }));
}, intervalMs);

在服务器上,转发到模型:

ClientMsg::VideoFrame { mime, data } => {
    let _ = runner.send_video_frame(&mime, &data).await;  // non-fatal on error
}

保持帧适中(640×480,JPEG 质量约 0.6)——视觉不需要全分辨率,较小的帧意味着更低的延迟和成本。

视觉提示

告诉 agent 它可以看见,这样它就能自然地使用摄像头:

You can hear the customer and SEE what they show their camera. When they show
you an item (e.g. a damaged product for a return), briefly describe what you see
and use it to help resolve the issue.

试用

customer_service 示例有一个摄像头面板:启动摄像头,举起一个物体,然后说 “我想退回这个——你能看到它吗?”。它在 Gemini(连续视频)上效果最佳;在 OpenAI 上,agent 对周期性快照进行推理。

下一步:情感对话 →