Documentación

Decisiones basadas en el esquema

Convierte un esquema JSON, o un modelo pydantic, en preguntas de Laya, y recupera valores tipados con confianza calibrada. Este es el puente que convierte a Laya en un motor de salida estructurada: describes la forma que quieres, y Laya la responde en una sola pasada hacia adelante.

import laya

schema = {
    "type": "object",
    "properties": {
        "department": {"type": "string", "enum": ["billing", "support", "sales"],
                       "description": "Which team should handle this?"},
        "urgency": {"type": "integer", "minimum": 0, "maximum": 2},
        "needs_human": {"type": "boolean"},
    },
}

agent = laya.load("convaiinnovations/laya")
values = agent.decide("I was charged twice, refund me.", schema=schema)
# {"department": "billing", "urgency": 2, "needs_human": True}

Con pydantic (instala laya[structured]):

from typing import Literal
from pydantic import BaseModel

class Ticket(BaseModel):
    department: Literal["billing", "support", "sales"]
    urgency: Literal[0, 1, 2]
    needs_human: bool

ticket = agent.decide("I was charged twice, refund me.", schema=Ticket)

El subconjunto admitido

El nivel superior debe ser un objeto con properties. Cada propiedad se convierte en una pregunta.

Cada fila de abajo es un esquema real: tests/test_structured_docs.py compila la primera columna y verifica la pregunta que el compilador produce realmente, así que esta tabla no puede desviarse del código. Una celda es o bien un esquema de propiedad por sí solo, o bien una llamada a un punto de entrada.

Esquema JSON Pregunta de Laya Valor devuelto
{"enum": ["billing", "support"]} choice el valor elegido, con su tipo original
{"const": "billing"} choice ese único valor
{"type": "boolean"} noul true / false
{"type": "integer", "minimum": 0, "maximum": 5} score el nivel de mayor probabilidad, como entero
{"type": "number", "minimum": 0, "maximum": 5} score el nivel, como entero
{"type": "string", "enum": ["low", "high"], "description": "How urgent?"} choice How urgent? es el enunciado de la pregunta
{"anyOf": [{"enum": ["x", "y"]}, {"type": "null"}]} choice igual que la fila de enum simple; si no hay respuesta, la clave se omite
{"oneOf": [{"type": "boolean"}, {"type": "null"}]} noul igual que la fila de boolean simple
{"type": ["integer", "null"], "minimum": 1, "maximum": 3} score igual que la fila de entero acotado

Literal[...] y Optional[...] son las grafías de pydantic para las filas de enum y anyOf: questions_from_pydantic las convierte a esas formas y se aplican las mismas filas.

title no se lee. pydantic pone uno en cada campo de model_json_schema() lo hayas pedido o no, y un nombre por propiedad no puede etiquetar las opciones por alternativa a partir de las que se construye una pregunta, así que la palanca del enunciado es description — consulta Cómo se asigna internamente más abajo.

La proyección es exacta: un enum: [1, 2, 3] devuelve 2, no "2"; un entero acotado devuelve un nivel entre minimum y maximum; un booleano es noul >= 0.5.

Rechazos

Un esquema que no se puede responder desde un conjunto fijo de opciones lanza laya.structured.SchemaError (un ValueError) indicando la ruta exacta. Cada fila también se ejecuta, con el campo llamado name:

esquema de propiedad mensaje
{"type": "string"} properties.name: a free string cannot be a fixed option set; use 'enum' or a boolean
{"type": "array", "items": {"type": "string"}} properties.name: arrays are not supported; ask one field per element
{"type": "object", "properties": {"inner": {"type": "boolean"}}} properties.name: nested objects are not supported; flatten the schema
{"$ref": "#/definitions/node"} properties.name: $ref/recursion is not supported; flatten the schema
{"enum": [1, "1"]} properties.name: enum values produce duplicate choice labels
{"enum": []} properties.name: 'enum' must not be empty
{"type": "number"} properties.name: a numeric field needs integer 'minimum' and 'maximum' to become a score
{"type": "integer", "minimum": 5, "maximum": 2} properties.name: 'maximum' 2 is below 'minimum' 5
{"type": "integer", "minimum": 0, "maximum": 10} properties.name: 11 levels exceeds MAX_SCORE_LEVELS=10; narrow the range or use an enum
{"anyOf": [{"type": "string"}, {"type": "integer"}]} properties.name: only 'Optional[...]' unions (one non-null branch) are supported, got 2
{"type": ["string", "integer"]} properties.name: 'type' has multiple non-null types; unions are not supported
{"format": "date"} properties.name: unsupported schema {'format': 'date'}
"boolean" properties.name: property must be an object, got str

Los propios puntos de entrada rechazan esto:

llamada mensaje
plan_from_json_schema("not a schema") expected a JSON schema object, got str
plan_from_json_schema({"type": "object"}) the top level must be an object with 'properties'
plan_from_json_schema({"type": "object", "properties": {}}) 'properties' must be a non-empty object
plan_from_json_schema({"type": "object", "properties": {"p%d" % i: {"type": "boolean"} for i in range(33)}}) 33 properties exceeds MAX_PROPERTIES=32
plan_from_json_schema({"type": "object", "properties": {"name": {"enum": ["v%d" % i for i in range(33)]}}}) properties.name: 33 options exceeds MAX_OPTIONS=32
decide(None, "I was charged twice.", schema=42) expected a JSON schema dict or a pydantic model, got int

Límites: MAX_PROPERTIES = 32, MAX_OPTIONS = 32, MAX_SCORE_LEVELS = 10.

La API

función propósito
laya.decide(runner, state, schema=..., *, questions=..., return_details=..., min_confidence=..., **predict_kwargs) la función libre, sirve para Agent y Router
agent.decide(state, schema=..., ...) / router.decide(state, schema=..., ...) métodos de conveniencia
laya.decide_batch(runner, states, schema=..., ...) / agent.decide_batch(...) / router.decide_batch(...) lo mismo sobre muchos estados, en una sola llamada por lotes
questions_from_json_schema(schema) de esquema a preguntas de Laya
questions_from_pydantic(model) de modelo pydantic a preguntas (requiere pydantic)
answers_to_json(answers, schema) proyecta las respuestas en bruto sobre los valores del esquema
answer_to_pydantic(model, answers) proyecta las respuestas en bruto en una instancia de pydantic
plan_from_json_schema(schema) el plan de campos validado (avanzado)

Pasa exactamente uno de schema o questions. Con questions, decide devuelve las respuestas en bruto en lugar de proyectarlas. Los argumentos de palabra clave extra se reenvían a predict, así que los hooks, model=, task= y el presupuesto de tokens funcionan:

router.decide(state, schema=Ticket, model="multilingual", hooks=[Metrics()])

Puntuar muchos estados

decide_batch es la forma de mayor rendimiento: el esquema se planifica una vez y sus preguntas se ejecutan sobre cada estado a través de predict_batch, de modo que los estados comparten pasadas hacia adelante en lugar de una por llamada. Los resultados vuelven en el orden de entrada, proyectados exactamente como los proyecta decide, y return_details=True da un DecisionResult por estado:

values = agent.decide_batch(ticket_texts, schema=Ticket)          # values[i] matches ticket_texts[i]
results = router.decide_batch(states, schema=Ticket, return_details=True, batch_size=64)

En un Router cada estado sigue enrutándose por su cuenta, así que una llamada puede abarcar varios checkpoints. Los argumentos de palabra clave llegan a predict_batch, así que batch_size=, model= y los hooks funcionan como con decide. Lo tienen Agent, ONNXAgent y Router; un runner sin predict_batch lanza TypeError en lugar de recaer silenciosamente en un bucle — llama a decide por estado en ese caso. El procesamiento por lotes puede desplazar argmax limítrofes igual que lo hace predict_batch; el README registra las aceleraciones medidas para ambas unidades.

Confianza y probabilidades

Por defecto, decide devuelve solo los valores. Pasa return_details=True para obtener un DecisionResult con la confianza por campo, las probabilidades, las respuestas en bruto y el uso y el enrutamiento de la llamada:

result = agent.decide(state, schema=Ticket, return_details=True)
result.values["department"]            # "billing"
result.answer_confidence["department"] # 0.94  max(p): the quantity min_confidence gates on
result.confidence["department"]        # 0.71  normalized entropy, which depends on label count
result.probabilities["department"]     # {"billing": 0.94, "support": 0.06, "sales": 0.0}
result.usage                           # {"input_tokens": ..., "output_tokens": 0,
                                       #  "state_tokens": ..., "state_tokens_dropped": ...,
                                       #  "truncated": ..., "truncated_questions": [...]}
result.routing                         # the Router decision, when a Router answered

usage es el bloque que construyó predict(), reenviado entero: input_tokens suma el estado a lo largo de una fila por pregunta y output_tokens es siempre 0 porque no se genera nada, mientras que las otras cuatro claves son el informe de truncamiento (#174): state_tokens es lo que necesita todo el estado serializado, state_tokens_dropped lo máximo que cedió el head de cualquier pregunta individual, y truncated / truncated_questions dicen cuál. Una séptima clave, options, está presente solo cuando el presupuesto del head dejó que las opciones de alguna pregunta compartieran un tramo de tokens (#538); docs/http-api.md documenta tanto este bloque como ese campo como claves de respuesta, y tests/test_structured_docs.py ata la lista de esta página al código que la construye.

confidence y answer_confidence son cantidades distintas, y los nombres siguen las definiciones. answer_confidence es max(p), la masa de probabilidad sobre la respuesta que se informa. Es lo que ajusta el escalado por temperatura, sobre lo que se calcula cada cifra de calibración de este repositorio, y contra lo que se compara min_confidence — que es la razón para aplicar gating sobre ella en lugar de sobre confidence. confidence es la entropía normalizada, que depende de cuántas opciones tenía la pregunta: tests/test_confidence.py fija que una distribución de dos opciones vuelve como 0.90 en un noul y 0.53 en un choice equivalente, así que no se compara contra un umbral. Un campo que no informó ningún answer_confidence utilizable se asigna a None, que no es lo mismo que un 0.0 informado.

Aplica el gating sobre la misma cantidad que usa la puerta:

if result.answer_confidence["department"] < 0.6:
    result.values["department"] = "human-review"

Que answer_confidence sea el número correcto sobre el que filtrar no es lo mismo que sea una probabilidad fiable. Leerlo como “aproximadamente c de las respuestas devueltas con c son correctas” solo se sostiene después de que las temperaturas se hayan ajustado y validado sobre datos reservados para ese checkpoint y esa forma de pregunta. Los checkpoints publicados son demasiado confiados tal como se distribuyen y laya-multilingual no trae ninguna temperatura ajustada — consulta las secciones Calibración y Límites honestos del README, y el notebook de ajuste fino para el bucle de ajuste. Ajusta antes de confiar en el nivel; infórmalo porque es la cantidad que usan tanto la puerta como el harness de evaluación.

Cómo se asigna internamente

  • Los valores Enum y Literal se convierten en preguntas choice con los valores como etiquetas de texto; la etiqueta se reasigna al valor original a la salida, así que los enteros siguen siendo enteros.
  • Un entero acotado se convierte en una pregunta score con un nivel por valor; el valor devuelto es minimum + argmax.
  • Un booleano se convierte en una pregunta noul; el valor es noul >= 0.5.
  • Una description se convierte en las instrucciones de la pregunta, así que una buena descripción es lo que hace precisa la decisión. Esto sigue la misma regla que la guía de hooks: sé explícito sobre lo que significa cada opción.
  • Una rama null se descarta antes de planificar el campo, así que Optional[X] formula exactamente la pregunta que formula X. La clave del campo simplemente está ausente de los valores cuando no hay respuesta para él, que es lo que hace seguro declarar un campo opcional sin cambiar lo que ve el modelo.

Véase también