Guias
Tutorial: uma aplicação por trás de um endpoint HTTP
Avalie um serviço que você acessa por HTTP, sem importar o código dele: aponte o Oloproof para a URL, execute a suíte, descubra o que ele erra, implante uma mudança e compare. Um pequeno serviço local faz o papel do seu, então tudo roda offline.
O que você vai construir
Um serviço de recepção de pedidos que lê a mensagem de um cliente e extrai dois campos: um intent (where_is_order, cancel, return ou other) e um order_id (quatro dígitos, ou null). Você vai verificar o formato de toda resposta, o que não precisa de referência, e medir se cada campo está certo, o que precisa. Termos como caso, execução, métrica e gate são definidos em Conceitos.
Pré-requisitos
- Python 3.11 ou posterior, e o Oloproof em um ambiente virtual:
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 (httpx, que client.py importa, é instalado com o Oloproof):
oloproof init --example http order-intake
cd order-intake- A porta 8765 livre nesta máquina. Se estiver ocupada, escolha outra e mude-a tanto no comando do servidor quanto em oloproof.yaml.
Nada aqui usa um provedor, uma chave de API ou a internet: o serviço escuta em 127.0.0.1.
Os arquivos
order-intake/
server.py the stand-in service (Python standard library only)
client.py a callable that calls the service with a token (used near the end)
oloproof.yaml the suite: dataset, HTTP system, evaluators
release.yaml rules for a run
compare.yaml a rule for a comparison
data/messages.jsonl 20 casesExecute os comandos a partir de order-intake/. Inicie o serviço em um segundo terminal e deixe-o rodando:
python server.py --port 8765intake service (v1) on http://127.0.0.1:8765/extractO contrato HTTP
Para cada caso, o Oloproof envia uma requisição: o input do caso como corpo JSON, com o método que você declarar (POST por padrão). Ele lê a resposta como JSON. Um status 400 ou acima, um timeout ou uma conexão recusada é registrado como erro de execução para aquele caso, nunca como resposta errada.
Requisição e resposta de um caso:
POST /extract
{"message": "Where is order 1042? It has not arrived."}
200 OK
{"result": {"intent": "where_is_order", "order_id": "1042"}, "service": {"rules": "v1"}}output_path: result diz ao Oloproof para manter só result como saída do caso; sem isso, a saída é o corpo inteiro. Um caminho com pontos como data.answer vai mais fundo.
version: 1
project: order-intake
dataset: data/messages.jsonl
system:
name: order-intake
http:
url: http://127.0.0.1:8765/extract
method: POST
version: rules-v1
output_path: result
timeout_s: 30
evaluators:
- type: json_schema
criterion: format_valid
field: null
schema:
type: object
required: [intent, order_id]
properties:
intent: {enum: [where_is_order, cancel, return, other]}
order_id: {type: [string, "null"], pattern: '^\d{4}$'}
additionalProperties: false
- type: exact_match
criterion: intent_correct
field: intent
- type: exact_match
criterion: order_id_correct
field: order_idUm sistema HTTP precisa declarar uma version. O Oloproof não consegue ver uma implantação: ele coloca em cache a saída de cada caso pela URL, pelo método, pelo caminho da saída e por essa versão, então a versão é a forma de dizer a ele que o serviço mudou. Se você esquecer de mudá-la, uma nova implantação nunca é chamada.
Cabeçalhos e autenticação ainda não podem ser configurados em system.http. A seção sobre tokens abaixo mostra a alternativa.
O dataset
{"id":"m04","input":{"message":"Has order #5120 shipped yet?"},"expected":{"intent":"where_is_order","order_id":"5120"}}
{"id":"m07","input":{"message":"Do you ship to Canada?"},"expected":{"intent":"other","order_id":null}}
{"id":"m16","input":{"message":"Please refund and take back the lamp from order #1560."},"expected":{"intent":"return","order_id":"1560"}}input é exatamente o corpo da requisição. expected contém a referência de cada campo; null é um valor de referência real, que significa "não há id de pedido nesta mensagem".
Escolhendo os avaliadores
| Critério | Avaliador | Precisa de expected | Mede |
|---|---|---|---|
| format_valid | json_schema sobre a saída | não | formato: um intent conhecido e um id bem formado |
| intent_correct | exact_match em intent | sim | sucesso da tarefa no primeiro campo |
| order_id_correct | exact_match em order_id | sim | sucesso da tarefa no segundo campo |
O schema aprovaria {"intent": "other", "order_id": null} para toda mensagem: bem formado e inútil. Só as verificações contra a referência dizem se o serviço fez o seu trabalho. Avaliar os campos separadamente mostra qual deles falha, o que uma única verificação combinada esconderia.
release.yaml não permite nenhuma falha de formato e pede que cada campo esteja certo em pelo menos 70% das vezes, julgado pelo intervalo de 95%:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- id: valid-format
metric: format_valid
kind: observed_count
max_failures: 0
- id: intent-floor
metric: intent_correct
min: 0.70
- id: order-id-floor
metric: order_id_correct
min: 0.70Execute
oloproof runRun run_01M4... [DECIDED/COMPLETE]
Gate: BLOCK (exit 3)
│ valid-format │ format_valid │ PASS │ observed_failures_within_limit │
│ intent-floor │ intent_correct │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ order-id-floor │ order_id_correct │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ format_valid │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ intent_correct │ 75.0% │ [50.8%, 91.4%] │ 15 / 20 observed · 0 missing · 0 excluded │
│ order_id_correct │ 75.0% │ [50.8%, 91.4%] │ 15 / 20 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/20 miss; judgment 0 hit/60 missToda resposta é bem formada. Cada campo está certo 15 vezes em 20, mas 20 casos deixam um intervalo que desce até 50,8%, então nenhum dos pisos de 70% é demonstrado: INSUFFICIENT_EVIDENCE, e o gate bloqueia com saída 3.
Inspecione as falhas
oloproof inspect RUN_ID --failures8 of 20 cases failed, errored or did not finish
m04
output: {"intent": "where_is_order", "order_id": null}
order_id_correct: failed
m06
output: {"intent": "other", "order_id": "7011"}
intent_correct: failed
...
m18
output: {"intent": "other", "order_id": null}
intent_correct: failed
order_id_correct: failedLeia as entradas ao lado delas (oloproof inspect RUN_ID --case m04) e aparecem dois defeitos: o padrão do id só reconhece "order 1234", não "order #5120", "order no. 8123" nem um "#1673" sozinho; e formulações como "send back", "stop order" e "where's my parcel" não correspondem a nenhum intent. Essa é a próxima ação: ampliar as duas regras.
Implante uma mudança
Pare o serviço e inicie o candidato, que traz essas correções:
python server.py --port 8765 --rules v2Mude version: rules-v1 para version: rules-v2 em oloproof.yaml, porque a URL não mudou e, sem isso, o Oloproof reaproveitaria as saídas antigas. Depois:
oloproof runGate: ALLOW (exit 0)
│ intent_correct │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │
│ order_id_correct │ 100.0% │ [83.1%, 100.0%] │ 20 / 20 observed · 0 missing · 0 excluded │Os dois pisos passam e o comando sai com 0.
Compare as duas implantações
compare.yaml pergunta se os intents do candidato são melhores do que os da linha de base:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- id: intent-better
kind: superiority
metric: intent_correctoloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlformat_valid: +0.0 points [-23.6, +23.6] · 20 paired · 0 missing · 0 excluded
intent_correct: +25.0 points [-8.5, +58.3] · 20 paired · 0 missing · 0 excluded
order_id_correct: +25.0 points [-8.5, +58.3] · 20 paired · 0 missing · 0 excluded
Decisions
intent-better intent_correct superiority INSUFFICIENT_EVIDENCE interval_overlaps_zero
Gate: BLOCK (exit 3)O candidato passa nos seus próprios pisos, mas a comparação não consegue mostrar que ele é melhor: cinco casos mudaram, e sobre 20 casos pareados o intervalo do ganho ainda inclui o zero. As duas afirmações são verdadeiras ao mesmo tempo. "Atende ao requisito" e "supera a linha de base" são perguntas separadas, e uma suíte tão pequena só responde à segunda para efeitos grandes. O remédio é ter mais mensagens reais.
Um serviço que exige um token
Inicie o serviço de forma que ele exija um bearer token:
INTAKE_TOKEN=s3cret python server.py --port 8765 --rules v2 --require-tokensystem.http não envia cabeçalhos personalizados, então com uma nova version (digamos rules-v2-auth) toda chamada é recusada:
Gate: BLOCK (exit 3)
│ valid-format │ format_valid │ INSUFFICIENT_EVIDENCE │ no_observations │
│ intent_correct │ │ [0.0%, 100.0%] │ 0 / 0 observed · 20 missing · 0 excluded │e oloproof inspect RUN_ID --failures mostra execution ERROR: TransientError: system returned HTTP 401 em cada caso. Erros de execução são evidência faltante, não falhas: nada foi observado, então toda regra é INSUFFICIENT_EVIDENCE.
A alternativa é um callable Python que faz a requisição por conta própria. client.py acrescenta o cabeçalho a partir de uma variável de ambiente, então o token nunca entra em oloproof.yaml nem em nenhum registro armazenado:
URL = os.environ.get("INTAKE_URL", "http://127.0.0.1:8765/extract")
def extract(case: dict[str, Any]) -> dict[str, Any]:
headers = {"Authorization": f"Bearer {os.environ['INTAKE_TOKEN']}"}
response = httpx.post(URL, json=case, headers=headers, timeout=30)
response.raise_for_status()
return response.json()["result"]Substitua o bloco http: em oloproof.yaml por:
system:
name: order-intake
version: rules-v2
callable: client:extracte execute com o token no ambiente:
INTAKE_TOKEN=s3cret oloproof runOs 20 casos são observados de novo e o gate libera. O cache do callable segue o seu próprio código-fonte e a sua version, não o serviço por trás dele, então a mesma regra vale: mude version quando implantar.
Solução de problemas
| Sintoma | Causa e correção |
|---|---|
| system connection failed (ConnectError) em todo caso | O serviço não está rodando, ou escuta em outra porta. |
| system connection failed (RemoteProtocolError) | Outra coisa responde nessa porta. Escolha uma livre. |
| KeyError: "missing output path 'results'" | output_path nomeia um campo que a resposta não tem. |
| system returned HTTP 401 ou 403 | O endpoint precisa de credenciais: use a alternativa com o callable. |
| Você implantou uma mudança e a linha do cache mostra só hits | A version não mudou, então as saídas armazenadas foram reaproveitadas. |
| an HTTP system needs a declared version | Acrescente version em system ou system.http. |
Limitações
- Nada de cabeçalhos personalizados, autenticação, parâmetros de query ou templates de requisição em system.http: o input do caso é o corpo JSON como está. Use um callable para qualquer outra coisa.
- As respostas precisam ser JSON. Respostas em streaming não são lidas como stream.
- O Oloproof não consegue detectar uma implantação; a version declarada é toda a identidade do que respondeu.
- O seu serviço cuida do próprio estado: o Oloproof envia requisições e registra respostas; ele não reinicia, não isola e não desfaz nada que uma requisição altere.