ServicesAI Services

Calling AI Service

SDK pathway (User City) and OpenAI-compatible pathway (curl / OpenAI SDK / downcity agent) calling examples.

SDK Pathway

const city = new City({
  federation_url: "http://127.0.0.1:43127",
  user_token: "ub_xxx",
});

// List models
const catalog = await city.ai.catalog();

// Text generation
const result = await city.ai.text({
  model: catalog.get("deepseek-v4-flash"),
  prompt: "Hello",
});

// Stream
const stream = await city.ai.stream({
  model: "deepseek-v4-flash",
  prompt: "Stream a text",
});

With user_token, city.ai.catalog() returns only models whose required runtime env is already configured. The same route called with an administrator session token returns the full code-registered model list plus env_requirements.

CityModel And Providers

If you are calling a model from the current Federation, prefer CityModel. The model catalog returns CityModel objects that implement AI SDK LanguageModelV3, so Downcity Agent and AI SDK can use them directly:

import { Agent } from "@downcity/agent";

const catalog = await city.ai.catalog();
const model = catalog.get("deepseek-v4-flash");
if (!model) throw new Error("model not found");

await city.ai.text({
  model,
  prompt: "hello",
});

const agent = new Agent({
  model,
});

This path does not require product code to create an AI SDK provider. Agent calls CityModel.doStream() directly, and CityModel invokes Federation /v1/ai/stream. city.ai.stream() uses the same CityModel and only adds client-side conversion to UIMessageChunk. A Session with its own AgentModel instance takes priority.

OpenAI-Compatible Pathway

Create a compatible provider only for the OpenAI SDK, curl, or another OpenAI-compatible client. The provider still calls Federation; it does not call the real upstream model directly:

import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

const provider = createOpenAICompatible({
  name: "downcity",
  baseURL: city.ai.base_url,
  apiKey: user_token,
});

const model = provider.languageModel("deepseek-v4-flash");

The request body stays standard OpenAI-compatible JSON. The model field comes from languageModel(model_id), and bureau_id is resolved from the bearer user_token on the server.

The request is routed by Federation AIService:

POST /v1/ai/chat/completions
  -> verify user_token
  -> resolve bureau_id / user_id from the token
  -> read body.model
  -> find the provider bound to that model
  -> let the provider call the real upstream model
  -> return an OpenAI-compatible response

The frontend only chooses a model_id; it does not need to know whether the real provider is OpenAI, DeepSeek, Gemini, or a custom provider.

# curl
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}'
// OpenAI SDK
const openai = new OpenAI({
  baseURL: city.ai.base_url,
  apiKey: user_token,
});
await openai.chat.completions.create({
  model: "deepseek-v4-flash",
  messages: [{ role: "user", content: "hello" }],
  stream: true,
});

Pathway Selection

ScenarioPathway
Product code calls Fed text or stream generationcity.ai.text() / city.ai.stream()
Downcity Agent uses a Fed modelpass CityModel directly
AI SDK ecosystem needs LanguageModelpass CityModel directly
OpenAI SDK, compatible clients, or curl debugging/v1/ai/chat/completions
Image, TTS, ASR, or videoCity SDK methods such as city.ai.image_create() / city.ai.tts() / city.ai.asr()
Non-Fed model APIscreate the provider in product code