Noul
Uma pergunta Noul pede ao modelo TypeSafe que avalie uma pergunta de sim/não e devolva a probabilidade de a resposta ser sim.
Usa um Noul quando a resposta é sim ou não. Por exemplo, se esta mensagem pede um reembolso, se este currículo menciona sistemas distribuídos, se este comentário contém dados pessoais. Se a resposta é uma de várias opções, usa um Choice. Se for uma posição num espetro, usa um Score. Escolhe um tipo de pergunta compara os três.
Uma resposta de Noul é um único número que representa a probabilidade de a resposta ser sim, em que 0 significa não e 1 significa sim.
Estrutura do pedido
O corpo do pedido POST para a API da TypeSafe tem os mesmos três campos de topo que qualquer outro tipo de pergunta: state, que é o conteúdo a avaliar; model; e questions. Cada pergunta Noul tem os seguintes campos:
type: É sempre"noul".instructions: A pergunta de sim/não a que o modelo responde, ou uma afirmação para ele julgar.criteria: Opcional. Um objeto com descriçõestrueefalsedo que significam um sim e um não.
Abaixo está um pedido em que o estado é uma mensagem de suporte e as duas perguntas são se o cliente quer falar com uma pessoa e se já contactou o suporte antes:
{
"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"
}
}
}
}Escolhes tu os ids das perguntas, is_human_escalation e is_repeat_contact neste caso. Os ids não são enviados ao modelo. Cada resposta é devolvida com o mesmo id. A primeira pergunta assenta apenas no instructions. A segunda acrescenta criteria para dizer o que conta como sim e o que conta como não.
Com o SDK de Python, as mesmas perguntas são 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)
O método system_one e o endpoint https://api.typesafe.ai/v1/systemone têm ambos o nome do System One, o modelo de IA da TypeSafe. Como construir com a TypeSafe explica onde o usar no teu código.
Se estiveres a usar um agente de programação, instala primeiro a habilidade de agente da TypeSafe para que ele conheça as formas do pedido e da resposta.
Estrutura da resposta
A resposta tem uma entrada em answers por pergunta, com os ids do pedido:
{
"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 as respostas aqui estão próximas de 1. O cliente diz “talk to a real person”, por isso is_human_escalation é 0.99. “I have asked three times now” corresponde à descrição true de is_repeat_contact, por isso é 0.93.
Ler um Noul
O número é, em si, a resposta e a certeza. Um valor próximo de 1 é um sim forte. Um valor próximo de 0 é um não forte. Um valor próximo de 0.5 significa que o modelo dá ao sim e ao não uma probabilidade semelhante.
A tabela abaixo mostra respostas registadas de jev-1.13.0 à pergunta is_human_escalation para diferentes mensagens de clientes:
| Estado | noul |
|---|---|
| Obrigado, isso resolveu! | 0.02 |
| Como faço para repor a minha palavra-passe? | 0.07 |
| Preciso que isto fique resolvido hoje, custe o que custar. | 0.26 |
| És um bot? | 0.40 |
| Há alguma forma de falar com alguém sobre a minha fatura? | 0.84 |
| Já perguntei três vezes. Posso falar por favor com uma pessoa real? | 0.99 |
As duas primeiras e as duas últimas são claras. “Preciso que isto fique resolvido hoje” é urgente mas nunca pede uma pessoa, e obtém 0.26. “És um bot?” sugere que se quer um humano sem o pedir, e o modelo divide quase igualmente com 0.40. Ambas são o tipo de mensagem em que é preciso tomar uma decisão com base num limiar no teu código.
Não há um valor de confidence separado para um Noul, ao contrário de um Choice ou de um Score. A distribuição de probabilidade de um Noul tem apenas dois resultados, sim e não, por isso o único valor noul descreve-a por completo. Um Choice ou um Score reparte a probabilidade por várias opções ou níveis, e confidence resume essa repartição.
O mais habitual é o teu código converter noul num booleano através de um limiar:
wants_human = response.answers["is_human_escalation"].noul > 0.9
if wants_human:
route_to_agent(ticket)
else:
route_to_bot(ticket)
Onde fixar o limiar depende do custo de errar. Usa 0.5 quando agir sobre um sim ou sobre um não for igualmente fácil. Aumenta-o quando agir sobre um falso sim for caro, como chamar alguém por telefone ou emitir um reembolso. Baixa-o quando deixar passar um sim verdadeiro for caro, como não assinalar um problema de segurança. Os valores intermédios podem ir para uma pessoa em vez de para qualquer dos dois caminhos de código. É a mesma divisão em três caminhos que a página Confiança descreve para as respostas de Choice e Score.
Um valor de Noul vai de 0 a 1, mas não é uma escala da coisa sobre a qual perguntaste. É a probabilidade de a resposta ser sim. Se a pergunta for realmente sobre um grau, o valor não mede o grau. Abaixo, “O candidato é forte em Python?” é perguntado sobre quatro candidatos, ao lado de um Score com quatro níveis: sem experiência, alguma familiaridade, uso regular num emprego, experiência profunda.
| Candidato | Noul: “O candidato é forte em Python?” | Score: “Quanta experiência de Python tem o candidato?” |
|---|---|---|
| A minha experiência é em Java e Go. Nunca usei Python. | 0.03 | 0.0 (Sem experiência) |
| Usei Python ocasionalmente para pequenos scripts a par do meu trabalho principal em Java. | 0.14 | 1.0 (Alguma familiaridade) |
| Usei Python todos os dias durante dois anos no meu último emprego, sobretudo pipelines de dados. | 0.81 | 2.05 (Uso regular num emprego) |
| Escrevi Python diariamente durante oito anos, incluindo a manutenção de uma grande base de código Django. | 0.92 | 2.89 (Experiência profunda) |
O Noul julga uma única proposição, “forte”, e os valores são a probabilidade de ela ser verdadeira. Podes criar níveis no intervalo de 0 a 1 no teu código, como de 0.3 a 0.7 para “alguma experiência”, mas o modelo não os verá, por isso nada na resposta foi julgado contra eles. Um valor intermédio pode significar experiência média ou um caso pouco claro, e o espaçamento entre candidatos não é algo que escolheste. O Score julga cada descrição de nível por si só, por isso cada candidato caiu num nível que escreveste ou perto dele, e as probabilidades devolvidas mostram como o modelo dividiu o seu julgamento entre os níveis. Se discordares, reformula um nível e volta a executá-lo. Escolhe um tipo de pergunta explica a diferença.
Escrever uma pergunta Noul
Faz uma pergunta de sim/não por Noul. Se uma pergunta tiver duas condições, como “O cliente está furioso e a pedir um reembolso?”, o modelo tem de julgar ambas ao mesmo tempo e o valor significa menos. Faz dois Nouls e combina-os em código.
Formula a pergunta de modo a que um valor alto signifique sim. “A mensagem contém dados pessoais?” é claro. “A mensagem está livre de dados pessoais?” inverte o significado, e o código que a ler mais tarde vai entendê-la ao contrário.
Uma afirmação funciona tão bem como uma pergunta. Para “O cliente está a pedir um reembolso”, um valor próximo de 1 significa que a afirmação é verdadeira. Experimenta ambas as formulações com os teus próprios dados para veres qual funciona melhor.
Torna inequívoca a fronteira entre sim e não. “Este candidato tem alguma experiência com Python?” funciona bem porque “alguma” não deixa meio-termo. Quando a fronteira é subtil, acrescenta criteria com descrições true e false, como faz a pergunta is_repeat_contact de cima. A instrução basta para a maioria dos Nouls, por isso experimenta as tuas perguntas com e sem criteria e fica com a que der melhores respostas nos teus documentos.
Boa prática: faz mais de uma pergunta por chamada
Para uma lista de verificação de condições, faz muitas perguntas Noul num único pedido: uma pergunta por condição, e o código decide o que a combinação significa. As perguntas são avaliadas em paralelo, por isso acrescentar Nouls quase não altera o tempo de resposta. Faz várias perguntas ao mesmo tempo explica isto com mais detalhe.
Tratar várias respostas Noul em código
O pedido de duas perguntas de cima dá ao código o suficiente para encaminhar a mensagem. O exemplo abaixo escala para uma pessoa quando o cliente a pede, e sobe a prioridade quando já contactou antes. Um valor intermédio em qualquer das perguntas vai para um revisor em vez de para um caminho 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 a mensagem de cima, o valor da resposta noul de is_human_escalation é 0.99 e o de is_repeat_contact é 0.93, por isso o código encaminha-a para um agente com prioridade alta. A mensagem “Como faço para repor a minha palavra-passe?” dá 0.07 em ambas as perguntas e é encaminhada para o bot.
Os limiares vivem no teu código. Se os revisores virem demasiadas mensagens, estreita a diferença entre NO e YES. Se passarem demasiados encaminhamentos errados, alarga-a. Se mais tarde precisares de saber se a mensagem menciona um pagamento, ou se contém dados pessoais, acrescenta outro Noul a SUPPORT_QUESTIONS. O número de pedidos continua a ser um.
Instruções estruturadas
As instruções podem ser um objeto em vez de uma string, com a pergunta num campo e dados complementares nos restantes. Usa estrutura nas perguntas explica quando isso ajuda. Aqui é usada para uma pergunta construída com código: um currículo que acabou de chegar é comparado com registos de uma base de dados de candidatos que podem ser a mesma pessoa. Cada registo entra num campo potential_duplicate tal como está, a question é a mesma para todos os registos, e todos os registos são verificados num único pedido. As chaves de pergunta geradas por código contêm o ID de base de dados de cada registo:
{
"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`?"
}
}
}
}A resposta:
{
"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 resposta é a probabilidade de o currículo corresponder à pessoa desse registo. O registo 18 escreve o nome de forma diferente, mas coincide na localização e no empregador, e obtém 0.74. O registo 42 tem o mesmo nome noutra cidade com outro empregador, e obtém 0.09. O registo 77 é um nome parecido na mesma localização com um empregador diferente, e obtém 0.08. Aplica um limiar a cada valor no teu código, como em Tratar várias respostas Noul em código, e envia os valores intermédios para uma pessoa.
Com o SDK de Python, as perguntas são construídas a partir dos registos candidatos. O texto da pergunta é fixo e o registo muda:
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
]
O cookbook de cascata de extração de dados estruturados usa instruções estruturadas para verificar um registo extraído. Cada campo recebe o mesmo conjunto de perguntas. O objeto instructions de cada pergunta tem o texto da pergunta na propriedade main_question. Há também as propriedades field_spec e extracted_field, que mudam em cada campo.
Noul nos cookbooks
Dá uma olhadela aos nossos cookbooks para veres aplicações que usam perguntas Noul:
- Perguntas em paralelo executa uma lista de verificação regulamentar de 13 perguntas sobre um artigo num único pedido.
- Autoconsistência: nouls pontua uma reclamação de seguro contra uma rubrica de 15 perguntas e mede quão estáveis são os valores entre execuções.
- Reordenação usa a própria probabilidade, não um limiar: um Noul por par consulta-candidato, e depois ordena os candidatos pelo valor.
- Pesquisa linha a linha emparelha um Choice que encontra a linha correspondente com um Noul que verifica se o documento contém alguma resposta.
- Recuperação de estrutura faz um Noul por par de linhas, se uma quebra de linha dividiu uma frase, para reconstruir parágrafos a partir de texto simples.