VisibleBase
参考

UserClient

产品侧如何读取模型目录,并调用 text、stream、image、tts 等 service。

UserClient 是给终端产品用的。

它绑定的是一个用户在某个产品下的调用上下文:

  • base_url
  • product_id
  • user_token

你可以把它放在浏览器、插件、App、桌面端,或者产品后端代用户调用 Base。

最小示例

import { UserClient } from "@visiblebase/client";
import type { UIMessageChunk } from "ai";

const client = new UserClient({
  base_url: "https://base.example.com",
  product_id: "prod_xxx",
  user_token: "ub_xxx",
});

const models = await client.models();

const result = await client.text({
  model: models.primary(),
  prompt: "写一段欢迎语",
});

为什么先读 models()

client.models() 返回的不是普通数组,而是一个可调用目录:

const models = await client.models();

models("gpt-5.4");
models.primary();
models.all();

推荐这样用:

const models = await client.models();
const model = models("gpt-5.4") ?? models.primary();

这样你的业务代码不会到处散落模型 ID 字符串。

text()

const result = await client.text({
  model: models.primary(),
  prompt: "写一段欢迎语",
});

text() 固定返回 AI SDK UIMessage,也就是 UI 层可以直接保存和渲染的完整消息。

输入仍然是开放对象:

  • model 是可选
  • 其他字段由你的 base.text() handler 决定
  • handler 返回值应该是一个 UIMessage

如果没有传 model,Base 会尝试用当前 service 的 default() 补齐 query fallback。

如果你要调用自定义 service,并且返回结构不是 UIMessage,用 invoke<T>()

stream()

const body = await client.stream({
  model: models("gpt-5.4"),
  prompt: "流式输出一段文案",
});

stream() 固定返回 AI SDK UIMessageChunk 流:

const stream: ReadableStream<UIMessageChunk> = await client.stream({
  prompt: "流式输出一段文案",
});

它不是 HTTP 原始字节流。SDK 会把 Base 返回的 AI SDK UIMessage SSE body 解析成 chunk 对象。

你可以按 chunk 消费:

const reader = stream.getReader();
const first = await reader.read();

Base 侧的 stream handler 应该返回 AI SDK 的 createUIMessageStreamResponse()streamText().toUIMessageStreamResponse() 结果。

如果你想要一次性 JSON 结果,应该用 text(),不是 stream()

image() / video()

image()video() 固定返回 AI SDK UIMessage。建议在 message 的 parts 里用 file part 表达图片或视频文件:

const imageMessage = await client.image({
  prompt: "一只站在雪地里的狐狸",
  model: models("image-basic"),
});

Base 侧的 base.image() / base.video() handler 也会被约束为返回 UIMessage

tts() / asr()

tts()asr() 仍然保留开放返回类型,因为语音输入输出在不同产品里的传输方式差异更大:

await client.tts({
  text: "你好",
  voice: "alloy",
});

如果你需要更严格的返回结构,可以用 invoke<T>() 包自定义 service。

invoke()

如果你要动态决定 service 名称,可以直接调用:

await client.invoke("rewrite", {
  prompt: "把这段话改得更专业",
  tone: "formal",
});

这适合:

  • 前端按配置动态切换 service
  • 你定义了自定义 service,不想为它单独再包一层方法

service()

client.service() 有两种用法。

读取当前 service 列表

const services = await client.service();

创建某个 service 的调用器

await client.service("text").invoke({
  prompt: "你好",
});

它等价于:

await client.invoke("text", {
  prompt: "你好",
});

常见错误

UserClient 收到非 2xx HTTP 响应时会抛出 Error。这个错误会带两个额外字段:

  • status:HTTP 状态码。
  • body:Base 返回的原始响应文本,通常是 {"error":"..."}
try {
  await client.text({
    model: "gpt-5.4",
    prompt: "你好",
  });
} catch (error) {
  const status = error instanceof Error && "status" in error ? error.status : undefined;
  const body = error instanceof Error && "body" in error ? error.body : undefined;

  console.log(status, body);
}

client.stream() 有两个失败阶段:HTTP 状态不是 2xx 时和其他方法一样抛带 status/body 的错误;如果 HTTP 已经成功但响应体为空,或不是 AI SDK UIMessage stream,解析阶段会抛普通解析错误。

401 / 403

通常是这些原因:

  • user_token 缺失
  • token 已过期
  • token 签名无效
  • product_id 和 token 内的 product 不一致

422

通常是这些原因:

  • query.model 最终为空
  • 请求里传了不存在的 model
  • 当前 service 没有任何 match() 命中 handler

什么时候不要用 UserClient

这些动作不应该交给 UserClient

  • 创建 product
  • 签发 user_token
  • 修改 Runtime env

这些都属于可信环境动作,应该交给 AdminClient 或你自己的后端。

目录