Models
Configure the AI models available to your project. Language models power conversations and generation. Decision models classify inputs, score them against a rubric, and estimate probabilities. Embedding models power semantic search. Audio models transcribe stored files and turn text into speech, and image models generate images. Browse providers for installation and credentials.
Language models
Register the models you want to use in models.language. You can mix providers in the same
catalog. Export model bindings when you want to reuse them in your code:
import { anthropic } from "@sixb/anthropic"
import { vercelGateway } from "@sixb/vercel-ai-gateway"
export const openaiModel = vercelGateway("openai/gpt-5.5")
export const anthropicModel = anthropic("claude-sonnet-5")
Add them to your existing project configuration:
import { createSixb } from "@sixb/core"
import { anthropicModel, openaiModel } from "./lib/models"
export const sixb = createSixb({
// ...your existing providers
models: { language: [openaiModel, anthropicModel] },
})
The first language model is the default. Chat users can choose from this catalog and adjust the
reasoning effort their selected model supports. An AI workflow step
can select a model and reasoning effort through its model and reasoning options.
A model your code passes runs as given, with its own options, whether or not models.language
lists it; decision, audio and image models work the same way. The catalog is what chat users, the
Agent and the API choose from, and supplies the default when no model is passed:
defineAgentStep("second-opinion", {
model: vercelGateway("openai/gpt-5.5"),
instructions: "Review the draft and list its weakest claims.",
})
Tiers
Configure models by tier instead of a list, so your code names the job rather than the model:
const flash = vercelGateway("deepseek/deepseek-v4.1-flash", { reasoning: "high" })
const sonnet = vercelGateway("anthropic/claude-sonnet-5.5", { reasoning: "medium" })
const opus = vercelGateway("anthropic/claude-opus-5.5", { reasoning: "high" })
const gpt = vercelGateway("openai/gpt-5.5", {
description: "A second opinion, useful to compare an analysis.",
})
createSixb({
models: { language: { light: flash, standard: sonnet, heavy: [opus, gpt] } },
})
defineAgentStep("summarize-tickets", { model: "light", instructions })
await sixb.models.language.generate({ model: "heavy", prompt })
The tiers are light, standard and heavy. Each takes a model or a list, and standard is
required: its first model is the default.
model | Runs |
|---|---|
| A tier | That tier's first model |
| Omitted, or a tier left out | The first standard model |
| A tier, with a list configured | The first model |
Every model of every tier stays in the catalog. The Agent sees them grouped by tier, for
sixb models text and when it delegates:
it picks a tier, or a model whose description fits the task.
With tiers, the chat's model chip reads Auto, and its menu lists the models by tier. Choosing
one pins it, with a reasoning control, in that conversation and on that browser; × returns to
Auto. On Auto, without a decision model, every message runs on standard.
With tiers and a decision model, Sixb picks the model of each chat message that names none. Before the answer starts, the default decision model scores the message's tiers:
| Probability | Tier |
|---|---|
heavy ≥ 0.4 | heavy |
light ≥ 0.8 | light |
| Otherwise | standard |
Switching models drops the prompt cache, so a thread keeps its model unless the switch is free:
| The tier is | Last answer under 5 minutes ago | Idle 5 minutes or more |
|---|---|---|
| Higher | Move up | Move up |
| The same | Stay | Stay |
| Lower | Stay | Move down |
If the decision model fails or takes over 2 seconds, the message aims for standard; the cache
rule still applies, so a thread on heavy whose last answer is under 5 minutes old stays there.
A message that names a model or a reasoning asks nothing: it runs that model, or the default, as chosen. A continuation keeps its answer's model, and the model never changes mid-answer. Running the Agent is the only permission the question needs. The question counts toward the sender's AI usage and limits, but not toward the run's cost.
Name and description
Give a model your own name and description with the name and description options of its
binding:
const gptModel = vercelGateway("openai/gpt-5.5", {
name: "GPT",
description: "A second opinion, useful to compare an analysis.",
})
They show in the chat's model picker, in the models the Agent can ask for a second opinion, and in the models it can delegate to. Each replaces the provider's own text; without them, the provider's name and description apply.
Default reasoning
Give a model a default reasoning effort with the reasoning option of its binding:
const glmModel = vercelGateway("zai/glm-5.3-flash", { reasoning: "low" })
createSixb({ models: { language: [glmModel, openaiModel] } })
Sixb applies it whenever this model runs without an explicit choice: chat messages, AI workflow
steps, and generate() calls. Without one, the provider's own default applies. An explicit
reasoning always wins. Sixb checks the level of each model in models.language or on an AI
workflow step against the capabilities the provider declares at startup; if the provider later
reports that the model does not support it, the call falls back to the provider default. A
provider you write yourself sets the level as defaultReasoning on the LanguageModel it returns.
The default is an effort level. An AI workflow step can instead set its own reasoning to a level
or a token budget such as { budgetTokens: 4096 }; Sixb checks it against the step's model the same
way at startup, budget range included.
Chat follows the project defaults until a user picks a model or an effort. Choosing a model starts from that model's default reasoning, and Reset forgets the choice, so later changes to your defaults reach the user again. The chosen reasoning is fixed when a message is sent, so a retry runs with the same setting.
Chat answers and AI workflow steps can use the model's full output limit, reasoning included, as
long as the conversation leaves room for it in the context window. However long the conversation
gets, they keep at least 16,384 tokens (or a quarter of the window): older turns are summarized
before the conversation takes that room. An attached image counts as about 5,400 tokens and other
files by their size, so a conversation with files keeps the same room for the answer. To spend less
per answer, cap it with maxOutputTokens on the binding, such as
vercelGateway("zai/glm-5.3-flash", { maxOutputTokens: 32_000 }).
A chat answer cut short by this limit says so and can be continued; an AI workflow step fails.
A conversation is summarized near its model's limit, whatever the provider's price tiers; to
summarize earlier, set a smaller contextWindow on the model definition. It is also summarized
when it resumes after an hour idle with 200,000 tokens or more, if summarizing brings it under
200,000: its prompt cache has expired, and the summary is far cheaper to read again than the whole
conversation.
A summary brings the conversation well under that limit, so the next turns do not summarize again.
The latest turns stay as they are, and the agent can still read the whole conversation with
sixb history. A conversation that does not fit even summarized fails with
agent.context_limit_exceeded: start a new one, or switch to a model with a
larger window.
A tool result over 30 KB reaches the agent as a short preview, and the whole of it as a file in
its sandbox, which it reads in pieces or searches; later turns find the file at the same path. The
file is stored with the conversation, which keeps a command's output cut to 20,000 characters per
stream: sixb history names the file, and whoever reads the conversation can download it.
The same limit holds within a turn, before each model call, as tool results pile up:
- right before the limit, the oldest tool results the agent has read are cleared in one batch that brings the conversation down to 60% of it, when that frees at least a tenth of the limit: the three most recent stay when that is enough, the newest always does. A short note in their place tells the agent to run the tool again if it needs one back, and a result it reads again is never cleared or cut again;
- if that is not enough, the turns before the current one are summarized, and the turn goes on;
- results too large to fit the window at all are cut, each saying how much was left out.
A turn whose results do not fit together ends with agent.context_limit_exceeded rather than read
and clear them in turn.
The conversation shown to users and sixb history keep every result whole. A provider that
rejects a call as too long for the window gets it again once, summarized, and the turn goes on. A
turn that fails keeps the steps it completed, and can be continued.
Subagents and AI workflow steps keep each call under their own model's limit the same way:
| Run | Clears old results | Summarizes |
|---|---|---|
| Subagent | yes | what came before, when it continues after a question or an approval |
| AI workflow step | yes | nothing: its history starts with its prompt |
One still too long for its model fails with what to do: a narrower task or prompt, or a model with a larger window.
Chat shows how much of its model's limit a conversation uses in a disc beside the model; it turns amber from 80% and red past the limit. It opens on what the conversation uses, part by part, then the free space and the room kept for the answer, which add up to the model's window:
| Part | What it counts |
|---|---|
| Sixb instructions | Sixb's own prompt |
| Project instructions | SIXB.md |
| Memory, Skills, Ontology | their index in the prompt, and the files the agent read with read or view_file |
| Tools | the tool definitions |
| Summary | what a condensed conversation keeps of its earlier messages |
| Messages, Tool results, Files and images | the conversation itself |
The total is the provider's count of the latest call plus an estimate of what came after it; the
split by part is an estimate by size. sixb agent shows the total at the end of its status line,
as ctx 38k/184k · 92% cached, and the parts with /context.
A custom chat reads it from GET /api/agent-threads/:threadId/context, against the model the next
message goes to with ?provider=…&modelId=…, else the latest turn's. A tool result saved as a file
shows a Full output link under its call.
Generate a response
Use sixb.models.language.generate() inside an action or workflow step when your code supplies
the context for a model call. This step returns a summary of its input document:
import { defineWorkflowStep } from "@sixb/core"
export const summarize = defineWorkflowStep("summarize")
.input({ document: "string" })
.output({ summary: "string" })
.run(async ({ input, sixb }) => {
const { output } = await sixb.models.language.generate({
instructions: "Summarize in one sentence. Use only facts in the document.",
prompt: input.document,
})
return { summary: output }
})
Add it to a workflow with .then(summarize).
You can also generate inside an action's writeback or effects handler.
Direct generation does not require a sandbox. For a task that needs tools, use an
AI workflow step.
Model and response controls
Pass a model binding to override the default; it does not need to be in models.language. A
tier such as model: "heavy" runs that tier's model. Use
reasoning to choose a supported effort and maxOutputTokens to limit the response. Inside a step
or action handler:
import { anthropicModel } from "../../lib/models"
const { output } = await sixb.models.language.generate({
model: anthropicModel,
prompt: input.document,
reasoning: "high",
maxOutputTokens: 1_000,
})
Available reasoning controls depend on the model and provider. See the provider READMEs for supported options.
The result also includes usage, cost, and finishReason. Text cut short by the output limit
has finishReason: "length". Calls are recorded and respect your usage limits.
Structured output
Add an output schema to get a typed result. Inside a step or action handler:
const { output } = await sixb.models.language.generate({
instructions: "Summarize the document and identify whether it requests a follow-up.",
prompt: input.document,
output: {
summary: "string",
requiresFollowUp: "boolean",
},
})
output.summary // string
output.requiresFollowUp // boolean
Sixb validates the response against the schema. Invalid or incomplete output throws an error. The selected model must support structured output.
Decision models
Register a provider in models.decision. For example, install the TypeSafe provider and set
its credentials:
bun add @sixb/typesafe
export TYPESAFE_API_KEY="..."
import { createSixb } from "@sixb/core"
import { typesafe } from "@sixb/typesafe"
export const sixb = createSixb({
// ...your existing providers
models: {
decision: [typesafe("jev-1.13.0")],
},
})
The first decision model is the default; pass model to evaluate() to run another one, listed or
not. Decision models need neither a language model nor a sandbox.
Define questions
Use question.choice() to select an option, question.score() to score against ordered levels,
and question.probability() for a yes/no probability. Questions can be reused across calls:
import { question } from "@sixb/core"
export const triageQuestions = {
category: question.choice({
instructions: "Identify the main issue in description.",
options: {
maintenance: "Breakdowns, leaks, and repairs",
billing: "Invoices, payments, and refunds",
other: "Any other request",
},
}),
severity: question.score({
instructions: "Assess the operational impact described in description.",
levels: [
"No operational impact",
"Degraded operation; workaround available",
"Operation stopped; no workaround",
],
}),
blocked: question.probability(
"Does description explicitly report equipment that cannot operate?"
),
}
Evaluate an input
Call sixb.models.decision.evaluate() inside an action's
writeback or effects handler or a workflow step:
import { triageQuestions } from "../../lib/triage-questions"
const { output } = await sixb.models.decision.evaluate({
input: { description: "The cooling unit has stopped." },
questions: triageQuestions,
})
output.category.choice // "maintenance" | "billing" | "other"
output.category.probabilities.maintenance // number from 0 to 1
output.severity.score // number from 0 to 2, possibly fractional
output.blocked.probability // number from 0 to 1
Scores are the expected level index, starting at zero. Use multiple probability questions when several labels can apply to the same input. Validate thresholds on your application's data. Calls are recorded and respect your usage limits.
For a workflow step, decisionOutput() derives the output schema from your questions:
import { decisionOutput, defineWorkflowStep } from "@sixb/core"
import { triageQuestions } from "../../lib/triage-questions"
export const triage = defineWorkflowStep("triage")
.input({ description: "string" })
.output(decisionOutput(triageQuestions))
.run(async ({ input, sixb }) => {
const { output } = await sixb.models.decision.evaluate({
input,
questions: triageQuestions,
})
return output
})
Add it to a workflow with .then(triage).
Audio transcription
Register a transcriber in models.audio.transcription. The first is the default:
import { createSixb } from "@sixb/core"
import { vercelGateway } from "@sixb/vercel-ai-gateway"
export const sixb = createSixb({
// ...your existing providers
models: {
audio: { transcription: [vercelGateway.transcription("microsoft/mai-transcribe-2")] },
},
})
Use an ordinary workflow step or an action's writeback/effects handler:
import { defineWorkflowStep, ref } from "@sixb/core"
export const transcribe = defineWorkflowStep("transcribe")
.input({ audio: ref.file() })
.output({ text: "string" })
.run(async ({ input, sixb }) => {
const { output } = await sixb.models.audio.transcribe({ audio: input.audio })
return output
})
The file must already exist in the project's blob storage, with an audio/* media type.
Sixb verifies its size and digest before sending it. Supported encodings and provider limits
depend on the selected model; Sixb does not transcode files or extract audio from video.
Pass a model to override the default, listed in models.audio.transcription or not, or an
AbortSignal as signal to cancel.
Audio you should not keep, such as a dictation, can be passed as bytes instead. It reaches the model without being written to blob storage:
await sixb.models.audio.transcribe({ audio: { data: bytes, mediaType: "audio/webm" } })
The result contains output.text, usage, cost, and callId. Audio-only projects need
neither a language model nor a sandbox. Calls use the same accounting and limits
as other models. Authorize access to the owning object in the application before accepting
a user-supplied file reference; a FileRef is an identifier, not an access grant.
Image generation
Register image models in models.image. The first is the default:
import { createSixb } from "@sixb/core"
import { vercelGateway } from "@sixb/vercel-ai-gateway"
export const sixb = createSixb({
// ...your existing providers
models: {
image: [
vercelGateway.image("bfl/flux-3-image", { description: "Fast drafts and thumbnails" }),
vercelGateway.image("google/gemini-3.1-flash-lite-image", {
description: "Edits a reference image",
}),
],
},
})
A description says when to pick a model; without one, the provider's own description applies.
Generate images in a workflow step or an action's writeback/effects handler:
const { files, cost } = await sixb.models.image.generate({
prompt: "Isometric warehouse floor plan, muted palette",
size: "1024x1024",
})
files are the generated images as FileRefs, already saved to the project's blob storage. Store one on an object
property or show it in a page like any other file.
| Option | Meaning |
|---|---|
n | How many images to generate. Defaults to 1; some models return one per call. |
size, aspectRatio | Such as 1024x1024 or 16:9. Accepted values depend on the model. |
quality, style | Model-specific settings, such as low or vector_illustration. |
seed | Repeat a result, where the model supports it. |
images | Reference images, as FileRefs with an image/* media type, to edit or vary. |
model, signal | Override the default model, listed in models.image or not, or cancel the call. |
Settings a model ignores come back in warnings. Each call is priced by the size, quality and
style it requested and the number of images returned, and uses the same
accounting and limits as other models.
Authorize access to a reference image in the application before accepting a user-supplied
FileRef.
Speech generation
Register speech models in models.audio.speech, beside any transcribers. The first is the default:
export const sixb = createSixb({
// ...your existing providers
models: {
audio: { speech: [vercelGateway.speech("openai/tts-1-hd", { voice: "alloy" })] },
},
})
const { file, cost } = await sixb.models.audio.speak({
text: "Your order shipped this morning.",
mediaType: "audio/mpeg",
})
file is the generated audio as a FileRef in the project's blob storage. A request without a voice uses the voice
the binding sets, so you can switch models without changing calls. Pass model to run another
one, listed or not. You can also pass instructions for style, speed, and language. Voices and formats depend on the model; Sixb
never converts formats, so a model that cannot produce the requested mediaType fails the call.
Call models over the API
The same calls are available over HTTP, to a user or service account granted
can.run(project.models):
defineRole("model.users", { grantedTo: [designers], grants: [can.run(project.models)] })
GET /api/models says in capabilities.run whether the caller has it, and
sixb.models.canRun() answers the same in code.
POST /api/models/language/generations { prompt, instructions?, maxOutputTokens?, reasoning? }
POST /api/models/image/generations { prompt, n?, size?, aspectRatio?, quality?, style?, seed?, images? }
POST /api/models/audio/speech { text, voice?, mediaType?, instructions?, speed?, language? }
POST /api/models/audio/transcriptions { audio }
Each body takes an optional model: { provider, modelId }; without one, the kind's default
answers. A text request's model can also be a tier, such as "light". Reference
images and audio are FileRefs from POST /api/files. Audio can
also be sent inline as { data, mediaType }, data in base64; it is then never stored.
The response carries the callId, the model that answered, usage and cost, plus text or
files:
{
"callId": "call_…",
"model": { "provider": "vercel-ai-gateway", "modelId": "bfl/flux-3-image-klein" },
"files": [{ "blobId": "blob_…", "mediaType": "image/jpeg", "sizeBytes": 82749 }],
"warnings": [],
"usage": { "outputImages": 1 },
"cost": { "status": "reported", "money": { "currency": "USD", "amountNanos": "8000000" } }
}
From the CLI, the same calls write their files where you say:
sixb models image "Isometric warehouse floor plan" --size 1024x1024 --output out/plan.png
sixb models image "Same plan, night lighting" --image out/plan.png --output out/night.png
sixb models speech "Your order shipped." --format mp3 --output out/shipped.mp3
sixb models transcribe call.wav
sixb models text "Summarize this week's incidents in one line" --model light
sixb models text --file incidents.md --model heavy
{ "callId": "call_…", "model": "bfl/flux-3-image-klein", "files": ["out/plan.jpg"], "cost": "$0.008" }
--output takes a file, numbered when a call returns several, or a directory ending in /. The
extension follows the file's type, so files lists the paths actually written. --image uploads
a reference image; repeat it for several. sixb models calls files <call-id> --output <path>
downloads a call's files again. sixb models text takes a model or a tier as --model, and reads
a long prompt from --file: a path, or - for standard input.
With several language models, the Agent uses sixb models text to ask another one for a second
opinion or a quick generation. Its prompt lists them with their tier, name and description. The
model answers once, from the prompt alone: it does not see the project's data.
A call counts toward AI usage and limits like any other: a limit that
is reached answers 429. An option the model does not accept answers 400 before any inference,
and a provider failure answers 502. Calls are synchronous; download a generated file through
its call, below.
Read a call's files later
Every image and speech result carries a callId. The files a call produced stay readable through
it, by whoever can see the run that made the call:
const call = await sixb.models.calls.files(callId) // { callId, files, recordedAt } or null
GET /api/ai/model-calls/:callId/files the call's files
GET /api/ai/model-calls/:callId/files/content?path=/files/0 one file's bytes
| Where the call ran | Who can read its files |
|---|---|
| A request to the API | The user or service account that made it |
| An agent thread, or a subagent of it | Whoever can read the thread |
| An action or workflow run, or a workflow's agent step | Whoever can view the run |
Anyone else gets null, or a 404 from the API. Seeing a call in
AI usage shows what it cost, not what it produced.
List the catalog
GET /api/models and sixb models list show every configured model by kind, with its default,
tier, description, the options a request can use, and its price. With tiers, its
languageRouting says whether a decision model picks each chat message's model:
sixb models list --kind image
sixb models get bfl/flux-3-image
A price comes from the provider binding, as it bills: per token, image, character or second of audio, by size or voice tier where it varies. A model with no known price says "price unknown"; it can still be called, except while a cost limit applies.
Embedding models
Embedding models turn text into vectors for semantic search. Export an embedding model binding from your project:
import { vercelGateway } from "@sixb/vercel-ai-gateway"
export const productEmbedding = vercelGateway.embedding("openai/text-embedding-3-small", {
dimensions: 1536,
})
Register it in models.embedding alongside any language models:
import { createSixb } from "@sixb/core"
import { anthropicModel, openaiModel, productEmbedding } from "./lib/models"
export const sixb = createSixb({
// ...your existing providers
models: {
language: [openaiModel, anthropicModel],
embedding: [productEmbedding],
},
})
Reference the same binding in an object's vector search profile. Sixb uses it to index the object's text and embed search queries. Both operations count toward AI usage and limits.
An embeddings-only project needs neither a language model nor a sandbox.
Reranking models
Reranking models compare a search query with candidate documents. Register a binding to make it available to explicit query reranking:
// lib/models.ts
export const relevanceModel = vercelGateway.reranking("voyage/rerank-2.5-lite")
// sixb.config.ts — alongside your other model bindings
models: {
embedding: [productEmbedding],
reranking: [relevanceModel],
}
Registration does not change search behavior. Each query must choose its reranking model.
Web search models
A web search model searches the web and returns links with a title and a snippet. Sixb picks one:
| Configured | Searches with |
|---|---|
models.webSearch | Its first model |
| Otherwise | The default language model's own web search, else the first language model's that has one |
| Neither | Nothing: no web search |
A language model only brings a web search when its provider offers one under the same key: Vercel AI Gateway models do. To choose another, list it first:
models: {
language: [openaiModel],
webSearch: [mySearchModel],
}
The agent searches with it through its web_search tool. Each
search counts toward AI usage and limits.
Providers
Each provider card opens its package README for installation, credentials, supported models, and provider-specific options.
Connect directly to Anthropic models.
@sixb/anthropicUse models deployed through Azure AI Foundry.
@sixb/azure-ai-foundryClassify and score inputs with Jev decision models.
@sixb/typesafeAccess models from multiple vendors through one gateway.
@sixb/vercel-ai-gateway