Clef, Clairic, and CopilotKit: A Promising Trio and the Road to Local Hosting

  1. Using Jev for Agentic UI: Just a Trend or a Game Changer?
  2. Clef, Clairic, and CopilotKit: A Promising Trio and the Road to Local Hosting

Clef selects the tool, Clairic extracts the parameters, and open weights bring the decision part to your own hardware

In September 2026, Jev caused quite a stir. TypeSafe presented it as the first System One model. Jev does not write text. It answers predefined questions with predefined options, typically within a few hundred milliseconds. In the Decision Index by Artificial Analysis, it scores in the range of fast LLMs but needs only a fraction of their time. This makes Jev a strong fit for decisions inside an Agentic UI, such as selecting the next tool.

Since October 1, 2026, Cloudflare's Clef offers an open-weight alternative with the same API. Like Jev, however, Clef only classifies. Cloudflare's Clairic, released on October 19, adds extraction. Unlike classic extraction models, it does not only copy values from the text. It also derives and computes values such as the date behind in 10 days, and it forecasts how a series continues.

In this article, I combine both models in a flight assistant built with Angular and CopilotKit. Finally, I show how far the road to local hosting already reaches.

📂 Source code

The Example: Flight Search with Angular and CopilotKit

The example is the flight search I use to demonstrate all kinds of concepts. In a chat, users ask for flights or for the flights they have booked. The server decides which tool to call, runs it, and streams the result to the client as AG-UI events. CopilotKit renders each flight as a widget. Below each answer, the application reports how long the two models took:

Chat view of the flight assistant: the user asks "Did I already book Paris?". The assistant calls the tool findBookings and shows the booked flight from Wien to Paris. Below, the metrics: Clef took 614 ms, Clairic 610 ms, and running both models in parallel 626 ms.

For the question Did I already book Paris?, Clef selects the tool findBookings. At the same time, Clairic extracts Paris as the destination. The application then shows the booked flight from Wien to Paris.

Compensating for the Limits of Decision Models

As my article on Jev shows, a decision model quickly determines the tool to call. However, it only chooses among options you define up front. This applies to Clef as well. Tool parameters therefore have to be options too. This is why the example in that article contained a data structure with all the airports our airline serves. For values such as names, dates, or flight numbers, this approach reaches its limits, because nobody can list all of them in advance.

Clairic closes this gap. The example divides the work as follows:

  • Clef selects the tool: findFlights, findBookings, or none. It also answers whether the conversation names the departure, the destination, and the date.
  • Clairic extracts the parameters: departure, destination, date, and the maximum number of flights.
  • The server sends the request to both models at the same time.

In my tests, Clairic tended to name a city even when the traveller stated none for that end of the journey. It also carried the day of a flight over to its return flight. Hence, the server only takes a departure, destination, or date from Clairic when Clef confirms that the conversation names it. This way, Clef serves as an additional check for Clairic's answers.

What Are Clef and Clairic?

Both models come from Cloudflare and run on Workers AI. Clef makes decisions, and Clairic extracts values.

Clef

Clef follows the System One API that TypeSafe introduced with Jev. Like Jev, it answers a question by choosing one of several predefined values and returns a probability for each of them. This suits tool calling well. The options are the available tools, so Clef cannot invent a tool, and the application always receives a value it expects. The probabilities show how sure Clef is. As Clef generates no text, it answers quickly. Cloudflare offers Clef in two sizes, Clef with 27B and Clef-flash with 9B parameters, and publishes both under the Apache 2.0 license.

My article on Jev describes this approach in detail. In our example, the function evaluate sends the request to Clef.

Clairic

Cloudflare calls Clairic the first model of a new class: System One extraction models. Clef and Jev belong to the class of System One decision models. Like them, Clairic does not write text. It receives a state, which consists of text and up to four images, and a set of typed fields. For each field, it returns a typed value.

Clairic supports the following field types:

  • text returns a span of the input together with its character offsets.
  • number returns a number and respects settings such as integer and decimals.
  • date, time, and datetime return values in ISO format.
  • duration returns a duration in a unit of your choice.
  • list returns several text, number, or date items in the order of the input.
  • estimate returns a distribution within a declared range, and forecast returns one estimate per future step of a series.

Clairic does more than copy values from the input. It can also compute them. In Cloudflare's examples, it adds a price and its tax or counts the days between two dates. In our example, it turns expressions such as the day after tomorrow or in 10 days into a date.

Clairic is available as a preview on Workers AI. Cloudflare names the current limits openly. Multi-step arithmetic is not a strength yet, and every answer is well formed but not necessarily correct.

Implementation

The code for Clef is very similar to the code in my article on Jev. The constant QUESTIONS contains the questionnaire. It asks which tool the traveller wants and whether the conversation names the departure, the destination, and the date. A value counts as named when Clef's probability exceeds 0.5.

This section therefore focuses on Clairic and on combining both models.

Describing the Fields

Clef works with a questionnaire. Clairic's counterpart is a set of fields. The constant FIELDS describes the four parameters of the tools:

// server/src/ticketing/parameters.ts
import { normalizeCity } from '../data/cities.ts';
import { extract, type Answer, type Field } from '../models/clairic.ts';

[...]

const SAME_ROUTE =
  ' A last message that only changes the day or the number of flights keeps the route from ' +
  'before.';

const FIELDS: Record<string, Field> = {
  from: {
    type: 'text',
    instructions:
      'City the journey starts from, which the traveller asks about in the last message. Earlier ' +
      'messages count only when the last one builds on them: a return flight starts where the ' +
      'flight shown ends.' +
      SAME_ROUTE,
  },
  to: {
    type: 'text',
    instructions: [...],
  },
  date: {
    type: 'date',
    instructions:
      'Day the traveller wants to fly on, for what they ask about in the last message. Earlier ' +
      'messages count only when the last one builds on them, and a return flight does not take ' +
      'the day of the flight shown. Only a day the traveller states counts.',
  },
  limit: {
    type: 'number',
    integer: true,
    instructions:
      'How many flights at most the traveller wants to see for what they ask about in the last ' +
      'message. Earlier messages count only when the last one builds on them. Only a number ' +
      'the traveller states for the flights counts.',
  },
};

Each field has a type and instructions in plain language. from and to are text fields. For date, Clairic returns an ISO date. The field limit expects an integer.

The instructions define what counts. They refer to the last message, as it contains the current request. Earlier messages only count when the last one builds on them, for instance when the traveller asks for the return flight. The sentence Only a day the traveller states counts aims to keep Clairic from inventing a date.

The State

The state is the input Clairic extracts the values from. Clef accepts any JSON structure as its state, but Clairic expects text. Hence, the function toText turns the conversation into a string. A typical state looks like this:

Conversation between a traveller and a flight assistant. The last message is the new one.
Today is Sunday, 2026-10-25.
Traveller: I am planning a business trip. Could you please show me the connections from Graz to Hamburg?
Assistant: Flights I found:
Flights shown: Graz to Hamburg.
Traveller: Great. And the return flight in 10 days?

The first line tells Clairic what the text is about. The second line contains today's date. Clairic uses it to turn relative statements such as in 10 days into a date. The conversation follows. For the last answer that showed flights, toText adds their routes. This line tells Clairic which route the assistant has shown, so it can derive where a return flight starts and ends.

The server builds the conversation from the AG-UI messages that CopilotKit sends. It considers the last ten entries.

Calling Clairic

The function extract sends the state and the fields to Clairic:

// server/src/models/clairic.ts
const MODEL = '@cf/cloudflare/clairic';

export type Field = {
  type: 'text' | 'number' | 'date';
  instructions?: string;
  integer?: boolean;
};

[...]

export async function extract(state: string, fields: Record<string, Field>): Promise<Result> {
  const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];
  const token = process.env['CLOUDFLARE_API_TOKEN'];

  const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/${MODEL}`;

  const referenceDate = getReferenceDate();
  const body = JSON.stringify({ model: 'clairic', state, fields, reference_date: referenceDate });

  const response = await fetch(url, {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
    body,
  });

  if (!response.ok) {
    const reason = await response.text();
    throw new Error(`Clairic answered ${response.status}: ${reason}`);
  }

  const json = await response.json();
  const envelope = json as { result: Result };
  return envelope.result;
}

Besides the state and the fields, the request contains a reference date. Clairic uses this ISO date for dates written without a year. The account ID and the API token come from environment variables. As the token has to stay secret, this call belongs on the server. Clef runs on Workers AI as well and uses the same keys. Workers AI wraps the answer in an envelope, so extract returns its result property.

The type Field covers the three field types the example needs. The function extractParameters calls extract with the result of toText and the constant FIELDS.

Combining Both Models

The function decide starts both requests at the same time and waits for both answers. The function toAction then combines them into a tool call:

// server/src/ticketing/decide.ts
[...]
import {
  extractParameters,
  toParameters,
  type Extraction,
  type ToolParameters,
} from "./parameters.ts";
import { selectTool, type ToolSelection } from "./selection.ts";
import type { State } from "./state.ts";

[...]

export async function decide(state: State): Promise<Decision> {
  const started = performance.now();
  const result = await Promise.all([
    selectTool(state),
    extractParameters(state),
  ]);
  const durationMs = performance.now() - started;
  const [selection, extraction] = result;

  const action = toAction(selection, extraction);
  [...]
}

The function selectTool asks Clef, and extractParameters asks Clairic. When toAction builds the parameters, it only accepts a departure, destination, or date from Clairic when Clef confirms that the conversation names it. If a flight search lacks the departure or the destination, the assistant asks the user for it instead of calling the tool.

Agentic UI with Angular

If you don’t just want to connect an agent but embed Agentic UI into a scalable architecture:
In my book Agentic UI with Angular, I cover the underlying patterns and trade-offs in depth.

Cover of the eBook Agentic UI with Angular

More about the eBook →

The Result

For the return flight request in the state above, Clef returned the following answers:

{
  "model": "clef",
  "answers": {
    "tool": {
      "type": "choice",
      "choice": "findFlights",
      "probabilities": {
        "findFlights": 0.9918,
        "findBookings": 0.0051,
        "none": 0.0031
      },
      "confidence": 0.9755
    },
    "namesFrom": { "type": "noul", "noul": 0.8561 },
    "namesTo": { "type": "noul", "noul": 0.9297 },
    "namesDate": { "type": "noul", "noul": 0.9204 }
  },
  "usage": {
    "input_tokens": 1135,
    "output_tokens": 0
  }
}

Clef selects findFlights with a probability of 0.99. The three yes/no answers lie between 0.86 and 0.93 and therefore above the threshold of 0.5. The server takes the departure, the destination, and the date from Clairic. For the same request, Clairic returned the following result. For readability, I rounded its probabilities:

{
  "model": "clairic",
  "answers": {
    "from": {
      "type": "text",
      "p_null": 0.008,
      "span": [215, 222],
      "guess": "Hamburg",
      "confidence": 0.99,
      "value": "Hamburg"
    },
    "to": {
      "type": "text",
      "p_null": 0.004,
      "span": [207, 211],
      "guess": "Graz",
      "confidence": 0.98,
      "value": "Graz"
    },
    "date": {
      "type": "date",
      "p_null": 0.0002,
      "guess": "2026-11-04",
      "confidence": 0.98,
      "value": "2026-11-04"
    },
    "limit": {
      "type": "number",
      "p_null": 0.9999,
      "guess": 10,
      "confidence": 0.36,
      "value": null
    }
  },
  "truncated": false,
  "usage": {
    "state_tokens": 92,
    "image_tokens": 0,
    "schema_tokens": 811,
    "input_tokens": 1087,
    "output_tokens": 0,
    "passes": 3
  }
}

The object answers contains one entry per field. Each entry provides these properties:

  • value is the extracted value. It is null when the input does not determine the field.
  • guess is Clairic's best guess, even when value is null.
  • p_null is the probability that the input does not contain the value.
  • confidence indicates how sure Clairic is about its answer.
  • span holds the start and end offsets of the value within the state. Only text fields have it.

Clairic recognizes the return flight and swaps the cities of the outbound flight. Hamburg becomes the departure and Graz the destination. Both spans point to the traveller's first message, which asks for the connections from Graz to Hamburg. As a date field, date has no span. Clairic computed its value from in 10 days and today's date.

The field limit is the most interesting one. The message contains the number 10, so Clairic's guess is 10. However, p_null is close to 1, as the 10 refers to days and not to the number of flights. Consequently, value is null.

As with Clef, the probabilities help with the control flow. Below a threshold, the application can ask the user to confirm a value. The right threshold depends on the use case, and tests with real inputs help to find it. The usage sections show that neither model generates output tokens. Most of Clairic's 1,087 input tokens come from the field descriptions: 811 schema tokens versus 92 tokens for the state.

Deriving Values

Deriving values is one of Clairic's strengths. The second screenshot shows an example:

Chat view: the user writes "Show flights to London, from Graz. In 10 days". The assistant calls findFlights and shows two flights from Graz to London on Wednesday, November 4, 2026. Clef took 614 ms, Clairic 615 ms, and running both models in parallel 617 ms.

The message Show flights to London, from Graz. In 10 days contains no date. Clairic computes it from the current date in the state. On October 25, 2026, in 10 days results in November 4. The flight API cannot filter by date. Hence, the server filters the flights for this day in memory.

This ability ends at text fields. A text answer is always a span of the input and therefore a verbatim copy. Clairic cannot use its world knowledge to turn an airport code such as HAM into Hamburg, even if the instructions ask for it. Jev managed this in my previous article, because Hamburg was one of its options. In this specific case, a deterministic solution is the better choice anyway. The function normalizeCity maps aliases such as Vienna to the city names of the flight API. A table of airport codes would work the same way.

Performance and Trade-offs

In most of my test runs, both models answered in under a second, including the network. Workers AI does not report how long the model itself needed. As the server calls both models in parallel, the slower of the two determines the total time. However, some tool calls get by with Clef alone, for instance when a tool has no parameters or when its parameters come from a fixed set of values, such as the airports in my article on Jev. This offers further potential for optimization.

At the time of writing in October 2026, Cloudflare only charges for input tokens: $0.24 per million for Clef and $0.10 per million for Clairic. TypeSafe charges even less for Jev: $0.042 per million input tokens. In my test setup, Jev combined with Clairic delivered results comparable to those of Clef. For extraction models such as Clairic, no independent benchmark comparable to the Decision Index exists yet. Nevertheless, these results are very promising, especially for Agentic UIs, which have to react to user input quickly.

This speed comes with a trade-off. An agent framework lets an LLM pick a tool and fill in its arguments based on the tool descriptions. For Clef and Clairic, I had to program every use case myself: the questions for Clef, the fields for Clairic, and the code that combines both results into a tool call. No comfortable agent framework took this work off my hands. The implementation is therefore more complex and leaves more room for errors.

Local Hosting with Open-Weight Models

System One models do not write text token by token. Instead, they read the input in one or a few parallel passes. How much hardware a model needs still depends on its size, but narrow tasks such as tool selection open the door to smaller models. This makes System One models good candidates for local hosting on your own servers, on edge devices, or in the browser. Local inference removes the network share of the round trip and keeps sensitive data on your premises. How fast the model itself answers then depends on the local hardware.

Clef already allows this step. Cloudflare publishes its weights on Hugging Face. The model card names Transformers, vLLM, and SGLang for self-hosting. Quantized variants on Hugging Face also run on llama.cpp, Ollama, and LM Studio. SGLang and Ollama, from version 0.35, provide an endpoint for System One requests. In the example, only the function evaluate knows where Clef runs. Switching to a local server therefore only affects this function. Clef-flash with its 9B parameters places lower demands on the hardware than Clef with 27B.

Clairic is cloud-only for now. Cloudflare intends to release its weights after further development. Until then, the extraction part of the example stays on Workers AI, and the conversation still leaves your premises.

Besides Clef, there are further decision models for classification tasks such as tool selection:

Model Runs locally Characteristics
Clef and Clef-flash Yes Cloudflare. 27B and 9B parameters on Qwen. Apache 2.0. Same API as Jev. Reads images. Hosted on Workers AI as well.
Jev No TypeSafe's hosted API. First System One model. Choice, yes/no, and score questions.
Kev Yes Four sizes from 0.8B to 27B on Qwen. Apache 2.0. Same API as Jev. Runs on Apple Silicon via MLX.
Laya Yes Encoder models with 322M to 421M parameters. Apache 2.0. Multilingual variant for over 100 languages. Jev-compatible server. Runs on CPU and via ONNX Runtime in Node.js and the browser.
Liquid AI d1 Yes 3B and 600M parameters. Built for edge devices. Supports llama.cpp.

For extracting parameters, the following models are available:

Model Runs locally Characteristics
Clairic Not yet Cloudflare. First System One extraction model. Typed fields, derived values, estimates, and forecasts. Open weights announced.
GLiNER2 Yes Fastino. 74M to 340M parameters. Apache 2.0. Runs on CPU. Returns spans for entities and structured records. Computes no values.
NuExtract 2.0 Yes NuMind. 2B to 8B parameters on Qwen-VL. Generative model that fills a JSON template from text and images. MIT for 2B and 8B.

None of the open alternatives works like Clairic yet. GLiNER2 also returns spans but computes no values. NuExtract generates JSON and therefore behaves more like a small LLM. The community project Clarke follows Clairic's approach. At the time of writing, it only provides training code and no published weights.

Laya and GLiNER2 show how small such models can be. With fewer than half a billion parameters, these small language models run on an ordinary CPU.

Conclusion

Clef and Clairic complement each other. Clef decides which tool to call, and Clairic extracts its parameters, including values it derives, such as dates. Both run on Workers AI and share one set of keys. Running in parallel, they delivered typed results in under a second in most of my test runs. The price is more code: without an agent framework, each use case needs its own implementation.

The road to local hosting is half done. Clef's weights are available today, and a local server only changes where the request goes. Once Cloudflare releases Clairic's weights, the whole example can run on your own hardware.


Interested in production-ready Agentic UI architectures?

In my workshop, we dive into AG-UI, A2UI, MCP Apps, HITL patterns, and modern Angular architectures for real-world agentic systems.

Workshop: Agentic AI with Angular – AG-UI, A2UI, MCP Apps & HITL patterns

See all details →

FAQ

What are System One models?

System One models answer typed questions about an input instead of generating text. Decision models such as Jev and Clef choose among predefined options and return probabilities. Extraction models such as Clairic return typed values like text spans, numbers, and dates.

How does Clef relate to Jev?

Clef follows the System One API that TypeSafe introduced with Jev. An existing Jev integration switches to Clef by changing the endpoint and the model. Unlike Jev, Clef comes with open weights under the Apache 2.0 license.

How do Clef and Clairic differ?

Clef selects among options you define up front. This suits decisions such as tool selection. Clairic extracts values you cannot list in advance, such as names and dates, and derives values such as the date behind in 10 days.

Can Clef and Clairic run locally?

Clef can. Its weights are available on Hugging Face, and servers such as vLLM, SGLang, and Ollama run it. Clairic is a cloud service on Workers AI for now, but Cloudflare intends to release its weights.

Does Clairic normalize text values?

No. Text answers are spans of the input, so an airport code such as HAM stays HAM. A deterministic lookup table handles such mappings.

Agentic UI with Angular

Architecting Agentic AI with Open Standards

Integriere AI-Agents in Angular mit offenen Standards.

Mehr zum Buch