Documentación

Choice

Un Choice es un tipo de pregunta de System One para seleccionar una opción de un conjunto definido. La respuesta incluye la opción seleccionada, una probabilidad para cada opción y la confianza.

Usa un Choice cuando la respuesta sea una de un conjunto fijo de opciones. Por ejemplo, qué equipo gestiona un ticket, a qué categoría pertenece un producto o en qué lenguaje está escrito un fragmento de código. Si la respuesta es una posición en un espectro, usa un Score. Si es un sí o un no, usa un Noul. Elige un tipo de pregunta compara los tres.

Una respuesta de Choice es la opción seleccionada en choice. El modelo también devuelve una probabilidad para cada opción en probabilities y un valor de confidence para la opción seleccionada.

Preguntas de ejemplo:

"What programming language is this code written in"
  → options: python, javascript, typescript, go, rust, other

"What type of meeting is this based on the title and description"
  → options: standup, planning, retrospective, one on one, brainstorm, none of the above

"Which product category does this item belong to"
  → options: electronics, clothing, home garden, food and beverage

Estructura de la solicitud

El cuerpo de la solicitud POST a la API de TypeSafe tiene una estructura concreta. El nivel superior tiene tres campos: state, el contenido que se evalúa; model; y questions, un mapa de los id de pregunta que tú eliges a objetos de pregunta. Cada pregunta Choice tiene los siguientes campos:

  • type: Siempre "choice".
  • instructions: La pregunta que responde el modelo.
  • criteria: Las opciones de respuesta, como un mapa. Cada clave es un nombre de opción y cada valor es una descripción de esa opción.

Abajo hay una solicitud en la que el estado es un ticket de soporte de una tienda de zapatos en línea y la pregunta es qué equipo debería gestionarlo:

request
{
  "state": "My running shoes arrived in the wrong size. Can I swap them for a size 10?",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    }
  }
}

Tú eliges el id de la pregunta, department en este caso. La respuesta se devuelve bajo el mismo id. El modelo nunca ve el id de la pregunta. Los nombres de las opciones y sus descripciones se envían ambos al modelo, así que escribe descripciones que separen unas opciones de otras.

Nuestros SDK de cliente ofrecen preguntas con tipo. En Python, la misma pregunta es un Choice:

from typesafe_sdk import Choice, TypeSafeClient

with TypeSafeClient() as client:
    response = client.system_one(
        state="My running shoes arrived in the wrong size. Can I swap them for a size 10?",
        questions={
            "department": Choice(
                instructions="Which team should handle this?",
                criteria={
                    "returns": "Exchanges, wrong or damaged items",
                    "shipping": "Delivery status, delays, lost packages",
                    "billing": "Charges, invoices, payment problems",
                },
            ),
        },
    )

    print(response.answers["department"].choice)

Usa el método system_one o el endpoint https://api.typesafe.ai/v1/systemone para llamar a un modelo System One. El campo model selecciona qué modelo gestiona la solicitud. Cómo construir con TypeSafe explica en qué parte de tu código llamarlo.

Usa uno de nuestros SDK de cliente o llama directamente a la API HTTP. Si un agente de programación está escribiendo la integración por ti, 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. Esta es la respuesta a la solicitud de ejemplo de arriba:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 1.0,
      "probabilities": {
        "shipping": 0.0,
        "returns": 1.0,
        "billing": 0.0
      }
    }
  },
  "usage": {
    "input_tokens": 328,
    "output_tokens": 34
  }
}

Además de type, cada respuesta de Choice tiene tres valores:

  • choice: La opción con la mayor probabilidad.
  • probabilities: La distribución de probabilidad completa entre todas las opciones. La suma de todos los valores es 1.
  • confidence: Un número de 0 a 1 calculado a partir de cómo se reparte probabilities. Una forma plana, con la probabilidad repartida entre varias opciones, significa confianza baja. Un único pico en una opción significa confianza alta.

Este ticket es fácil, así que toda la probabilidad está en returns y la confianza es 1.0. Un ticket que mencione una talla equivocada y un reembolso que falta repartiría la probabilidad entre returns y billing, y la confianza bajaría.

Buena práctica: haz más de una pregunta por llamada

Haz todas las preguntas Choice que tu código pueda necesitar en una sola solicitud, en lugar de una solicitud por pregunta. Las preguntas se evalúan en paralelo. Añadir preguntas apenas cambia el tiempo de respuesta, y el código puede ignorar las respuestas que no necesite. Las preguntas extra siguen costando tokens. Haz varias preguntas a la vez lo explica en detalle; la siguiente sección muestra cinco preguntas Choice en una sola llamada.

La misma lógica se aplica a las opciones dentro de una sola pregunta Choice. Una pregunta Choice acepta hasta 255 opciones, y añadir opciones cuesta unos pocos tokens cada una, así que dale al modelo la lista completa de equipos, categorías o productos en lugar de una lista reducida. Añade una opción other o none of the above cuando la lista pueda no cubrir todas las entradas, para que el modelo pueda decir que ninguna de las demás encaja.

Para clasificar documentos a través de una jerarquía profunda o una taxonomía grande, encadena preguntas Choice nivel por nivel. El cookbook de clasificación jerárquica muestra cómo ejecutar una búsqueda por haces sobre las probabilidades de Choice, conservando las mejores K rutas candidatas en cada nivel en lugar de comprometerse con una única ruta voraz.

Un ejemplo más complejo

El ejemplo básico de arriba enruta un ticket a un equipo. Un sistema de soporte más grande podría necesitar también el motivo de la devolución, el problema de entrega, qué quiere el cliente y el tono del cliente.

La solicitud de abajo hace cinco preguntas Choice sobre un ticket más ambiguo que el primero: involucra a tres equipos y no dice qué quiere el cliente.

request
{
  "state": "Shoes arrived two weeks late and in the wrong size. Also I see two charges of $120 on my card. What are you going to do about this?",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "Which team should handle this?",
      "criteria": {
        "returns": "Exchanges, wrong or damaged items",
        "shipping": "Delivery status, delays, lost packages",
        "billing": "Charges, invoices, payment problems"
      }
    },
    "return_reason": {
      "type": "choice",
      "instructions": "If the customer wants to return something, why?",
      "criteria": {
        "wrong_size": "The item doesn't fit",
        "wrong_item": "A different product was delivered",
        "damaged": "The item arrived broken or faulty",
        "changed_mind": "The item is fine, the customer no longer wants it",
        "other": "A return reason that fits none of the above"
      }
    },
    "shipping_issue": {
      "type": "choice",
      "instructions": "If this is a shipping problem, which kind is it?",
      "criteria": {
        "not_delivered": "The package never arrived",
        "delayed": "The package is late but still on its way",
        "wrong_address": "The package went to the wrong place",
        "damaged_in_transit": "The package arrived damaged",
        "other": "A shipping problem that fits none of the above"
      }
    },
    "requested_resolution": {
      "type": "choice",
      "instructions": "What does the customer want to happen?",
      "criteria": {
        "exchange": "Swap the item for a different one",
        "refund": "Money back",
        "replacement": "The same item sent again",
        "information": "Just an answer, no action needed"
      }
    },
    "tone": {
      "type": "choice",
      "instructions": "What is the customer's tone?",
      "criteria": {
        "calm": null,
        "frustrated": null,
        "angry": null
      }
    }
  }
}

Dos de estas preguntas Choice son especulativas: return_reason solo importa si el department es returns, y shipping_issue solo importa si es shipping. La pregunta tone usa descripciones null porque los nombres de las opciones ya son claros por sí solos.

La respuesta de TypeSafe:

{
  "model": "jev-1.13.0",
  "answers": {
    "department": {
      "type": "choice",
      "choice": "returns",
      "confidence": 0.42,
      "probabilities": {
        "shipping": 0.04,
        "billing": 0.35,
        "returns": 0.61
      }
    },
    "return_reason": {
      "type": "choice",
      "choice": "wrong_size",
      "confidence": 1.0,
      "probabilities": {
        "other": 0.0,
        "wrong_size": 1.0,
        "changed_mind": 0.0,
        "damaged": 0.0,
        "wrong_item": 0.0
      }
    },
    "shipping_issue": {
      "type": "choice",
      "choice": "delayed",
      "confidence": 0.67,
      "probabilities": {
        "wrong_address": 0.0,
        "other": 0.26,
        "not_delivered": 0.0,
        "damaged_in_transit": 0.0,
        "delayed": 0.74
      }
    },
    "requested_resolution": {
      "type": "choice",
      "choice": "refund",
      "confidence": 0.2,
      "probabilities": {
        "replacement": 0.34,
        "refund": 0.4,
        "information": 0.02,
        "exchange": 0.24
      }
    },
    "tone": {
      "type": "choice",
      "choice": "frustrated",
      "confidence": 0.76,
      "probabilities": {
        "frustrated": 0.84,
        "angry": 0.16,
        "calm": 0.0
      }
    }
  },
  "usage": {
    "input_tokens": 589,
    "output_tokens": 212
  }
}

Cada pregunta se responde por sí sola contra el ticket:

  • La respuesta de department es returns con una probabilidad de 0.61, pero billing tiene 0.35 por el cargo duplicado. El ticket pertenece a dos equipos, y la confianza repartida de 0.42 lo refleja.
  • El return_reason es wrong_size con una confianza de 1.0, algo esperable porque el ticket lo dice con claridad.
  • La respuesta de shipping_issue se reparte entre delayed y other. Es una pregunta especulativa y el department no salió como shipping, así que el código puede ignorarla, como se muestra en el fragmento de código de ejemplo de abajo.
  • La respuesta de requested_resolution se inclina por refund con 0.40, con replacement y exchange repartiéndose casi todo el resto, y la confianza es 0.20. El cargo duplicado sugiere devolución del dinero, la talla equivocada sugiere un cambio, y el cliente nunca dice cuál quiere.
  • La respuesta de tone es frustrated con una probabilidad de 0.84 y una confianza de 0.76.

El código de ejemplo de abajo lee las respuestas que necesita, ignora el resto y trata una respuesta de baja confianza como un motivo para preguntar en lugar de actuar:

from typesafe_sdk import Choice, TypeSafeClient

TRIAGE_QUESTIONS = {
    "department": Choice(
        instructions="Which team should handle this?",
        criteria={
            "returns": "Exchanges, wrong or damaged items",
            "shipping": "Delivery status, delays, lost packages",
            "billing": "Charges, invoices, payment problems",
        },
    ),
    "return_reason": Choice(
        instructions="If the customer wants to return something, why?",
        criteria={
            "wrong_size": "The item doesn't fit",
            "wrong_item": "A different product was delivered",
            "damaged": "The item arrived broken or faulty",
            "changed_mind": "The item is fine, the customer no longer wants it",
            "other": "A return reason that fits none of the above",
        },
    ),
    "shipping_issue": Choice(
        instructions="If this is a shipping problem, which kind is it?",
        criteria={
            "not_delivered": "The package never arrived",
            "delayed": "The package is late but still on its way",
            "wrong_address": "The package went to the wrong place",
            "damaged_in_transit": "The package arrived damaged",
            "other": "A shipping problem that fits none of the above",
        },
    ),
    "requested_resolution": Choice(
        instructions="What does the customer want to happen?",
        criteria={
            "exchange": "Swap the item for a different one",
            "refund": "Money back",
            "replacement": "The same item sent again",
            "information": "Just an answer, no action needed",
        },
    ),
    "tone": Choice(
        instructions="What is the customer's tone?",
        criteria={"calm": None, "frustrated": None, "angry": None},
    ),
}

def triage(ticket: str) -> None:
    with TypeSafeClient() as client:
        response = client.system_one(
            state=ticket,
            questions=TRIAGE_QUESTIONS,
        )
    answers = response.answers

    department = answers["department"]
    if department.confidence < 0.3:
        # Not clear which team to send to. Let a person decide.
        send_to_manual_triage(ticket)
        return

    if department.choice == "returns":
        # return_reason answer is only used here
        assign(ticket, team="returns", issue=answers["return_reason"].choice)
    elif department.choice == "shipping":
        # shipping_issue answer is only used here
        assign(ticket, team="shipping", issue=answers["shipping_issue"].choice)
    else:
        assign(ticket, team="billing")

    # A second team with a real share of the probability gets a copy
    for team, probability in department.probabilities.items():
        if team != department.choice and probability > 0.25:
            notify(ticket, team=team)

    resolution = answers["requested_resolution"]
    if resolution.confidence < 0.5:
        # The customer hasn't said what they want. Ask, don't guess.
        ask_customer_what_they_want(ticket)
    elif resolution.choice == "refund":
        flag_for_refund_approval(ticket)

    if answers["tone"].choice == "angry":
        flag_for_senior_agent(ticket)

Para el ticket de arriba, esto asigna el ticket al equipo de returns con el problema wrong_size, envía una copia al equipo de billing porque su cuota de 0.35 supera el umbral de 0.25, y pregunta al cliente qué quiere porque la confianza de la resolución, 0.20, está por debajo de 0.5. El código no usa la respuesta de shipping_issue.

Una solicitud, cinco respuestas, y la lógica de enrutamiento son if normales. Si más adelante necesitas saber el idioma del cliente, o de qué producto trata el ticket, añade otra pregunta Choice a TRIAGE_QUESTIONS; el número de solicitudes sigue siendo uno.

La demo del asistente doméstico evalúa cada petición del usuario contra una larga lista de preguntas Choice en una sola llamada: la categoría de la petición, la habitación, el dispositivo y la acción. La mayoría de esas preguntas son irrelevantes para cualquier petición concreta y el código las ignora.

Instrucciones y criteria estructurados

Empieza con una descripción de una línea por opción. Cuando dos opciones sean parecidas y el modelo siga confundiéndolas, describe cada una con un objeto en lugar de una cadena. Dale campos para qué cubre la opción, qué pertenece en cambio a una opción vecina y algunos ejemplos de entrada.

Las dos opciones de respuesta de abajo, return_policy y return_status, son fáciles de confundir. Un ticket sobre cualquiera de las dos puede mencionar devoluciones y reembolsos, así que cada opción dice para qué no es.

request
{
  "state": "I sent the shoes back a week ago. When do I get my money?",
  "questions": {
    "return_topic": {
      "type": "choice",
      "instructions": {
        "question": "Which returns topic is the customer asking about?",
        "focus": "Classify the information the customer wants."
      },
      "criteria": {
        "return_policy": {
          "what": "Whether and how an item can be returned",
          "not_for": "Progress of a return already sent",
          "examples": [
            "Can I return shoes I've worn once?",
            "How long do I have to return an order?"
          ]
        },
        "return_status": {
          "what": "Progress of a return already sent",
          "not_for": "Whether and how an item can be returned",
          "examples": [
            "Has my return arrived yet?",
            "When will my refund be paid?"
          ]
        }
      }
    }
  }
}

La respuesta es return_status con confianza 1.0:

{
  "model": "jev-1.13.0",
  "answers": {
    "return_topic": {
      "type": "choice",
      "choice": "return_status",
      "confidence": 1.0,
      "probabilities": {
        "return_policy": 0.0,
        "return_status": 1.0
      }
    }
  },
  "usage": {
    "input_tokens": 407,
    "output_tokens": 32
  }
}

Los nombres de campo question, focus, what, not_for y examples no forman parte de la API, y ninguno está reservado. Los eliges tú, igual que eliges los nombres de las opciones. El modelo ve los nombres junto con los valores, así que usa nombres cortos que etiqueten lo que viene después.