Como construir um pipeline de IA digitado com Jev e Clairic.


Um e-mail de suporte chega: “Peça NW-47790, os postes da barraca quebraram na primeira noite, paguei US$ 389 e quero cada centavo de volta hoje ou estou contestando a cobrança.”

Para agir de acordo com isso, seu código precisa de respostas para algumas pequenas perguntas. É um pedido de reembolso? Quão urgente é isso? Should a person handle it? Qual é o número do pedido? How much money is involved? É o mesmo problema, quer você esteja classificando uma caixa de entrada de suporte ou criando um chatbot personalizado isso tem que descobrir o que cada cliente está pedindo.

A ação usual é enviar o e-mail inteiro para um modelo de chat com um prompt que termina “responder em JSON” e depois validar o que retornar. Funciona, mas você está pagando um modelo construído para escrever redações para preencher cinco campos e está analisando sua estimativa em um esquema.

Os modelos System One são construídos exatamente para esse tipo de trabalho. Neste tutorial, construiremos um pequeno pipeline de caixa de entrada de suporte com dois deles: Jev do TypeSafe toma as decisões e Clairic da Cloudflare lê os valores. O Plain TypeScript então transforma suas respostas em uma ação. Nenhuma engenharia imediata para formatos de saída e nenhum reparo JSON.

Pensando, rápido e lento. O Sistema 1 é o tipo de pensamento rápido e intuitivo que você usa para ler um rosto ou um sinal. O Sistema 2 é o tipo lento e deliberado que você usa para divisões longas.

Um modelo System One é um modelo de IA que entende a linguagem natural, mas não gera texto. You give it some input (the estado) e um conjunto de perguntas digitadas, e retorna respostas digitadas com probabilidades. Não há texto livre para analisar, porque o modelo só pode responder na forma que você solicitou.

Isso é diferente de solicitar uma saída estruturada a um modelo de chat. Guia do SitePoint para mostra como restringir um modelo de chat e validar seu JSON com Zod. With a System One model, the type é a interface: uma pergunta sim/não retorna como uma probabilidade, uma pergunta escolha uma retorna como uma de suas opções e uma data retorna como uma data.

  • Cada resposta carrega uma confiança ou probabilidade. Seu código pode agir com base em respostas certas e enviar respostas inseguras para uma pessoa.
  • Eles são rápidos e baratos. Na execução abaixo, cada modelo respondeu em algumas centenas de milissegundos por uma fração de centavo.

Dois modelos, dois empregos

Jev e Clairic são modelos do System One, mas fazem trabalhos diferentes, e é isso que os torna um bom par.

Fenômeno (TypeSafe)
Trabalho
Tipos de perguntas Escolha (escolha uma opção), Pontuação O novo (probabilidade de sim) texto, número, data, tempo, datahora, duração, lista, estimativa, previsão
O que você recebe de volta A opção, a pontuação ou a probabilidade, mais confiança O valor (valores de texto como extensões exatas de caracteres da entrada), uma confiança e a probabilidade de que não exista
Preço
Status Geralmente disponível (jev-1.14.0) Visualização

Jev não pode lhe fornecer o número do pedido e Clairic não tem como escolher em uma lista de opções ou responder sim ou não. Dê a cada um o trabalho para o qual foi criado.

Se você leu o SitePoint você viu Jev decidindo qual modelo lidará com a solicitação de um agente. Aqui vamos usá-lo no próprio texto recebido.

O que estamos construindo

Entrarão cinco e-mails inventados para uma loja de equipamentos para atividades ao ar livre. Para cada um:

  1. Fenómeno decide o que o cliente deseja, quão urgente é e se uma pessoa precisa intervir.
  2. Clairic extrai o número do pedido, o valor do reembolso, a data do pedido e qualquer prazo.
  3. Código simples aplica as regras da loja: pequenos reembolsos vão para uma fila de reembolso, clientes irritados vão para uma pessoa, números de pedidos perdidos recebem uma mensagem automática “qual é o número do seu pedido?” responder e assim por diante.

Ambos os modelos leem o mesmo e-mail, portanto são executados ao mesmo tempo.

Pré-requisitos

  • Node.js 23 ou posterior, que executa arquivos TypeScript diretamente (no Node 22, adicione --experimental-strip-types)
  • Uma chave de API TypeSafe do Console TypeSafe
  • Um ID de conta Cloudflare e um token de API com permissão Workers AI

Coloque os três valores em seu ambiente:

export TYPESAFE_API_KEY=...
export CLOUDFLARE_ACCOUNT_ID=...
export CLOUDFLARE_API_TOKEN=...

Nenhum SDK é necessário. Ambos os modelos são um único fetch chamar.

Etapa 1: a caixa de entrada

Aqui está a caixa de entrada de teste. Cada e-mail existe para testar um caminho diferente através das regras:


export const emails = (
  {
    id: 'refund-small',
    text: `Hi there, I ordered the trail socks (3 pack) on October 20, order #NW-48213, and one pair arrived with a hole in the heel. Could I get the $18.95 back for that pack? Happy to send a photo. Thanks, Priya`,
  },
  {
    id: 'refund-angry',
    text: `This is the THIRD time I've emailed. Order NW-47790 — the tent poles snapped on the first night and my kids were out in the rain. I paid $389 and I want every cent back today or I'm disputing the charge with my bank and leaving a review everyone will see.`,
  },
  {
    id: 'where-is-it',
    text: `Hey, just checking on order NW-48577 from last Monday. My hiking trip starts on the 14th so I really need the boots before then. Any chance you can tell me where it's at?`,
  },
  {
    id: 'no-order-number',
    text: `Hello, I bought a rain jacket from you a couple of weeks ago and it's way too big. Can I swap it for a medium?`,
  },
  {
    id: 'pre-sale',
    text: `Does the Ridgeline 40L pack fit carry-on rules for most airlines? And does it come in green? Thinking of buying two.`,
  },
);

Etapa 2: Jev toma as decisões

O fenômeno também state (o e-mail) e um conjunto de perguntas nomeadas. Pediremos três, um de cada tipo:

const JEV_QUESTIONS = {
  intent: {
    type: 'choice',
    instructions: 'What is the customer mainly asking the store to do in this email?',
    criteria: {
      refund: 'Give money back for an order or a charge',
      order_status: 'Say where an order is or when it will arrive',
      exchange: 'Swap an item for a different size, colour or product',
      product_question: 'Answer a question about a product before or after buying',
      other: 'Anything else',
    },
  },
  urgency: {
    type: 'score',
    instructions: 'How soon does this email need a reply from the store?',
    criteria: ('Can wait a few days', 'Should be answered today', 'Needs a reply within the hour'),
  },
  needs_person: {
    type: 'noul',
    instructions:
      'Does this email need a person rather than an automatic reply? Yes if the customer is very upset, threatens a chargeback, a bad review or legal action, or raises a safety problem with a product.',
  },
} as const;

Algumas coisas a serem observadas:

  • Escolha os critérios são um mapa de opções para descrição. A descrição é o que Jev julga contra, então escreva como se fosse informar um novo membro da equipe.
  • Pontuação os critérios são níveis ordenados, o mais baixo primeiro. A resposta é um número entre 0 e o nível superior, então 1,7 significa “entre hoje e dentro de uma hora, tendendo a ser urgente”.
  • O novo é o nome do TypeSafe para uma pergunta sim/não. A resposta é a probabilidade de sim.

Todas as três perguntas vão em uma solicitação. Jev lê o e-mail uma vez e responde a todas as perguntas em paralelo, portanto, adicionar uma pergunta custa muito pouco:

async function askJev(state: string) {
  const started = performance.now();
  const res = await fetch('https://api.typesafe.ai/v1/systemone', {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.TYPESAFE_API_KEY}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: 'jev-1.14.0', state, questions: JEV_QUESTIONS }),
  });
  if (!res.ok) throw new Error(`Jev ${res.status}: ${await res.text()}`);
  const body = await res.json();
  return { ...body, ms: Math.round(performance.now() - started) };
}

Nós fixamos jev-1.14.0 em vez do jev-latest apelido. Seus limites são ajustados em relação às probabilidades de uma versão, então você deseja escolher quando essa versão muda.

Para o e-mail furioso da barraca, Jev respondeu com:

{
  "intent": { "type": "choice", "choice": "refund", "confidence": 1.0 },
  "urgency": { "type": "score", "score": 1.75, "confidence": 0.62 },
  "needs_person": { "type": "noul", "noul": 0.98 }
}

(Cortado: a resposta real também inclui a probabilidade de cada opção e nível.)

Etapa 3: Clairic lê os valores

Clairic leva o mesmo statealém de um conjunto de digitados campos em vez de perguntas:

const CLAIRIC_FIELDS = {
  order_number: { type: 'text', instructions: 'The order number the customer gives' },
  amount: { type: 'number', decimals: 2, unit: 'USD', instructions: 'The amount of money the customer wants back, in dollars' },
  ordered_on: { type: 'date', instructions: 'The date the customer placed the order' },
  needed_by: { type: 'date', instructions: 'The date the customer says they need the order by' },
};

async function askClairic(state: string) {
  const started = performance.now();
  const url = `https://api.cloudflare.com/client/v4/accounts/${process.env.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/cloudflare/clairic`;
  const res = await fetch(url, {
    method: 'POST',
    headers: { Authorization: `Bearer ${process.env.CLOUDFLARE_API_TOKEN}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ model: 'clairic', state, fields: CLAIRIC_FIELDS, reference_date: '2026-11-02' }),
  });
  if (!res.ok) throw new Error(`Clairic ${res.status}: ${await res.text()}`);
  const { result } = await res.json(); 
  return { ...result, ms: Math.round(performance.now() - started) };
}

reference_date ancora datas relativas como “dia 14” ou “última segunda-feira”. Em produção, passe a data de chegada do e-mail.

Cada resposta retorna com três coisas que vale a pena usar:

  • value: a resposta, ou null se o e-mail não disser.
  • confidence: quão certo Clairic tem desse valor.
  • p_null: a probabilidade de o valor não estar na entrada.

Para o “onde está?” e-mail, Clairic leu o prazo assim:

"needed_by": { "type": "date", "value": "2026-11-14", "confidence": 0.99999, "p_null": 0.002 }

“Dia 14” passou a ser uma data completa, calculada a partir da data de referência. Campos de texto como order_number também retorne como extensões de caracteres do e-mail original. O valor é copiado da entrada, não gerado, portanto não é possível inventar um número de pedido que não esteja lá.

Etapa 4: execute os dois ao mesmo tempo

Os dois modelos não dependem um do outro, então envie as duas solicitações juntas:

for (const email of emails) {
  const (jev, clairic) = await Promise.all((askJev(email.text), askClairic(email.text)));
  rows.push({ email, jev, clairic, action: decide(jev.answers, clairic.answers) });
}

O tempo para cada e-mail é o modelo mais lento, não os dois somados.

Etapa 5: coloque as regras no código

Esta é a parte que um modelo de chat geralmente acaba fazendo mal: as regras de negócios. Aqui estão códigos comuns que você pode ler, testar e alterar sem tocar em um prompt:

const AUTO_REFUND_LIMIT = 100; 
const SURE = 0.9;

function decide(jev, clairic) {
  const { intent, urgency } = jev;
  const needsPerson = jev.needs_person.noul;
  const order = clairic.order_number;
  const amount = clairic.amount;

  if (needsPerson > 0.5) return 'person: upset or risky';
  if (intent.confidence < 0.6) return 'person: unclear what they want';
  if (intent.choice === 'product_question' || intent.choice === 'other') return `${intent.choice} queue`;

  
  if (order.value === null) return 'auto-reply: ask for the order number';
  if (order.confidence < SURE) return 'person: order number unclear';
  const orderNo = String(order.value).replace(/^#/, '');

  if (intent.choice === 'refund') {
    if (typeof amount.value !== 'number') return 'person: refund with no amount';
    return amount.value <= AUTO_REFUND_LIMIT && amount.confidence >= SURE
      ? `refund queue: $${amount.value.toFixed(2)} on ${orderNo}`
      : 'person: refund over the limit';
  }
  if (intent.choice === 'order_status') {
    
    const neededBy = clairic.needed_by.confidence >= SURE ? clairic.needed_by.value : null;
    const rush = urgency.score >= 1.5 || neededBy !== null;
    return `${rush ? 'priority ' : ''}tracking reply for ${orderNo}${neededBy ? ` (needed by ${neededBy})` : ''}`;
  }
  return `${intent.choice} queue for ${orderNo}`;
}

Observe que a confiança do modelo fica próxima à regra de negócios. Um reembolso de $ 18,95 só será automático se Clairic tiver certeza sobre o número do pedido e o valor. Aumentar o limite de reembolso ou a barra de confiança é uma mudança de uma linha e você não precisa ligar para nenhum dos modelos novamente para ver o que aconteceria. Salve as respostas e reproduza-as.

Os resultados

Aqui está a execução de todos os cinco e-mails:

E-mail Fenómeno Clairic Ação
reembolso-pequeno 407ms 420ms fila de reembolso: $ 18,95 em NW-48213
raiva do reembolso 252ms 515ms pessoa: chateada ou arriscada
Cadê 254ms 333ms resposta de rastreamento prioritário para NW-48577 (necessário até 14/11/2026)
sem número de pedido 245ms 561ms resposta automática: peça o número do pedido
pré-sal 261ms 503ms fila product_question

Every email went where it should. Toda a execução usou 2.703 tokens de entrada em Jev e 2.292 em Clairic, cerca de US$ 0,0003 no total. Nesse ritmo, 10.000 e-mails de suporte custariam cerca de US$ 0,70.

O que as pontuações de confiança capturaram

A parte interessante da corrida não foram as respostas certas. Foram os dois errados.

“Da última segunda-feira.” A execução utilizou 2 de novembro de 2026 como data de referência, que é uma segunda-feira. “Última segunda-feira” significa 26 de outubro, mas Clairic respondeu 2026-11-02a própria data de referência. Its confidence on that answer was 0,54em comparação com 0,99 ou superior em quase todos os outros valores na execução.

“Algumas semanas atrás.” Clairic transformou isso em 2026-10-19que é uma leitura justa, mas ainda é um palpite. Sua confiança era 0,43.

Nenhuma das datas alterou qualquer ação, porque as regras confiam apenas em valores de 0,9 ou superiores. Esse é o hábito de construir: um valor abaixo da sua barra conta como desconhecido. Um modelo de chat que retornasse JSON teria entregue a você "2026-11-02"

Mais um problema: Clairic copiou o número do pedido como #NW-48213hash incluído, porque foi assim que o cliente escreveu. Os valores de texto são extensões da entrada, portanto normalizá-los é tarefa do seu código. Essa é uma linha de código.

Onde cada modelo cai

Fenómeno

  • Ele decide; it doesn’t read values out. Asking it for an order number is the wrong tool.
  • Score confidence can be modest on fuzzy scales. A confiança na urgência variou de 0,28 a 0,86 nos cinco e-mails, e a vaga pergunta de pré-venda foi a menos certa. Use a pontuação como um sinal combinado com outras evidências (como um prazo real de Clairic), e não como uma regra completa.
  • Thresholds are only meaningful on your own data. 0,5 para needs_person trabalhei em cinco e-mails; compare-o com algumas centenas de reais antes de confiar nele.

Clairic

  • Ele está em versão prévia, então espere mudanças e fixe seus testes na saída antes de confiar nele.
  • Ele lê e calcula valores, mas não consegue escolher entre suas opções. Classification belongs to Jev or to code.
  • As datas relativas são onde há menos certeza, como mostra o caso da “última segunda-feira”. Sempre passe reference_datee verifique a confiança nas datas.

Ambos

  • Nenhum dos dois escreve nada. They tell your code what’s in the email; eles não podem responder a isso.

Os modelos System One cuidam da leitura e do roteamento. They don’t hold a conversation. Depois que um e-mail é roteado, ainda é necessário escrever a resposta, fazer uma pergunta de acompanhamento ou explicar uma política de devolução com as próprias palavras do cliente. That’s where a chat model earns its cost.

A divisão que funciona é: os modelos do Sistema Um decidem, o código aplica as regras e um modelo de bate-papo apenas obtém os casos em que as palavras importam.

Concluindo

Construímos um pipeline de triagem de caixa de entrada de suporte a partir de dois modelos pequenos e algum código comum:

  • Fenómeno respondeu três perguntas sobre cada e-mail: o que querem, qual a urgência e se uma pessoa deve cuidar disso.
  • Clairic retirou o número do pedido, valor e datas, cada um com uma pontuação de confiança.
  • Texto datilografado aplicou as regras, confiando apenas em valores acima de uma barra de confiança.

Cada e-mail demorava cerca de meio segundo e custava menos de um centésimo de centavo. Mais importante ainda, as duas respostas que estariam erradas voltaram sinalizadas como inseguras.

O código completo tem cerca de 160 linhas sem dependências. Tente adicionar uma quarta pergunta do Jev (o cliente menciona uma foto?) ou uma estimate campo em Clairic (quantos dias até a viagem?) e veja quão pouco o resto do pipeline precisa mudar.



Source link