Como construir com TypeSafe
Projete software com IA mantendo o código no controle e dando ao System One decisões estreitas e estruturadas.
O System One é o modelo da TypeSafe para construir software com IA, não agentes. Ele não gera código nem escolhe a própria próxima ação. Ele fornece primitivas de IA que se incorporam ao software, de modo que o código permanece no controle enquanto o modelo cuida de julgamentos de senso comum sobre dados não estruturados.
Três arquiteturas de software
A TypeSafe é projetada para construir software com IA, em que o código é dono do fluxo de trabalho e a IA cuida de decisões estreitas e estruturadas.
O código tradicional é uma árvore de decisão complexa feita de primitivas de software simples. Como cada primitiva é confiável, os desenvolvedores podem compô-las em abstrações de nível mais alto.
Um agente processa instruções e escolhe o próprio próximo passo. Isso funciona bem quando uma pessoa monitora o processo, mas cada laço é mais uma oportunidade de sair dos trilhos.
O código cuida do trabalho determinístico e é dono do fluxo de controle. O modelo aparece apenas onde o sistema precisa de senso comum programável ou de interpretar dados não estruturados. Cada tarefa de IA é mantida atômica e restrita.


O que torna o System One componível
Estruturado
O System One é type-safe por construção. As decisões e probabilidades seguem os tipos de software estruturados e o schema JSON que seu código espera, então ele nunca precisa recuperar um valor de um texto gerado.
Paralelo
As perguntas são avaliadas de forma independente e em paralelo. O resultado de uma primitiva não se torna contexto oculto que altera o resultado de outra.
Comparável
As saídas são ordenáveis e podem alimentar if inteligentes, limiares e comparações.
Rápido
A maioria das consultas é concluída em cerca de 100 ms. O System One é rápido o suficiente para caminhos de requisição em tempo real e interfaces de usuário.
Confiança calibrada
O RLCD comunica a incerteza por meio de probabilidades calibradas, em vez de tender ao excesso de confiança.
Autoconsistente
O System One é projetado para retornar respostas estáveis em avaliações repetidas. Veja o cookbook de autoconsistência.
Como toda saída é restrita às opções fornecidas, o modelo retorna uma distribuição de probabilidade completa sobre essas opções, em vez de inventar um valor fora do schema. A meta da TypeSafe é uma razão de inteligência para velocidade e custo maior que 100×; a aposta por trás disso é que inteligência mais barata criará muito mais demanda.
Projete um fluxo de trabalho com System One
Use código sempre que puder
Mantenha o trabalho determinístico no código. Ele é confiável e barato. Evite laços
whilede agente quando um fluxo de trabalho de software pode expressar o mesmo comportamento.Exemplo: mantenha regras determinísticas no código
days_overdue = (today - invoice.due_date).days if days_overdue > 30: route_to_collections(invoice)Navegue pelos padrões do System One para ver formas limitadas de compor decisões do modelo com código.
Decomponha o estado de entrada
Inclua apenas o contexto relevante para as perguntas atuais. Isso ajuda o modelo a evitar distrações e a degradação do contexto. Não confie em conhecimento armazenado nos pesos do modelo quando a informação atual pode vir da sua própria base de conhecimento.
Exemplo: envie apenas o contexto relevante
request { "state": { "ticket_message": "My flight was cancelled. Can I get a refund?", "refund_policy": "Cancelled flights are eligible for a full refund." }, "questions": { "policy_supports_refund": { "type": "noul", "instructions": "Does the refund policy support the refund requested in the ticket?" } } }Use estrutura no estado de entrada
Use JSON aninhado para os campos
stateequestions. Aponte as perguntas para valores específicos quando isso eliminar ambiguidade e inclua os caracteres de crase em torno de cada caminho dentro da pergunta.Exemplo: referencie um valor aninhado
Use um caminho com pontos e índices entre crases para apontar uma pergunta a um valor aninhado específico, como
support.tickets[0].message.request { "state": { "support": { "tickets": [ { "message": "I was charged twice for order A-104." }, { "message": "How do I reset my password?" } ] }, "commerce": { "orders": [ { "id": "A-104", "charges": [ { "amount_usd": 49, "status": "captured" }, { "amount_usd": 49, "status": "captured" } ] } ] }, "account": { "security": { "password_reset": "Email a reset link to the address on file." } } }, "questions": { "duplicate_charge": { "type": "noul", "instructions": "Do `support.tickets[0].message` and `commerce.orders[0].charges` indicate a duplicate charge?" }, "password_reset_supported": { "type": "noul", "instructions": "Can `account.security.password_reset` resolve the request in `support.tickets[1].message`?" } } }Decomponha as perguntas
Faça as perguntas mais explícitas, estreitas, específicas e atômicas que puder. Divida perguntas complexas ou mal definidas em perguntas separadas, cada uma avaliando uma única propriedade.
Exemplo: decomponha a detecção de spam
One broad question (bad) { "is_spam": { "type": "noul", "instructions": "Is `message` spam?" } }Decomposed questions (good) { "requests_credentials": { "type": "noul", "instructions": "Does `message.body` ask the recipient to provide a password or other login credential?" }, "offers_unexpected_reward": { "type": "noul", "instructions": "Does `message.body` claim the recipient received an unexpected prize, payment, or reward?" }, "creates_time_pressure": { "type": "noul", "instructions": "Does `message.subject` or `message.body` pressure the recipient to act quickly?" }, "sender_identity_mismatch": { "type": "noul", "instructions": "Does the organization named in `message.sender.display_name` conflict with the domain in `message.sender.email`?" }, "link_domain_mismatch": { "type": "noul", "instructions": "Does the domain in `message.links[0].url` conflict with the organization named in `message.sender.display_name`?" }, "disguises_link_destination": { "type": "noul", "instructions": "Does `message.links[0].text` conceal or misrepresent the destination in `message.links[0].url`?" } }Exemplo: verifique um trace de chamadas de ferramenta
One broad question (bad) { "tool_calls_are_correct": { "type": "noul", "instructions": "Is `trace.tool_calls` correct for `request` and `available_tools`?" } }Decomposed questions (good) { "geocode_tool_is_relevant": { "type": "noul", "instructions": "Is `trace.tool_calls[0].name` an appropriate tool for resolving `request.location`?" }, "geocode_location_matches": { "type": "noul", "instructions": "Does `trace.tool_calls[0].arguments.city` match `request.location`?" }, "geocode_arguments_match_schema": { "type": "noul", "instructions": "Does `trace.tool_calls[0].arguments` conform to `available_tools.geocode_city.parameters`?" }, "geocode_result_matches_call": { "type": "noul", "instructions": "Does `trace.tool_results[0].tool_call_id` match `trace.tool_calls[0].id`?" }, "weather_tool_is_relevant": { "type": "noul", "instructions": "Is `trace.tool_calls[1].name` an appropriate tool for answering `request.text`?" }, "weather_arguments_match_schema": { "type": "noul", "instructions": "Does `trace.tool_calls[1].arguments` conform to `available_tools.get_weather.parameters`?" }, "weather_uses_geocoded_coordinates": { "type": "noul", "instructions": "Do the coordinates in `trace.tool_calls[1].arguments` match those in `trace.tool_results[0].output`?" }, "weather_date_matches": { "type": "noul", "instructions": "Does `trace.tool_calls[1].arguments.date` match `request.date`?" }, "weather_unit_matches": { "type": "noul", "instructions": "Does `trace.tool_calls[1].arguments.unit` match `request.unit`?" } }Use estrutura nas perguntas
Mantenha as perguntas curtas.
instructionsecriteriageralmente são strings e, para uma pergunta curta e sem ambiguidade, uma string basta. Eles também podem ser objetos ou arrays. Coloque a pergunta em um campo e os dados que orientam a pergunta nos outros.A estrutura ajuda nestas situações:
- A pergunta precisa de contexto ou exemplos. Uma frase longa de informação de fundo ou uma lista de entradas de exemplo pertence a campos nomeados ao lado da pergunta, onde seu código pode adicioná-los ou trocá-los sem reescrever a pergunta.
- Parte da pergunta vem do seu código. Quando um valor vem de um banco de dados, coloque-o em seu próprio campo em vez de inseri-lo em um modelo de string.
- Várias perguntas têm instruções parecidas. Uma requisição recebe um estado e pode incluir várias perguntas. Adicionar dados complementares pode ajudar a diferenciar as perguntas.
Exemplo: referencie um registro do seu código
Este Noul compara um currículo no estado com um registro de um banco de dados de candidatos. O registro entra em
potential_duplicatecomo está, e a pergunta se refere a ele pelo nome.questions { "same_as_record_18": { "type": "noul", "instructions": { "potential_duplicate": { "name": "John Smith", "location": "Oakland, California", "last_employer": "Google" }, "question": "Is the resume for the same person as `potential_duplicate`?" } } }Os dados de “potential_duplicate” vindos do código podem mudar com o tempo. A “question” se refere a eles usando crases.
As descrições dentro de
criteriatambém podem ser objetos. Para um Choice, a descrição de cada opção pode ser um objeto que diz o que a opção cobre, o que pertence a outra opção e alguns exemplos. Use os mesmos nomes de campo em todas as opções para que o modelo possa compará-las diretamente.Exemplo: defina criteria de Choice contrastivos
questions { "card_help_topic": { "type": "choice", "instructions": { "question": "Which disposable virtual card topic is the user asking about?", "focus": "Classify the information the user wants." }, "criteria": { "get_disposable_virtual_card": { "what": "Purpose, eligibility, or setup", "not_for": "Quantity, transaction, or merchant restrictions", "examples": [ "How can I get a disposable virtual card?", "What are disposable cards for?" ] }, "disposable_card_limits": { "what": "Quantity, transaction, or merchant restrictions", "not_for": "Purpose, eligibility, or setup", "examples": [ "How many disposable cards can I make per day?", "Where can I use a disposable card?" ] } } } }A página de cada tipo de pergunta tem um exemplo trabalhado:
- Noul compara um currículo com vários registros de candidatos, uma pergunta por registro, com as perguntas construídas no código.
- Choice descreve duas opções facilmente confundidas, com o que cada uma cobre, para que não serve e exemplos.
- Score dá a cada nível uma descrição e situações de exemplo.
O cookbook da cascata de extração de dados estruturados mostra o caso de redação compartilhada, fazendo a mesma bateria de perguntas sobre cada campo de um registro extraído.
Uma pergunta ou um critério curto e sem ambiguidade pode continuar sendo uma string. Adicione estrutura quando ela separar orientações que, de outra forma, se confundiriam. Para o conjunto completo de lugares em que a estrutura é aceita, veja Avançado: estrutura.
Faça muitas perguntas
Faça muitas perguntas estreitas e independentes sobre o mesmo estado em uma única requisição. É assim que você maximiza a eficácia e a inteligência por dólar com a API: as perguntas são executadas em paralelo, e o código pode combinar seus sinais sem adicionar viagens seriais ao modelo.
Veja o padrão Fan-out especulativo e o cookbook de perguntas paralelas.
Combine as saídas das perguntas no código (ou alimente um modelo clássico de ML)
Combine respostas independentes com regras determinísticas ou somas ponderadas. Para composição aprendida, use as probabilidades como features em um modelo clássico de machine learning a jusante.
Exemplo: combine sinais com uma pontuação ponderada
answers = response.answers # Combine independent signals into one application-specific score. quality = ( 0.4 * answers["answers_request"].noul + 0.4 * answers["citations_are_supported"].noul + 0.2 * (1 - answers["contradicts_context"].noul) )Composite Scoring mostra como preservar julgamentos individuais ao combiná-los. Se você não tiver rótulos para um modelo a jusante, use um ensemble de modelos de raciocínio caros para gerá-los; o cookbook do AutoResearch mostra como treinar um modelo clássico com saídas do System One.
Roteie conforme a incerteza
Faça o código tomar ações diferentes para respostas confiantes e não confiantes. Escalone casos incertos para uma pessoa ou para um modelo de raciocínio mais caro. Teste os limiares plotando a confiança contra a acurácia nos seus dados.
Exemplo: roteie por confiança
answer = response.answers["card_help_topic"] if answer.confidence < 0.8: route_to_human_review(ticket) else: route_to_handler(answer.choice, ticket)Veja Confiança e Roteamento com gating por confiança para escolher limiares e combiná-los com o risco de cada ação.
Reunindo tudo
Este fluxo de trabalho de ticket de suporte mantém o trabalho determinístico no código, envia apenas contexto estruturado relevante, avalia muitas perguntas atômicas em uma única requisição e compõe as respostas com portões de confiança explícitos.
triage_ticket.py
from typesafe_sdk import Choice, Noul, NoulCriteria, Score, TypeSafeClient
def triage_ticket(ticket, customer):
# Handle deterministic states without calling a model.
if ticket["status"] == "closed":
return "no_action"
open_orders = [
order for order in customer["orders"] if order["status"] != "delivered"
]
# Include only the structured context needed by the questions below.
state = {
"ticket": {
"message": ticket["message"],
"sender": ticket["sender"],
"links": ticket["links"],
},
"customer": {
"plan": customer["plan"],
"open_orders": open_orders,
},
"policy": {
"sensitive_credentials": ["password", "security code", "API key"],
},
}
# Ask structured, atomic questions together so they run in parallel.
questions = {
"topic": Choice(
instructions={
"question": "Which team should handle `ticket.message`?",
"focus": "Classify the customer's primary request.",
},
criteria={
"billing": {
"what": "Charges, invoices, refunds, or subscriptions",
"not_for": "Order tracking or account access",
"examples": ["I was charged twice", "Where is my refund?"],
},
"orders": {
"what": "Order status, delivery, cancellation, or returns",
"not_for": "Charges or account access",
"examples": ["Where is my order?", "Cancel my shipment"],
},
"account": {
"what": "Login, profile, permissions, or security",
"not_for": "Charges or order tracking",
"examples": ["Reset my password", "I cannot sign in"],
},
},
),
"requests_credentials": Noul(
instructions={
"question": "Does the message request a sensitive credential?",
"compare": [
"`ticket.message`",
"`policy.sensitive_credentials`",
],
"focus": "Look for a request to disclose the credential itself.",
},
criteria=NoulCriteria(
true={
"what": "Asks the recipient to disclose a listed credential",
"examples": [
"Reply with your password",
"Send us your API key",
],
},
false={
"what": "Does not ask the recipient to disclose a credential",
"not_for": "A legitimate instruction to reset a credential",
"examples": ["Use this link to reset your password"],
},
),
),
"sender_identity_mismatch": Noul(
instructions={
"question": "Does the claimed sender identity conflict with its domain?",
"compare": [
"`ticket.sender.display_name`",
"`ticket.sender.email`",
],
"focus": "Compare the named organization with the email domain.",
},
criteria=NoulCriteria(
true={
"what": "Claims an organization unrelated to the email domain",
"examples": ["Acme Payroll sent from claim-bonus.example"],
},
false={
"what": "The identity and domain agree or make no conflicting claim",
"examples": ["Acme Payroll sent from acme.example"],
},
),
),
"unexpected_reward": Noul(
instructions={
"question": "Does the message announce an unexpected reward?",
"inspect": "`ticket.message`",
"focus": "Look for an unsolicited prize, payment, or reward claim.",
},
criteria=NoulCriteria(
true={
"what": "Announces an unrequested prize, payment, or reward",
"examples": ["You were selected for a $1,000 bonus"],
},
false={
"what": "Contains no reward claim or discusses an expected payment",
"not_for": "A customer asking about a known refund or payroll deposit",
"examples": ["When will my approved refund arrive?"],
},
),
),
"refund_requested": Noul(
instructions={
"question": "Does the customer explicitly request a refund or credit?",
"inspect": "`ticket.message`",
"focus": "Require a requested remedy, not a billing complaint alone.",
},
criteria=NoulCriteria(
true={
"what": "Directly asks for money back or an account credit",
"examples": ["Please refund the duplicate charge"],
},
false={
"what": "Does not ask for a refund or credit",
"not_for": "A complaint or billing question without a requested remedy",
"examples": ["Why was I charged twice?"],
},
),
),
"mentions_open_order": Noul(
instructions={
"question": "Does the message refer to a supplied open order?",
"compare": [
"`ticket.message`",
"`customer.open_orders`",
],
"focus": "Match an order id or other identifying details.",
},
criteria=NoulCriteria(
true={
"what": "Refers to an open order by id or identifying details",
"examples": ["Where is order A-104?"],
},
false={
"what": "Does not identify any supplied open order",
"not_for": "A generic order question with no matching details",
"examples": ["How long does shipping usually take?"],
},
),
),
"frustration": Score(
instructions={
"question": "How frustrated does the customer appear?",
"inspect": "`ticket.message`",
"focus": "Judge expressed frustration, not issue severity.",
},
criteria=[
{
"what": "Calm and matter-of-fact",
"signals": ["Neutral wording", "No complaint about the experience"],
},
{
"what": "Frustrated but civil",
"signals": ["Expresses annoyance", "Remains constructive"],
},
{
"what": "Very angry or threatening to leave",
"signals": ["Hostile language", "Threatens cancellation or churn"],
},
],
),
}
with TypeSafeClient() as client:
response = client.system_one(
state=state,
questions=questions,
)
# Compose independent spam signals with weights controlled by code.
answers = response.answers
spam_risk = (
0.45 * answers["requests_credentials"].noul
+ 0.30 * answers["sender_identity_mismatch"].noul
+ 0.25 * answers["unexpected_reward"].noul
)
# Escalate uncertain judgments instead of guessing.
spam_is_uncertain = 0.4 < spam_risk < 0.6
if spam_is_uncertain or answers["topic"].confidence < 0.75:
return route_to_human_review(ticket)
if spam_risk >= 0.6:
return quarantine_as_spam(ticket)
# Let code decide which speculative answers matter on this path.
if answers["topic"].choice == "billing":
return route_to_billing(
ticket,
refund_requested=answers["refund_requested"].noul >= 0.7,
)
if answers["topic"].choice == "orders":
return route_to_orders(
ticket,
mentions_open_order=answers["mentions_open_order"].noul >= 0.7,
)
priority = (
"high"
if answers["frustration"].confidence >= 0.7
and answers["frustration"].score >= 1.5
else "normal"
)
return route_to_account_support(ticket, priority=priority)