Documentação

Choice

Um Choice é um tipo de pergunta de System One para selecionar uma opção de um conjunto definido. A resposta inclui a opção selecionada, uma probabilidade para cada opção e a confiança.

Usa um Choice quando a resposta é uma de um conjunto fixo de opções. Por exemplo, que equipa trata de um ticket, a que categoria pertence um produto ou em que linguagem está escrito um fragmento de código. Se a resposta é uma posição num espetro, usa um Score. Se for um sim ou um não, usa um Noul. Escolhe um tipo de pergunta compara os três.

Uma resposta de Choice é a opção selecionada em choice. O modelo também devolve uma probabilidade para cada opção em probabilities e um valor de confidence para a opção selecionada.

Perguntas de exemplo:

"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

Estrutura do pedido

O corpo do pedido POST para a API da TypeSafe tem uma estrutura específica. O nível de topo tem três campos: state, o conteúdo a avaliar; model; e questions, um mapa dos ids de pergunta que escolhes para objetos de pergunta. Cada pergunta Choice tem os seguintes campos:

  • type: É sempre "choice".
  • instructions: A pergunta a que o modelo responde.
  • criteria: As opções de resposta, como um mapa. Cada chave é um nome de opção e cada valor é uma descrição dessa opção.

Abaixo está um pedido em que o estado é um ticket de suporte de uma loja de sapatos online e a pergunta é que equipa deve tratá-lo:

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"
      }
    }
  }
}

Escolhes tu o id da pergunta, department neste caso. A resposta é devolvida com o mesmo id. O modelo nunca vê o id da pergunta. Os nomes das opções e as respetivas descrições são ambos enviados ao modelo, por isso escreve descrições que separem as opções umas das outras.

Os nossos SDK de cliente fornecem perguntas tipadas. Em Python, a mesma pergunta é um 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 o método system_one ou o endpoint https://api.typesafe.ai/v1/systemone para chamar um modelo System One. O campo model seleciona qual o modelo que trata do pedido. Como construir com a TypeSafe explica em que parte do teu código o chamar.

Usa um dos nossos SDK de cliente ou chama diretamente a API HTTP. Se um agente de programação estiver a escrever a integração por ti, instala primeiro a skill 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. Esta é a resposta ao pedido de exemplo de cima:

{
  "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
  }
}

Além do type, cada resposta de Choice tem três valores:

  • choice: A opção com a probabilidade mais alta.
  • probabilities: A distribuição de probabilidade completa por todas as opções. A soma de todos os valores é 1.
  • confidence: Um número de 0 a 1 calculado a partir de como probabilities está distribuído. Uma forma plana, com a probabilidade repartida por várias opções, significa confiança baixa. Um único pico numa opção significa confiança alta.

Este ticket é fácil, por isso toda a probabilidade está em returns e a confiança é 1.0. Um ticket que mencione um tamanho errado e um reembolso em falta repartiria a probabilidade entre returns e billing, e a confiança cairia.

Boa prática: faz mais de uma pergunta por chamada

Faz todas as perguntas Choice de que o teu código possa precisar num único pedido, em vez de um pedido por pergunta. As perguntas são avaliadas em paralelo. Acrescentar perguntas quase não altera o tempo de resposta, e o código pode ignorar as respostas de que não precisa. As perguntas extra continuam a custar tokens. Faz várias perguntas ao mesmo tempo explica isto em detalhe; a secção seguinte mostra cinco perguntas Choice numa só chamada.

A mesma lógica aplica-se às opções dentro de uma única pergunta Choice. Uma pergunta Choice aceita até 255 opções, e acrescentar opções custa alguns tokens cada, por isso dá ao modelo a lista completa de equipas, categorias ou produtos em vez de uma lista reduzida. Acrescenta uma opção other ou none of the above quando a lista possa não cobrir todas as entradas, para que o modelo possa dizer que nenhuma das outras serve.

Para classificar documentos através de uma hierarquia profunda ou de uma taxonomia grande, encadeia perguntas Choice nível a nível. O cookbook de classificação hierárquica mostra como executar uma pesquisa em feixe sobre as probabilidades de Choice, mantendo os melhores K caminhos candidatos em cada nível em vez de te comprometeres com um único caminho ganancioso.

Um exemplo mais complexo

O exemplo básico de cima encaminha um ticket para uma equipa. Um sistema de suporte maior pode também precisar do motivo da devolução, do problema de entrega, do que o cliente quer e do tom do cliente.

O pedido abaixo faz cinco perguntas Choice sobre um ticket mais ambíguo do que o primeiro: envolve três equipas e não diz o que o cliente quer.

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
      }
    }
  }
}

Duas destas perguntas Choice são especulativas: return_reason só importa se o department for returns, e shipping_issue só importa se for shipping. A pergunta tone usa descrições null porque os nomes das opções são claros por si só.

A resposta da 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 pergunta é respondida por si só face ao ticket:

  • A resposta de department é returns com uma probabilidade de 0.61, mas billing tem 0.35 por causa da cobrança duplicada. O ticket pertence a duas equipas, e a confiança repartida de 0.42 reflete isso.
  • O return_reason é wrong_size com uma confiança de 1.0, o que era de esperar porque o ticket o diz com clareza.
  • A resposta de shipping_issue reparte-se entre delayed e other. É uma pergunta especulativa e o department não veio como shipping, por isso o código pode ignorá-la, como mostra o fragmento de código de exemplo abaixo.
  • A resposta de requested_resolution inclina-se para refund com 0.40, com replacement e exchange a repartirem quase todo o resto, e a confiança é 0.20. A cobrança duplicada sugere devolução do dinheiro, o tamanho errado sugere uma troca, e o cliente nunca diz qual prefere.
  • A resposta de tone é frustrated com uma probabilidade de 0.84 e uma confiança de 0.76.

O código de exemplo abaixo lê as respostas de que precisa, ignora o resto e trata uma resposta de baixa confiança como um motivo para perguntar em vez de agir:

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 o ticket de cima, isto atribui o ticket à equipa de returns com o problema wrong_size, envia uma cópia à equipa de billing porque a sua quota de 0.35 ultrapassa o limiar de 0.25, e pergunta ao cliente o que quer porque a confiança da resolução, 0.20, está abaixo de 0.5. O código não usa a resposta de shipping_issue.

Um pedido, cinco respostas, e a lógica de encaminhamento são if comuns. Se mais tarde precisares de saber a língua do cliente, ou de que produto trata o ticket, acrescenta outra pergunta Choice a TRIAGE_QUESTIONS; o número de pedidos continua a ser um.

A demonstração do assistente de casa inteligente avalia cada pedido do utilizador contra uma longa lista de perguntas Choice numa só chamada: a categoria do pedido, a divisão, o dispositivo e a ação. A maioria dessas perguntas é irrelevante para qualquer pedido concreto e o código ignora-as.

Instruções e criteria estruturados

Começa com uma descrição de uma linha por opção. Quando duas opções são parecidas e o modelo continua a confundi-las, descreve cada uma com um objeto em vez de uma string. Dá-lhe campos para o que a opção abrange, o que pertence antes a uma opção vizinha e alguns exemplos de entrada.

As duas opções de resposta abaixo, return_policy e return_status, são fáceis de confundir. Um ticket sobre qualquer uma delas pode mencionar devoluções e reembolsos, por isso cada opção diz para que não serve.

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?"
          ]
        }
      }
    }
  }
}

A resposta é return_status com confiança 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
  }
}

Os nomes de campo question, focus, what, not_for e examples não fazem parte da API e nenhum está reservado. Escolhe-los tu, da mesma forma que escolhes os nomes das opções. O modelo vê os nomes juntamente com os valores, por isso usa nomes curtos que etiquetem o que vem a seguir.