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 chatsFü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: trueDer 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 8797Die 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.80Keine 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 runGate: 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.yamlno-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 runGate: 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.yamlgoal_reached: +26.7 points [+1.2, +54.1] · 30 paired · 0 missing · 0 excluded
resolves-more goal_reached superiority PASS difference_above_zeroJeder 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: 6Ollama 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
| Symptom | Ursache und Abhilfe |
|---|---|
| Jede Regel ist INSUFFICIENT_EVIDENCE mit simulated_user_not_validated | Niemand 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 input | Ein Fall hat keine Persona, oder sie steht unter einem anderen Schlüssel: Setzen Sie persona_field. |
| provider connection failed bei jedem Fall | Der 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 Oloproof | Der 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.