场景指南
模型与通路
Provider + AIService 两条独立通路的架构与工作原理。
Downcity 的 AI 服务有 两条独立通路,共用同一个模型注册:
Provider.model({ id: "deepseek-v4-flash", ... })
│
ai.use(model)
│
┌─────────────────────┴─────────────────────┐
│ │
SDK 通路 OpenAI 兼容通路
给 User City 用 给 downcity agent / curl / OpenAI SDK 用
│ │
POST /v1/ai/text POST /v1/ai/chat/completions
POST /v1/ai/stream ↑
│ OpenAI 格式 body
▼ { model, messages, stream }
Provider text action │
Provider stream action ▼
(ai-sdk 封装) Provider openai action
│ (透传 或 格式转换)
▼ │
UIMessage ▼
UIMessageStream Response
(上游原始响应)自动透传
当 Provider 不提供 openai action 但提供了 baseURL + envKey 时:
POST /v1/ai/chat/completions
body: { model: "deepseek-v4-flash", messages: [...], stream: true }
│
▼
resolve → model.actions.openai 不存在 → baseURL+envKey 存在
→ 自动透传
│
▼
fetch("https://api.deepseek.com/v1/chat/completions", {
body: JSON.stringify(originalBody), // 原样转发
})
│
▼
上游 Response 原样返回(SSE 或 JSON)格式转换
非 OpenAI 格式 Provider 需要 openai action 做双向转换:
const kimiCode = new Provider("kimi-code", {
text: anthropicTextAction,
stream: anthropicStreamAction,
openai: async (ctx) => {
const body = openaiToAnthropic(ctx.input);
const res = await fetch("https://api.kimi.com/coding/v1/messages", { body: JSON.stringify(body) });
return ctx.input.stream ? convertStream(res) : convertJSON(await res.json());
},
});SDK 调用
const client = new City({
role: "user",
city_url,
town_id,
user_token,
});
const result = await client.ai.text({ prompt: "hello", model: "deepseek-v4-flash" });OpenAI 兼容调用
curl http://127.0.0.1:43127/v1/ai/chat/completions \
-H "Authorization: Bearer ub_xxx" \
-d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hello"}],"stream":true}'