Handleidingen
Tutorial: een supportdesk tegen gesimuleerde klanten
Evalueer een conversationele assistent tegen gebruikers die reageren op wat hij zegt. Dertig gesimuleerde klanten, elk met een doel en de feiten die ze kennen, praten met de supportdesk van een winkel; Oloproof meet of elk van hen kreeg waarvoor ze kwamen, houdt elke beslissing achter totdat een mens de gesimuleerde klanten realistisch heeft bevonden, en beslist daarna over een release en een vergelijking. Elke stap draait op deze machine zonder model en zonder netwerk, en een optionele stap zet een echt model in als de klanten.
Wat je gaat bouwen
De supportdesk van een winkel beantwoordt vragen over terugbetalingen, levering en adreswijzigingen. Drie vragen beslissen of een versie mag worden uitgebracht:
- Kreeg elke klant waarvoor hij kwam? goal_reached, uit een controle na elk antwoord.
- Beloofde een antwoord iets wat de winkel niet kan beloven? every_turn leest elk antwoord op het woord "guarantee".
- Mag je erop vertrouwen dat de antwoorden iets zeggen over echte klanten? Pas zodra een mens een steekproef van de gesprekken heeft gelezen en de gesimuleerde klanten realistisch heeft bevonden.
Termen als case, run, gate en suite worden gedefinieerd in Kernbegrippen; alles over gesimuleerde gebruikers staat in Gesimuleerde gebruikers.
Vereisten
- Python 3.11 of nieuwer, en Oloproof:
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:
oloproof init --example simulated-users support-desk
cd support-desk- Poort 8797 vrij voor de vervangende gesimuleerde gebruiker (pas hem anders op beide plaatsen aan).
Elke stap tot "Optioneel: een echt model als de klanten" is offline en deterministisch: geen API-sleutel, geen provideraccount, geen kosten.
De bestanden
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 chatsVoer elk commando uit vanuit support-desk/.
De desk houdt zijn eigen lus
# 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}De desk vraagt om het eerste bericht van de klant, beantwoordt het, en geeft elk antwoord terug voor het volgende bericht, totdat de klant stopt. Hij ziet de persona nooit. Oloproof legt vast wat er tussen hen werd uitgewisseld; de desk legt zelf niets vast. Deze baseline kan geen leveringsadres wijzigen, en hij belooft terugbetalingen "guaranteed within 5 days".
De persona's
Elke case is één klant: 12 willen een terugbetaling, 10 willen weten wanneer een bestelling aankomt, en 8 zijn verhuisd. Bijvoorbeeld:
{"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"}}}}De klant geeft het ordernummer en de postcode alleen wanneer erom gevraagd wordt. Drie persona's stellen ook een opening in, hun eerste bericht woord voor woord.
De 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: trueDe vervangende gesimuleerde gebruiker
Een gesimuleerde gebruiker is een model dat elke persona speelt. Hier is het user_server.py, dat dezelfde API spreekt op 127.0.0.1 en geen model is: hij leest de persona uit de prompt die Oloproof stuurt, noemt zijn doel, geeft zijn ordernummer of postcode wanneer erom gevraagd wordt, vertrekt wanneer de desk niet kan helpen, en zegt, gevraagd of zijn doel bereikt is, ja wanneer het laatste antwoord van de desk begint met "Done:". Start hem in een tweede terminal en laat hem draaien:
python user_server.py --port 8797De 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.80Geen enkel antwoord mag iets beloven. Minstens 80% van de klanten moet zijn doel bereiken, alleen beslist wanneer de ondergrens van het interval boven 80% uitkomt. En geen enkele regel beslist over gesimuleerde klanten totdat mensen minstens 80% van hun gesprekken realistisch hebben bevonden, gemeten aan dezelfde soort ondergrens.
Draaien
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 │Elke klant kreeg antwoord, 22 van de 30 bereikten hun doel, en 12 gesprekken bevatten een belofte. Er is nog niets beslist: deze klanten zijn gesimuleerd, en niemand heeft gecontroleerd of ze zich gedragen als echte.
De gesimuleerde klanten lezen
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%De acht die vertrokken, zijn de klanten die verhuisd waren: de baseline kan geen adres wijzigen.
De gesprekken labelen op realisme
Trek een willekeurige steekproef van de gesprekken van de run in een sheet, laat een mens elk ervan lezen, met de persona en hoe het eindigde, en laat hem aangeven of een echte klant de kant van de klant had kunnen schrijven en het daar, op die manier, had kunnen beëindigen. Hier speelt fill_realism.py die persoon en kopieert zijn oordelen uit 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%.De persoon vond één gesprek onrealistisch: klant c05 opent met "Order: F-15. Refund.", wat leest als een formulier, niet als een mens. Met 29 van de 30 is de exacte ondergrens 82.8%, boven de lat, dus regels mogen nu over deze klanten beslissen.
oloproof gate RUN_ID --policy release.yamlno-promises: FAIL (observed_failures_exceed_limit)
resolved: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)Een echte wijziging maken
desk_v2.py wijzigt een adres zodra hij de bestelling en de nieuwe postcode heeft, en zegt dat hij een terugbetaling heeft gestart die "usually arrives within 5 days" in plaats van die te beloven. Kopieer het over desk.py, zet version: candidate onder system in oloproof.yaml, laat de vervanger draaien, en:
oloproof runGate: ALLOW (exit 0)
│ no-promises │ no_promises │ PASS │ observed_failures_within_limit │
│ resolved │ goal_reached │ PASS │ lower_bound_meets_minimum │De kandidaat ontmoette dezelfde gesimuleerde klanten op dezelfde suite, dus het realisme dat op de gesprekken van de baseline werd gemeten, dekt hem: één validatie dekt één gesimuleerde gebruiker op één suite, met welk systeem hij ook sprak.
De kandidaat met de baseline vergelijken
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_zeroElke klant ontmoette beide desks, dus de vergelijking is gepaard, klant per klant.
Gesimuleerd naast echt
Realismelabels beoordelen elk gesprek op zichzelf. De gesimuleerde klanten naast echte zetten laat zien wat een steekproef kan missen. real.yaml speelt 20 chats opnieuw af die de menselijke medewerkers van de winkel met echte klanten voerden:
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 klanten stuurden meer en kortere berichten: ze zeggen wat ze willen verspreid over drie regels, waar de vervanger het in één nette zin zegt. Dat is het weten waard over elke gesimuleerde gebruiker, en het beslist hier nooit iets: twee ongepaarde steekproeven vergelijken vraagt een methode die Oloproof niet heeft toegelaten.
Optioneel: een echt model als de klanten
Deze stap verlaat het offline pad. Hij heeft een chatmodel nodig: lokaal Ollama (ollama pull qwen2.5vl, of elk chatmodel), zonder sleutel en zonder kosten; in de cloud provider: anthropic of openai met api_key_env, afgerekend per bericht. Laat de gesimuleerde gebruiker in oloproof.yaml ernaar wijzen:
simulated_user:
provider: openai_compatible
model: qwen2.5vl
base_url: http://localhost:11434/v1
max_turns: 6Ollama beantwoordt één verzoek tegelijk, dus zet concurrency: {system: 1} en een timeout_s van een paar minuten. Een ander model is een andere gesimuleerde gebruiker: de realismelabels hierboven dekken hem niet, dus zijn gesprekken hebben een eigen steekproef nodig voordat een regel erover beslist.
Tien van de persona's, gespeeld door qwen2.5vl op deze machine tegen de kandidaat-desk, bereikten hun doel in 8 gesprekken, waar de vervanger het in alle 30 bereikte. Het model vond wat de vervanger niet kon vinden: twee klanten vroegen naar de "status" en de "shipping status" van hun bestelling, woorden die de desk niet kent, en kregen elke keer zijn begroeting terug. De een raakte door zijn berichten heen. De desk begreep de derde poging van de ander, en die klant vertrok toch: de doelcontrole, het oordeel van een model, miste het antwoord. Een realismelabel is waar dat zichtbaar wordt: een echte klant die hoort wanneer zijn bestelling aankomt, vertrekt niet ontevreden. Anders dan de vervanger gaf het model ook vaak zijn ordernummer voordat erom gevraagd werd, en schreef het één langer bericht waar de vervanger er twee korte schreef.
Problemen oplossen
| Symptoom | Oorzaak en oplossing |
|---|---|
| Elke regel is INSUFFICIENT_EVIDENCE met simulated_user_not_validated | Niemand heeft de gesprekken van deze gesimuleerde gebruiker op deze suite gelabeld, of het realistische aandeel haalt de lat niet. Label een steekproef zoals hierboven. |
| a case for a simulated user needs a 'persona' object in its input | Een case heeft geen persona, of die staat onder een andere sleutel: zet persona_field. |
| provider connection failed bij elke case | De vervanger draait niet, of luistert op een andere poort dan base_url noemt. |
| the simulated user's goal check answered ... | Het model beantwoordde de doelcontrole niet met ja of nee. Probeer een ander model, of een groter. |
| conversation/v1 is written by Oloproof | De desk legde het gesprek zelf vast. Haal dat weg: Oloproof legt vast wat er door de gesimuleerde gebruiker ging. |
Beperkingen
- De vervangende klant is een regel. Zijn gesprekken tonen het mechanisme; die van een echt model zijn rommeliger, zoals de optionele stap laat zien.
- Of een doel bereikt werd, is het oordeel van het model van de gesimuleerde gebruiker, alleen gecontroleerd via de realismelabels.
- Een gehoste werkruimte beslist nog niet over gesimuleerde gebruikers.
- Gesimuleerde klanten zijn geen echte: een gevalideerde gesimuleerde gebruiker is bewijs over klanten zoals zijn persona's, voor zover mensen hem realistisch vonden, en niet verder.