Noul
Una pregunta Noul le pide al modelo de TypeSafe que evalúe una pregunta de sí/no y devuelva la probabilidad de que la respuesta sea sí.
Usa un Noul cuando la respuesta sea sí o no. Por ejemplo, si este mensaje pide un reembolso, si este currículum menciona sistemas distribuidos, si este comentario contiene datos personales. Si la respuesta es una de varias opciones, usa un Choice. Si es una posición en un espectro, usa un Score. Elige un tipo de pregunta compara los tres.
Una respuesta de Noul es un único número que representa la probabilidad de que la respuesta sea sí, donde 0 significa no y 1 significa sí.
Estructura de la solicitud
El cuerpo de la solicitud POST a la API de TypeSafe tiene los mismos tres campos de nivel superior que cualquier otro tipo de pregunta: state, que es el contenido que se evalúa; model; y questions. Cada pregunta Noul tiene los siguientes campos:
type: Siempre"noul".instructions: La pregunta de sí/no que responde el modelo, o una afirmación para que la juzgue.criteria: Opcional. Un objeto con descripcionestrueyfalsede qué significan un sí y un no.
Abajo hay una solicitud en la que el estado es un mensaje de soporte y las dos preguntas son si el cliente quiere hablar con una persona y si ha contactado antes con soporte:
{
"state": "I have asked three times now. Can I please just talk to a real person?",
"questions": {
"is_human_escalation": {
"type": "noul",
"instructions": "Is the customer asking for a human agent?"
},
"is_repeat_contact": {
"type": "noul",
"instructions": "Has the customer contacted support about this before?",
"criteria": {
"true": "Mentions a prior attempt, ticket, or that they have asked before",
"false": "No sign of any previous contact"
}
}
}
}Tú eliges los id de las preguntas, is_human_escalation e is_repeat_contact en este caso. Los id no se envían al modelo. Cada respuesta se devuelve bajo el mismo id. La primera pregunta se apoya solo en instructions. La segunda añade criteria para decir qué cuenta como sí y qué cuenta como no.
Con el SDK de Python, las mismas preguntas son objetos Noul:
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state="I have asked three times now. Can I please just talk to a real person?",
questions={
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
},
)
print(response.answers["is_human_escalation"].noul)
print(response.answers["is_repeat_contact"].noul)
Tanto el método system_one como el endpoint https://api.typesafe.ai/v1/systemone llevan el nombre de System One, el modelo de IA de TypeSafe. Cómo construir con TypeSafe explica dónde usarlo en tu código.
Si usas un agente de programación, instala primero la habilidad de agente de TypeSafe para que conozca las formas de la solicitud y la respuesta.
Estructura de la respuesta
La respuesta tiene una entrada en answers por cada pregunta, bajo los id de la solicitud:
{
"model": "jev-1.13.0",
"answers": {
"is_human_escalation": {
"type": "noul",
"noul": 0.99
},
"is_repeat_contact": {
"type": "noul",
"noul": 0.93
}
},
"usage": {
"input_tokens": 360,
"output_tokens": 39
}
}
Ambas respuestas aquí se acercan a 1. El cliente dice «hablar con una persona real», así que is_human_escalation es 0.99. «Ya he preguntado tres veces» coincide con la descripción true de is_repeat_contact, así que es 0.93.
Leer un Noul
El número es la respuesta y la certeza a la vez. Un valor cerca de 1 es un sí contundente. Un valor cerca de 0 es un no contundente. Un valor cerca de 0.5 significa que el modelo da a sí y a no una probabilidad parecida.
La tabla de abajo muestra respuestas registradas de jev-1.13.0 a la pregunta is_human_escalation para distintos mensajes de clientes:
| Estado | noul |
|---|---|
| Gracias, ¡eso lo arregló! | 0.02 |
| ¿Cómo restablezco mi contraseña? | 0.07 |
| Necesito que esto se resuelva hoy, cueste lo que cueste. | 0.26 |
| ¿Eres un bot? | 0.40 |
| ¿Hay alguna forma de hablar con alguien sobre mi factura? | 0.84 |
| Ya he preguntado tres veces. ¿Puedo hablar por favor con una persona real? | 0.99 |
Las dos primeras y las dos últimas están claras. «Necesito que esto se resuelva hoy» es urgente pero nunca pide una persona, y obtiene 0.26. «¿Eres un bot?» insinúa que se quiere a un humano sin pedirlo, y el modelo se reparte casi a partes iguales con 0.40. Ambos son el tipo de mensaje en el que hay que tomar una decisión basada en un umbral de tu código.
No hay un valor de confidence aparte para un Noul, a diferencia de un Choice o un Score. La distribución de probabilidad de un Noul solo tiene dos resultados, sí y no, así que el único valor noul la describe por completo. Un Choice o un Score reparte la probabilidad entre varias opciones o niveles, y confidence resume ese reparto.
Lo más habitual es que tu código convierta noul en un booleano mediante un umbral:
wants_human = response.answers["is_human_escalation"].noul > 0.9
if wants_human:
route_to_agent(ticket)
else:
route_to_bot(ticket)
Dónde fijar el umbral depende del coste de equivocarse. Usa 0.5 cuando actuar sobre un sí o sobre un no sea igual de fácil. Súbelo cuando actuar sobre un falso sí sea caro, como avisar a alguien por teléfono o emitir un reembolso. Bájalo cuando dejar pasar un sí verdadero sea caro, como no señalar un problema de seguridad. Los valores intermedios pueden ir a una persona en lugar de a cualquiera de las dos rutas de código. Es la misma división en tres caminos que describe la página de Confianza para las respuestas de Choice y Score.
Un valor de Noul va de 0 a 1, pero no es una escala de la cosa por la que preguntaste. Es la probabilidad de que la respuesta sea sí. Si la pregunta trata en realidad de un grado, el valor no mide el grado. Abajo, «¿Es fuerte el candidato en Python?» se pregunta sobre cuatro candidatos, junto a un Score con cuatro niveles: sin experiencia, cierta familiaridad, uso habitual en un empleo, experiencia profunda.
| Candidato | Noul: «¿Es fuerte el candidato en Python?» | Score: «¿Cuánta experiencia tiene el candidato con Python?» |
|---|---|---|
| Mi experiencia es en Java y Go. No he usado Python. | 0.03 | 0.0 (Sin experiencia) |
| He usado Python ocasionalmente para pequeños scripts junto a mi trabajo principal en Java. | 0.14 | 1.0 (Cierta familiaridad) |
| Usé Python a diario durante dos años en mi anterior empleo, sobre todo canalizaciones de datos. | 0.81 | 2.05 (Uso habitual en un empleo) |
| He escrito Python a diario durante ocho años, incluido el mantenimiento de una gran base de código Django. | 0.92 | 2.89 (Experiencia profunda) |
El Noul juzga una sola proposición, «fuerte», y los valores son cuán probable es. Podrías crear niveles en el rango de 0 a 1 en tu código, como 0.3 a 0.7 para «cierta experiencia», pero el modelo no los verá, así que nada en la respuesta se juzgó contra ellos. Un valor intermedio puede significar experiencia media o un caso poco claro, y el espaciado entre candidatos no es algo que hayas elegido tú. El Score juzga cada descripción de nivel por sí sola, así que cada candidato cayó en un nivel que escribiste o cerca de él, y las probabilidades devueltas muestran cómo dividió el modelo su juicio entre los niveles. Si no estás de acuerdo, reformula un nivel y vuelve a ejecutarlo. Elige un tipo de pregunta explica la diferencia.
Escribir una pregunta Noul
Haz una pregunta de sí/no por Noul. Si una pregunta tiene dos condiciones, como «¿Está el cliente furioso y pide un reembolso?», el modelo tiene que juzgar ambas a la vez y el valor significa menos. Haz dos Noul y combínalos en el código.
Formula la pregunta de modo que un valor alto signifique sí. «¿Contiene el mensaje datos personales?» es claro. «¿Está el mensaje libre de datos personales?» invierte el significado, y el código que lo lea después lo entenderá al revés.
Una afirmación funciona igual de bien que una pregunta. Para «El cliente está solicitando un reembolso», un valor cerca de 1 significa que la afirmación es verdadera. Prueba ambas formulaciones con tus propios datos para ver cuál funciona mejor.
Haz que el límite entre sí y no sea inequívoco. «¿Tiene este candidato alguna experiencia con Python?» funciona bien porque «alguna» no deja término medio. Cuando el límite sea sutil, añade criteria con descripciones true y false, como hace la pregunta is_repeat_contact de arriba. La instrucción basta para la mayoría de los Noul, así que prueba tus preguntas con y sin criteria y quédate con la que dé mejores respuestas en tus documentos.
Buena práctica: haz más de una pregunta por llamada
Para una lista de comprobación de condiciones, haz muchas preguntas Noul en una sola solicitud: una pregunta por condición, y el código decide qué significa la combinación. Las preguntas se evalúan en paralelo, así que añadir Noul apenas cambia el tiempo de respuesta. Haz varias preguntas a la vez lo explica con más detalle.
Manejar varias respuestas Noul en el código
La solicitud de dos preguntas de arriba le da al código suficiente para enrutar el mensaje. El ejemplo de abajo escala a una persona cuando el cliente la pide, y sube la prioridad cuando ya se ha puesto en contacto antes. Un valor intermedio en cualquiera de las dos preguntas va a un revisor en lugar de a una ruta de código:
from typesafe_sdk import Noul, NoulCriteria, TypeSafeClient
SUPPORT_QUESTIONS = {
"is_human_escalation": Noul(
instructions="Is the customer asking for a human agent?",
),
"is_repeat_contact": Noul(
instructions="Has the customer contacted support about this before?",
criteria=NoulCriteria(
true="Mentions a prior attempt, ticket, or that they have asked before",
false="No sign of any previous contact",
),
),
}
YES = 0.8
NO = 0.2
def route(message: str) -> None:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state=message,
questions=SUPPORT_QUESTIONS,
)
answers = response.answers
wants_human = answers["is_human_escalation"].noul
repeat = answers["is_repeat_contact"].noul
if NO < wants_human < YES or NO < repeat < YES:
# The model isn't sure either way. Let a person decide.
send_to_review(message)
return
priority = "high" if repeat > YES else "normal"
if wants_human > YES:
route_to_agent(message, priority=priority)
else:
route_to_bot(message, priority=priority)
Para el mensaje de arriba, el valor de la respuesta noul de is_human_escalation es 0.99 y el de is_repeat_contact es 0.93, así que el código lo enruta a un agente con prioridad alta. El mensaje «¿Cómo restablezco mi contraseña?» da 0.07 en ambas preguntas y se enruta al bot.
Los umbrales viven en tu código. Si los revisores ven demasiados mensajes, estrecha la diferencia entre NO y YES. Si se cuelan demasiadas rutas erróneas, amplíala. Si más adelante necesitas saber si el mensaje menciona un pago, o si contiene datos personales, añade otro Noul a SUPPORT_QUESTIONS. El número de solicitudes sigue siendo uno.
Instrucciones estructuradas
Las instrucciones pueden ser un objeto en lugar de una cadena, con la pregunta en un campo y datos complementarios en los demás. Usa estructura en las preguntas cubre cuándo ayuda eso. Aquí se usa para una pregunta construida con código: un currículum que acaba de llegar se compara con registros de una base de datos de candidatos que podrían ser la misma persona. Cada registro va a un campo potential_duplicate tal cual, la question es la misma para todos los registros, y todos los registros se comprueban en una sola solicitud. Las claves de pregunta generadas por código contienen el ID de base de datos de cada registro:
{
"state": {
"resume": {
"name": "John Smith",
"location": "Oakland, CA",
"summary": "Backend engineer with eight years of Python and Go experience.",
"experience": [
{
"employer": "Google",
"title": "Senior Backend Engineer",
"years": "2021-2025"
},
{
"employer": "Microsoft",
"title": "Software Engineer",
"years": "2017-2021"
}
]
}
},
"questions": {
"same_as_record_18": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "Jon Smith",
"location": "Oakland, CA",
"last_employer": "Google"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
},
"same_as_record_42": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "John Smith",
"location": "Austin, TX",
"last_employer": "Lone Star Freight"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
},
"same_as_record_77": {
"type": "noul",
"instructions": {
"potential_duplicate": {
"name": "John Smithers",
"location": "Oakland, CA",
"last_employer": "Bay Health Clinic"
},
"question": "Is the resume for the same person as `potential_duplicate`?"
}
}
}
}La respuesta:
{
"model": "jev-1.13.0",
"answers": {
"same_as_record_18": {
"type": "noul",
"noul": 0.74
},
"same_as_record_42": {
"type": "noul",
"noul": 0.09
},
"same_as_record_77": {
"type": "noul",
"noul": 0.08
}
},
"usage": {
"input_tokens": 535,
"output_tokens": 58
}
}
Cada respuesta es la probabilidad de que el currículum corresponda a la persona de ese registro. El registro 18 escribe el nombre de otra forma, pero coincide en ubicación y empleador, y obtiene 0.74. El registro 42 tiene el mismo nombre en otra ciudad con otro empleador, y obtiene 0.09. El registro 77 es un nombre parecido en la misma ubicación con otro empleador, y obtiene 0.08. Aplica un umbral a cada valor en tu código, como en Manejar varias respuestas Noul en el código, y envía los valores intermedios a una persona.
Con el SDK de Python, las preguntas se construyen a partir de los registros de candidatos. El texto de la pregunta es fijo y el registro cambia:
from typesafe_sdk import Noul, TypeSafeClient
SAME_PERSON = "Is the resume for the same person as `potential_duplicate`?"
def duplicate_questions(candidates: list[dict]) -> dict[str, Noul]:
"""One Noul per candidate record, all asking the same question."""
return {
f"same_as_record_{candidate['id']}": Noul(
instructions={
"potential_duplicate": {
"name": candidate["name"],
"location": candidate["location"],
"last_employer": candidate["last_employer"],
},
"question": SAME_PERSON,
},
)
for candidate in candidates
}
def find_duplicates(resume: dict, candidates: list[dict]) -> list[str]:
with TypeSafeClient() as client:
response = client.system_one(
model="jev-latest",
state={"resume": resume},
questions=duplicate_questions(candidates),
)
return [
question_id
for question_id, answer in response.answers.items()
if answer.noul > 0.7
]
El cookbook de cascada de extracción de datos estructurados usa instrucciones estructuradas para verificar un registro extraído. Cada campo recibe el mismo conjunto de preguntas. El objeto instructions de cada pregunta tiene el texto de la pregunta en la propiedad main_question. También hay propiedades field_spec y extracted_field que cambian para cada campo.
Noul en los cookbooks
Echa un vistazo a nuestros cookbooks para ver aplicaciones que usan preguntas Noul:
- Preguntas en paralelo ejecuta una lista de comprobación normativa de 13 preguntas sobre un artículo en una sola solicitud.
- Autoconsistencia: nouls puntúa una reclamación de seguro contra una rúbrica de 15 preguntas y mide cuán estables son los valores entre ejecuciones.
- Reordenación usa la propia probabilidad, no un umbral: un Noul por cada par consulta-candidato, y luego ordena los candidatos por el valor.
- Búsqueda línea por línea combina un Choice que encuentra la línea coincidente con un Noul que comprueba si el documento contiene alguna respuesta.
- Recuperación de estructura hace un Noul por cada par de líneas, preguntando si un salto de línea dividió una frase, para reconstruir párrafos a partir de texto plano.