Cómo construir con TypeSafe
Diseña software con IA manteniendo el control en el código y dando a System One decisiones acotadas y estructuradas.
System One es el modelo de TypeSafe para construir software con IA, no agentes. No genera código ni elige su propia siguiente acción. Proporciona primitivas de IA que se integran en el software, de modo que el código mantiene el control mientras el modelo se encarga de los juicios de sentido común sobre datos no estructurados.
Tres arquitecturas de software
TypeSafe está diseñado para construir software con IA, donde el código es dueño del flujo de trabajo y la IA se encarga de decisiones acotadas y estructuradas.
El código tradicional es un árbol de decisión complejo hecho con primitivas de software simples. Como cada primitiva es fiable, los desarrolladores pueden componerlas en abstracciones de mayor nivel.
Un agente procesa instrucciones y elige su siguiente paso. Esto funciona bien cuando una persona supervisa el proceso, pero cada bucle introduce otra oportunidad de descarrilar.
El código se encarga del trabajo determinista y es dueño del flujo de control. El modelo aparece solo donde el sistema necesita sentido común programable o necesita interpretar datos no estructurados. Cada tarea de IA se mantiene atómica y restringida.


Qué hace que System One sea componible
Estructurado
System One es type-safe por construcción. Las decisiones y las probabilidades se ajustan a los tipos de software estructurados y al esquema JSON que espera tu código, así que nunca tiene que recuperar un valor a partir de prosa generada.
Paralelo
Las preguntas se evalúan de forma independiente y en paralelo. El resultado de una primitiva no se convierte en contexto oculto que cambia el resultado de otra primitiva.
Comparable
Las salidas son ordenables y pueden impulsar sentencias if inteligentes, umbrales y comparaciones.
Rápido
La mayoría de las consultas se completan en unos 100 ms. System One es lo bastante rápido para rutas de solicitudes en tiempo real e interfaces de usuario.
Confianza calibrada
RLCD comunica la incertidumbre mediante probabilidades calibradas en lugar de tender al exceso de confianza.
Autoconsistente
System One está diseñado para devolver respuestas estables en evaluaciones repetidas. Consulta el cookbook de autoconsistencia.
Como cada salida está restringida a las opciones suministradas, el modelo devuelve una distribución de probabilidad completa sobre esas opciones en lugar de inventar un valor fuera del esquema. El objetivo de TypeSafe es una relación de inteligencia a velocidad y coste superior a 100×; la apuesta de fondo es que la inteligencia más barata creará mucha más demanda.
Diseñar un flujo de trabajo con System One
Usa código cuando puedas
Mantén el trabajo determinista en el código. Es fiable y barato. Evita los bucles
whilede los agentes cuando un flujo de trabajo de software pueda expresar el mismo comportamiento.Ejemplo: mantener las reglas deterministas en el código
days_overdue = (today - invoice.due_date).days if days_overdue > 30: route_to_collections(invoice)Explora los patrones de System One para ver formas acotadas de componer decisiones del modelo con código.
Descompón el estado de entrada
Incluye solo el contexto relevante para las preguntas actuales. Esto ayuda al modelo a evitar distracciones y la degradación del contexto. No te apoyes en conocimiento almacenado en los pesos del modelo cuando la información actual puede venir de tu propia base de conocimiento.
Ejemplo: enviar solo el 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 estructura en el estado de entrada
Usa JSON anidado para los campos
stateyquestions. Apunta las preguntas a valores concretos cuando eso elimine ambigüedad, e incluye los caracteres de comilla invertida alrededor de cada ruta dentro de la pregunta.Ejemplo: referenciar un valor anidado
Usa una ruta con puntos e índices entre comillas invertidas para apuntar una pregunta a un valor anidado concreto, 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`?" } } }Descompón las preguntas
Plantea las preguntas más explícitas, acotadas, concretas y atómicas que puedas. Divide las preguntas complejas o mal definidas en preguntas separadas que evalúen cada una una sola propiedad.
Ejemplo: descomponer la detección 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`?" } }Ejemplo: verificar una traza de llamadas a herramientas
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 estructura en las preguntas
Mantén las preguntas cortas.
instructionsycriteriasuelen ser cadenas y, para una pregunta corta e inequívoca, con una cadena basta. También pueden ser objetos o arrays. Pon la pregunta en un campo y los datos que la guían en los demás.La estructura ayuda en estas situaciones:
- La pregunta necesita contexto o ejemplos. Una frase larga de información de fondo o una lista de entradas de ejemplo pertenece a campos con nombre junto a la pregunta, donde tu código pueda añadirlos o cambiarlos sin reescribir la pregunta.
- Parte de la pregunta viene de tu código. Cuando un valor viene de una base de datos, ponlo en su propio campo en lugar de insertarlo en una plantilla de cadena.
- Varias preguntas tienen instrucciones similares. Una solicitud toma un estado y puede incluir varias preguntas. Añadir datos complementarios puede ayudar a diferenciar las preguntas.
Ejemplo: referenciar un registro de tu código
Este Noul compara un currículum del estado con un registro de una base de datos de candidatos. El registro se coloca en
potential_duplicatetal cual, y la pregunta se refiere a él por su nombre.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`?" } } }Los datos de “potential_duplicate” procedentes del código pueden cambiar con el tiempo. La “question” hace referencia a ellos mediante comillas invertidas.
Las descripciones dentro de
criteriatambién pueden ser objetos. En una Choice, la descripción de cada opción puede ser un objeto que diga qué cubre la opción, qué corresponde a otra opción y algunos ejemplos. Usa los mismos nombres de campo en todas las opciones para que el modelo pueda compararlas directamente.Ejemplo: definir criterios 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?" ] } } } }La página de cada tipo de pregunta incluye un ejemplo desarrollado:
- Noul compara un currículum con varios registros de candidatos, una pregunta por registro, con las preguntas construidas en código.
- Choice describe dos opciones que se confunden fácilmente, con lo que cubre cada una, para qué no sirve y ejemplos.
- Score da a cada nivel una descripción y situaciones de ejemplo.
El cookbook de cascada de extracción de datos estructurados muestra el caso de redacción compartida, planteando la misma batería de preguntas sobre cada campo de un registro extraído.
Una pregunta o un criterio cortos e inequívocos pueden seguir siendo una cadena. Añade estructura cuando separe una guía que de otro modo se entremezclaría. Para ver el conjunto completo de lugares donde se acepta estructura, consulta Avanzado: estructura.
Haz muchas preguntas
Haz muchas preguntas acotadas e independientes sobre el mismo estado en una sola solicitud. Así maximizas la eficacia y la inteligencia por dólar con la API: las preguntas se ejecutan en paralelo y el código puede combinar sus señales sin añadir viajes de ida y vuelta en serie al modelo.
Consulta el patrón de fan-out especulativo y el cookbook de preguntas en paralelo.
Combina las salidas de las preguntas en código (o aliméntalas a un modelo clásico de ML)
Combina respuestas independientes con reglas deterministas o sumas ponderadas. Para una composición aprendida, usa las probabilidades como características en un modelo clásico de aprendizaje automático posterior.
Ejemplo: combinar señales con una puntuación 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) )La puntuación compuesta (Composite Scoring) muestra cómo preservar los juicios individuales al combinarlos. Si no tienes etiquetas para un modelo posterior, usa un conjunto de modelos de razonamiento caros para generarlas; el cookbook de AutoResearch muestra cómo entrenar un modelo clásico con salidas de System One.
Enruta según la incertidumbre
Haz que el código tome acciones distintas para respuestas con confianza y sin ella. Escala los casos inciertos a una persona o a un modelo de razonamiento más caro. Prueba los umbrales representando la confianza frente a la precisión en tus datos.
Ejemplo: enrutar por confianza
answer = response.answers["card_help_topic"] if answer.confidence < 0.8: route_to_human_review(ticket) else: route_to_handler(answer.choice, ticket)Consulta Confianza y Enrutamiento condicionado por confianza para elegir umbrales y ajustarlos al riesgo de cada acción.
Reunirlo todo
Este flujo de trabajo de tickets de soporte mantiene el trabajo determinista en el código, envía solo el contexto estructurado relevante, evalúa muchas preguntas atómicas en una sola solicitud y compone las respuestas con puertas de confianza 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)