diff --git a/docs.json b/docs.json
index 9b9d7aee..1e18a3b9 100644
--- a/docs.json
+++ b/docs.json
@@ -1,7 +1,7 @@
{
"$schema": "https://mintlify.com/docs.json",
"banner": {
- "content": "🚀 New: LFM2.5-VL-3B — our most capable vision-language model is now available! [Learn more →](/lfm/models/lfm25-vl-3b)",
+ "content": "🚀 New: d1 — our first decision model is now available! [Learn more →](/lfm/models/decision-models)",
"dismissible": true
},
"theme": "mint",
@@ -68,6 +68,7 @@
"lfm/models/text-models",
"lfm/models/vision-models",
"lfm/models/audio-models",
+ "lfm/models/decision-models",
"lfm/models/liquid-nanos"
]
},
@@ -125,7 +126,8 @@
"pages": [
"guides/hardware-evaluation",
"guides/use-case-evaluation",
- "guides/migration-guide"
+ "guides/migration-guide",
+ "guides/decision-model-guide"
]
},
{
@@ -159,6 +161,7 @@
"icon": "laptop",
"pages": [
"examples/laptop-examples/invoice-extractor-tool-with-liquid-nanos",
+ "examples/laptop-examples/build-a-game-with-decision-models",
"examples/laptop-examples/audio-to-text-in-real-time",
"examples/laptop-examples/lfm2-english-to-korean",
"examples/laptop-examples/flight-search-assistant",
diff --git a/examples/index.mdx b/examples/index.mdx
index f9d4ee69..fb32af77 100644
--- a/examples/index.mdx
+++ b/examples/index.mdx
@@ -10,6 +10,10 @@ title: "Examples Library"
Turn invoices into structured JSON using a lightweight Vision Language Model. 100% local, no API costs.
+
+ Build a pixel-art racing game where d1 chooses the safest lane in real time using structured decision outputs.
+
+
Build a real-time audio transcription CLI using LFM2-Audio-1.5B with llama.cpp. 100% local processing without internet connection.
diff --git a/examples/laptop-examples/build-a-game-with-decision-models.mdx b/examples/laptop-examples/build-a-game-with-decision-models.mdx
new file mode 100644
index 00000000..f6cb5859
--- /dev/null
+++ b/examples/laptop-examples/build-a-game-with-decision-models.mdx
@@ -0,0 +1,212 @@
+---
+title: "Build a game with decision models"
+---
+
+
+ Browse the complete example on GitHub
+
+
+This example shows how to use Liquid's [decision model](/lfm/models/decision-models) to make structured choices in real time. You will build a pixel-art survival racer where an AI chooses which lane to drive in, hundreds of times per race.
+
+
+
+Two cars race side by side on identical roads that get faster over time. Each car has three lives, and hitting traffic costs one. Play **You vs d1** with the arrow keys, or watch **Jev vs d1** to compare two decision models head-to-head.
+
+Decision models are purpose-built for classification, routing, and scoring. Instead of generating text token by token, they return a structured answer in a single call. That makes them fast enough to use inside a game loop, and cheap enough to call on every tick.
+
+## Quickstart
+
+### 1. Clone the repository
+
+```sh
+git clone https://github.com/Liquid4All/cookbook.git
+cd cookbook/examples/road-decider
+```
+
+### 2. Add your API keys
+
+Create a `.env` file from the template:
+
+```sh
+cp .env.example .env
+```
+
+Obtain a LIQUID_API_KEY:
+
+1. Go to [console.liquid.ai](https://console.liquid.ai). If you don't have an account yet, register and join an organization.
+2. Navigate to **Dashboard > API Keys**.
+3. Create a new key and copy it. Keys are prefixed with `liquid_`.
+
+Add your Liquid API key:
+
+```sh
+LIQUID_API_KEY=your-liquid-api-key
+```
+
+
+ Jev vs d1 mode is optional. To enable it, add an OpenRouter key as `OPENROUTER_API_KEY`.
+
+
+### 3. Install dependencies and start the demo
+
+```sh
+npm install
+npm run dev
+```
+
+Open [http://localhost:5173](http://localhost:5173) and start a race. If a key is missing, the start screen tells you which one and disables the modes that need it.
+
+## What's inside?
+
+The project is a vanilla JavaScript browser game with a Vite dev-server proxy:
+
+- `ai/ai.js` defines the decision question, builds the game state, calls the proxy route, and normalizes the answer.
+- `game/game.js` owns the game loop, AI tick scheduling, collisions, and race results.
+- `game/road.js` and `game/items.js` generate identical traffic patterns for both roads.
+- `ui/` renders the canvas sprites, confidence bars, HUD, and keyboard input.
+- `vite.config.js` keeps API keys server-side and forwards decision requests to Liquid or OpenRouter.
+
+## How it works
+
+Every decision tick, the AI car does four things:
+
+1. Defines a structured **choice** question.
+2. Summarizes the road ahead as a compact text state.
+3. Sends the state and question to the decision API.
+4. Applies the returned lane choice to the car.
+
+### Define the question
+
+The game asks the model to pick one of three named lanes:
+
+```js
+const QUESTION = {
+ lane: {
+ type: "choice",
+ instructions:
+ "Pick the safest lane. Row 1 is most urgent. Prefer the current lane when options are equally safe.",
+ criteria: {
+ left: "Left lane",
+ center: "Center lane",
+ right: "Right lane",
+ },
+ },
+};
+```
+
+The `type` tells the model what kind of answer to return. The `criteria` object lists the available options.
+
+### Build the state
+
+The AI does not receive the entire game screen. Instead, the game summarizes the nearest obstacle in each lane:
+
+```js
+export function buildState(car, road) {
+ const currentLane = LANES[Math.round(car.laneIndex)];
+ const lookAhead = road.getLookAhead(car.y, CONFIG.LOOK_AHEAD_ROWS);
+
+ const laneSummaries = LANES.map((lane) => {
+ const firstObstacle = lookAhead[lane].findIndex((item) => item !== null);
+ if (firstObstacle === -1) return `${lane}: clear`;
+ return `${lane}: obstacle at row ${firstObstacle + 1}`;
+ });
+
+ return [
+ `Current lane: ${currentLane}. Pick the lane with the most room ahead.`,
+ "",
+ ...laneSummaries,
+ ].join("\n");
+}
+```
+
+That produces a state like:
+
+```txt
+Current lane: center. Pick the lane with the most room ahead.
+
+left: obstacle at row 2
+center: clear
+right: obstacle at row 4
+```
+
+This compact format gives the model the safety-relevant information without asking it to reason over raw pixels or a full grid.
+
+### Call the decision API
+
+The browser calls a local proxy route for each racer:
+
+```js
+fetch(`/api/decision/${racer}`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ state,
+ questions: QUESTION,
+ }),
+});
+```
+
+The Vite proxy adds the API key and model server-side:
+
+```js
+const upstream = await fetch(racer.url, {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${racer.apiKey}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ ...body, model: racer.model }),
+});
+```
+
+This keeps keys out of the browser while letting the frontend use a simple local API.
+
+### Interpret the response
+
+The API returns the selected choice, per-lane probabilities, and confidence:
+
+```json
+{
+ "answers": {
+ "lane": {
+ "type": "choice",
+ "choice": "center",
+ "probabilities": { "left": 0.03, "center": 0.93, "right": 0.04 },
+ "confidence": 0.93
+ }
+ }
+}
+```
+
+The game validates that the choice is one of the known lanes, moves the car, and updates the live confidence display below the road.
+
+## Configuration
+
+By default, d1 runs through Liquid's API and Jev runs through OpenRouter:
+
+| Variable | Default | Description |
+| --- | --- | --- |
+| `LIQUID_API_KEY` | | API key for d1. Required. |
+| `LIQUID_MODEL_NAME` | `d1:free` | d1 model name. |
+| `LIQUID_BASE_URL` | `https://api.liquid.ai` | API host for d1. |
+| `OPENROUTER_API_KEY` | | OpenRouter API key. Required for Jev vs d1 mode. |
+| `OPENROUTER_MODEL_NAME` | `typesafe/jev-1.13` | Jev model name. |
+| `OPENROUTER_BASE_URL` | `https://openrouter.ai` | API host for Jev. |
+
+To run d1 through OpenRouter instead of Liquid's API:
+
+```sh
+LIQUID_API_KEY=your-openrouter-api-key
+LIQUID_MODEL_NAME=your-openrouter-d1-model
+LIQUID_BASE_URL=https://openrouter.ai
+```
+
+## Need help?
+
+
+
+
+ Connect with the community and ask questions about this example.
+
+
+
diff --git a/guides/decision-model-guide.mdx b/guides/decision-model-guide.mdx
new file mode 100644
index 00000000..419e208a
--- /dev/null
+++ b/guides/decision-model-guide.mdx
@@ -0,0 +1,557 @@
+---
+title: "Decision Model Guide"
+description: "Replace LLM classification, routing, and scoring calls with decision models for faster, cheaper, and more consistent structured decisions."
+---
+
+If you use an LLM to classify text, route requests, score content, or make yes/no decisions, you are paying for text generation to get a structured answer. Decision models return typed decisions directly without generating tokens, parsing JSON, or validating schemas.
+
+This guide shows when and how to migrate these calls from LLMs to the [d1 decision model](/lfm/models/decision-models).
+
+## When to Migrate
+
+Migrate when the LLM's output is a **bounded decision**, meaning the set of possible answers is known before the call.
+
+| Task | LLM pattern | Primitive |
+|---|---|---|
+| [Classification](#classification) | Structured output with enum or JSON schema | [Choice](/lfm/models/decision-models#choice) |
+| [Routing](#routing) | Prompt that picks from a list | [Choice](/lfm/models/decision-models#choice) |
+| [Binary decision](#binary-decision) | Prompt that returns true/false | [Noul](/lfm/models/decision-models#noul) |
+| [Scoring](#scoring) | Prompt that returns a number or rating | [Score](/lfm/models/decision-models#score) |
+| [Reranking](#reranking) | LLM scores or filters search results | [Noul](/lfm/models/decision-models#noul) or [Score](/lfm/models/decision-models#score) |
+| [Multiple decisions](#multi-question) | Several classification calls on the same input | Any combination, in one call |
+
+Keep your LLM when the task requires **generating new content**:
+
+- **Text generation**: drafting emails, summaries, reports, code
+- **Open-ended Q&A**: answering user questions in natural language
+- **Multi-turn conversation**: chatbots and copilots
+- **Complex reasoning**: multi-step logic, math, planning
+
+If the answer is one of N known options, use a decision model. If the answer is a new string the model must compose, use an LLM.
+
+| | LLM | Decision model (d1) |
+|---|---|---|
+| **Output** | Generated tokens parsed into a label | Typed decision with probabilities |
+| **Latency** | Grows with output length and reasoning | Low and predictable. No tokens to generate. |
+| **Output tokens** | Billed (even for a one-word answer) | Zero. No tokens generated. |
+| **Uncertainty** | Not available, or an unreliable self-reported number | Calibrated probabilities for every answer |
+| **Schema errors** | Possible (malformed JSON, out-of-schema values) | None. Answers always match the question type. |
+| **Multiple decisions** | Sequential calls or complex prompt | One call, all evaluated in parallel |
+
+## Setup
+
+Install the relevant SDK and configure your client. The LLM examples use the OpenAI SDK, which works with any OpenAI-compatible endpoint. The decision model examples use the [TypeSafe SDK](https://pypi.org/project/typesafe-sdk/) to call the [d1 decision model](/lfm/models/decision-models) through the Liquid AI API. To get an API key, see [Decision Models: Setup](/lfm/models/decision-models#setup).
+
+
+
+ ```bash
+ pip install openai pydantic
+ ```
+
+ ```python
+ import os
+ from openai import OpenAI
+
+ client = OpenAI(
+ base_url=os.environ["LLM_BASE_URL"],
+ api_key=os.environ["LLM_API_KEY"],
+ )
+ ```
+
+
+ ```bash
+ pip install typesafe-sdk
+ ```
+
+ ```python
+ import os
+ from typesafe_sdk import TypeSafeClient
+
+ client = TypeSafeClient(
+ api_key=os.environ["LIQUID_API_KEY"],
+ base_url="https://api.liquid.ai",
+ )
+ ```
+
+
+
+## Migration Examples
+
+Each example shows the same task solved with an LLM and with a decision model.
+
+### Classification
+
+The standard LLM pattern for classification uses a JSON schema or enum constraint. The model generates tokens that conform to the schema, which you then parse.
+
+
+
+ ```python
+ from pydantic import BaseModel
+ from typing import Literal
+
+ class Classification(BaseModel):
+ department: Literal["billing", "technical", "shipping", "returns", "account"]
+
+ inquiry = "I was charged twice for my subscription last month."
+
+ completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {
+ "role": "system",
+ "content": "Classify this customer inquiry into a department.",
+ },
+ {"role": "user", "content": inquiry},
+ ],
+ response_format=Classification,
+ )
+
+ department = completion.choices[0].message.parsed.department # "billing"
+ ```
+
+
+ ```python
+ from typesafe_sdk import Choice
+
+ inquiry = "I was charged twice for my subscription last month."
+
+ result = client.system_one(
+ model="d1:free",
+ state=inquiry,
+ questions={
+ "department": Choice(
+ instructions="Which department should handle this customer inquiry?",
+ criteria={
+ "billing": "Charges, invoices, refunds, or payment methods",
+ "technical": "Bug reports, feature requests, or how-to questions",
+ "shipping": "Order status, delivery tracking, or address changes",
+ "returns": "Return requests, exchanges, or product condition",
+ "account": "Login issues, profile updates, or subscription management",
+ },
+ ),
+ },
+ )
+
+ department = result.answers["department"].choice # "billing"
+ confidence = result.answers["department"].confidence # 0.99
+ runner_up = sorted(
+ result.answers["department"].probabilities.items(),
+ key=lambda x: x[1], reverse=True,
+ )[1] # ("account", 0.0006)
+ ```
+
+
+
+**What changes:**
+- Options are defined inline with descriptions. No Pydantic model or JSON schema needed.
+- You get a probability distribution over all options alongside the top pick, plus a `confidence` value that summarizes how clear-cut the answer is.
+- Zero output tokens generated. The model evaluates all options in a single call.
+- Each option's description lives next to its label in `criteria` instead of in a separate prompt.
+
+### Routing
+
+A common pattern uses a small, cheap LLM to classify the complexity of an incoming request and route it to the appropriate model.
+
+
+
+ ```python
+ from pydantic import BaseModel
+ from typing import Literal
+
+ class Route(BaseModel):
+ tier: Literal["fast", "standard", "powerful"]
+
+ user_message = "What's the weather like today?"
+
+ completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {
+ "role": "system",
+ "content": "Which model tier should handle this request?\n"
+ "fast: greetings, factual lookups, short answers, simple formatting\n"
+ "standard: moderate analysis, summarization, structured output\n"
+ "powerful: multi-step reasoning, code generation, complex analysis",
+ },
+ {"role": "user", "content": user_message},
+ ],
+ response_format=Route,
+ )
+
+ selected = completion.choices[0].message.parsed.tier # "fast"
+ ```
+
+
+ ```python
+ from typesafe_sdk import Choice
+
+ user_message = "What's the weather like today?"
+
+ result = client.system_one(
+ model="d1:free",
+ state=user_message,
+ questions={
+ "route": Choice(
+ instructions="Which model tier should handle this request?",
+ criteria={
+ "fast": "Greetings, factual lookups, short answers, simple formatting",
+ "standard": "Moderate analysis, summarization, structured output",
+ "powerful": "Multi-step reasoning, code generation, complex analysis",
+ },
+ ),
+ },
+ )
+
+ route = result.answers["route"]
+ selected = route.choice # "fast"
+ confidence = route.confidence # 0.98
+
+ # If the router is uncertain, fall back to the most capable tier
+ if confidence < 0.5:
+ selected = "powerful"
+ ```
+
+
+
+**What changes:**
+- The router call is typically faster and cheaper than an LLM-based router.
+- You get a `confidence` value, so you can fall back to a stronger model when the router is uncertain instead of trusting a binary label.
+- Adding or removing a tier is a change to `criteria`, not a prompt rewrite.
+
+### Binary Decision
+
+The typical LLM approach asks whether content is safe, either with structured output or by parsing a yes/no from the response.
+
+
+
+ ```python
+ from pydantic import BaseModel
+
+ class ModerationResult(BaseModel):
+ is_harmful: bool
+
+ user_message = "You're an absolute idiot and I hope your company goes bankrupt."
+
+ completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {
+ "role": "system",
+ "content": "Determine if this message contains harmful, threatening, or abusive content.",
+ },
+ {"role": "user", "content": user_message},
+ ],
+ response_format=ModerationResult,
+ )
+
+ is_harmful = completion.choices[0].message.parsed.is_harmful # True
+ ```
+
+
+ ```python
+ from typesafe_sdk import Noul
+
+ user_message = "You're an absolute idiot and I hope your company goes bankrupt."
+
+ result = client.system_one(
+ model="d1:free",
+ state=user_message,
+ questions={
+ "is_harmful": Noul(
+ instructions="Does this message contain harmful, threatening, or abusive content?",
+ ),
+ },
+ )
+
+ p = result.answers["is_harmful"].noul # 0.98
+
+ if p > 0.8:
+ action = "block"
+ elif p < 0.2:
+ action = "allow"
+ else:
+ action = "human_review"
+ ```
+
+
+
+**What changes:**
+- The probability (0.0 to 1.0) lets you set thresholds for different actions instead of committing to a binary label.
+- The result is always a float. No string parsing or schema validation needed.
+- Decision models produce more consistent results on repeated evaluations of the same input, reducing verdict flips.
+
+### Scoring
+
+The LLM approach for scoring asks the model to rate something on a scale, typically by generating a number or picking from a rubric with structured output.
+
+
+
+ ```python
+ from pydantic import BaseModel, Field
+ from typing import Literal
+
+ class UrgencyRating(BaseModel):
+ urgency: Literal[1, 2, 3, 4] = Field(
+ description="1=Can wait, 2=Handle today, 3=Within hours, 4=Critical"
+ )
+
+ ticket_text = (
+ "Our production API is returning 500 errors on every request. "
+ "All customers are affected. Revenue impact is approximately $12,000 per hour. "
+ "Started 15 minutes ago."
+ )
+
+ completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {
+ "role": "system",
+ "content": "Rate the urgency of this support request on a scale of 1-4:\n"
+ "1 = Can wait, no immediate impact\n"
+ "2 = Should handle today\n"
+ "3 = Time-sensitive, needs attention within hours\n"
+ "4 = Critical system failure",
+ },
+ {"role": "user", "content": ticket_text},
+ ],
+ response_format=UrgencyRating,
+ )
+
+ urgency = completion.choices[0].message.parsed.urgency # 4
+ ```
+
+
+ ```python
+ from typesafe_sdk import Score
+
+ ticket_text = (
+ "Our production API is returning 500 errors on every request. "
+ "All customers are affected. Revenue impact is approximately $12,000 per hour. "
+ "Started 15 minutes ago."
+ )
+
+ result = client.system_one(
+ model="d1:free",
+ state=ticket_text,
+ questions={
+ "urgency": Score(
+ instructions="How urgent is this support request?",
+ criteria=[
+ "Can wait: no immediate business impact",
+ "Should handle today: minor inconvenience",
+ "Time-sensitive: noticeable customer impact, needs attention within hours",
+ "Critical: system failure affecting customers or revenue",
+ ],
+ ),
+ },
+ )
+
+ score = result.answers["urgency"].score # 2.999 (continuous)
+ confidence = result.answers["urgency"].confidence # 0.998
+ distribution = result.answers["urgency"].probabilities
+ # {0: 0.0, 1: 0.0, 2: 0.001, 3: 0.999}
+ ```
+
+
+
+**What changes:**
+- Levels are indexed from 0 in the order you list them, so a 4-level rubric scores from 0 to 3. A score of 2.999 means nearly all weight is on the top level (Critical).
+- The score is continuous, not a forced integer. It captures how strongly the model leans toward a level.
+- You get the full probability distribution across all levels, so you can detect ambiguous cases.
+- No JSON parsing, no schema validation, no retries on malformed output.
+
+### Reranking
+
+In retrieval-augmented generation (RAG) pipelines, a common pattern uses an LLM to score the relevance of each retrieved chunk before passing results to the generation step.
+
+
+
+ ```python
+ from pydantic import BaseModel, Field
+ from typing import Literal
+
+ class RelevanceScore(BaseModel):
+ score: Literal[1, 2, 3, 4, 5] = Field(
+ description="1=Not relevant, 5=Highly relevant"
+ )
+
+ query = "How do I reset my API key?"
+ chunks = [
+ "To reset your API key, go to Dashboard > API Keys and click 'Regenerate'.",
+ "Our pricing plans start at $29/month for the Starter tier.",
+ "API keys are scoped to a single project. Each project can have up to 5 active keys.",
+ ]
+
+ scored_chunks = []
+ for chunk in chunks:
+ completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {
+ "role": "system",
+ "content": f"Rate how relevant this passage is to the query: '{query}'\n"
+ "1 = not relevant, 5 = highly relevant.",
+ },
+ {"role": "user", "content": chunk},
+ ],
+ response_format=RelevanceScore,
+ )
+ scored_chunks.append({
+ "chunk": chunk,
+ "score": completion.choices[0].message.parsed.score,
+ })
+
+ top_chunks = sorted(scored_chunks, key=lambda x: x["score"], reverse=True)
+ ```
+
+
+ ```python
+ from typesafe_sdk import Noul
+
+ query = "How do I reset my API key?"
+ chunks = [
+ "To reset your API key, go to Dashboard > API Keys and click 'Regenerate'.",
+ "Our pricing plans start at $29/month for the Starter tier.",
+ "API keys are scoped to a single project. Each project can have up to 5 active keys.",
+ ]
+
+ scored_chunks = []
+ for chunk in chunks:
+ result = client.system_one(
+ model="d1:free",
+ state=f"Query: {query}\n\nPassage: {chunk}",
+ questions={
+ "relevant": Noul(
+ instructions="Is this passage relevant to answering the query?",
+ ),
+ },
+ )
+ scored_chunks.append({
+ "chunk": chunk,
+ "score": result.answers["relevant"].noul,
+ })
+
+ top_chunks = sorted(scored_chunks, key=lambda x: x["score"], reverse=True)
+ # Each score is a calibrated probability (0.0 to 1.0).
+ # Set a threshold to filter: [c for c in scored_chunks if c["score"] > 0.5]
+ ```
+
+
+
+**What changes:**
+- Each relevance score is a calibrated probability, not an arbitrary integer on a 1-5 scale.
+- You can threshold directly (keep everything above 0.5) instead of guessing where to draw the line on integer scores.
+- Each call is faster and cheaper, which compounds across large result sets.
+- If you need graded relevance instead of relevant/not relevant, use a [Score](/lfm/models/decision-models#score) with defined relevance levels.
+
+### Multi-Question
+
+LLM pipelines often chain multiple classification calls sequentially, one per decision. Decision models evaluate all questions against the same state in a single call.
+
+
+
+ ```python
+ from pydantic import BaseModel
+ from typing import Literal
+
+ message = (
+ "The checkout page crashes with a white screen whenever I try to apply a promo code. "
+ "I've tried three different browsers."
+ )
+
+ # Call 1: Classify intent
+ class IntentResult(BaseModel):
+ intent: Literal["billing", "technical", "shipping", "account"]
+
+ intent_completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {"role": "system", "content": "Classify the primary intent of this message."},
+ {"role": "user", "content": message},
+ ],
+ response_format=IntentResult,
+ )
+ intent = intent_completion.choices[0].message.parsed.intent
+
+ # Call 2: Score urgency
+ class UrgencyResult(BaseModel):
+ urgency: Literal[1, 2, 3]
+
+ urgency_completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {"role": "system", "content": "Rate the urgency of this message from 1 to 3."},
+ {"role": "user", "content": message},
+ ],
+ response_format=UrgencyResult,
+ )
+ urgency = urgency_completion.choices[0].message.parsed.urgency
+
+ # Call 3: Check if it's a bug
+ class BugCheck(BaseModel):
+ is_bug: bool
+
+ bug_completion = client.chat.completions.parse(
+ model="your-model",
+ messages=[
+ {"role": "system", "content": "Determine if this message is a bug report."},
+ {"role": "user", "content": message},
+ ],
+ response_format=BugCheck,
+ )
+ is_bug = bug_completion.choices[0].message.parsed.is_bug
+ ```
+
+
+ ```python
+ from typesafe_sdk import Choice, Noul, Score
+
+ message = (
+ "The checkout page crashes with a white screen whenever I try to apply a promo code. "
+ "I've tried three different browsers."
+ )
+
+ result = client.system_one(
+ model="d1:free",
+ state=message,
+ questions={
+ "intent": Choice(
+ instructions="What is the primary topic of this inquiry?",
+ criteria={
+ "billing": "Charges, invoices, refunds, or payment methods",
+ "technical": "Bug reports, feature requests, or product questions",
+ "shipping": "Order status, delivery, or address changes",
+ "account": "Login, profile, or subscription management",
+ },
+ ),
+ "urgency": Score(
+ instructions="How urgent is this request?",
+ criteria=[
+ "Can wait: no immediate impact",
+ "Should handle today: minor inconvenience",
+ "Time-sensitive: significant business impact",
+ ],
+ ),
+ "is_bug": Noul(
+ instructions="Is the customer reporting a software bug?",
+ ),
+ },
+ )
+
+ intent = result.answers["intent"].choice
+ urgency = result.answers["urgency"].score
+ is_bug = result.answers["is_bug"].noul > 0.7
+ ```
+
+
+
+**What changes:**
+- Three network round-trips become one.
+- All three decisions are evaluated against the exact same snapshot of state.
+- Each question keeps its own type and probabilities. With an LLM you could merge the three schemas into one call, but you would still get only labels, with no probabilities.
+- Questions are evaluated in parallel, so adding a question adds little latency compared to adding another LLM call.
+
+## Next Steps
+
+- [Decision Models](/lfm/models/decision-models) for the API reference, primitives, and full code examples
+- [Model Library](/lfm/models/complete-library) for all available Liquid AI models
diff --git a/images/examples/laptop-examples/road-decider/demo.png b/images/examples/laptop-examples/road-decider/demo.png
new file mode 100644
index 00000000..32b0d4e5
Binary files /dev/null and b/images/examples/laptop-examples/road-decider/demo.png differ
diff --git a/lfm/models/complete-library.mdx b/lfm/models/complete-library.mdx
index a21407a6..4dfca1f3 100644
--- a/lfm/models/complete-library.mdx
+++ b/lfm/models/complete-library.mdx
@@ -38,6 +38,10 @@ Start with the model family that matches your input and output shape, then choos
Interleaved audio/text models for TTS, ASR, and voice chat.
+
+ Purpose-built models for classification, routing, and scoring.
+
+
Task-specific models for extraction, summarization, RAG, and translation.
@@ -116,4 +120,7 @@ Quantization reduces model size and speeds up inference with minimal quality los
| [LFM2-350M-PII-Extract-JP](/lfm/models/lfm2-350m-pii-extract-jp) | LFM2 | [✓](https://huggingface.co/LiquidAI/LFM2-350M-PII-Extract-JP) | [✓](https://huggingface.co/LiquidAI/LFM2-350M-PII-Extract-JP-GGUF) | ✗ | ✗ | Yes (TRL) |
| [LFM2-2.6B-Transcript](/lfm/models/lfm2-2.6b-transcript) | LFM2 | [✓](https://huggingface.co/LiquidAI/LFM2-2.6B-Transcript) | [✓](https://huggingface.co/LiquidAI/LFM2-2.6B-Transcript-GGUF) | ✗ | [✓](https://huggingface.co/onnx-community/LFM2-2.6B-Transcript-ONNX) | Yes (TRL) |
+| **Decision Models** | | | | | | |
+| [d1](/lfm/models/decision-models) | d1 | API | — | — | — | No |
+
Looking for an older model? Deprecated models and their recommended replacements are listed on the [Deprecations](/lfm/help/deprecations) page.
diff --git a/lfm/models/decision-models.mdx b/lfm/models/decision-models.mdx
new file mode 100644
index 00000000..c59cad31
--- /dev/null
+++ b/lfm/models/decision-models.mdx
@@ -0,0 +1,649 @@
+---
+title: "Decision Models"
+description: "Purpose-built models for structured decisions: classification, routing, and scoring in a single call with zero generated tokens"
+icon: "scale-balanced"
+---
+
+Decision models are a new class of AI model purpose-built for structured decisions. Instead of generating text token by token, a decision model evaluates a situation and returns calibrated probabilities across a fixed set of outcomes in a single call with zero generated tokens.
+
+## Primitives
+
+Every question you ask a decision model uses one of three types:
+
+- **Noul**: A yes/no question that returns a probability between 0 and 1. Think of it as a boolean on a sliding scale. "Is this message spam?" might return `0.92`, meaning a 0.92 probability that the answer is yes.
+
+- **Choice**: A pick-one-from-many question that returns a probability distribution over named options. "Which department should handle this?" might return `{"billing": 0.65, "technical": 0.30, "account": 0.05}`.
+
+- **Score**: A rate-on-a-scale question that returns a probability-weighted position on an ordered rubric. "How urgent is this?" with three levels might return a score of `1.85` (between "Medium" and "High"), with a probability distribution over each level.
+
+Pick the type that matches the shape of the answer your code needs.
+
+| If the answer is... | Use | Example |
+|---|---|---|
+| Pick one from a set of unordered categories | **Choice** | Which department handles this ticket? |
+| Rate something along an ordered scale with defined levels | **Score** | How severe is this bug? |
+| Yes or no, where the probability itself is useful | **Noul** | Does this message contain personal data? |
+
+**Noul vs Score**: A Noul at 0.5 means maximum uncertainty between yes and no. It says nothing about degree or intensity. If you want to measure degree (skill level, frustration, severity), use a Score with defined levels. If you need a yes/no gate, use a Noul.
+
+**When the answer could fit more than one type**, think about what your code does next. If it branches on a category, use a Choice. If it compares against a threshold on a continuum, use a Score. If it gates on a boolean, use a Noul.
+
+## Setup
+
+1. Go to [console.liquid.ai](https://console.liquid.ai). If you don't have an account yet, register and join an organization.
+2. Navigate to **Dashboard > API Keys**.
+3. Create a new key and copy it. Keys are prefixed with `liquid_`.
+
+Set the key as an environment variable:
+
+```bash
+export LIQUID_API_KEY="liquid_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+```
+
+## Your First Call
+
+A single API call contains three things:
+
+1. **Model**: which decision model to use.
+2. **State**: the context the model should evaluate. This can be plain text (a customer message, an email, a log entry) or a JSON object with structured fields.
+3. **Questions**: one or more typed questions, each with a name, a type, and instructions. Choice and Score questions also take criteria that define the options or levels. You can ask multiple questions in the same call, and the model evaluates them all at once.
+
+
+
+ ```bash
+ curl -s https://api.liquid.ai/decisions/v1/systemone \
+ -H "Authorization: Bearer $LIQUID_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "model": "d1:free",
+ "state": "I have been waiting over three weeks for my order and nobody has responded to my emails. This is completely unacceptable.",
+ "questions": {
+ "is_complaint": {
+ "type": "noul",
+ "instructions": "Is this message a complaint from the customer?"
+ }
+ }
+ }'
+ ```
+
+
+ ```bash
+ pip install typesafe-sdk
+ ```
+
+ ```python
+ import os
+ from typesafe_sdk import TypeSafeClient, Noul
+
+ client = TypeSafeClient(
+ api_key=os.environ["LIQUID_API_KEY"],
+ base_url="https://api.liquid.ai",
+ )
+
+ result = client.system_one(
+ model="d1:free",
+ state="I have been waiting over three weeks for my order and nobody has responded to my emails. This is completely unacceptable.",
+ questions={
+ "is_complaint": Noul(
+ instructions="Is this message a complaint from the customer?",
+ ),
+ },
+ )
+ print(result.answers["is_complaint"].noul) # 0.999
+ ```
+
+
+ ```bash
+ npm install @typesafe-ai/sdk
+ ```
+
+ ```typescript
+ import { TypeSafeClient } from "@typesafe-ai/sdk";
+
+ const client = new TypeSafeClient({
+ apiKey: process.env.LIQUID_API_KEY!,
+ baseURL: "https://api.liquid.ai",
+ });
+
+ const result = await client.systemOne({
+ model: "d1:free",
+ state: "I have been waiting over three weeks for my order and nobody has responded to my emails. This is completely unacceptable.",
+ questions: {
+ is_complaint: {
+ type: "noul",
+ instructions: "Is this message a complaint from the customer?",
+ },
+ },
+ });
+ console.log(result.answers.is_complaint.noul); // 0.999
+ ```
+
+
+
+**Response:**
+
+```json
+{
+ "model": "d1:free",
+ "answers": {
+ "is_complaint": {
+ "type": "noul",
+ "noul": 0.999
+ }
+ },
+ "usage": {
+ "input_tokens": 84,
+ "output_tokens": 0
+ }
+}
+```
+
+**Reading the response:**
+
+- **`answers.is_complaint.noul`**: The probability that the answer is "yes." Here, `0.999` means a 0.999 probability that this is a complaint.
+- **`usage.output_tokens`**: Always `0`. Decision models do not generate tokens.
+- **`usage.input_tokens`**: The total tokens consumed to evaluate the state and questions.
+
+The response contains one answer per question, keyed by the question name. A Noul answer is a single probability. Choice and Score answers also include a full probability distribution and a `confidence` value.
+
+## Usage Examples
+
+### Noul
+
+A Noul question returns a single probability between 0 and 1. Your code receives a float that you can threshold to make a binary decision, use as a weight, or pass directly to downstream logic. Values near 0 or 1 express a clear answer; values near 0.5 express genuine uncertainty.
+
+
+
+ ```bash
+ curl -s https://api.liquid.ai/decisions/v1/systemone \
+ -H "Authorization: Bearer $LIQUID_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "model": "d1:free",
+ "state": "You'\''re an absolute idiot and I hope your company goes bankrupt. I'\''m going to find out where you work.",
+ "questions": {
+ "is_harmful": {
+ "type": "noul",
+ "instructions": "Does this user message contain harmful, threatening, or abusive content?"
+ }
+ }
+ }'
+ ```
+
+
+ ```python
+ from typesafe_sdk import Noul
+
+ result = client.system_one(
+ model="d1:free",
+ state="You're an absolute idiot and I hope your company goes bankrupt. I'm going to find out where you work.",
+ questions={
+ "is_harmful": Noul(
+ instructions="Does this user message contain harmful, threatening, or abusive content?",
+ ),
+ },
+ )
+ ```
+
+
+ ```typescript
+ const result = await client.systemOne({
+ model: "d1:free",
+ state: "You're an absolute idiot and I hope your company goes bankrupt. I'm going to find out where you work.",
+ questions: {
+ is_harmful: {
+ type: "noul",
+ instructions: "Does this user message contain harmful, threatening, or abusive content?",
+ },
+ },
+ });
+ ```
+
+
+
+**Response** (`answers` field):
+
+```json
+{
+ "is_harmful": {
+ "type": "noul",
+ "noul": 0.99
+ }
+}
+```
+
+**Using the result:**
+
+```python
+p = result.answers["is_harmful"].noul
+
+if p > 0.8:
+ action = "block"
+elif p < 0.2:
+ action = "allow"
+else:
+ action = "human_review"
+```
+
+### Choice
+
+A Choice question returns the selected option, a probability distribution over all options, and a `confidence` value. `confidence` summarizes how clear-cut the answer is: high when one option clearly wins, lower when the probability is spread across several options. The distribution tells you not just the top pick but how much probability mass sits on the runner-up, which is useful for flagging ambiguous cases or routing to a fallback.
+
+
+
+ ```bash
+ curl -s https://api.liquid.ai/decisions/v1/systemone \
+ -H "Authorization: Bearer $LIQUID_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "model": "d1:free",
+ "state": "Hi, I was charged twice for my subscription last month. The amounts are $29.99 on March 3rd and again on March 5th. Can you refund the duplicate charge?",
+ "questions": {
+ "intent": {
+ "type": "choice",
+ "instructions": "What is the primary topic of this customer inquiry?",
+ "criteria": {
+ "billing": "Questions about charges, invoices, refunds, or payment methods",
+ "technical": "Bug reports, feature requests, or how-to questions about the product",
+ "shipping": "Order status, delivery tracking, or shipping address changes",
+ "returns": "Return requests, exchanges, or product condition issues",
+ "account": "Login issues, profile updates, or subscription management"
+ }
+ }
+ }
+ }'
+ ```
+
+
+ ```python
+ from typesafe_sdk import Choice
+
+ result = client.system_one(
+ model="d1:free",
+ state="Hi, I was charged twice for my subscription last month. The amounts are $29.99 on March 3rd and again on March 5th. Can you refund the duplicate charge?",
+ questions={
+ "intent": Choice(
+ instructions="What is the primary topic of this customer inquiry?",
+ criteria={
+ "billing": "Questions about charges, invoices, refunds, or payment methods",
+ "technical": "Bug reports, feature requests, or how-to questions about the product",
+ "shipping": "Order status, delivery tracking, or shipping address changes",
+ "returns": "Return requests, exchanges, or product condition issues",
+ "account": "Login issues, profile updates, or subscription management",
+ },
+ ),
+ },
+ )
+ ```
+
+
+ ```typescript
+ const result = await client.systemOne({
+ model: "d1:free",
+ state: "Hi, I was charged twice for my subscription last month. The amounts are $29.99 on March 3rd and again on March 5th. Can you refund the duplicate charge?",
+ questions: {
+ intent: {
+ type: "choice",
+ instructions: "What is the primary topic of this customer inquiry?",
+ criteria: {
+ billing: "Questions about charges, invoices, refunds, or payment methods",
+ technical: "Bug reports, feature requests, or how-to questions about the product",
+ shipping: "Order status, delivery tracking, or shipping address changes",
+ returns: "Return requests, exchanges, or product condition issues",
+ account: "Login issues, profile updates, or subscription management",
+ },
+ },
+ },
+ });
+ ```
+
+
+
+**Response** (`answers` field):
+
+```json
+{
+ "intent": {
+ "type": "choice",
+ "choice": "billing",
+ "probabilities": {
+ "billing": 0.9997,
+ "account": 0.0002,
+ "returns": 0.00005,
+ "shipping": 0.00003,
+ "technical": 0.00002
+ },
+ "confidence": 0.9996
+ }
+}
+```
+
+**Using the result:**
+
+```python
+intent = result.answers["intent"].choice
+confidence = result.answers["intent"].confidence
+
+queue_map = {
+ "billing": "billing-team",
+ "technical": "engineering-support",
+ "shipping": "logistics",
+ "returns": "returns-desk",
+ "account": "account-management",
+}
+
+target_queue = queue_map[intent]
+print(f"Route to: {target_queue} (confidence: {confidence:.2f})")
+# Route to: billing-team (confidence: 1.00)
+```
+
+### Score
+
+A Score question returns a continuous value across an ordered rubric you define, plus the probability distribution over each level. The value is the probability-weighted position on the scale: if you define four levels (0 through 3), a score of `2.9995` means the model places nearly all weight on the top level.
+
+
+
+ ```bash
+ curl -s https://api.liquid.ai/decisions/v1/systemone \
+ -H "Authorization: Bearer $LIQUID_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "model": "d1:free",
+ "state": "Our production API is returning 500 errors on every request. All customers are affected. Revenue impact is approximately $12,000 per hour. Started 15 minutes ago.",
+ "questions": {
+ "urgency": {
+ "type": "score",
+ "instructions": "How urgent is this support request?",
+ "criteria": [
+ "Can wait: no immediate business impact, can be addressed in normal queue",
+ "Should handle today: minor inconvenience or non-blocking issue",
+ "Time-sensitive: noticeable customer impact, needs attention within hours",
+ "Blocking revenue: critical system failure affecting customers or revenue"
+ ]
+ }
+ }
+ }'
+ ```
+
+
+ ```python
+ from typesafe_sdk import Score
+
+ result = client.system_one(
+ model="d1:free",
+ state="Our production API is returning 500 errors on every request. All customers are affected. Revenue impact is approximately $12,000 per hour. Started 15 minutes ago.",
+ questions={
+ "urgency": Score(
+ instructions="How urgent is this support request?",
+ criteria=[
+ "Can wait: no immediate business impact, can be addressed in normal queue",
+ "Should handle today: minor inconvenience or non-blocking issue",
+ "Time-sensitive: noticeable customer impact, needs attention within hours",
+ "Blocking revenue: critical system failure affecting customers or revenue",
+ ],
+ ),
+ },
+ )
+ ```
+
+
+ ```typescript
+ const result = await client.systemOne({
+ model: "d1:free",
+ state: "Our production API is returning 500 errors on every request. All customers are affected. Revenue impact is approximately $12,000 per hour. Started 15 minutes ago.",
+ questions: {
+ urgency: {
+ type: "score",
+ instructions: "How urgent is this support request?",
+ criteria: [
+ "Can wait: no immediate business impact, can be addressed in normal queue",
+ "Should handle today: minor inconvenience or non-blocking issue",
+ "Time-sensitive: noticeable customer impact, needs attention within hours",
+ "Blocking revenue: critical system failure affecting customers or revenue",
+ ],
+ },
+ },
+ });
+ ```
+
+
+
+**Response** (`answers` field):
+
+```json
+{
+ "urgency": {
+ "type": "score",
+ "score": 2.9995,
+ "confidence": 0.9995,
+ "probabilities": {
+ "0": 0.00001,
+ "1": 0.00002,
+ "2": 0.0004,
+ "3": 0.9996
+ },
+ "legend": {
+ "0": "Can wait: no immediate business impact, can be addressed in normal queue",
+ "1": "Should handle today: minor inconvenience or non-blocking issue",
+ "2": "Time-sensitive: noticeable customer impact, needs attention within hours",
+ "3": "Blocking revenue: critical system failure affecting customers or revenue"
+ }
+ }
+}
+```
+
+**Using the result:**
+
+```python
+score = result.answers["urgency"].score
+
+if score >= 2.5:
+ print("ACTION: Page on-call engineer immediately")
+elif score >= 1.5:
+ print("ACTION: Escalate to senior support")
+else:
+ print("ACTION: Add to standard queue")
+```
+
+### Combining All Three Primitives
+
+You can ask multiple questions of different types in the same call. The model evaluates all of them at once against the same state, so you get a complete decision in one round trip. This is useful when a single piece of input needs to be classified, routed, and prioritized together.
+
+
+
+ ```bash
+ curl -s https://api.liquid.ai/decisions/v1/systemone \
+ -H "Authorization: Bearer $LIQUID_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "model": "d1:free",
+ "state": "{\"message\": \"The checkout page crashes with a white screen whenever I try to apply a promo code. I'\''ve tried three different browsers. This is blocking a $4,200 order for our team and we need to place it before our procurement window closes on Friday.\", \"account\": {\"plan\": \"enterprise\", \"arr\": 52000, \"customer_since\": \"2022-03-15\"}}",
+ "questions": {
+ "is_bug": {
+ "type": "noul",
+ "instructions": "Is the customer reporting a software bug (as opposed to a usage question, feature request, or account issue)?"
+ },
+ "team": {
+ "type": "choice",
+ "instructions": "Which team should handle this ticket?",
+ "criteria": {
+ "billing": "Billing, invoicing, charges, and payment method issues",
+ "engineering": "Software bugs, crashes, and technical malfunctions",
+ "frontend": "UI/UX issues, display problems, and browser-specific bugs",
+ "account": "Account setup, permissions, plan changes, and access issues"
+ }
+ },
+ "urgency": {
+ "type": "score",
+ "instructions": "How urgent is this ticket? Consider business impact, time sensitivity, and customer tier.",
+ "criteria": [
+ "Low: no immediate impact, standard queue",
+ "Medium: some business impact, handle within 24 hours",
+ "High: significant business impact or time-sensitive, handle within 4 hours"
+ ]
+ }
+ }
+ }'
+ ```
+
+
+ ```python
+ import json
+ from typesafe_sdk import Choice, Noul, Score
+
+ ticket = {
+ "message": "The checkout page crashes with a white screen whenever I try to apply a promo code. I've tried three different browsers. This is blocking a $4,200 order for our team and we need to place it before our procurement window closes on Friday.",
+ "account": {
+ "plan": "enterprise",
+ "arr": 52000,
+ "customer_since": "2022-03-15",
+ },
+ }
+
+ result = client.system_one(
+ model="d1:free",
+ state=json.dumps(ticket),
+ questions={
+ "is_bug": Noul(
+ instructions="Is the customer reporting a software bug (as opposed to a usage question, feature request, or account issue)?",
+ ),
+ "team": Choice(
+ instructions="Which team should handle this ticket?",
+ criteria={
+ "billing": "Billing, invoicing, charges, and payment method issues",
+ "engineering": "Software bugs, crashes, and technical malfunctions",
+ "frontend": "UI/UX issues, display problems, and browser-specific bugs",
+ "account": "Account setup, permissions, plan changes, and access issues",
+ },
+ ),
+ "urgency": Score(
+ instructions="How urgent is this ticket? Consider business impact, time sensitivity, and customer tier.",
+ criteria=[
+ "Low: no immediate impact, standard queue",
+ "Medium: some business impact, handle within 24 hours",
+ "High: significant business impact or time-sensitive, handle within 4 hours",
+ ],
+ ),
+ },
+ )
+ ```
+
+
+ ```typescript
+ const ticket = {
+ message: "The checkout page crashes with a white screen whenever I try to apply a promo code. I've tried three different browsers. This is blocking a $4,200 order for our team and we need to place it before our procurement window closes on Friday.",
+ account: {
+ plan: "enterprise",
+ arr: 52000,
+ customer_since: "2022-03-15",
+ },
+ };
+
+ const result = await client.systemOne({
+ model: "d1:free",
+ state: JSON.stringify(ticket),
+ questions: {
+ is_bug: {
+ type: "noul",
+ instructions: "Is the customer reporting a software bug (as opposed to a usage question, feature request, or account issue)?",
+ },
+ team: {
+ type: "choice",
+ instructions: "Which team should handle this ticket?",
+ criteria: {
+ billing: "Billing, invoicing, charges, and payment method issues",
+ engineering: "Software bugs, crashes, and technical malfunctions",
+ frontend: "UI/UX issues, display problems, and browser-specific bugs",
+ account: "Account setup, permissions, plan changes, and access issues",
+ },
+ },
+ urgency: {
+ type: "score",
+ instructions: "How urgent is this ticket? Consider business impact, time sensitivity, and customer tier.",
+ criteria: [
+ "Low: no immediate impact, standard queue",
+ "Medium: some business impact, handle within 24 hours",
+ "High: significant business impact or time-sensitive, handle within 4 hours",
+ ],
+ },
+ },
+ });
+ ```
+
+
+
+**Response** (`answers` field):
+
+```json
+{
+ "is_bug": {
+ "type": "noul",
+ "noul": 0.9998
+ },
+ "team": {
+ "type": "choice",
+ "choice": "engineering",
+ "probabilities": {
+ "engineering": 0.80,
+ "frontend": 0.18,
+ "billing": 0.02,
+ "account": 0.0005
+ },
+ "confidence": 0.74
+ },
+ "urgency": {
+ "type": "score",
+ "score": 1.996,
+ "confidence": 0.995,
+ "probabilities": {
+ "0": 0.0002,
+ "1": 0.004,
+ "2": 0.996
+ },
+ "legend": {
+ "0": "Low: no immediate impact, standard queue",
+ "1": "Medium: some business impact, handle within 24 hours",
+ "2": "High: significant business impact or time-sensitive, handle within 4 hours"
+ }
+ }
+}
+```
+
+**Using the result:**
+
+```python
+answers = result.answers
+
+is_bug = answers["is_bug"].noul > 0.7
+team = answers["team"].choice
+team_confidence = answers["team"].confidence
+urgency = answers["urgency"].score
+
+if is_bug and urgency > 1.5:
+ print(f"ESCALATE to {team} on-call (SLA: 4h)")
+elif urgency > 1.0:
+ print(f"PRIORITIZE in {team} queue (SLA: 24h)")
+else:
+ print(f"QUEUE in {team} standard backlog")
+
+if team_confidence < 0.5:
+ probs = answers["team"].probabilities
+ runner_up = sorted(probs.items(), key=lambda x: x[1], reverse=True)[1]
+ print(f"NOTE: Routing uncertain. Runner-up: {runner_up[0]} ({runner_up[1]:.0%})")
+
+# ESCALATE to engineering on-call (SLA: 4h)
+# The NOTE line only prints when team confidence is below 0.5.
+```
+
+## When to Use Decision Models
+
+Decision models are a good fit when the answer is a structured decision: classification, categorization, routing (tickets, emails, requests), scoring, triage, content moderation, guardrails and safety checks, LLM-as-judge replacement, agent tool-call approval, and model routing or cascades.
+
+Use a language model instead for free-form text generation, creative writing, multi-turn conversation, complex multi-step reasoning, open-ended Q&A, summarization, and code generation.
+
+To move existing LLM calls to d1, see the [Decision Model Guide](/guides/decision-model-guide).
+
+## Next Steps
+
+- [Decision Model Guide](/guides/decision-model-guide) for migrating LLM classification, routing, and scoring calls to d1
+- [Model Library](/lfm/models/complete-library) for all available Liquid AI models
diff --git a/link-snapshot.yaml b/link-snapshot.yaml
index 2d737196..80bb4325 100644
--- a/link-snapshot.yaml
+++ b/link-snapshot.yaml
@@ -112,6 +112,7 @@ active:
- /examples/laptop-examples/audio-car-cockpit
- /examples/laptop-examples/audio-to-text-in-real-time
- /examples/laptop-examples/browser-control
+ - /examples/laptop-examples/build-a-game-with-decision-models
- /examples/laptop-examples/flight-search-assistant
- /examples/laptop-examples/invoice-extractor-tool-with-liquid-nanos
- /examples/laptop-examples/lfm2-english-to-korean
@@ -119,6 +120,7 @@ active:
- /examples/web/audio-webgpu-demo
- /examples/web/hand-voice-racer
- /examples/web/vl-webgpu-demo
+ - /guides/decision-model-guide
- /guides/hardware-evaluation
- /guides/migration-guide
- /guides/use-case-evaluation
@@ -151,6 +153,7 @@ active:
- /lfm/key-concepts/vision-capabilities
- /lfm/models/audio-models
- /lfm/models/complete-library
+ - /lfm/models/decision-models
- /lfm/models/lfm2-1.2b
- /lfm/models/lfm2-1.2b-extract
- /lfm/models/lfm2-1.2b-rag