Przewodniki
Samouczek: biuro obsługi klienta wobec symulowanych klientów
Oceń asystenta konwersacyjnego na użytkownikach, którzy reagują na to, co mówi. Trzydziestu symulowanych klientów, każdy z celem i faktami, które zna, rozmawia z biurem obsługi klienta sklepu; Oloproof mierzy, czy każdy dostał to, po co przyszedł, wstrzymuje każdą decyzję, dopóki człowiek nie uzna symulowanych klientów za realistycznych, a potem rozstrzyga o wydaniu i o porównaniu. Każdy krok działa na tej maszynie bez modelu i bez sieci, a opcjonalny krok podstawia prawdziwy model w roli klientów.
Co zbudujesz
Biuro obsługi klienta sklepu odpowiada w sprawie zwrotów pieniędzy, pytań o dostawę i zmian adresu. O tym, czy wersja może zostać wydana, decydują trzy pytania:
- Czy każdy klient dostał to, po co przyszedł? goal_reached, na podstawie sprawdzenia po każdej odpowiedzi.
- Czy jakakolwiek odpowiedź obiecała coś, czego sklep nie może obiecać? every_turn czyta każdą odpowiedź w poszukiwaniu słowa "guarantee".
- Czy odpowiedziom można zaufać, że mówią coś o prawdziwych klientach? Dopiero wtedy, gdy człowiek przeczyta próbę rozmów i uzna symulowanych klientów za realistycznych.
Terminy takie jak przypadek, przebieg, bramka i zestaw są zdefiniowane w Pojęciach; wszystko o symulowanych użytkownikach jest w Symulowani użytkownicy.
Wymagania wstępne
- Python 3.11 lub nowszy oraz Oloproof:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- Przykładowy projekt, dostarczany z pakietem. Skopiuj go do nowego katalogu i pracuj tam:
oloproof init --example simulated-users support-desk
cd support-desk- Wolny port 8797 dla zastępczego symulowanego użytkownika (jeśli nie, zmień go w obu miejscach).
Każdy krok aż do "Opcjonalnie: prawdziwy model w roli klientów" działa offline i deterministycznie: bez klucza API, bez konta u dostawcy, bez kosztów.
Pliki
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 chatsUruchamiaj każde polecenie z support-desk/.
Biuro obsługi zachowuje własną pętlę
# 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}Biuro obsługi prosi o pierwszą wiadomość klienta, odpowiada na nią i oddaje każdą odpowiedź, aby otrzymać następną wiadomość, dopóki klient nie przestanie. Nigdy nie widzi persony. Oloproof zapisuje to, co przeszło między nimi; samo biuro obsługi niczego nie zapisuje. Ten punkt odniesienia nie potrafi zmienić adresu dostawy i obiecuje zwroty pieniędzy "guaranteed within 5 days".
Persony
Każdy przypadek to jeden klient: 12 chce zwrotu pieniędzy, 10 chce wiedzieć, kiedy dotrze zamówienie, a 8 się przeprowadziło. Na przykład:
{"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"}}}}Klient podaje numer zamówienia i kod pocztowy tylko wtedy, gdy zostanie o nie poproszony. Trzy persony ustawiają też opening, swoją pierwszą wiadomość słowo w słowo.
Zestaw
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: trueZastępczy symulowany użytkownik
Symulowany użytkownik to model, który odgrywa każdą personę. Tutaj jest nim user_server.py, który mówi tym samym API na 127.0.0.1 i nie jest modelem: odczytuje personę z promptu wysyłanego przez Oloproof, podaje swój cel, podaje numer zamówienia lub kod pocztowy, gdy zostanie o nie poproszony, odchodzi, gdy biuro obsługi nie może pomóc, a zapytany, czy jego cel został osiągnięty, odpowiada tak, gdy ostatnia odpowiedź biura obsługi zaczyna się od "Done:". Uruchom go w drugim terminalu i zostaw włączonego:
python user_server.py --port 8797Polityka
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Żadna odpowiedź nie może niczego obiecywać. Co najmniej 80% klientów musi osiągnąć swój cel, co jest rozstrzygane dopiero wtedy, gdy dolna granica przedziału przekroczy 80%. I żadna reguła nie decyduje na podstawie symulowanych klientów, dopóki ludzie nie uznają co najmniej 80% ich rozmów za realistyczne, według tego samego rodzaju dolnej granicy.
Uruchom
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 │Każdy klient otrzymał odpowiedź, 22 z 30 osiągnęło swój cel, a 12 rozmów zawiera obietnicę. Nic nie jest jeszcze rozstrzygnięte: ci klienci są symulowani i nikt nie sprawdził, czy zachowują się jak prawdziwi.
Przeczytaj symulowanych klientów
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%Ośmiu, którzy odeszli, to klienci, którzy się przeprowadzili: punkt odniesienia nie potrafi zmienić adresu.
Nadaj rozmowom etykiety realizmu
Wylosuj do arkusza losową próbę rozmów przebiegu, poproś osobę, by przeczytała każdą z nich wraz z personą i tym, jak się zakończyła, i zaznaczyła, czy prawdziwy klient mógł napisać stronę klienta i zakończyć rozmowę w tym miejscu i w ten sposób. Tutaj fill_realism.py zastępuje tę osobę, kopiując jej werdykty z 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%.Osoba uznała jedną rozmowę za nierealistyczną: klient c05 zaczyna od "Order: F-15. Refund.", co brzmi jak formularz, a nie jak człowiek. Przy 29 z 30 dokładna dolna granica wynosi 82,8%, powyżej progu, więc reguły mogą teraz decydować na podstawie tych klientów.
oloproof gate RUN_ID --policy release.yamlno-promises: FAIL (observed_failures_exceed_limit)
resolved: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)Wprowadź prawdziwą zmianę
desk_v2.py zmienia adres, gdy ma już zamówienie i nowy kod pocztowy, i mówi, że rozpoczęło zwrot pieniędzy, który "usually arrives within 5 days", zamiast go obiecywać. Skopiuj go na miejsce desk.py, ustaw version: candidate pod system w oloproof.yaml, nie wyłączaj zastępczego użytkownika i:
oloproof runGate: ALLOW (exit 0)
│ no-promises │ no_promises │ PASS │ observed_failures_within_limit │
│ resolved │ goal_reached │ PASS │ lower_bound_meets_minimum │Kandydat trafił na tych samych symulowanych klientów na tym samym zestawie, więc realizm zmierzony na rozmowach punktu odniesienia obejmuje i jego: jedna walidacja obejmuje jednego symulowanego użytkownika na jednym zestawie, niezależnie od tego, z którym systemem rozmawiał.
Porównaj kandydata z punktem odniesienia
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_zeroKażdy klient rozmawiał z oboma biurami obsługi, więc porównanie jest sparowane klient po kliencie.
Symulowani obok prawdziwych
Etykiety realizmu oceniają każdą rozmowę osobno. Zestawienie symulowanych klientów z prawdziwymi pokazuje, co może umknąć próbie. real.yaml odtwarza 20 czatów, które ludzcy konsultanci sklepu prowadzili z prawdziwymi klientami:
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]Prawdziwi klienci wysyłali więcej i krótszych wiadomości: mówią, czego chcą, w trzech linijkach, gdzie zastępczy użytkownik mówi to w jednym schludnym zdaniu. Warto to wiedzieć o każdym symulowanym użytkowniku, a tutaj nigdy niczego nie rozstrzyga: porównanie dwóch niesparowanych prób wymaga metody, której Oloproof nie przyjął.
Opcjonalnie: prawdziwy model w roli klientów
Ten krok opuszcza ścieżkę offline. Wymaga modelu czatu: lokalnie Ollama (ollama pull qwen2.5vl lub dowolny model czatu), bez klucza i bez kosztów; w chmurze provider: anthropic lub openai z api_key_env, rozliczane za każdą wiadomość. W oloproof.yaml skieruj na niego symulowanego użytkownika:
simulated_user:
provider: openai_compatible
model: qwen2.5vl
base_url: http://localhost:11434/v1
max_turns: 6Ollama odpowiada na jedno żądanie naraz, więc ustaw concurrency: {system: 1} i timeout_s wynoszący kilka minut. Inny model to inny symulowany użytkownik: powyższe etykiety realizmu go nie obejmują, więc jego rozmowy potrzebują własnej próby, zanim jakakolwiek reguła zacznie decydować na ich podstawie.
Dziesięć person, odgrywanych przez qwen2.5vl na tej maszynie wobec biura obsługi w wersji kandydata, osiągnęło swój cel w 8 rozmowach, podczas gdy zastępczy użytkownik osiągnął go we wszystkich 30. Model znalazł to, czego zastępczy użytkownik nie potrafił: dwóch klientów zapytało o "status" i "shipping status" swojego zamówienia, słowa, których biuro obsługi nie zna, i za każdym razem dostało z powrotem jego powitanie. Jednemu skończyły się wiadomości. Biuro obsługi zrozumiało trzecią próbę drugiego, a ten klient i tak odszedł: sprawdzenie celu, ocena modelu, przeoczyło odpowiedź. Właśnie w etykiecie realizmu to widać: prawdziwy klient, któremu powiedziano, kiedy dotrze jego zamówienie, nie odchodzi niezadowolony. W odróżnieniu od zastępczego użytkownika model często podawał też numer zamówienia, zanim ktoś o niego poprosił, i pisał jedną dłuższą wiadomość tam, gdzie zastępczy użytkownik pisał dwie krótkie.
Rozwiązywanie problemów
| Objaw | Przyczyna i rozwiązanie |
|---|---|
| Każda reguła daje INSUFFICIENT_EVIDENCE z simulated_user_not_validated | Nikt nie nadał etykiet rozmowom tego symulowanego użytkownika na tym zestawie albo odsetek rozmów realistycznych nie osiąga progu. Nadaj etykiety próbie, jak wyżej. |
| a case for a simulated user needs a 'persona' object in its input | Przypadek nie ma persony albo jest ona pod innym kluczem: ustaw persona_field. |
| provider connection failed w każdym przypadku | Zastępczy użytkownik nie działa albo nasłuchuje na innym porcie niż ten, który podaje base_url. |
| the simulated user's goal check answered ... | Model nie odpowiedział na sprawdzenie celu słowem tak ani nie. Spróbuj innego modelu albo większego. |
| conversation/v1 is written by Oloproof | Biuro obsługi samo zapisało rozmowę. Usuń to: Oloproof zapisuje to, co przeszło przez symulowanego użytkownika. |
Ograniczenia
- Zastępczy klient to reguła. Jego rozmowy pokazują mechanikę; rozmowy prawdziwego modelu są bardziej chaotyczne, jak pokazuje krok opcjonalny.
- To, czy cel został osiągnięty, jest oceną modelu symulowanego użytkownika, sprawdzaną tylko przez etykiety realizmu.
- Hostowany obszar roboczy nie decyduje jeszcze na podstawie symulowanych użytkowników.
- Symulowani klienci nie są prawdziwymi: zwalidowany symulowany użytkownik jest dowodem o klientach podobnych do jego person, w takim zakresie, w jakim ludzie uznali go za realistycznego, i nie dalej.