Pular para o conteúdo

Guias

Usuários simulados

Uma conversa roteirizada testa o seu assistente contra um usuário que nunca reage. Um usuário simulado reage: um modelo interpreta uma pessoa que cada caso descreve, escreve cada mensagem seguinte a partir do que o seu assistente acabou de dizer, e para quando o seu objetivo é alcançado ou quando desiste. O Oloproof registra o que passou entre os dois, mede isso como qualquer conversa, e só deixa um gate decidir sobre isso depois que pessoas consideraram o usuário simulado realista. O tutorial percorre uma central de atendimento conversando com clientes simulados.

Personas

A entrada de cada caso contém uma persona: quem o usuário é, o que ele quer e o que ele sabe.

{"id": "c25", "input": {"persona": {
  "description": "A retired teacher who ordered a cookbook last week. Writes short, polite messages.",
  "goal": "Have the order of a cookbook delivered to my new address.",
  "facts": {"order": "B-35", "postcode": "10275"}
}}}
  • description e goal são obrigatórios. Escreva um objetivo que um leitor possa verificar só pela conversa: "fazer a loja iniciar um reembolso" pode ser decidido pelo que o assistente diz, "receber um reembolso" não pode, porque o dinheiro chega depois.
  • facts são o que o usuário sabe e só compartilha quando a conversa precisa, como um número de pedido. O seu assistente nunca vê a persona.
  • opening é a primeira mensagem do usuário, palavra por palavra, quando todo sistema deve ser abordado da mesma forma.
  • max_turns limita as mensagens desse usuário, substituindo o valor do projeto.

Uma persona é conteúdo do dataset: ela é coberta pelo digest da suíte e é a mesma para todo sistema medido contra ela.

Declarando o usuário simulado

O modelo que interpreta todas as personas é declarado no sistema, como um juiz:

system:
  name: support-desk
  callable: desk:talk
  simulated_user:
    provider: openai_compatible
    base_url: http://localhost:11434/v1
    model: qwen2.5vl
    max_turns: 6

Todos os campos estão na referência de configuração. Um modelo local não custa nada; um hospedado cobra cada mensagem que escreve, e os seus tokens são registrados ao lado da conversa e nunca contados como custo do seu sistema.

O seu sistema mantém o seu loop

O Oloproof não conduz a conversa. O seu sistema pede a primeira mensagem do usuário, responde a ela do jeito que responde aos usuários, e devolve cada resposta para receber a próxima mensagem:

from oloproof import current_case

def talk(case):
    user = current_case().simulated_user()
    message = user.opening()
    while message is not None:
        message = user.reply(my_assistant.answer(message))
    return {}

reply retorna None quando o usuário para. O seu sistema pode encerrar a conversa ele mesmo com user.end(answer), como faria uma transferência para uma pessoa; um sistema async def usa await em aopening(), areply(answer) e aend(answer). O Oloproof registra o que passou pelo handle como conversation/v1 e como a conversa terminou como simulated_user/v1, e recusa qualquer um dos dois vindo do seu sistema (Referência do SDK).

Como uma conversa termina

Depois de cada resposta do seu sistema, o modelo do usuário simulado é consultado, numa pergunta separada com temperatura 0 e sem a encenação em volta, sobre se aquela resposta cumpriu o objetivo. Se cumpriu, a conversa termina como goal_reached. Caso contrário, o usuário escreve a sua próxima mensagem, ou vai embora (user_left), até ter enviado max_turns mensagens (turn_limit). Um sistema que para sem responder à última mensagem deixa a conversa unanswered e truncada.

Perguntar separadamente é o que torna utilizável um modelo local pequeno: interpretar uma persona e decidir se um objetivo foi alcançado são tarefas diferentes, e um modelo a quem se pedem as duas numa só resposta tende a continuar conversando depois de alcançar o objetivo, ou a dizer que está indo embora quando está satisfeito.

Medindo as conversas

A conversa é a unidade: cada avaliador dá um veredito por conversa, então todo intervalo, comparação e gate se aplica sem mudança.

TipoPassa quando
conversation_completedo seu sistema respondeu a todas as mensagens
goal_reacheda verificação do objetivo constatou que o objetivo foi alcançado
turns_to_goalisso aconteceu em até max_turns das mensagens do usuário
every_turntoda resposta cumpre as condições declaradas: palavras que ela precisa conter ou não pode conter, um padrão, um comprimento
conversation_judgeum juiz baseado em modelo da transcrição inteira a aprova, depois de validado contra os rótulos das pessoas

As réplicas (replicates:) encontram usuários com sementes diferentes, então uma persona são várias conversas. A versão do usuário simulado (o seu modelo, prompt, temperatura e orçamento) faz parte da chave de cache de cada execução, então um usuário simulado diferente mede o seu sistema de novo.

Validando o usuário simulado

Um resultado obtido com usuários simulados só vale para usuários reais na medida em que o usuário simulado se parece com eles. Por isso, toda regra sobre uma execução cujos usuários foram simulados fica INSUFFICIENT_EVIDENCE com simulated_user_not_validated até que pessoas tenham considerado as suas conversas realistas:

oloproof labels export RUN_ID --criterion simulated_user_realistic --sample 30 --local --out realism.csv
oloproof labels import realism.csv
oloproof simulated-users RUN_ID

A planilha mostra cada conversa sorteada com a persona e como ela terminou. Uma pessoa marca passed como sim quando um cliente real poderia ter escrito o lado do usuário e terminado a conversa ali, daquele jeito, o que cobre também a verificação do objetivo: uma conversa que terminou como goal_reached quando o objetivo não foi alcançado não é realista. O usuário simulado é validado na suíte quando o limite inferior exato da proporção realista atinge minimum_simulated_user_realism em release.yaml. Uma conversa sorteada que fica sem rótulo conta como não realista, e uma conversa só é realista quando todos que a rotularam dizem isso.

Uma validação cobre a versão de um usuário simulado em uma suíte, qualquer que seja o sistema com que ele conversou, então um candidato medido contra as mesmas personas é decidido com os rótulos da linha de base. Um novo modelo, uma nova configuração de prompt ou um novo conjunto de personas precisa de novos rótulos. Definir require_validated_evaluators: false decide sobre usuários simulados sem eles, conscientemente.

Simulado ao lado do real

oloproof simulated-users RUN_ID --real REAL_RUN_ID coloca a forma das conversas simuladas ao lado de uma execução de conversas reais (chats registrados reproduzidos como conversation/v1): quantas mensagens o usuário enviou, qual o tamanho delas, qual o tamanho das respostas, com que frequência uma conversa foi interrompida, e como as simuladas terminaram. Isso é descritivo e nunca decide: comparar duas amostras não pareadas exige um método que o Oloproof não admitiu. Leia isso pelo que os rótulos de realismo deixam passar, como usuários simulados que escrevem numa frase arrumada o que os reais espalham por três.

Sem suporte

  • Decidir sobre usuários simulados em um espaço de trabalho hospedado: as suas decisões ficam retidas até que a sua fila de revisão consiga sortear e selar rótulos de realismo por conta própria.
  • Um gate sobre como as conversas simuladas se comparam com as reais.
  • Taxas por turno dentro de uma conversa, e reproduzir uma conversa a partir de um turno.
  • Usuários simulados para um sistema HTTP: o próprio código do seu sistema conduz o loop, então ele precisa ser um callable.