Documentação

Perguntas e respostas

Uma pergunta do Laya é uma decisão tipada, e o tipo decide tanto o que você pergunta quanto o que recebe de volta. Existem três, e um mesmo estado pode carregar todas elas em uma única passada direta:

import laya

agent = laya.load("convaiinnovations/laya")

questions = {
    "dept": {"type": "choice", "instructions": "Which team should handle this?",
             "criteria": {"billing": "money and invoices",
                          "technical": "bugs and outages",
                          "sales": "pricing and contracts"}},
    "urgent": {"type": "noul", "instructions": "Is this urgent?"},
    "severity": {"type": "score", "instructions": "How severe is this?",
                 "criteria": ["trivial", "minor", "moderate", "serious", "critical"]},
}

result = agent.system_one({"text": "I was charged twice and nobody has replied for a week. "
                                   "Please refund me."}, questions)
result["answers"]["dept"]["choice"]        # 'billing'
result["answers"]["urgent"]["noul"]        # 0.8727
result["answers"]["severity"]["score"]     # 2.9046

Uma única passada direta responde às três. Esse é o sentido da interface tipada: uma pergunta noul não é um choice com duas opções que por acaso são “sim” e “não” — é uma cabeça diferente com um formato de saída diferente, e o tipo diz ao Laya qual usar.

Os três tipos

choice — escolher um de um conjunto

{"type": "choice",
 "instructions": "Which team should handle this?",
 "criteria": {"billing": "money and invoices", "technical": "bugs and outages"}}

criteria é um mapeamento ordenado de rótulo para descrição. A ordem é posicional na pergunta renderizada, então duas perguntas com os mesmos rótulos em ordem diferente são perguntas diferentes. A descrição é opcional; {"billing": None} renderiza apenas o rótulo. Os rótulos são devolvidos exatamente como você os escreveu, então um rótulo não textual volta como ele mesmo em choice e como a chave em probabilities.

Vale a pena escrever a descrição. Não é decoração: o texto renderizado da pergunta é o que o modelo lê, então um {"a": None, "b": None} seco não dá a ele nada para distinguir as opções.

{"type": "choice", "choice": "billing",
 "probabilities": {"billing": 0.9881, "technical": 0.0057, "sales": 0.0062},
 "confidence": 0.9339, "answer_confidence": 0.9881,
 "action": {"act_probability": 1.0}}

noul — sim ou não

{"type": "noul", "instructions": "Is this urgent?"}

noul é o nome do projeto para uma decisão de dois caminhos, e sua resposta é a probabilidade de verdadeiro, não um booleano com limiar:

{"type": "noul", "noul": 0.8727, "confidence": 0.8727, "answer_confidence": 0.8727,
 "action": {"act_probability": 1.0}}

O limiar é você quem escolhe, porque depende do que um falso positivo custa a você. Não há nenhum campo bool para confundir com uma decisão.

Você pode renomear as duas opções quando a decisão não é naturalmente um sim/não — labels aceita exatamente as chaves false e true, e a polaridade não muda: noul continua sendo P(true).

{"type": "noul", "instructions": "Does this need a human?",
 "labels": {"false": "automatic", "true": "escalate"}}

Os rótulos não são uma forma de consertar uma pergunta que o modelo erra. O noul segue seus próprios rótulos de opção, com mais força no checkpoint em inglês, então um par de rótulos que lê como uma decisão (“approve” / “reject”) pode puxar a resposta para o rótulo em vez do estado. Valide qualquer renomeação nos seus próprios dados antes de confiar nela.

score — um nível ordenado

{"type": "score", "instructions": "How severe is this?",
 "criteria": ["trivial", "minor", "moderate", "serious", "critical"]}

criteria é uma lista ordenada, e ela deve estar em ordem crescente — a posição é a escala.

{"type": "score", "score": 2.9046,
 "legend": {"0": "trivial", "1": "minor", "2": "moderate", "3": "serious", "4": "critical"},
 "probabilities": {"0": 0.0134, "1": 0.05, "2": 0.0518, "3": 0.7881, "4": 0.0967},
 "confidence": 0.5187, "answer_confidence": 0.7881,
 "action": {"act_probability": 1.0}}

score é um valor esperado, não o nível mais provável. Acima, score é 2.90 enquanto o único nível mais provável é serious (3), com 0.788. Ambos são úteis e respondem a perguntas diferentes: o valor esperado minimiza o erro quadrático sobre a escala, e o argmax minimiza a discordância com o modelo. Se você quer o rótulo, tome o argmax de probabilities ou leia a contraparte de answer_confidence na legenda — não arredonde score e suponha que seja o rótulo. legend existe para você nunca precisar adivinhar qual índice significa o quê.

Ler a confiança

Toda resposta carrega dois números de confiança, e eles medem coisas diferentes.

campo o que é aplicar gating sobre ele?
answer_confidence max(p) — a probabilidade da resposta que está sendo reportada sim, após o ajuste
confidence 1 - H(p) / log(k) — quão concentrada está a distribuição inteira não
probabilities a distribuição completa (choice, score) —

answer_confidence é a quantidade que o escalonamento por temperatura ajusta e sobre a qual cada número de calibração do repositório é calculado, o que é o que faz dele o número sobre o qual aplicar gating. Ele não está calibrado como distribuído: a propriedade que costuma ser atribuída a ele — a de que das respostas retornadas com confiança c, cerca de c estão certas — só vale depois que as temperaturas foram ajustadas e validadas em dados reservados para o seu checkpoint com o seu número de opções. Os checkpoints distribuídos são excessivamente confiantes e o quanto depende do número de opções, então um limiar sem ajuste pode selecionar abaixo da própria precisão do modelo (#394).

# THRESHOLD is a number you measured on your own held-out data, not one the model ships.
# Fit and validate the temperatures first — the fine-tuning notebook has the loop:
#   notebooks/laya_finetune_typed_decisions_2xT4_kaggle.ipynb
ans = result["answers"]["dept"]
if ans["answer_confidence"] >= THRESHOLD:
    ...

confidence é a entropia normalizada: alta quando a distribuição está concentrada, baixa quando está espalhada, independentemente de a resposta principal estar correta. É um sinal útil e não está na mesma escala, então os dois não devem ser comparados contra um único número:

# the same three answers, and the two numbers are not the same
dept      confidence 0.9339   answer_confidence 0.9881
urgent    confidence 0.8727   answer_confidence 0.8727
severity  confidence 0.5187   answer_confidence 0.7881

Para noul eles são iguais por construção — sobre duas opções, max(p, 1-p) é max(p) — então uma resposta noul não pode dizer qual dos dois você esteve lendo. As duas chaves estão presentes em todos os tipos para que a escolha seja explícita em vez de implícita.

Um limiar é uma política, não uma propriedade do modelo. Os dois checkpoints são distribuídos excessivamente confiantes, e o quanto depende do número de opções, então um número medido em uma pergunta de 3 opções não se transfere para uma de 20. Meça-o nos seus próprios dados; a seção de Calibração do BENCHMARKS.md tem o ciclo de ajuste e os valores ajustados.

action e act_probability

action.act_probability é a pontuação de uma cabeça separada para “um agente deveria agir sobre isto afinal?”, distinta da confiança da própria resposta. Ela é reportada para todos os tipos de pergunta. Nada na biblioteca aplica um limiar sobre ela por você.

Presets

Três conjuntos de perguntas prontos, para os casos comuns não precisarem de critérios escritos à mão:

from laya import triage_questions, guard_questions, moderation_questions

agent.system_one(ticket, triage_questions())

Use-os como ponto de partida, não como contrato — leia as perguntas que eles produzem com render_options e verifique se os rótulos cabem no seu domínio antes de colocá-los em produção.

Ler as opções de volta

Como a ordem das opções é posicional e o texto da opção é o que o modelo lê, vale a pena conseguir ver exatamente o que foi enviado:

from laya import render_options

render_options({"t": "choice", "crit": {"billing": None, "sales": "pricing"}})
# ['billing', 'sales: pricing']

render_options({"t": "score", "crit": ["low", "high"]})
# ['level 0: low', 'level 1: high']

Os mesmos rótulos em ordem diferente renderizam nessa ordem, que é por que a ordem faz parte da identidade da pergunta:

render_options({"t": "choice", "crit": {"x": "first", "y": "second"}})
# ['x: first', 'y: second']
render_options({"t": "choice", "crit": {"y": "second", "x": "first"}})
# ['y: second', 'x: first']

Repare nas chaves. render_options aceita a forma interna de chaves curtas {"t": ..., "crit": ...}, não a forma {"type": ..., "criteria": ...} que você escreve em uma pergunta — passar a forma pública lança KeyError: 't'.

A conversão é um mapeamento curto e estável que você pode embutir, o que evita recorrer a um helper privado — Agent._to_internal é interno e pode mudar:

def as_internal(q):
    """The short-key shape `render_options` reads, from a question as you wrote it."""
    crit = q.get("criteria")
    if q["type"] == "choice" and isinstance(crit, list):
        crit = {c: None for c in crit}
    return {"t": q["type"], "ins": q["instructions"], "crit": crit}

render_options(as_internal(question))

Isso espelha o que a biblioteca faz para uma pergunta choice escrita como lista de rótulos; um dicionário criteria e uma lista score passam sem mudança.

Limites que vale conhecer antes de projetar em cima disto

  • Confiança não é garantia de correção em contagens altas de opções. Em uma pergunta de 20 opções as distribuições de respostas certas e erradas se sobrepõem muito, e um limiar pode acabar selecionando abaixo da própria precisão do modelo. Veja #394.
  • A negação não é tratada de forma confiável em perguntas de escolha forçada. Uma pergunta de cancelamento pode retornar o rótulo de cancelamento para um estado que diz não cancelar, com alta confiança, nos dois checkpoints. Veja #377.
  • noul pode seguir seus rótulos em vez do estado, então valide qualquer renomeação.
  • Mais opções não é de graça. Depois de cerca de 20, o modelo degrada rápido; use o helper de pré-seleção para reduzir um espaço grande de rótulos antes de perguntar, ou divida-o em uma pergunta grossa e uma fina.