Guide
Tutorial: un'applicazione dietro un endpoint HTTP
Valuta un servizio che raggiungi via HTTP, senza importarne il codice: punta Oloproof all'URL, esegui la suite, trova che cosa sbaglia, distribuisci una modifica e confronta. Un piccolo servizio locale fa le veci del tuo, quindi tutto gira offline.
Che cosa costruirai
Un servizio di acquisizione ordini che legge il messaggio di un cliente ed estrae due campi: un intent (where_is_order, cancel, return o other) e un order_id (quattro cifre, oppure null). Controllerai il formato di ogni risposta, che non richiede un riferimento, e misurerai se ogni campo è corretto, che invece lo richiede. Termini come caso, esecuzione, metrica e gate sono definiti in Concetti.
Prerequisiti
- Python 3.11 o successivo, e Oloproof in un ambiente virtuale:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- Il progetto di esempio, incluso nel pacchetto. Copialo in una nuova directory e lavora lì (httpx, che client.py importa, viene installato con Oloproof):
oloproof init --example http order-intake
cd order-intake- La porta 8765 libera su questa macchina. Se è occupata, scegline un'altra e cambiala sia nel comando del server sia in oloproof.yaml.
Nulla qui usa un provider, una chiave API o internet: il servizio ascolta su 127.0.0.1.
I file
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 casesEsegui i comandi da order-intake/. Avvia il servizio in un secondo terminale e lascialo in esecuzione:
python server.py --port 8765intake service (v1) on http://127.0.0.1:8765/extractIl contratto HTTP
Per ogni caso Oloproof invia una richiesta: l'input del caso come corpo JSON, con il metodo che dichiari (POST per impostazione predefinita). Legge la risposta come JSON. Uno stato pari o superiore a 400, un timeout o una connessione rifiutata viene registrato come errore di esecuzione per quel caso, mai come risposta sbagliata.
Richiesta e risposta per un 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 dice a Oloproof di tenere solo result come output del caso; senza di esso l'output è l'intero corpo. Un percorso puntato come data.answer arriva più in profondità.
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_idUn sistema HTTP deve dichiarare una version. Oloproof non può vedere un deployment: mette in cache l'output di ogni caso in base all'URL, al metodo, al percorso dell'output e a quella versione, quindi la versione è il modo in cui gli dici che il servizio è cambiato. Se dimentichi di cambiarla, un nuovo deployment non viene mai chiamato.
Header e autenticazione non si possono ancora configurare su system.http. La sezione sui token qui sotto mostra il ripiego.
Il 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 è esattamente il corpo della richiesta. expected contiene il riferimento per ogni campo; null è un vero valore di riferimento, che significa "in questo messaggio non c'è un id d'ordine".
Scegliere i valutatori
| Criterio | Valutatore | Richiede expected | Misura |
|---|---|---|---|
| format_valid | json_schema sull'output | no | formato: un intent noto e un id ben formato |
| intent_correct | exact_match su intent | sì | successo del compito per il primo campo |
| order_id_correct | exact_match su order_id | sì | successo del compito per il secondo campo |
Lo schema farebbe passare {"intent": "other", "order_id": null} per ogni messaggio: ben formato e inutile. Solo i controlli sul riferimento dicono se il servizio ha fatto il suo lavoro. Valutare i campi separatamente mostra quale fallisce, cosa che un unico controllo combinato nasconderebbe.
release.yaml non ammette fallimenti di formato e chiede che ogni campo sia corretto almeno il 70% delle volte, giudicato sull'intervallo al 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.70Eseguilo
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 missOgni risposta è ben formata. Ogni campo è corretto 15 volte su 20, ma 20 casi lasciano un intervallo che scende fino al 50,8%, quindi nessuna delle due soglie del 70% è dimostrata: INSUFFICIENT_EVIDENCE, e il gate blocca con uscita 3.
Esamina i fallimenti
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: failedLeggi gli input accanto a essi (oloproof inspect RUN_ID --case m04) e compaiono due difetti: il pattern dell'id riconosce solo "order 1234", non "order #5120", "order no. 8123" né un "#1673" isolato; e formulazioni come "send back", "stop order" e "where's my parcel" non corrispondono ad alcun intent. Questa è l'azione successiva: ampliare entrambe le regole.
Distribuisci una modifica
Ferma il servizio e avvia il candidato, che contiene quelle correzioni:
python server.py --port 8765 --rules v2Cambia version: rules-v1 in version: rules-v2 in oloproof.yaml, perché l'URL non è cambiato e altrimenti Oloproof riutilizzerebbe i vecchi output. Poi:
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 │Entrambe le soglie passano e il comando esce con 0.
Confronta i due deployment
compare.yaml chiede se gli intent del candidato sono migliori di quelli della baseline:
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)Il candidato supera le proprie soglie, eppure il confronto non può mostrare che sia migliore: sono cambiati cinque casi, e su 20 casi appaiati l'intervallo del guadagno include ancora lo zero. Entrambe le affermazioni sono vere allo stesso tempo. "Soddisfa il requisito" e "batte la baseline" sono domande separate, e una suite così piccola risponde alla seconda solo per effetti grandi. Il rimedio sono più messaggi reali.
Un servizio che richiede un token
Avvia il servizio in modo che richieda un bearer token:
INTAKE_TOKEN=s3cret python server.py --port 8765 --rules v2 --require-tokensystem.http non invia header personalizzati, quindi con una nuova version (per esempio rules-v2-auth) ogni chiamata viene rifiutata:
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 su ogni caso. Gli errori di esecuzione sono evidenze mancanti, non fallimenti: non è stato osservato nulla, quindi ogni regola è INSUFFICIENT_EVIDENCE.
Il ripiego è un callable Python che fa la richiesta da sé. client.py aggiunge l'header da una variabile d'ambiente, così il token non entra mai in oloproof.yaml né in alcun record memorizzato:
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"]Sostituisci il blocco http: in oloproof.yaml con:
system:
name: order-intake
version: rules-v2
callable: client:extracted esegui con il token nell'ambiente:
INTAKE_TOKEN=s3cret oloproof runTutti i 20 casi vengono di nuovo osservati e il gate lascia passare. La cache del callable segue il suo sorgente e la sua version, non il servizio dietro di esso, quindi vale la stessa regola: cambia version quando distribuisci.
Risoluzione dei problemi
| Sintomo | Causa e rimedio |
|---|---|
| system connection failed (ConnectError) su ogni caso | Il servizio non è in esecuzione, o ascolta su un'altra porta. |
| system connection failed (RemoteProtocolError) | Qualcos'altro risponde su quella porta. Scegline una libera. |
| KeyError: "missing output path 'results'" | output_path nomina un campo che la risposta non ha. |
| system returned HTTP 401 o 403 | L'endpoint richiede credenziali: usa il ripiego con il callable. |
| Hai distribuito una modifica e la riga della cache riporta solo hit | La version non è cambiata, quindi sono stati riutilizzati gli output memorizzati. |
| an HTTP system needs a declared version | Aggiungi version sotto system o system.http. |
Limitazioni
- Niente header personalizzati, autenticazione, parametri di query o template di richiesta su system.http: l'input del caso è il corpo JSON così com'è. Usa un callable per qualsiasi altra cosa.
- Le risposte devono essere JSON. Le risposte in streaming non vengono lette come stream.
- Oloproof non può rilevare un deployment; la version dichiarata è l'intera identità di ciò che ha risposto.
- Il tuo servizio gestisce il proprio stato: Oloproof invia richieste e registra risposte; non reimposta, non isola e non annulla nulla di ciò che una richiesta modifica.