Zum Inhalt springen

Anleitungen

Tutorial: eine Konversation über mehrere Turns

Ein ausführbarer Rundgang zur Evaluation eines Konversationsassistenten mit dem Python-SDK: Ihre Anwendung spielt eine geskriptete Konversation gegen eine frische Sitzung ab, zeichnet ihre Antworten als conversation/v1-Artefakt auf, und zwei Evaluatoren beurteilen jede ganze Konversation. Er läuft offline mit einem geskripteten Stellvertreter für das Judge-Modell, vergleicht dann eine Kandidatenkorrektur und endet mit der Alternative, Turns als geclusterte Fälle zu evaluieren.

Begriffe wie Intervall, Entscheidungszustand und Release-Aktion sind in Konzepte definiert; Aufzeichnen, was ein System getan hat behandelt Artefakte.

Was Oloproof hier tut und was nicht

OloproofIhre Anwendung
speichert die geskripteten Konversationen als Datensatz, identisch für jedes Systemsteuert die Konversation: stellt jeden geskripteten Turn der Reihe nach
prüft, dass jeder geskriptete Turn beantwortet wurde (ConversationCompleted)besitzt den Sitzungszustand, startet pro Fall eine frische Sitzung und setzt sie zurück
beurteilt das ganze Transkript mit einem Modell (ConversationJudge)entscheidet, was passiert, wenn sie nicht weitermachen kann, und zeichnet auf, dass sie aufgehört hat
berechnet Intervalle, vergleicht zwei Systeme und entscheidet gegen eine Policyzeichnet das conversation/v1-Artefakt auf

Oloproof hat keinen Nutzersimulator: Es schreibt nie einen Nutzer-Turn, die Nutzerseite ist also das, was der Datensatz skriptet. Es hat keine Metrik auf Turn-Ebene innerhalb einer aufgezeichneten Konversation und kann eine aufgezeichnete Konversation nicht gegen ein neues System abspielen. Die Konversations-Evaluatoren gibt es nur im Python-SDK: ConversationCompleted und ConversationJudge sind keine Evaluatortypen in oloproof.yaml, daher verwendet dieses Tutorial ein Skript statt oloproof run.

Voraussetzungen

  • Python 3.11 oder neuer und pip install oloproof, wie im Schnellstart.
  • Die Beispieldateien, die mit dem Paket ausgeliefert werden. Kopieren Sie sie in ein neues Verzeichnis, damit der Store des Laufs dort landet:
oloproof init --example conversation ~/oloproof-conversation
cd ~/oloproof-conversation
DateiWas sie ist
assistant.pydie getestete Anwendung: ein Tarif-Assistent mit Sitzungszustand
systems.pyder Adapter: spielt ein Skript ab, zeichnet conversation/v1 auf
judge_offline.pyder geskriptete Stellvertreter für das Judge-Modell
evaluate.pyführt die Evaluation, den Vergleich und die Turn-Alternative aus
release.yamldie Policy für einen Lauf
comparison.yamldie Policy für den Kandidaten gegen die Baseline
turns_release.yamldie Policy für die Turn-Alternative
data/conversations.jsonl40 geskriptete Konversationen
data/turns.jsonldieselben Konversationen, ein Fall pro Turn

Kein Schlüssel, kein Netzwerk und keine Provider-Kosten, bis zum optionalen Live-Schritt am Ende.

Die Anwendung

assistant.py beantwortet Fragen zu drei Preistarifen. Es hält ein Stück Zustand, den Tarif, um den es in der Konversation geht, damit eine Nachfrage wie "Does that include SSO?" das "that" auflösen kann. Die Baseline hat einen absichtlichen Fehler: Sie merkt sich den Tarif nicht, sodass eine Nachfrage zum Standardtarif beantwortet wird. Ein Nutzer, der nach einer Person fragt, beendet die Konversation mit HandoffRequested.

class PlanAssistant:
    def __init__(self, *, remembers_plan):
        self.remembers_plan = remembers_plan
        self.reset()

    def reset(self):
        """Forget everything, so one conversation never leaks into the next."""
        self.current_plan = None

    def ask(self, question): ...

Das ist der Teil, den Sie durch Ihre eigene Anwendung ersetzen: einen Chatbot-Client, eine Agentensitzung, eine HTTP-Sitzung zu Ihrem Dienst. Was immer es ist, es besitzt seinen Zustand und dessen Zurücksetzen; Oloproof sieht nur, was der Adapter aufzeichnet.

Der Datensatz: Das Skript ist die Eingabe

Eine Zeile von data/conversations.jsonl ist eine Konversation:

{"expected": {"plan": "enterprise"}, "id": "conv_00", "input": {"turns": ["What does the enterprise plan cost?", "Does that include SSO?"]}, "metadata": {"pattern": "pronoun_followup"}}

Die Turns des Nutzers sind Inhalt des Datensatzes, vom Digest der Suite erfasst und für jedes daran gemessene System identisch; das macht zwei Systeme vergleichbar. expected ist die Referenz, die dem Judge gezeigt wird. Die 40 Konversationen sind 24 mit einer Nachfrage, die keinen Tarif nennt, 12, die den Tarif in jedem Turn nennen, und 4, die im zweiten von drei Turns nach einer Person fragen.

Der Adapter

systems.py startet pro Fall eine frische Sitzung, stellt jeden geskripteten Turn der Reihe nach und zeichnet auf, was zurückkam:

from oloproof import CONVERSATION, current_case, system


def replay(case, *, remembers_plan):
    script = [str(turn) for turn in case["turns"]]
    session = PlanAssistant(remembers_plan=remembers_plan)  # a new session per case
    turns = []
    truncated = False
    for index, question in enumerate(script, start=1):
        try:
            reply = session.ask(question)
        except HandoffRequested:
            truncated = True  # the recording stops here and says so
            break
        turns.append({"index": index, "asked": question, "answer": reply["answer"]})
    current_case().artifact(
        CONVERSATION,
        {"turns": turns, "declared_turns": len(script), "truncated": truncated},
    )
    last = turns[-1]["answer"] if turns else None
    return {"answer": last, "turns_answered": len(turns)}


@system(name="plan-assistant", version="baseline", records=(CONVERSATION,))
def baseline(case):
    return replay(case, remembers_plan=False)


@system(name="plan-assistant", version="candidate-remembers-plan", records=(CONVERSATION,))
def candidate(case):
    return replay(case, remembers_plan=True)

Eine frische Sitzung pro Fall ist wichtig: Oloproof führt Fälle nebenläufig und in keiner festen Reihenfolge aus, und eine zwischen Fällen geteilte Sitzung würde den Zustand einer Konversation in eine andere durchsickern lassen. records= deklariert, dass das System das Artefakt aufzeichnet; ohne das werden die Konversations-Evaluatoren abgewiesen, bevor irgendetwas läuft, statt jeden Fall als fehlend zu zählen.

Das Artefakt conversation/v1

Was die Baseline für conv_00 aufgezeichnet hat, aus oloproof export RUN_ID (die cases.jsonl des Bundles):

{"declared_turns": 2, "truncated": false, "turns": [{"answer": "The enterprise plan costs a price agreed per contract.", "asked": "What does the enterprise plan cost?", "index": 1, ...}, {"answer": "The starter plan does not include SSO.", "asked": "Does that include SSO?", "index": 2, ...}]}

Und für eine Konversation, die nach einer Person fragte:

{"declared_turns": 3, "truncated": true, "turns": [{"answer": "The team plan costs $20 a month.", "asked": "What does the team plan cost?", "index": 1, ...}]}
FeldBedeutung
turns[].indexwelchen geskripteten Turn dies beantwortet; lückenlos ab 1
turns[].answerwas der Assistent zurückgab, beliebiges JSON
turns[].askedoptional, nur zum Lesen; die Engine ordnet über index zu
turns[].retrievaloptional, was dieser Turn abgerufen hat, in der Form retrieval/v1
declared_turnswie viele Turns das Skript deklariert hat
truncateddie Aufzeichnung endet vor dem Skript, egal was sie abgeschnitten hat

Das Artefakt wird beim Aufzeichnen geprüft: Eine Aufzeichnung mit weniger Turns als deklariert muss truncated: true sagen, Indizes müssen lückenlos sein, und eine Aufzeichnung kann nicht mehr Turns beantworten, als gestellt wurden. Ein fehlerhaftes Artefakt stoppt den Lauf mit einem SystemContractError.

Die beiden Evaluatoren, und warum beide

  • ConversationCompleted ist deterministisch: Hat der Assistent jeden geskripteten Turn beantwortet? Er läuft zuerst, weil jede andere Aussage über eine Konversation, die im ersten von drei Turns aufgehört hat, eine Aussage über eine andere Konversation ist. Eine abgeschnittene Konversation besteht ihn nicht; das ist ein Ergebnis, kein fehlender Fall.
  • ConversationJudge ist ein Modell-Judge über das ganze Transkript, jeden Nutzer- und Assistenten-Turn, weil die Fehler, die man einem Konversationsprodukt vorwirft, relational sind: Eine Antwort, die einer aus dem vorherigen Turn widerspricht, ist nur neben ihr falsch. Eine abgeschnittene Konversation wird nach dem Aufgezeichneten beurteilt, und das Transkript sagt dem Judge, wo sie aufgehört hat.
evaluators = [
    ConversationCompleted(),
    ConversationJudge(criterion="plan_coherent", provider=..., model=..., rubric_text=RUBRIC),
]

Offline beurteilen

Ein Judge braucht ein Modell. Um ohne Netzwerk zu laufen, übergibt judge_offline.py einen geskripteten Provider, denselben Helfer, den Oloproofs eigene Tests verwenden (FakeProvider aus den Interna der Engine, keine öffentliche API). Er beantwortet jeden Judge-Prompt nach einer festen Regel: bestanden, wenn jeder Assistenten-Turn den Tarif nennt, den die Referenz nennt. Das macht die Urteile deterministisch und das Tutorial reproduzierbar. Es misst nichts darüber, wie sich ein echtes Judge-Modell verhält.

Die Policy

release.yaml:

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
  - {id: completion-floor, metric: conversation_completed, min: 0.75}
  - {id: coherence-floor, metric: plan_coherent, min: 0.80}

Ausführen

python evaluate.py
baseline run run_...
  conversation_completed: 0.900 [0.763, 0.972] over 40 conversations
  plan_coherent: 0.400 [0.249, 0.567] over 40 conversations
  gate BLOCK (exit 3)
  completion-floor: PASS (lower_bound_meets_minimum)
  coherence-floor: INSUFFICIENT_EVIDENCE (evaluator_not_validated)
  first failing conversation:
    conversation_completed: passed=True {'turns_recorded': 2, 'turns_declared': 2, 'truncated': False}
    plan_coherent: passed=False {'provider_model': 'offline-rule'}

So lesen Sie es:

  • Vollständigkeit ist 36 von 40, die vier Übergaben. Ihre untere Schranke liegt über 0,75, also ist diese Regel PASS.
  • Kohärenz ist 40 %, und ihre Regel liest INSUFFICIENT_EVIDENCE mit evaluator_not_validated, nicht FAIL. Die Regeln eines Judges entscheiden erst, wenn der Judge gegen menschliche Labels gemessen wurde (require_validated_evaluators ist standardmäßig an; Judges erklärt es). Die Schätzung wird trotzdem angezeigt und ist trotzdem Evidenz: Sie kann nur nicht allein freigeben oder blockieren.
  • gate BLOCK (exit 3): Die Policy blockiert auf INSUFFICIENT_EVIDENCE. Exit 3 ist dieser Zustand, kein Fehlschlag.

Den Offline-Stellvertreter zu validieren wäre sinnlos, da er eine für dieses Beispiel geschriebene Regel ist. Mit einem echten Judge labeln Sie eine Stichprobe des Laufs mit oloproof review RUN_ID --criterion plan_coherent --by YOU --sample 20 und führen dann oloproof evaluators validate EVALUATOR_ID --by YOU aus.

Eine fehlgeschlagene Konversation untersuchen

Das SDK schreibt in denselben Store, den die CLI liest, .oloproof/ in dem Verzeichnis, aus dem Sie ausgeführt haben:

oloproof inspect RUN_ID --case conv_00
output: {
  "answer": "The starter plan does not include SSO.",
  "turns_answered": 2
}
judgments:
  conversation_completed: passed
  plan_coherent: failed
    judge text, not verified:
      every answer is about enterprise: False

Der Nutzer fragte nach dem Enterprise-Tarif, und die Nachfrage wurde zum Starter-Tarif beantwortet. oloproof inspect RUN_ID --failures listet jede fehlgeschlagene Konversation auf; alle 24 Nachfragen ohne Tarifnamen scheitern auf dieselbe Weise. Die nächste Maßnahme liegt in der Anwendung: den Tarif im Sitzungszustand behalten.

Eine Kandidatenänderung und der Vergleich

candidate in systems.py setzt remembers_plan=True. evaluate.py führt beide Systeme über dieselben Skripte aus und vergleicht sie Fall für Fall unter comparison.yaml:

rules:
  - {id: coherence-better, kind: superiority, metric: plan_coherent}
  - {id: completion-no-worse, kind: non_inferiority, metric: conversation_completed, margin: 0.05}
comparison, candidate minus baseline
  conversation_completed: +0.000 [-0.127, +0.127] over 40 pairs
  plan_coherent: +0.600 [+0.337, +0.817] over 40 pairs
  gate BLOCK (exit 3)
  coherence-better: INSUFFICIENT_EVIDENCE (evaluator_not_validated)
  completion-no-worse: INSUFFICIENT_EVIDENCE (interval_overlaps_margin)

Der Kohärenzunterschied ist groß und sein Intervall schließt null aus, aber der Judge ist nicht validiert, also entscheidet seine Regel immer noch nicht. Die Vollständigkeit ist unverändert, und 40 Paare können nicht zeigen, dass sie innerhalb von fünf Punkten liegt: Das Intervall reicht 12,7 Punkte in beide Richtungen. Beides weist auf denselben nächsten Schritt: den Judge validieren und Konversationen hinzufügen.

Die Alternative: Turns als Fälle, als Cluster analysiert

Ein Urteil auf Konversationsebene sagt, wie oft eine Konversation gut lief, nicht welcher Turn schiefging. Da Oloproof keine Metrik auf Turn-Ebene innerhalb einer aufgezeichneten Konversation hat, ist der andere Weg, jeden Turn zu einem eigenen Fall zu machen und die Turns einer Konversation mit group_id zu verbinden:

{"expected": {"plan": "enterprise"}, "group_id": "conv_00", "id": "conv_00_t2", "input": {"history": ["What does the enterprise plan cost?"], "question": "Does that include SSO?"}}

Das System spielt den geskripteten Verlauf in eine frische Sitzung ein und beantwortet dann den Turn:

@system(name="plan-assistant-turns", version="baseline")
def turn_baseline(case):
    session = PlanAssistant(remembers_plan=False)
    for earlier in case["history"]:
        session.ask(str(earlier))
    return session.ask(str(case["question"]))

Turns einer Konversation sind nicht unabhängig, sobald also ein Fall eine group_id hat, wird die Suite nach Clustern analysiert, mit einer approximativen Methode, die eine Policy akzeptieren muss (turns_release.yaml setzt allow_approximate_methods: true; Geclusterte Fälle erklärt es):

turns as cases: turn_plan 0.667 [0.588, 0.749] over 72 turns
  gate BLOCK (exit 1)
  turn-plan-floor: FAIL (upper_bound_below_minimum)

Der Zielkonflikt:

Ein Fall pro KonversationEin Fall pro Turn, geclustert
Einheit der RateKonversationen, die gut liefenrichtig beantwortete Turns
Effektiver Stichprobenumfangdie Anzahl der Konversationenweiterhin die Anzahl der Konversationen, nicht der Turns
Welcher Turn scheitertedas Transkript lesenjeder Turn hat sein eigenes Urteil
Verlauf, den jeder Turn siehtdie eigenen früheren Antworten des Assistentendie früheren Nutzer-Turns des Skripts, abgespielt
Erkennt Abdriften durch eigene frühere Antwortenjanein, jeder Turn beginnt mit einem geskripteten Verlauf
EvaluatorenConversationCompleted, ConversationJudge (nur SDK)jeder Evaluator, in YAML oder im SDK

Die Turn-Alternative schließt die vier Übergabe-Konversationen aus, ihre 72 Turns stammen also aus 36 Konversationen. Hier kann sie entscheiden, wo der Judge es nicht konnte, weil ExactMatch deterministisch ist und keine Validierung braucht.

Optional: ein echtes Judge-Modell

Dieser Schritt braucht ein Modell, das auf Ihrem Rechner bereitgestellt wird. Das Offline-Tutorial und sein Test führen ihn nicht aus. Mit laufendem Ollama und geladenem llama3.1:

python evaluate.py --live

Der Judge ruft dann http://localhost:11434/v1 mit provider="openai_compatible" auf. Ein Loopback-Server braucht keinen Schlüssel und sendet nichts vom Rechner weg. Ein Cloud-Provider braucht seinen Schlüssel in der Umgebung, sendet jedes Transkript an diesen Provider und kostet pro Urteil Geld. Die Urteile eines echten Modells unterscheiden sich von denen des Stellvertreters, die Zahlen oben ändern sich also, und seine Regeln lesen weiterhin evaluator_not_validated, bis Sie ihn validieren.

Fehlerbehebung

SymptomUrsache und Abhilfe
this evaluator needs exactly one conversation/v1 artifact; the case recorded 0Der Adapter hat current_case().artifact(CONVERSATION, ...) nicht aufgerufen oder vorher eine Ausnahme geworfen. Zeichnen Sie auch auf, wenn die Konversation früh endet.
malformed conversation/v1 artifact: ... 0 of 2 turns recorded and truncated is falseDer Lauf stoppt mit einem SystemContractError. Eine Aufzeichnung mit weniger Turns als declared_turns muss truncated: true setzen.
conversation turn indexes must be contiguous starting at 1Nummerieren Sie die Turns 1, 2, 3 nach dem geskripteten Turn, den sie beantworten.
evaluator 'conversation_completed' needs conversation/v1 artifacts, but system ... does not declare that it records themFügen Sie records=(CONVERSATION,) zum @system-Dekorator hinzu.
Input tag 'conversation_completed' found using 'type' does not match any of the expected tags aus oloproof runKonversations-Evaluatoren gibt es nur im SDK. Verwenden Sie ein Skript wie hier.
Antworten sickern zwischen Konversationen durchEine Sitzung wird zwischen Fällen geteilt. Erzeugen Sie eine pro Fall.
Kohärenzregeln entscheiden nieDer Judge ist nicht validiert. Validieren Sie ihn oder setzen Sie require_validated_evaluators: false bewusst.

Einschränkungen

  • Kein Nutzersimulator: Jeder Nutzer-Turn stammt aus dem Skript des Datensatzes, die Konversation kann also nicht abhängig davon verzweigen, was der Assistent gesagt hat.
  • Keine Metrik auf Turn-Ebene innerhalb einer aufgezeichneten Konversation; verwenden Sie stattdessen Turns als geclusterte Fälle, mit dem Zielkonflikt oben.
  • Kein Konversations-Replay: Eine aufgezeichnete Konversation kann nicht gegen ein anderes System erneut ausgeführt werden. Zwei Systeme zu vergleichen bedeutet, dass jedes dasselbe Skript abspielt.
  • ConversationCompleted und ConversationJudge gibt es nur im Python-SDK.
  • Nur Text: Einem Judge wird JSON-Text gezeigt, nie Bilder oder Audio.
  • Der Offline-Judge ist eine geskriptete Regel. Seine Urteile zeigen die Mechanik, nicht die Genauigkeit eines echten Judges.

Wie es weitergeht

  • Judges behandelt Provider, Validierung und Rekalibrierung.
  • Geclusterte Fälle behandelt group_id und das Opt-in für approximative Methoden.
  • Die Python-API behandelt evaluate und evaluate_comparison.
  • Agenten behandelt dieselbe Grenze für die Tool-Schleife eines Agenten.