Zum Inhalt springen

Anleitungen

Tutorial: eine Anwendung hinter einem HTTP-Endpunkt

Evaluieren Sie einen Dienst, den Sie über HTTP erreichen, ohne seinen Code zu importieren: Richten Sie Oloproof auf die URL, führen Sie die Suite aus, finden Sie, was er verpasst, deployen Sie eine Änderung und vergleichen Sie. Ein winziger lokaler Dienst steht stellvertretend für Ihren, sodass alles offline läuft.

Was Sie bauen

Einen Dienst zur Auftragsannahme, der eine Kundennachricht liest und zwei Felder extrahiert: einen intent (where_is_order, cancel, return oder other) und eine order_id (vier Ziffern oder null). Sie prüfen das Format jeder Antwort, wofür keine Referenz nötig ist, und messen, ob jedes Feld stimmt, wofür eine nötig ist. Begriffe wie Fall, Lauf, Metrik und Gate sind in Kernkonzepte definiert.

Voraussetzungen

  • Python 3.11 oder neuer und Oloproof in einer virtuellen Umgebung:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • Das Beispielprojekt, das mit dem Paket ausgeliefert wird. Kopieren Sie es in ein neues Verzeichnis und arbeiten Sie dort (httpx, das client.py importiert, wird mit Oloproof installiert):
oloproof init --example http order-intake
cd order-intake
  • Port 8765 frei auf diesem Rechner. Ist er belegt, wählen Sie einen anderen und ändern Sie ihn sowohl im Serverbefehl als auch in oloproof.yaml.

Nichts hier nutzt einen Provider, einen API-Schlüssel oder das Internet: Der Dienst lauscht auf 127.0.0.1.

Die Dateien

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

Führen Sie die Befehle in order-intake/ aus. Starten Sie den Dienst in einem zweiten Terminal und lassen Sie ihn laufen:

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

Der HTTP-Vertrag

Für jeden Fall sendet Oloproof eine Anfrage: den input des Falls als JSON-Body, mit der Methode, die Sie deklarieren (standardmäßig POST). Die Antwort liest es als JSON. Ein Status von 400 oder höher, ein Timeout oder eine abgelehnte Verbindung wird als Ausführungsfehler für diesen Fall erfasst, nie als falsche Antwort.

Anfrage und Antwort für einen Fall:

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 weist Oloproof an, nur result als Ausgabe des Falls zu behalten; ohne diese Angabe ist der ganze Body die Ausgabe. Ein Pfad mit Punkten wie data.answer reicht tiefer.

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

Ein HTTP-System muss eine version deklarieren. Oloproof kann ein Deployment nicht sehen: Es cacht die Ausgabe jedes Falls unter der URL, der Methode, dem Ausgabepfad und dieser Version, also teilen Sie ihm über die Version mit, dass sich der Dienst geändert hat. Vergessen Sie, sie zu ändern, wird ein neues Deployment nie aufgerufen.

Header und Authentifizierung lassen sich auf system.http noch nicht konfigurieren. Der Abschnitt über Tokens weiter unten zeigt den Umweg.

Der Datensatz

{"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 ist genau der Anfrage-Body. expected enthält die Referenz für jedes Feld; null ist ein echter Referenzwert und bedeutet "diese Nachricht enthält keine Auftragsnummer".

Die Evaluatoren wählen

KriteriumEvaluatorBraucht expectedMisst
format_validjson_schema über die AusgabeneinFormat: ein bekannter Intent und eine wohlgeformte ID
intent_correctexact_match auf intentjaAufgabenerfolg für das erste Feld
order_id_correctexact_match auf order_idjaAufgabenerfolg für das zweite Feld

Das Schema ließe {"intent": "other", "order_id": null} für jede Nachricht bestehen: wohlgeformt und nutzlos. Nur die Referenzprüfungen sagen, ob der Dienst seine Arbeit getan hat. Die Felder getrennt zu bewerten zeigt, welches fehlschlägt, was eine kombinierte Prüfung verbergen würde.

release.yaml erlaubt keinen Formatfehler und verlangt, dass jedes Feld in mindestens 70% der Fälle stimmt, beurteilt am 95%-Intervall:

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

Ausführen

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

Jede Antwort ist wohlgeformt. Jedes Feld stimmt 15 von 20 Mal, aber 20 Fälle lassen ein Intervall bis hinunter zu 50,8%, also ist keine der beiden Untergrenzen von 70% belegt: INSUFFICIENT_EVIDENCE, und das Gate blockiert mit Exit 3.

Die Fehlschläge untersuchen

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

Lesen Sie die Eingaben daneben (oloproof inspect RUN_ID --case m04), und zwei Fehler werden sichtbar: Das ID-Muster passt nur auf "order 1234", nicht auf "order #5120", "order no. 8123" oder ein bloßes "#1673"; und Formulierungen wie "send back", "stop order" und "where's my parcel" werden keinem Intent zugeordnet. Das ist der nächste Schritt: beide Regeln erweitern.

Eine Änderung deployen

Stoppen Sie den Dienst und starten Sie den Kandidaten, der diese Korrekturen enthält:

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

Ändern Sie version: rules-v1 in version: rules-v2 in oloproof.yaml, denn die URL hat sich nicht geändert, und Oloproof würde sonst die alten Ausgaben wiederverwenden. Dann:

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 Untergrenzen bestehen, und der Befehl endet mit 0.

Die beiden Deployments vergleichen

compare.yaml fragt, ob die Intents des Kandidaten besser sind als die der 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)

Der Kandidat erfüllt seine eigenen Untergrenzen, und doch kann der Vergleich nicht zeigen, dass er besser ist: Fünf Fälle haben sich geändert, und über 20 gepaarte Fälle enthält das Intervall für den Gewinn noch null. Beide Aussagen sind zugleich wahr. "Erfüllt die Anforderung" und "schlägt die Baseline" sind getrennte Fragen, und eine so kleine Suite beantwortet die zweite nur bei großen Effekten. Mehr echte Nachrichten sind die Abhilfe.

Ein Dienst, der ein Token braucht

Starten Sie den Dienst so, dass er ein Bearer-Token verlangt:

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

system.http sendet keine eigenen Header, daher wird mit einer neuen version (etwa rules-v2-auth) jeder Aufruf abgelehnt:

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 │

und oloproof inspect RUN_ID --failures zeigt bei jedem Fall execution ERROR: TransientError: system returned HTTP 401. Ausführungsfehler sind fehlende Evidenz, keine Fehlschläge: Nichts wurde beobachtet, also ist jede Regel INSUFFICIENT_EVIDENCE.

Der Umweg ist ein Python-Callable, das die Anfrage selbst stellt. client.py fügt den Header aus einer Umgebungsvariable hinzu, sodass das Token nie in oloproof.yaml oder einen gespeicherten Datensatz gelangt:

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

Ersetzen Sie den http:-Block in oloproof.yaml durch:

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

und führen Sie mit dem Token in der Umgebung aus:

INTAKE_TOKEN=s3cret oloproof run

Alle 20 Fälle werden wieder beobachtet, und das Gate lässt durch. Der Cache des Callables folgt seinem eigenen Quellcode und seiner version, nicht dem Dienst dahinter, daher gilt dieselbe Regel: Ändern Sie version, wenn Sie deployen.

Fehlerbehebung

SymptomUrsache und Abhilfe
system connection failed (ConnectError) bei jedem FallDer Dienst läuft nicht oder lauscht auf einem anderen Port.
system connection failed (RemoteProtocolError)Etwas anderes antwortet auf diesem Port. Wählen Sie einen freien.
KeyError: "missing output path 'results'"output_path nennt ein Feld, das die Antwort nicht hat.
system returned HTTP 401 oder 403Der Endpunkt braucht Zugangsdaten: Nutzen Sie den Umweg über ein Callable.
Sie haben eine Änderung deployt, und die Cache-Zeile zeigt nur TrefferDie version hat sich nicht geändert, also wurden die gespeicherten Ausgaben wiederverwendet.
an HTTP system needs a declared versionErgänzen Sie version unter system oder system.http.

Einschränkungen

  • Keine eigenen Header, keine Authentifizierung, keine Query-Parameter und keine Anfrage-Vorlagen auf system.http: Der input des Falls ist der JSON-Body, so wie er ist. Nutzen Sie für alles andere ein Callable.
  • Antworten müssen JSON sein. Streaming-Antworten werden nicht als Stream gelesen.
  • Oloproof kann ein Deployment nicht erkennen; die deklarierte version ist die gesamte Identität dessen, was geantwortet hat.
  • Ihr Dienst verwaltet seinen Zustand selbst: Oloproof sendet Anfragen und erfasst Antworten; es setzt nichts zurück, isoliert nichts und macht nichts rückgängig, was eine Anfrage verändert.