Service 服务AI 模型服务
Provider
Provider 封装第三方 AI 提供商的 action 实现和连接信息。
Provider 封装第三方 AI 的 action 实现和连接信息。通过 .model() 生成模型配置,传给 AIService.use() 注册。
创建 Provider
import { Provider } from "@downcity/city";
const deepseek = new Provider("deepseek", {
// 连接信息
baseURL: "https://api.deepseek.com/v1",
envKey: "DEEPSEEK_API_KEY",
passthroughModel: "deepseek-chat",
// 环境变量声明
env: { DEEPSEEK_API_KEY: "DeepSeek API Key" },
// SDK 通路 action
text: async (ctx) => { /* generateText */ },
stream: async (ctx) => { /* streamText */ },
});图片 Provider
@downcity/city 不内置 Luchi、302.ai、OpenAI、Gemini 这类具体图片 Provider 构造器。City 包只提供 Provider 抽象、AIService 注册机制,以及 client.ai.image() 的统一调用协议。
具体上游适配应放在具体 City 项目里实现,例如 cities/edge/src/image-provider.ts。这些 adapter 可以把不同上游的响应归一成 AI SDK UIMessage file parts,然后通过 Provider.model() 注册给 AIService:
import { Provider } from "@downcity/city";
const imageProvider = new Provider("my-image", {
env: { MY_IMAGE_API_KEY: "My Image API Key" },
image: async (ctx) => {
const response = await fetch("https://image.example.com/generate", {
method: "POST",
headers: {
authorization: `Bearer ${ctx.env("MY_IMAGE_API_KEY")}`,
"content-type": "application/json",
},
body: JSON.stringify(ctx.input),
});
const data = await response.json();
return {
id: crypto.randomUUID(),
role: "assistant",
parts: [
{
type: "file",
mediaType: "image/png",
url: data.image_url,
},
],
};
},
});
ai.use(imageProvider.model({
id: "image-basic",
name: "Image Basic",
default: ["image"],
}));调用端仍然保持统一:
const msg = await client.ai.image({
model: "image-basic",
prompt: "A clean product photo of a ceramic mug",
ratio: "1:1",
quality: "standard",
count: 1,
});
const file = msg.parts.find((part) => part.type === "file");自动透传
有 baseURL + envKey 但没有 openai action 时,AIService 自动生成透传 action:
- 第三方工具的 OpenAI body 原样转发到上游
- 上游 Response 原样返回
passthroughModel替换body.model(有则替换)
// 这行不用写 — AIService 自动做
// openai: async (ctx) => fetch(`${baseURL}/chat/completions`, ...)Anthropic 格式 Provider
非 OpenAI 格式的 Provider 需要写 openai action 做格式转换:
const kimiCode = new Provider("kimi-code", {
env: { KIMI_CODE_API_KEY: "Kimi Code API Key" },
text: myAnthropicTextAction,
stream: myAnthropicStreamAction,
// 必须写:Anthropic ↔ OpenAI 格式双向转换
openai: async (ctx) => {
const anthropicBody = openaiToAnthropic(ctx.input);
const response = await fetch("https://api.kimi.com/coding/v1/messages", {
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
body: JSON.stringify(anthropicBody),
});
if (ctx.input.stream) {
return anthropicStreamToOpenAIStream(response);
}
return anthropicToOpenAIResponse(await response.json());
},
});Provider 字段
| 字段 | 必填 | 说明 |
|---|---|---|
baseURL | 透传时必填 | 上游 API 地址 |
envKey | 透传时必填 | API Key 环境变量名 |
passthroughModel | 否 | 透传时替换 body.model |
text | 否 | SDK 文本生成 action |
stream | 否 | SDK 流式生成 action |
image | 否 | SDK 图片生成 action |
video | 否 | SDK 视频生成 action |
openai | 否 | /chat/completions action(未提供则自动透传) |