Como construir com a TypeSafe
Concebe software com IA mantendo o código no controlo 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. Não gera código nem escolhe a sua própria ação seguinte. Fornece primitivas de IA que se integram no software, de modo que o código mantém o controlo enquanto o modelo trata dos julgamentos de senso comum sobre dados não estruturados.
Três arquiteturas de software
A TypeSafe foi concebida para construir software com IA, em que o código é dono do fluxo de trabalho e a IA trata de decisões estreitas e estruturadas.
O código tradicional é uma árvore de decisão complexa feita a partir de primitivas de software simples. Como cada primitiva é fiável, os programadores podem compô-las em abstrações de nível superior.
Um agente processa instruções e escolhe o seu passo seguinte. Isto funciona bem quando uma pessoa está a monitorizar o processo, mas cada ciclo introduz mais uma oportunidade de descarrilar.
O código trata do trabalho determinístico e é dono do fluxo de controlo. O modelo aparece apenas onde o sistema precisa de senso comum programável ou precisa de interpretar dados não estruturados. Cada tarefa de IA é mantida atómica e restringida.


O que torna o System One componível
Estruturado
O System One é type-safe por construção. As decisões e as probabilidades obedecem aos tipos de software estruturados e ao esquema JSON que o teu código espera, por isso nunca tem de recuperar um valor a partir de prosa gerada.
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 primitiva.
Comparável
As saídas são ordenáveis e podem alimentar instruções if inteligentes, limiares e comparações.
Rápido
A maioria das consultas conclui-se em cerca de 100 ms. O System One é suficientemente rápido para percursos de pedidos em tempo real e interfaces de utilizador.
Confiança calibrada
O RLCD comunica a incerteza através de probabilidades calibradas em vez de tender para o excesso de confiança.
Autoconsistente
O System One foi concebido para devolver respostas estáveis em avaliações repetidas. Vê o cookbook de autoconsistência.
Como cada saída está restringida às opções fornecidas, o modelo devolve uma distribuição de probabilidade completa sobre essas opções em vez de inventar um valor fora do esquema. O objetivo da TypeSafe é um rácio de inteligência para velocidade e custo superior a 100×; a aposta de fundo é que inteligência mais barata vai criar muito mais procura.
Conceber um fluxo de trabalho com o System One
Usa código quando puderes
Mantém o trabalho determinístico no código. É fiável e barato. Evita os ciclos
whilede agentes quando um fluxo de trabalho de software consegue expressar o mesmo comportamento.Exemplo: manter as regras determinísticas no código
days_overdue = (today - invoice.due_date).days if days_overdue > 30: route_to_collections(invoice)Percorre os padrões do System One para veres formas delimitadas de compor decisões do modelo com código.
Decompõe o estado de entrada
Inclui apenas o contexto relevante para as perguntas atuais. Isto ajuda o modelo a evitar distrações e a degradação do contexto. Não te apoies em conhecimento armazenado nos pesos do modelo quando a informação atual pode vir da tua própria base de conhecimento.
Exemplo: enviar 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?" } } }Usa estrutura no estado de entrada
Usa JSON aninhado para os campos
stateequestions. Aponta as perguntas para valores específicos quando isso elimina ambiguidade, e inclui os carateres de acento grave em torno de cada caminho dentro da pergunta.Exemplo: referenciar um valor aninhado
Usa um caminho com pontos e índices entre acentos graves 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`?" } } }Decompõe as perguntas
Faz as perguntas mais explícitas, estreitas, específicas e atómicas que conseguires. Divide perguntas complexas ou mal definidas em perguntas separadas, cada uma das quais avalia uma única propriedade.
Exemplo: decompor a deteçã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: verificar um traço de chamadas a ferramentas
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`?" } }Usa estrutura nas perguntas
Mantém as perguntas curtas.
instructionsecriteriasão normalmente strings e, para uma pergunta curta e inequívoca, uma string é tudo o que precisas. Também podem ser objetos ou arrays. Coloca a pergunta num 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 com nome ao lado da pergunta, onde o teu código os pode acrescentar ou trocar sem reescrever a pergunta.
- Parte da pergunta vem do teu código. Quando um valor vem de uma base de dados, coloca-o no seu próprio campo em vez de o inserir num modelo de string.
- Várias perguntas têm instruções semelhantes. Um pedido recebe um estado e pode incluir várias perguntas. Acrescentar dados suplementares pode ajudar a tornar as perguntas distintas.
Exemplo: referenciar um registo do teu código
Este Noul compara um currículo no estado com um registo de uma base de dados de candidatos. O registo é colocado em
potential_duplicatetal como está, e a pergunta refere-se 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” provenientes do código podem mudar ao longo do tempo. A “question” refere-se a eles através de acentos graves.
As descrições dentro de
criteriatambém podem ser objetos. Numa Choice, a descrição de cada opção pode ser um objeto que diz o que a opção abrange, o que pertence a uma opção diferente e alguns exemplos. Usa os mesmos nomes de campo em todas as opções para que o modelo as possa comparar diretamente.Exemplo: definir critérios 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 desenvolvido:
- Noul compara um currículo com vários registos de candidatos, uma pergunta por registo, com as perguntas construídas no código.
- Choice descreve duas opções facilmente confundíveis, com o que cada uma abrange, 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 partilhada, em que se faz a mesma bateria de perguntas sobre cada campo de um registo extraído.
Uma pergunta ou um critério curto e inequívoco pode continuar a ser uma string. Acrescenta estrutura quando esta separa orientações que, de outro modo, se misturariam. Para o conjunto completo de locais onde a estrutura é aceite, vê Avançado: estrutura.
Faz muitas perguntas
Faz muitas perguntas estreitas e independentes sobre o mesmo estado num único pedido. É assim que maximizas a eficácia e a inteligência por euro com a API: as perguntas são executadas em paralelo e o código pode combinar os seus sinais sem acrescentar idas e voltas em série ao modelo.
Vê o padrão de Fan-Out especulativo e o cookbook de perguntas em paralelo.
Combina as saídas das perguntas no código (ou alimenta-as para um modelo clássico de ML)
Combina respostas independentes com regras determinísticas ou somas ponderadas. Para composição aprendida, usa as probabilidades como características num modelo clássico de machine learning a jusante.
Exemplo: combinar 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 os julgamentos individuais ao combiná-los. Se não tens etiquetas para um modelo a jusante, usa um conjunto de modelos de raciocínio caros para as gerar; o cookbook de AutoResearch mostra como treinar um modelo clássico com saídas do System One.
Encaminha com base na incerteza
Faz o código tomar ações diferentes para respostas com confiança e sem confiança. Escalona os casos incertos para uma pessoa ou para um modelo de raciocínio mais caro. Testa os limiares traçando a confiança em função da precisão nos teus dados.
Exemplo: encaminhar 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)Vê Confiança e Confidence-Gated Routing para escolheres limiares e os ajustares ao risco de cada ação.
Reunir tudo
Este fluxo de trabalho de tickets de apoio mantém o trabalho determinístico no código, envia apenas o contexto estruturado relevante, avalia muitas perguntas atómicas num único pedido e compõe as respostas com portas de confiança explícitas.
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)