Ga naar de inhoud

Handleidingen

Tutorial: een applicatie achter een HTTP-endpoint

Evalueer een dienst die je via HTTP bereikt, zonder zijn code te importeren: richt Oloproof op de URL, draai de suite, vind wat hij mist, deploy een wijziging en vergelijk. Een kleine lokale dienst staat in voor de jouwe, dus alles draait offline.

Wat je gaat bouwen

Een dienst voor orderintake die een klantbericht leest en twee velden extraheert: een intent (where_is_order, cancel, return of other) en een order_id (vier cijfers, of null). Je controleert het formaat van elk antwoord, waarvoor geen referentie nodig is, en meet of elk veld juist is, waarvoor wel. Termen als case, run, metriek en gate worden gedefinieerd in Kernbegrippen.

Vereisten

  • Python 3.11 of nieuwer, en Oloproof in een virtuele omgeving:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • Het voorbeeldproject, dat met het pakket meekomt. Kopieer het naar een nieuwe map en werk daar (httpx, dat client.py importeert, wordt met Oloproof geïnstalleerd):
oloproof init --example http order-intake
cd order-intake
  • Poort 8765 vrij op deze machine. Is die bezet, kies dan een andere en wijzig die zowel in het servercommando als in oloproof.yaml.

Niets hier gebruikt een provider, een API-sleutel of het internet: de dienst luistert op 127.0.0.1.

De bestanden

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 cases

Voer de commando's uit vanuit order-intake/. Start de dienst in een tweede terminal en laat hem draaien:

python server.py --port 8765
intake service (v1) on http://127.0.0.1:8765/extract

Het HTTP-contract

Voor elke case stuurt Oloproof één verzoek: de input van de case als JSON-body, met de methode die je declareert (standaard POST). Het leest het antwoord als JSON. Een status van 400 of hoger, een time-out of een geweigerde verbinding wordt voor die case vastgelegd als uitvoeringsfout, nooit als fout antwoord.

Verzoek en antwoord voor één case:

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 vertelt Oloproof om alleen result als uitvoer van de case te houden; zonder dat is de hele body de uitvoer. Een pad met punten zoals data.answer gaat dieper.

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_id

Een HTTP-systeem moet een version declareren. Oloproof kan een deployment niet zien: het cachet de uitvoer van elke case onder de URL, de methode, het uitvoerpad en die versie, dus de versie is hoe je het vertelt dat de dienst veranderde. Vergeet je die te wijzigen, dan wordt een nieuwe deployment nooit aangeroepen.

Headers en authenticatie kunnen nog niet op system.http worden ingesteld. Het deel over tokens hieronder toont de omweg.

De 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 is precies de body van het verzoek. expected bevat de referentie voor elk veld; null is een echte referentiewaarde, die betekent "er staat geen ordernummer in dit bericht".

De evaluators kiezen

CriteriumEvaluatorHeeft expected nodigMeet
format_validjson_schema over de uitvoerneeformaat: een bekende intent en een goedgevormd id
intent_correctexact_match op intentjataaksucces voor het eerste veld
order_id_correctexact_match op order_idjataaksucces voor het tweede veld

Het schema zou {"intent": "other", "order_id": null} voor elk bericht laten slagen: goedgevormd en nutteloos. Alleen de referentiecontroles zeggen of de dienst zijn werk deed. De velden apart scoren laat zien welk veld faalt, wat één gecombineerde controle zou verbergen.

release.yaml staat geen formaatfout toe en vraagt dat elk veld in minstens 70% van de gevallen juist is, beoordeeld op het 95%-interval:

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.70

Draaien

oloproof run
Run 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 miss

Elk antwoord is goedgevormd. Elk veld is 15 van de 20 keer juist, maar 20 cases laten een interval tot 50.8% over, dus geen van beide ondergrenzen van 70% is aangetoond: INSUFFICIENT_EVIDENCE, en de gate blokkeert met exit 3.

De mislukkingen bekijken

oloproof inspect RUN_ID --failures
8 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: failed

Lees de inputs ernaast (oloproof inspect RUN_ID --case m04) en er verschijnen twee fouten: het id-patroon past alleen op "order 1234", niet op "order #5120", "order no. 8123" of een kale "#1673"; en formuleringen als "send back", "stop order" en "where's my parcel" passen bij geen intent. Dat is de volgende stap: verbreed beide regels.

Een wijziging deployen

Stop de dienst en start de kandidaat, die die oplossingen bevat:

python server.py --port 8765 --rules v2

Wijzig version: rules-v1 in version: rules-v2 in oloproof.yaml, omdat de URL niet veranderde en Oloproof anders de oude uitvoer zou hergebruiken. Daarna:

oloproof run
Gate: 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 │

Beide ondergrenzen slagen en het commando eindigt met 0.

De twee deployments vergelijken

compare.yaml vraagt of de intents van de kandidaat beter zijn dan die van de baseline:

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
  - id: intent-better
    kind: superiority
    metric: intent_correct
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
format_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)

De kandidaat haalt zijn eigen ondergrenzen, maar de vergelijking kan niet aantonen dat hij beter is: vijf cases veranderden, en over 20 gepaarde cases bevat het interval voor de winst nog steeds nul. Beide uitspraken zijn tegelijk waar. "Voldoet aan de eis" en "verslaat de baseline" zijn aparte vragen, en een suite zo klein beantwoordt de tweede alleen voor grote effecten. Meer echte berichten is de remedie.

Een dienst die een token nodig heeft

Start de dienst zo dat hij een bearer-token eist:

INTAKE_TOKEN=s3cret python server.py --port 8765 --rules v2 --require-token

system.http stuurt geen eigen headers, dus met een nieuwe version (zeg rules-v2-auth) wordt elke aanroep geweigerd:

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 │

en oloproof inspect RUN_ID --failures toont execution ERROR: TransientError: system returned HTTP 401 bij elke case. Uitvoeringsfouten zijn ontbrekend bewijs, geen mislukkingen: er werd niets waargenomen, dus elke regel is INSUFFICIENT_EVIDENCE.

De omweg is een Python-callable die het verzoek zelf doet. client.py voegt de header toe vanuit een omgevingsvariabele, zodat het token nooit in oloproof.yaml of een opgeslagen record komt:

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

Vervang het http:-blok in oloproof.yaml door:

system:
  name: order-intake
  version: rules-v2
  callable: client:extract

en draai met het token in de omgeving:

INTAKE_TOKEN=s3cret oloproof run

Alle 20 cases worden weer waargenomen en de gate laat door. De cache van de callable volgt zijn eigen broncode en version, niet de dienst erachter, dus dezelfde regel geldt: wijzig version wanneer je deployt.

Problemen oplossen

SymptoomOorzaak en oplossing
system connection failed (ConnectError) bij elke caseDe dienst draait niet, of luistert op een andere poort.
system connection failed (RemoteProtocolError)Iets anders antwoordt op die poort. Kies een vrije.
KeyError: "missing output path 'results'"output_path noemt een veld dat het antwoord niet heeft.
system returned HTTP 401 of 403Het endpoint heeft inloggegevens nodig: gebruik de omweg met de callable.
Je deployde een wijziging en de cacheregel toont alleen hitsDe version veranderde niet, dus de opgeslagen uitvoer werd hergebruikt.
an HTTP system needs a declared versionVoeg version toe onder system of system.http.

Beperkingen

  • Geen eigen headers, authenticatie, queryparameters of verzoeksjablonen op system.http: de input van de case is de JSON-body zoals hij is. Gebruik voor al het andere een callable.
  • Antwoorden moeten JSON zijn. Streamende antwoorden worden niet als stream gelezen.
  • Oloproof kan een deployment niet detecteren; de gedeclareerde version is de hele identiteit van wat antwoordde.
  • Je dienst beheert zijn eigen toestand: Oloproof stuurt verzoeken en legt antwoorden vast; het reset, sandboxt of draait niets terug dat een verzoek wijzigt.