Skip to content

Repository files navigation

AI Gateway Lab

AI Gateway Lab 是一个独立的功能展示与可执行测试仓库。它不是现有 xapi-frontend / xapi-frontend-v2 的页面,也不依赖它们的路由、登录态或组件。

它解决两个问题:

  1. 在浏览器中以分类实验室展示 AI Gateway 的全部公开能力。
  2. 在 Node.js 中直接运行同一套调用代码,用于本地验证、CI 和生产 smoke test。

网页和脚本共同导入 src/lib/gateway-client.js。没有“网页写一套、脚本再写一套”的重复实现。

当前覆盖范围

页面 AI Gateway 端点 特性
Overview GET /health, GET /v1/models 服务状态、实时模型目录、能力矩阵
Text protocols POST /v1/chat/completions JSON / SSE、default / cost、路由与费用 Headers
Text protocols POST /v1/responses JSON / SSE、协议翻译
Text protocols POST /v1/messages JSON / SSE、Anthropic headers
Embeddings POST /v1/embeddings 向量维度与分布预览、完整 Raw JSON
Images POST /v1/images/generations X-Provider、202 submit、Proxy task 轮询、图片渲染
Videos POST /v1/videos 中立 DTO、X-Provider、202 submit、任务轮询、视频播放
Rerank POST /v1/rerank 文档得分和重排对比
WebSocket WS /v1/realtime OpenAI Realtime GA,文本 turn 与原始事件流
WebSocket WS /v1/asr 火山流式 ASR,二进制 + gzip,按音频时长计费
WebSocket WS /v1/tts 豆包双向流式 TTS,PCM 24k 输出,按字符计费
WebSocket WS /v1/ast 豆包同声传译,protobuf v4,按模态 token 计费
WebSocket WS /v1/podcast 豆包双人播客生成,二进制 v3,按模态 token 计费
JS scripts 同上 离线全量测试与真实 Gateway smoke

不把以下接口包装成已实现功能:

  • POST /v1/messages/count_tokens:当前 AI Gateway 明确返回 404。
  • speed / quality:当前路径可被接受,但排序逻辑仍等同 default;页面不把它们展示为可选策略。
  • GET /internal/routing/explain:这是需要共享密钥的内部管理接口,不进入公开实验室。
  • 豆包 Realtime:虽然 adapter 已注册,但统一域名的 /v1/realtime 冲突规则明确选择 OpenAI;因此不伪装成第六个公共路径。
  • endpoint UUID 寻址:这是内部/测试兼容入口,公开实验室只展示稳定的 Host + shared path。

快速开始

要求:Node.js 20+(需要原生 Fetch API)。

npm install
cp .env.example .env.local

先运行确定性的本地 Mock Gateway:

npm run mock

在第二个终端启动网页:

npm run dev

访问 http://127.0.0.1:5175。开发模式默认通过 Vite 同源代理连接 http://127.0.0.1:4010,不需要 API Key,不产生费用,也不会触发浏览器的跨端口限制。

如果 Mock Gateway 不在默认地址,可在 .env.local 中设置 VITE_MOCK_GATEWAY_TARGET。设置 VITE_GATEWAY_BASE_URL 则会绕过开发代理并让浏览器直接请求该 Gateway,适合验证生产 CORS。

直接运行 JavaScript 测试

离线全量测试

以下命令会临时启动 Mock Gateway,依次验证 health、models、三种文本协议、embeddings、rerank、图片异步任务和视频异步任务,然后自动关闭服务器:

npm run test:mock

Mock Gateway 也可单独启动,供浏览器或其他客户端调用:

MOCK_GATEWAY_PORT=4010 npm run mock

真实 Gateway 安全 smoke

默认 smoke 不调用图片和视频,避免无意产生异步生成费用:

AI_GATEWAY_BASE_URL=https://ai.xapi.to \
AI_GATEWAY_API_KEY=sk-xapi-… \
AI_GATEWAY_MODEL=deepseek-v4-flash \
AI_GATEWAY_MESSAGES_MODEL=deepseek-v4-flash \
npm run smoke

该命令验证:

  • /health
  • /v1/models
  • Chat Completions
  • Responses
  • Anthropic Messages
  • Embeddings
  • Rerank

不同环境可以覆盖模型:

AI_GATEWAY_EMBEDDING_MODEL=text-embedding-3-small
AI_GATEWAY_RERANK_MODEL=cohere/rerank-3.5

真实全量 smoke(可能计费)

图片和视频必须显式确认:

AI_GATEWAY_ALLOW_BILLABLE=true \
AI_GATEWAY_BASE_URL=https://ai.xapi.to \
AI_GATEWAY_API_KEY=sk-xapi-… \
AI_GATEWAY_TASK_ORIGINS=https://p.xapi.to \
npm run smoke:all

未设置 AI_GATEWAY_ALLOW_BILLABLE=true 时,脚本会拒绝执行并退出。

在自己的 JavaScript 中复用

import { GatewayClient } from './src/lib/gateway-client.js';

const client = new GatewayClient({
  baseUrl: process.env.AI_GATEWAY_BASE_URL,
  apiKey: process.env.AI_GATEWAY_API_KEY,
});

const result = await client.chatCompletions({
  model: 'deepseek-v4-flash',
  messages: [{ role: 'user', content: 'Hello' }],
});

console.log(result.status);
console.log(result.metadata.provider);
console.log(result.metadata.cost);
console.log(result.data);

流式调用通过 onEvent 获取经过解析的 SSE 事件:

await client.messages(
  {
    model: 'deepseek-v4-flash',
    max_tokens: 256,
    stream: true,
    messages: [{ role: 'user', content: 'Hello' }],
  },
  {
    stream: true,
    onEvent(event) {
      console.log(event.event, event.data);
    },
  },
);

WebSocket Gateway

WebSocket 页面默认连接统一入口 wss://ai.xapi.to,独立于开发期 HTTP Mock 代理。可以用 VITE_WS_GATEWAY_BASE_URL 覆盖:

VITE_WS_GATEWAY_BASE_URL=wss://ai.test.xapi.to npm run dev

页面展示并严格限制为当前生产共享路径:

Path Adapter 页面可验证范围
/v1/realtime openai-realtime 持续双向语音;server VAD 自动分轮;用户转写、助手语音与字幕
/v1/asr volcengine-asr 麦克风 PCM16/16k、gzip 二进制帧、实时转写
/v1/tts doubao-tts 同一会话连续发送文本片段、PCM16/24k 音频边到边播放
/v1/ast doubao-ast 持续麦克风输入、protobuf v4、原文/译文字幕和 Float32/24k 译音
/v1/podcast doubao-podcast 话题或对话稿长任务、轮次文本和 PCM16/24k 音频流

五项能力分别实现真实的会话生命周期,没有共用“提交请求”模型:

  • Realtime:连接后保持双向会话,server_vad 自动提交每个语音轮次并触发回复。
  • ASR:录音期间持续发送 gzip 音频帧并持续更新转写;停止时发送协议规定的 final frame。
  • TTS:SessionStarted 后可多次发送 TaskRequest(text),收到的音频立即排队播放;结束会话时才发送 FinishSession
  • AST:麦克风持续发送 TaskRequest(audio),原文、译文和译音同时流式返回;结束口译时发送 FinishSession
  • Podcast:不是实时会话。SessionStarted 后立即发送 FinishSession 触发一次长任务,随后等待轮次、音频、usage 和结束事件。

麦克风规则:

  • 只有点击 Start microphone 才会调用 getUserMedia,页面加载和 WebSocket 连接不会自动申请权限。
  • 麦克风需要 HTTPS 或 localhost / 127.0.0.1
  • Realtime 的 Stop microphone 只停止本地麦克风,不提交 turn、不结束 WebSocket 会话。
  • ASR 的 Stop recognition 发送带负序列号的 final frame,等待最终转写后关闭识别流。
  • AST 的 End interpretation 发送 FinishSession,等待最终字幕后结束口译会话。
  • 停止、断开、切换能力或离开页面都会关闭浏览器音轨和 AudioContext。

浏览器不能在 WebSocket 握手中自行设置 AuthorizationXAPI-Key header,因此页面使用服务端已支持的子协议认证:

const credential = 'short-lived-ws-token';
const socket = new WebSocket(
  'wss://ai.xapi.to/v1/realtime',
  [`xapi-key.${credential}`],
);

凭据不会进入 URL。实验室允许直接粘贴 XAPI key 以便内部验证;面向终端用户的生产应用应由已登录后端调用 POST /api/keys/ws-token 获取短期凭据,再交给浏览器。当前 token 本质上仍按短期 ApiKey 校验,不应把它描述成已实现一次性消费或 endpoint 强绑定。

常见关闭码:

  • 4401:Key 无效、过期或已撤销。
  • 4402:余额或计费预留失败。
  • 429 通常发生在 Upgrade 阶段:连接频率或并发连接达到限制;浏览器 WebSocket API 不会暴露完整 HTTP response body。

异步图片/视频:

const submission = await client.images(
  { model: 'gpt-image-1', prompt: 'A routing diagram' },
  { provider: 'gpt88' },
);

const completed = await client.waitForTask(submission, {
  intervalMs: 2000,
  timeoutMs: 180000,
  onPoll(result) {
    console.log(result.data.status);
  },
});

浏览器配置与 API Key

网页右上角的 Configure target 可以设置:

  • Gateway Base URL
  • API Key
  • 是否仅在当前会话记住 Key

安全规则:

  • API Key 默认仅保存在 React 内存中。
  • 用户主动勾选后才写入 sessionStorage
  • 不使用 localStorage 保存 API Key。
  • API Key 不进入 URL、分享链接、README 或前端日志。
  • WebSocket 通过 Sec-WebSocket-Protocol: xapi-key.<token> 携带凭据。
  • Base URL 可以持久化,因为它不是秘密。
  • poll_url 的 origin 必须在允许列表中,客户端才会向它发送 API Key。

生产的图片/视频 poll_url 通常指向 Proxy,例如 https://p.xapi.to/v1/tasks/:id,而不是 https://ai.xapi.to。官方 ai.xapi.to 配置会自动允许 p.xapi.to;自定义部署通过以下变量添加:

VITE_TASK_ORIGINS=https://tasks.example.com,https://p.xapi.to
AI_GATEWAY_TASK_ORIGINS=https://tasks.example.com,https://p.xapi.to

不要把任意第三方 origin 加入列表,因为这会授权客户端向该 origin 发送 API Key。

CORS 要求

网页直接请求 Gateway,因此生产 Gateway 至少需要允许:

  • Request headers:Authorization, Content-Type, Accept, X-Provider, Anthropic-Version, X-API-Key, XAPI-Key
  • Methods:GET, POST, OPTIONS
  • Exposed response headers:
    • X-Routing-Provider
    • X-Routing-Attempts
    • X-Routing-Fallback
    • X-Routing-Translated
    • X-XAPI-Cost*
    • X-XAPI-Billing*
    • X-AI-Tokens-*
    • Retry-After

当前 AI Gateway 代码只显式 expose 了计费/usage headers。如果没有补充 X-Routing-*,请求仍可以成功,但浏览器页面无法读取 Provider、fallback 和 translation;页面会显示相应提示。Node.js 脚本不受浏览器 CORS 限制。

WebSocket Upgrade 不使用 Fetch CORS 流程,但浏览器仍会发送 Origin;部署层若启用 Origin allowlist,需要允许 Lab 的实际来源。

架构

React HTTP pages ─┐
Node smoke scripts├── src/lib/gateway-client.js ── Fetch API ── AI Gateway
                  │                                      │
React WS page ────┴── src/lib/ws-client.js ── WebSocket ─┤
                                                         └── Proxy task poll_url

关键目录:

src/
  components/           页面框架、请求/响应 Workbench
  lib/
    gateway-client.js   浏览器与 Node 共享客户端
    ws-client.js        WebSocket URL、子协议认证与 Realtime event helper
    audio-capture.js    麦克风采集、重采样和 PCM16 分帧
    audio-playback.js   PCM16/Float32 流式音频无缝排队播放
    volc-asr-client.js  ASR gzip 二进制协议编解码
    ast-protocol.js     AST protobuf v4 最小协议编解码
    doubao-protocol.js  TTS/Podcast v3 会话与二进制帧编解码
    capabilities.js     页面能力清单与真实状态标签
    gateway-context.jsx 浏览器配置和 Key 生命周期
  pages/                分类实验页
scripts/
  mock-gateway.mjs      确定性本地 Gateway
  smoke.mjs             默认无图片/视频的真实 smoke
  smoke-all.mjs         显式允许计费的全量 smoke
  test-mock.mjs         自包含全功能验证
test/
  gateway-client.test.js 共享客户端单元测试
  ws-client.test.js      WebSocket 凭据边界与 event helper 单元测试

测试和构建

npm test
npm run test:mock
npm run build

npm test 不需要网络;test:mock 也不连接生产。

部署

项目是标准 Vite SPA:

npm run build

产物位于 dist/。静态服务器需要把未知路径回退到 index.html,以支持 /text/images 等深链接。

部署时配置:

VITE_GATEWAY_BASE_URL=https://ai.xapi.to
VITE_WS_GATEWAY_BASE_URL=wss://ai.xapi.to
VITE_TASK_ORIGINS=https://p.xapi.to

不要在任何 VITE_* 变量中放 API Key;Vite 会把这些变量编译进公开的浏览器 bundle。

与生产契约保持一致

当前 capabilities.js 是经过后端控制器核对的显式清单。后续建议 AI Gateway 提供 /openapi.json 或公开 /v1/capabilities,再增加契约测试校验:

  1. 页面清单中的端点必须存在于 Gateway contract。
  2. Gateway 新增公开端点时,CI 要求 Lab 补充展示或显式标记为不展示。
  3. speed / quality 真正实现独立排序后,再加入页面的可用策略清单。

Git 与发布边界

本目录是独立 Git 仓库。创建远程 GitHub/GitLab 仓库、添加 remote、push、部署域名都属于外部状态变更,不由本地初始化自动执行。

Releases

Packages

Contributors

Languages