Pular para o conteúdo

Guias

Tutorial: uma central de atendimento diante de clientes simulados

Avalie um assistente conversacional contra usuários que reagem ao que ele diz. Trinta clientes simulados, cada um com um objetivo e os fatos que conhece, conversam com a central de atendimento de uma loja; o Oloproof mede se cada um conseguiu o que veio buscar, retém toda decisão até que uma pessoa tenha considerado os clientes simulados realistas, e então decide um lançamento e uma comparação. Todo passo roda nesta máquina sem modelo e sem rede, e um passo opcional troca os clientes por um modelo real.

O que você vai construir

A central de atendimento de uma loja responde a reembolsos, perguntas sobre entrega e mudanças de endereço. Três perguntas decidem se uma versão pode ser lançada:

  • Cada cliente conseguiu o que veio buscar? goal_reached, a partir de uma verificação depois de cada resposta.
  • Alguma resposta prometeu o que a loja não pode prometer? every_turn lê cada resposta procurando a palavra "guarantee".
  • Dá para confiar que as respostas dizem algo sobre clientes reais? Só depois que uma pessoa tiver lido uma amostra das conversas e considerado os clientes simulados realistas.

Termos como caso, execução, gate e suíte são definidos em Conceitos; tudo sobre usuários simulados está em Usuários simulados.

Pré-requisitos

  • Python 3.11 ou posterior, e o Oloproof:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • O projeto de exemplo, que vem com o pacote. Copie-o para um novo diretório e trabalhe nele:
oloproof init --example simulated-users support-desk
cd support-desk
  • A porta 8797 livre para o usuário simulado substituto (se não estiver, mude-a nos dois lugares).

Todo passo até "Opcional: um modelo real como os clientes" é offline e determinístico: nenhuma chave de API, nenhuma conta de provedor, nenhum custo.

Os arquivos

support-desk/
  desk.py                      the support desk under test (baseline)
  desk_v2.py                   the candidate change
  user_server.py               a stand-in simulated user speaking the OpenAI API on 127.0.0.1
  oloproof.yaml                the suite: the simulated user and four evaluators
  release.yaml                 rules for a run, with the realism bar
  compare.yaml                 a rule for a comparison
  data/customers.jsonl         30 personas
  labels/realism_verdicts.csv  one person's realism verdicts on the baseline's conversations
  fill_realism.py              copies those verdicts into a labelling sheet
  logged.py                    replays logged chats with human agents
  real.yaml                    a suite of 20 of those chats
  logged_release.yaml          a rule for the logged chats
  data/logged_chats.jsonl      the 20 chats

Execute todos os comandos a partir de support-desk/.

A central mantém o seu próprio loop

# desk.py (excerpt)
def talk(case: dict[str, Any]) -> dict[str, Any]:
    user = current_case().simulated_user()
    desk = Desk()
    message = user.opening()
    while message is not None:
        message = user.reply(desk.answer(message))
    return {"need": desk.need, "order": desk.order}

A central pede a primeira mensagem do cliente, responde a ela, e devolve cada resposta para receber a próxima mensagem, até o cliente parar. Ela nunca vê a persona. O Oloproof registra o que passou entre os dois; a própria central não registra nada. Esta linha de base não consegue mudar um endereço de entrega, e promete reembolsos "guaranteed within 5 days".

As personas

Cada caso é um cliente: 12 querem um reembolso, 10 querem saber quando um pedido chega, e 8 se mudaram. Por exemplo:

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

O cliente informa o número do pedido e o CEP só quando alguém pede. Três personas também definem um opening, a sua primeira mensagem palavra por palavra.

A suíte

version: 1
project: support-desk
dataset: data/customers.jsonl
system:
  name: support-desk
  version: baseline
  callable: desk:talk
  # A model plays each case's persona; here the stand-in in user_server.py.
  simulated_user:
    provider: openai_compatible
    model: stand-in-user
    base_url: http://127.0.0.1:8797/v1
    max_turns: 6
evaluators:
  # The desk answered every message the customer sent.
  - type: conversation_completed
  # The customer said they got what they came for.
  - type: goal_reached
  # ... within three of their own messages.
  - type: turns_to_goal
    max_turns: 3
  # No answer promises what the shop cannot promise.
  - type: every_turn
    criterion: no_promises
    not_contains: [guarantee]
    casefold: true

O usuário simulado substituto

Um usuário simulado é um modelo que interpreta cada persona. Aqui é user_server.py, que fala a mesma API em 127.0.0.1 e não é um modelo: ele lê a persona no prompt que o Oloproof envia, declara o seu objetivo, informa o número do pedido ou o CEP quando alguém pede, vai embora quando a central não consegue ajudar e, quando perguntado se o seu objetivo foi alcançado, diz sim quando a última resposta da central começa com "Done:". Inicie-o em um segundo terminal e deixe-o rodando:

python user_server.py --port 8797

A política

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
minimum_simulated_user_realism: 0.80
rules:
  - id: no-promises
    metric: no_promises
    kind: observed_count
    max_failures: 0
  - id: resolved
    metric: goal_reached
    min: 0.80

Nenhuma resposta pode prometer. Pelo menos 80% dos clientes precisam alcançar o seu objetivo, o que só é decidido quando o limite inferior do intervalo supera 80%. E nenhuma regra decide sobre clientes simulados até que pessoas tenham considerado pelo menos 80% das suas conversas realistas, pelo mesmo tipo de limite inferior.

Execute

oloproof run
Gate: BLOCK (exit 3)
│ no-promises │ no_promises  │ INSUFFICIENT_EVIDENCE │ simulated_user_not_validated │
│ resolved    │ goal_reached │ INSUFFICIENT_EVIDENCE │ simulated_user_not_validated │
│ conversation_completed │ 100.0%   │ [88.4%, 100.0%] │ 30 / 30 observed · 0 missing · 0 excluded │
│ goal_reached           │ 73.3%    │ [54.1%, 87.8%]  │ 22 / 30 observed · 0 missing · 0 excluded │
│ goal_within_3_turns    │ 73.3%    │ [54.1%, 87.8%]  │ 22 / 30 observed · 0 missing · 0 excluded │
│ no_promises            │ 60.0%    │ [40.6%, 77.4%]  │ 18 / 30 observed · 0 missing · 0 excluded │

Todo cliente foi respondido, 22 de 30 alcançaram o seu objetivo, e 12 conversas contêm uma promessa. Nada está decidido ainda: esses clientes são simulados, e ninguém verificou se eles se comportam como os reais.

Leia os clientes simulados

oloproof simulated-users RUN_ID
  Realism: no conversation has been drawn and labelled yet.
  Not validated: nothing labelled yet, against a bar of 80.0%.
Features (median [quartiles]; descriptive, never a gate):
  user messages           2 [1, 2]
  user message length     49 [10, 67]
  How the simulated conversations ended: goal_reached 73%, user_left 27%

Os oito que foram embora são os clientes que se mudaram: a linha de base não consegue mudar um endereço.

Rotule as conversas quanto ao realismo

Sorteie uma amostra aleatória das conversas da execução em uma planilha, peça a uma pessoa que leia cada uma, com a persona e como ela terminou, e marque se um cliente real poderia ter escrito o lado do cliente e terminado a conversa ali, daquele jeito. Aqui fill_realism.py faz o papel dessa pessoa, copiando os vereditos dela de labels/realism_verdicts.csv:

oloproof labels export RUN_ID --criterion simulated_user_realistic --sample 30 --local --out realism.csv
python fill_realism.py realism.csv
oloproof labels import realism.csv
oloproof simulated-users RUN_ID
  Realism: 29 of 30 drawn conversations labelled realistic (30 labelled), [82.8%, 99.9%] at 95%
  Validated: the lower bound clears the bar of 80.0%.

A pessoa considerou uma conversa não realista: o cliente c05 começa com "Order: F-15. Refund.", que parece um formulário, não uma pessoa. Com 29 de 30, o limite inferior exato é 82,8%, acima do patamar, então as regras agora podem decidir sobre esses clientes.

oloproof gate RUN_ID --policy release.yaml
no-promises: FAIL (observed_failures_exceed_limit)
resolved: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)

Faça uma mudança real

desk_v2.py muda um endereço assim que tem o pedido e o novo CEP, e diz que iniciou um reembolso que "usually arrives within 5 days" em vez de prometê-lo. Copie-o por cima de desk.py, defina version: candidate em system no oloproof.yaml, mantenha o substituto rodando, e:

oloproof run
Gate: ALLOW (exit 0)
│ no-promises │ no_promises  │ PASS  │ observed_failures_within_limit │
│ resolved    │ goal_reached │ PASS  │ lower_bound_meets_minimum      │

O candidato encontrou os mesmos clientes simulados na mesma suíte, então o realismo medido nas conversas da linha de base o cobre: uma validação cobre um usuário simulado em uma suíte, qualquer que seja o sistema com que ele conversou.

Compare o candidato com a linha de base

oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
goal_reached: +26.7 points [+1.2, +54.1] · 30 paired · 0 missing · 0 excluded
  resolves-more  goal_reached  superiority  PASS  difference_above_zero

Cada cliente encontrou as duas centrais, então a comparação é pareada cliente a cliente.

Simulado ao lado do real

Os rótulos de realismo julgam cada conversa isoladamente. Colocar os clientes simulados ao lado de clientes reais mostra o que uma amostra pode deixar passar. real.yaml reproduz 20 chats que os atendentes humanos da loja tiveram com clientes reais:

oloproof run --config real.yaml --policy logged_release.yaml
oloproof simulated-users BASELINE_RUN_ID --real REAL_RUN_ID
                          simulated (run_01M52Q1C)    real (run_01M52Q25)
  user messages           2 [1, 2]                    3 [3, 4]
  user message length     49 [10, 67]                 13 [4, 32]

Os clientes reais enviaram mais mensagens, e mais curtas: eles dizem o que querem em três linhas, onde o substituto diz isso numa frase arrumada. Vale a pena saber disso sobre qualquer usuário simulado, e aqui isso nunca decide nada: comparar duas amostras não pareadas exige um método que o Oloproof não admitiu.

Opcional: um modelo real como os clientes

Este passo sai do caminho offline. Ele precisa de um modelo de chat: localmente, o Ollama (ollama pull qwen2.5vl, ou qualquer modelo de chat), sem chave e sem custo; na nuvem, provider: anthropic ou openai com api_key_env, cobrado por mensagem. No oloproof.yaml, aponte o usuário simulado para ele:

  simulated_user:
    provider: openai_compatible
    model: qwen2.5vl
    base_url: http://localhost:11434/v1
    max_turns: 6

O Ollama atende uma requisição por vez, então defina concurrency: {system: 1} e um timeout_s de alguns minutos. Um modelo diferente é um usuário simulado diferente: os rótulos de realismo acima não o cobrem, então as suas conversas precisam da sua própria amostra antes que qualquer regra decida sobre elas.

Dez das personas, interpretadas por qwen2.5vl nesta máquina contra a central candidata, alcançaram o seu objetivo em 8 conversas, enquanto o substituto o alcançou em todas as 30. O modelo encontrou o que o substituto não conseguia: dois clientes pediram o "status" e o "shipping status" do seu pedido, palavras que a central não conhece, e receberam de volta a saudação dela todas as vezes. Um ficou sem mensagens. A central entendeu a terceira tentativa do outro, e esse cliente foi embora mesmo assim: a verificação do objetivo, um julgamento de modelo, não percebeu a resposta. É num rótulo de realismo que isso aparece: um cliente real a quem disseram quando o seu pedido chega não vai embora insatisfeito. Ao contrário do substituto, o modelo também muitas vezes deu o número do pedido antes de alguém pedir, e escreveu uma mensagem mais longa onde o substituto escreveu duas curtas.

Solução de problemas

SintomaCausa e correção
Toda regra fica INSUFFICIENT_EVIDENCE com simulated_user_not_validatedNinguém rotulou as conversas desse usuário simulado nessa suíte, ou a proporção realista não atinge o patamar. Rotule uma amostra como acima.
a case for a simulated user needs a 'persona' object in its inputUm caso não tem persona, ou ela está sob outra chave: defina persona_field.
provider connection failed em todos os casosO substituto não está rodando, ou escuta em outra porta que não a que base_url indica.
the simulated user's goal check answered ...O modelo não respondeu à verificação do objetivo com sim ou não. Tente outro modelo, ou um maior.
conversation/v1 is written by OloproofA central registrou a conversa ela mesma. Remova isso: o Oloproof registra o que passou pelo usuário simulado.

Limitações

  • O cliente substituto é uma regra. As suas conversas mostram a mecânica; as de um modelo real são mais bagunçadas, como mostra o passo opcional.
  • Se um objetivo foi alcançado é o julgamento do modelo do usuário simulado, verificado só por meio dos rótulos de realismo.
  • Um espaço de trabalho hospedado ainda não decide sobre usuários simulados.
  • Clientes simulados não são clientes reais: um usuário simulado validado é evidência sobre clientes parecidos com as suas personas, na medida em que as pessoas o consideraram realista, e não além disso.