Service 服务AI 模型服务
调用 AI
通过 User City 和第三方工具调用 AIService。
SDK 通路
import { City } from "@downcity/city";
const client = new City({
role: "user",
federation_url,
city_id,
user_token,
});
// 获取模型目录
const catalog = await client.ai.listModels();
const model = catalog.get("deepseek-v4-flash");
// 文本生成
const msg = await client.ai.text({ prompt: "hello", model });
console.log(msg.parts[0].text);
// 流式生成
const stream = await client.ai.stream({ prompt: "hello", model });
const reader = stream.getReader();
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value.type === "text-delta") process.stdout.write(value.textDelta);
}client.ai.listModels() 在 user_token 身份下只返回当前已经满足运行时 env 的模型;同一条路由在 admin_secret_key 身份下会返回全部代码注册模型和它们的 env_requirements。
CityModel 和 provider 的关系
如果调用目标就是当前 Federation 里的模型,优先使用 CityModel。模型目录返回的
CityModel 已经带有隐藏的运行时连接信息,Downcity Agent 和 City SDK 都可以直接使用它:
import { Agent, normalizeAgentModel } from "@downcity/agent";
const catalog = await client.ai.listModels();
const model = catalog.get("deepseek-v4-flash");
if (!model) throw new Error("model not found");
await client.ai.text({
model,
prompt: "hello",
});
const agent = new Agent({
prepare_session: async (session) => {
await session.set({ model: normalizeAgentModel(model) });
},
});这条路径不需要产品侧创建 AI SDK provider。宿主先把 CityModel 转换为运行时模型,
再注入本地 Session。
OpenAI 兼容通路
当调用方必须使用 AI SDK 标准 LanguageModel,或者需要接入 OpenAI SDK、
createOpenAICompatible() 这类外部生态时,再创建 provider。provider 仍然请求
Federation,而不是直接请求真实上游模型:
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
const provider = createOpenAICompatible({
name: "downcity",
baseURL: client.ai.base_url,
apiKey: user_token,
});
const model = provider.languageModel("deepseek-v4-flash");请求体保持标准 OpenAI-compatible JSON。model 来自
languageModel(model_id),city_id 由服务端从 bearer user_token 中解析,
不需要放进请求体。
请求会进入 Federation 的 AIService:
POST /v1/ai/chat/completions
-> 校验 user_token
-> 从 token 解析 city_id / user_id
-> 读取 body.model
-> 找到模型绑定的 provider
-> 由 provider 调用真实上游模型
-> 返回 OpenAI-compatible response前端只关心 model_id,不需要知道真实 provider 是 OpenAI、DeepSeek、Gemini
还是自定义 provider。
调用路径选择
| 场景 | 推荐方式 |
|---|---|
| 产品代码直接请求 Fed 文本或流式生成 | client.ai.text() / client.ai.stream() |
| Downcity Agent 使用 Fed 模型 | 直接传 CityModel |
外部 AI SDK 生态需要 LanguageModel | createOpenAICompatible({ baseURL: client.ai.base_url }) |
| OpenAI SDK、curl 或兼容客户端调试 | /v1/ai/chat/completions |
| 图片、TTS、ASR、video | client.ai.image_create() / client.ai.tts() / client.ai.asr() 等 City SDK 方法 |
| 使用非 Fed 模型 API | 产品侧自己创建对应 provider |
curl
curl http://127.0.0.1:43127/v1/ai/chat/completions \
-H "Authorization: Bearer <user_token>" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hello"}],"stream":true}'OpenAI SDK
import OpenAI from "openai";
const openai = new OpenAI({
baseURL: client.ai.base_url,
apiKey: user_token,
});
const stream = await openai.chat.completions.create({
model: "deepseek-v4-flash",
messages: [{ role: "user", content: "你好" }],
stream: true,
});
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content || "");
}