Zum Inhalt springen

Anleitungen

Tutorial: ein Support-Desk gegen simulierte Kunden

Evaluieren Sie einen Konversationsassistenten gegen Nutzer, die auf das reagieren, was er sagt. Dreißig simulierte Kunden, jeder mit einem Ziel und den Fakten, die er kennt, sprechen mit dem Support-Desk eines Shops; Oloproof misst, ob jeder bekommen hat, weswegen er gekommen ist, hält jede Entscheidung zurück, bis eine Person die simulierten Kunden realistisch gefunden hat, und entscheidet dann über ein Release und einen Vergleich. Jeder Schritt läuft auf diesem Rechner ohne Modell und ohne Netzwerk, und ein optionaler Schritt setzt ein echtes Modell als Kunden ein.

Was Sie bauen

Der Support-Desk eines Shops beantwortet Fragen zu Erstattungen, Lieferungen und Adressänderungen. Drei Fragen entscheiden, ob eine Version ausgeliefert werden darf:

  • Hat jeder Kunde bekommen, weswegen er gekommen ist? goal_reached, aus einer Prüfung nach jeder Antwort.
  • Hat eine Antwort etwas versprochen, was der Shop nicht versprechen kann? every_turn durchsucht jede Antwort nach dem Wort „guarantee“.
  • Lässt sich den Antworten zutrauen, etwas über echte Kunden auszusagen? Erst dann, wenn eine Person eine Stichprobe der Konversationen gelesen und die simulierten Kunden realistisch gefunden hat.

Begriffe wie Fall, Lauf, Gate und Suite sind in Kernkonzepte definiert; alles über simulierte Nutzer steht in Simulierte Nutzer.

Voraussetzungen

  • Python 3.11 oder neuer und Oloproof:
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:
oloproof init --example simulated-users support-desk
cd support-desk
  • Port 8797 frei für den simulierten Ersatz-Nutzer (andernfalls ändern Sie ihn an beiden Stellen).

Jeder Schritt bis „Optional: ein echtes Modell als Kunden“ ist offline und deterministisch: kein API-Schlüssel, kein Provider-Konto, keine Kosten.

Die Dateien

support-desk/
  desk.py                      the support desk under test (baseline)
  desk_v2.py                   the candidate change
  user_server.py               a stand-in simulated user speaking the OpenAI API on 127.0.0.1
  oloproof.yaml                the suite: the simulated user and four evaluators
  release.yaml                 rules for a run, with the realism bar
  compare.yaml                 a rule for a comparison
  data/customers.jsonl         30 personas
  labels/realism_verdicts.csv  one person's realism verdicts on the baseline's conversations
  fill_realism.py              copies those verdicts into a labelling sheet
  logged.py                    replays logged chats with human agents
  real.yaml                    a suite of 20 of those chats
  logged_release.yaml          a rule for the logged chats
  data/logged_chats.jsonl      the 20 chats

Führen Sie jeden Befehl in support-desk/ aus.

Der Desk behält seine eigene Schleife

# desk.py (excerpt)
def talk(case: dict[str, Any]) -> dict[str, Any]:
    user = current_case().simulated_user()
    desk = Desk()
    message = user.opening()
    while message is not None:
        message = user.reply(desk.answer(message))
    return {"need": desk.need, "order": desk.order}

Der Desk fragt nach der ersten Nachricht des Kunden, beantwortet sie und gibt jede Antwort für die nächste Nachricht zurück, bis der Kunde aufhört. Er sieht die Persona nie. Oloproof zeichnet auf, was zwischen beiden ausgetauscht wurde; der Desk selbst zeichnet nichts auf. Diese Baseline kann keine Lieferadresse ändern, und sie verspricht Erstattungen „guaranteed within 5 days“.

Die Personas

Jeder Fall ist ein Kunde: 12 wollen eine Erstattung, 10 wollen wissen, wann eine Bestellung ankommt, und 8 sind umgezogen. Zum Beispiel:

{"id": "c25", "input": {"persona": {"description": "A retired teacher who ordered a cookbook last week. Writes short, polite messages.", "goal": "Have the order of a cookbook delivered to my new address.", "facts": {"order": "B-35", "postcode": "10275"}}}}

Der Kunde nennt die Bestellnummer und die Postleitzahl erst auf Nachfrage. Drei Personas setzen außerdem ein opening, ihre erste Nachricht Wort für Wort.

Die Suite

version: 1
project: support-desk
dataset: data/customers.jsonl
system:
  name: support-desk
  version: baseline
  callable: desk:talk
  # A model plays each case's persona; here the stand-in in user_server.py.
  simulated_user:
    provider: openai_compatible
    model: stand-in-user
    base_url: http://127.0.0.1:8797/v1
    max_turns: 6
evaluators:
  # The desk answered every message the customer sent.
  - type: conversation_completed
  # The customer said they got what they came for.
  - type: goal_reached
  # ... within three of their own messages.
  - type: turns_to_goal
    max_turns: 3
  # No answer promises what the shop cannot promise.
  - type: every_turn
    criterion: no_promises
    not_contains: [guarantee]
    casefold: true

Der simulierte Ersatz-Nutzer

Ein simulierter Nutzer ist ein Modell, das jede Persona spielt. Hier ist es user_server.py, das dieselbe API auf 127.0.0.1 spricht und kein Modell ist: Es liest die Persona aus dem Prompt, den Oloproof sendet, nennt sein Ziel, gibt seine Bestellnummer oder Postleitzahl auf Nachfrage an, geht, wenn der Desk nicht helfen kann, und sagt auf die Frage, ob sein Ziel erreicht wurde, ja, wenn die letzte Antwort des Desks mit „Done:“ beginnt. Starten Sie es in einem zweiten Terminal und lassen Sie es laufen:

python user_server.py --port 8797

Die Policy

version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
require_validated_evaluators: true
minimum_simulated_user_realism: 0.80
rules:
  - id: no-promises
    metric: no_promises
    kind: observed_count
    max_failures: 0
  - id: resolved
    metric: goal_reached
    min: 0.80

Keine Antwort darf etwas versprechen. Mindestens 80 % der Kunden müssen ihr Ziel erreichen, entschieden nur dann, wenn die untere Grenze des Intervalls 80 % übersteigt. Und keine Regel entscheidet über simulierte Kunden, bevor Menschen mindestens 80 % ihrer Konversationen realistisch gefunden haben, gemessen an derselben Art unterer Grenze.

Ausführen

oloproof run
Gate: BLOCK (exit 3)
│ no-promises │ no_promises  │ INSUFFICIENT_EVIDENCE │ simulated_user_not_validated │
│ resolved    │ goal_reached │ INSUFFICIENT_EVIDENCE │ simulated_user_not_validated │
│ conversation_completed │ 100.0%   │ [88.4%, 100.0%] │ 30 / 30 observed · 0 missing · 0 excluded │
│ goal_reached           │ 73.3%    │ [54.1%, 87.8%]  │ 22 / 30 observed · 0 missing · 0 excluded │
│ goal_within_3_turns    │ 73.3%    │ [54.1%, 87.8%]  │ 22 / 30 observed · 0 missing · 0 excluded │
│ no_promises            │ 60.0%    │ [40.6%, 77.4%]  │ 18 / 30 observed · 0 missing · 0 excluded │

Jeder Kunde bekam Antworten, 22 von 30 haben ihr Ziel erreicht, und 12 Konversationen enthalten ein Versprechen. Noch ist nichts entschieden: Diese Kunden sind simuliert, und niemand hat geprüft, ob sie sich wie echte verhalten.

Die simulierten Kunden lesen

oloproof simulated-users RUN_ID
  Realism: no conversation has been drawn and labelled yet.
  Not validated: nothing labelled yet, against a bar of 80.0%.
Features (median [quartiles]; descriptive, never a gate):
  user messages           2 [1, 2]
  user message length     49 [10, 67]
  How the simulated conversations ended: goal_reached 73%, user_left 27%

Die acht, die gegangen sind, sind die Kunden, die umgezogen waren: Die Baseline kann keine Adresse ändern.

Die Konversationen auf Realitätsnähe labeln

Ziehen Sie eine Zufallsstichprobe der Konversationen des Laufs in eine Tabelle, lassen Sie eine Person jede davon lesen, mit der Persona und wie sie endete, und markieren, ob ein echter Kunde die Seite des Kunden geschrieben und die Konversation genau dort, auf diese Weise, beendet haben könnte. Hier vertritt fill_realism.py diese Person und kopiert ihre Urteile aus labels/realism_verdicts.csv:

oloproof labels export RUN_ID --criterion simulated_user_realistic --sample 30 --local --out realism.csv
python fill_realism.py realism.csv
oloproof labels import realism.csv
oloproof simulated-users RUN_ID
  Realism: 29 of 30 drawn conversations labelled realistic (30 labelled), [82.8%, 99.9%] at 95%
  Validated: the lower bound clears the bar of 80.0%.

Die Person fand eine Konversation unrealistisch: Kunde c05 beginnt mit „Order: F-15. Refund.“, was sich wie ein Formular liest, nicht wie ein Mensch. Bei 29 von 30 liegt die exakte untere Schranke bei 82.8 %, über der Messlatte, sodass Regeln jetzt über diese Kunden entscheiden dürfen.

oloproof gate RUN_ID --policy release.yaml
no-promises: FAIL (observed_failures_exceed_limit)
resolved: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)

Eine echte Änderung vornehmen

desk_v2.py ändert eine Adresse, sobald es die Bestellung und die neue Postleitzahl hat, und sagt, es habe eine Erstattung eingeleitet, die „usually arrives within 5 days“, statt sie zu versprechen. Kopieren Sie es über desk.py, setzen Sie version: candidate unter system in oloproof.yaml, lassen Sie den Ersatz-Nutzer laufen, und:

oloproof run
Gate: ALLOW (exit 0)
│ no-promises │ no_promises  │ PASS  │ observed_failures_within_limit │
│ resolved    │ goal_reached │ PASS  │ lower_bound_meets_minimum      │

Der Kandidat traf auf dieselben simulierten Kunden auf derselben Suite, also deckt die an den Konversationen der Baseline gemessene Realitätsnähe ihn ab: Eine Validierung gilt für einen simulierten Nutzer auf einer Suite, gleich mit welchem System er gesprochen hat.

Den Kandidaten mit der Baseline vergleichen

oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
goal_reached: +26.7 points [+1.2, +54.1] · 30 paired · 0 missing · 0 excluded
  resolves-more  goal_reached  superiority  PASS  difference_above_zero

Jeder Kunde traf auf beide Desks, also ist der Vergleich Kunde für Kunde gepaart.

Simuliert neben echt

Labels zur Realitätsnähe beurteilen jede Konversation für sich. Die simulierten Kunden neben echte zu stellen zeigt, was eine Stichprobe übersehen kann. real.yaml spielt 20 Chats ab, die die menschlichen Mitarbeiter des Shops mit echten Kunden geführt haben:

oloproof run --config real.yaml --policy logged_release.yaml
oloproof simulated-users BASELINE_RUN_ID --real REAL_RUN_ID
                          simulated (run_01M52Q1C)    real (run_01M52Q25)
  user messages           2 [1, 2]                    3 [3, 4]
  user message length     49 [10, 67]                 13 [4, 32]

Echte Kunden schickten mehr und kürzere Nachrichten: Sie verteilen, was sie wollen, auf drei Zeilen, wo der Ersatz-Nutzer es in einem ordentlichen Satz sagt. Das ist bei jedem simulierten Nutzer wissenswert, und es entscheidet hier nie etwas: Zwei ungepaarte Stichproben zu vergleichen braucht eine Methode, die Oloproof nicht zugelassen hat.

Optional: ein echtes Modell als Kunden

Dieser Schritt verlässt den Offline-Pfad. Er braucht ein Chat-Modell: lokal Ollama (ollama pull qwen2.5vl oder ein beliebiges Chat-Modell), ohne Schlüssel und ohne Kosten; in der Cloud provider: anthropic oder openai mit api_key_env, abgerechnet pro Nachricht. Richten Sie den simulierten Nutzer in oloproof.yaml darauf aus:

  simulated_user:
    provider: openai_compatible
    model: qwen2.5vl
    base_url: http://localhost:11434/v1
    max_turns: 6

Ollama beantwortet eine Anfrage nach der anderen, also setzen Sie concurrency: {system: 1} und ein timeout_s von einigen Minuten. Ein anderes Modell ist ein anderer simulierter Nutzer: Die Labels zur Realitätsnähe oben decken es nicht ab, also brauchen seine Konversationen eine eigene Stichprobe, bevor eine Regel über sie entscheidet.

Zehn der Personas, gespielt von qwen2.5vl auf diesem Rechner gegen den Kandidaten-Desk, erreichten ihr Ziel in 8 Konversationen, wo der Ersatz-Nutzer es in allen 30 erreichte. Das Modell fand, was der Ersatz-Nutzer nicht finden konnte: Zwei Kunden fragten nach dem „status“ und dem „shipping status“ ihrer Bestellung, Wörter, die der Desk nicht kennt, und bekamen jedes Mal seine Begrüßung zurück. Einem gingen die Nachrichten aus. Der Desk verstand den dritten Versuch des anderen, und dieser Kunde ging trotzdem: Die Zielprüfung, das Urteil eines Modells, übersah die Antwort. Ein Label zur Realitätsnähe ist der Ort, an dem sich das zeigt: Ein echter Kunde, dem gesagt wird, wann seine Bestellung ankommt, geht nicht unzufrieden. Anders als der Ersatz-Nutzer gab das Modell außerdem oft seine Bestellnummer an, bevor es gefragt wurde, und schrieb eine längere Nachricht, wo der Ersatz-Nutzer zwei kurze schrieb.

Fehlerbehebung

SymptomUrsache und Abhilfe
Jede Regel ist INSUFFICIENT_EVIDENCE mit simulated_user_not_validatedNiemand hat die Konversationen dieses simulierten Nutzers auf dieser Suite gelabelt, oder der realistische Anteil erreicht die Messlatte nicht. Labeln Sie eine Stichprobe wie oben.
a case for a simulated user needs a 'persona' object in its inputEin Fall hat keine Persona, oder sie steht unter einem anderen Schlüssel: Setzen Sie persona_field.
provider connection failed bei jedem FallDer Ersatz-Nutzer läuft nicht oder lauscht auf einem anderen Port als dem, den base_url nennt.
the simulated user's goal check answered ...Das Modell hat die Zielprüfung nicht mit ja oder nein beantwortet. Versuchen Sie ein anderes Modell oder ein größeres.
conversation/v1 is written by OloproofDer Desk hat die Konversation selbst aufgezeichnet. Entfernen Sie das: Oloproof zeichnet auf, was durch den simulierten Nutzer gegangen ist.

Einschränkungen

  • Der Ersatz-Kunde ist eine Regel. Seine Konversationen zeigen die Mechanik; die eines echten Modells sind unordentlicher, wie der optionale Schritt zeigt.
  • Ob ein Ziel erreicht wurde, ist das Urteil des Modells des simulierten Nutzers, geprüft nur über die Labels zur Realitätsnähe.
  • Ein gehosteter Workspace entscheidet noch nicht über simulierte Nutzer.
  • Simulierte Kunden sind keine echten: Ein validierter simulierter Nutzer ist Evidenz über Kunden wie seine Personas, so weit Menschen ihn realistisch fanden, und nicht weiter.