Call a model

Call Federation models with ModelCall, CityModel, and ModelStreamEvent.

Call a Federation model

const catalog = await city.ai.catalog();
const model = catalog.require("deepseek-v4-flash");
const stream = await model.stream({
  messages: [{
    role: "user",
    content: [{ type: "text", text: "Explain this repository" }],
  }],
});

for await (const event of stream) {
  if (event.type === "text_delta") process.stdout.write(event.delta);
}

model.stream() returns a ReadableStream<ModelStreamEvent>. A successful stream begins with model_start and provides final model_usage before model_finish. Invalid event ordering fails as a protocol error.

Use a model with Agent

Objects returned by the catalog implement ModelClient and can be passed directly to Agent:

const catalog = await city.ai.catalog();
const model = catalog.require("deepseek-v4-flash");
const agent = new Agent({ id: "reviewer", model });

Agent owns Session context, tool loops, and approvals. Federation owns final model selection and billing.

Multimodal fallback

Fallback checks file content only in the latest role: "user" message in ModelCall.messages. A new text-only user message does not trigger fallback again. When the final model's fallback rules reject a historical file, the execution view replaces that file with its filename, original URL, or MIME type as plain text. The original Session file remains unchanged. Trailing assistant or tool messages do not change which user message is inspected.

OpenAI-compatible clients

Use /v1/ai/chat/completions for the OpenAI SDK, curl, or an existing gateway. The boundary adapter converts that request into the same ModelCall, so routing, fallback, reasoning, usage, and billing stay consistent.

Call a decision model

A decision model takes raw state and typed questions, and returns answers with calibrated probabilities instead of generated text:

/** Call Jev through the current Federation user context. */
import { Embassy } from "@downcity/federation";

const embassy = new Embassy({
  federation_url: "http://localhost:3000",
  user_token: process.env.FEDERATION_USER_TOKEN,
});
const { answers } = await embassy.user.ai.decision({
  model: "jev-latest",
  state: { type: "text", text: "The customer has failed to connect Stripe for three days." },
  questions: [
    { id: "urgent", type: "boolean", instructions: "Does this message express urgency?" },
    { id: "route", type: "choice", instructions: "Which queue should handle this?", options: ["billing", "support"] },
    { id: "churn_risk", type: "score", instructions: "Churn risk", min: 1, max: 4 },
  ],
});

answers[0].value;        // true
answers[0].probability;  // 0.87
answers[1].distribution; // { billing: 0.72, support: 0.28 }

The official edge-worker and local-node both provide jev-latest through B.AI's official https://api.b.ai/v1/decisions endpoint. Configure B_API_KEY in the Federation's Global / Env to enable choice, score, and boolean decisions. Jev is also available in the Desktop Group dispatch model selector.

The official edge-worker uses Downcity SDK 1.5.0. Its model catalog exposes structured pricing with currency, billing units, rates, and applicable conditions.

Jev accepts 2–255 choice options. Score ranges must map to 2–10 levels (round(max - min) + 1), and boolean results use a 0.5 probability threshold. JSON state cannot be an empty object or array.

state is either { type: "text", text } or { type: "json", data }. Three question types are supported, and none of them produces free text:

TypeAnswerNotes
booleantrue / false—
choiceone of options1 to 255 unique options
scorea numberrequires min < max

Question ids must be unique and every question must be answered exactly once. Invalid input fails with 422 before any usage is recorded, and a Channel response that does not cover every question is rejected too.

embassy.user.ai.decision(input, signal) accepts an optional AbortSignal. Use question_id to associate each answer with its question. Group can bind this method as its decision client to select parallel responders each round; members keep their own execution models.

Decision models are not conversation models: they carry modalities: ["decision"] and cannot be passed to an Agent.