UserClient
产品侧如何读取模型目录,并调用 text、stream、image、tts 等 service。
UserClient 是给终端产品用的。
它绑定的是一个用户在某个产品下的调用上下文:
base_urlproduct_iduser_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 或你自己的后端。