가이드
튜토리얼: 시뮬레이션 고객을 상대하는 고객 지원 창구
대화형 어시스턴트를 그 말에 반응하는 사용자를 상대로 평가합니다. 각자 목표와 알고 있는 사실을 가진 시뮬레이션 고객 30명이 한 가게의 고객 지원 창구와 대화합니다. Oloproof는 각 고객이 원하던 것을 얻었는지 측정하고, 사람이 시뮬레이션 고객을 현실적이라고 판단할 때까지 모든 결정을 보류한 다음, 출시와 비교를 결정합니다. 모든 단계는 모델도 네트워크도 없이 이 머신에서 실행되며, 선택 단계에서 실제 모델을 고객으로 바꿀 수 있습니다.
만들 것
한 가게의 고객 지원 창구는 환불, 배송 문의, 주소 변경에 답합니다. 버전을 출시해도 되는지는 세 가지 질문이 결정합니다.
- 각 고객이 원하던 것을 얻었는가? 각 답변 뒤의 검사에서 나오는 goal_reached입니다.
- 가게가 약속할 수 없는 것을 약속한 답변이 있었는가? every_turn이 모든 답변에서 "guarantee"라는 단어를 찾습니다.
- 그 답변들이 실제 고객에 대해 무언가를 말한다고 믿을 수 있는가? 사람이 대화의 표본을 읽고 시뮬레이션 고객을 현실적이라고 판단한 뒤에야 그렇습니다.
케이스, 실행, 게이트, 스위트 같은 용어는 핵심 개념에 정의되어 있고, 시뮬레이션 사용자에 관한 모든 것은 시뮬레이션 사용자에 있습니다.
사전 준비
- Python 3.11 이상, 그리고 Oloproof:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- 패키지와 함께 제공되는 예제 프로젝트. 새 디렉터리로 복사해 그곳에서 작업합니다:
oloproof init --example simulated-users support-desk
cd support-desk- 대역 시뮬레이션 사용자를 위한 비어 있는 포트 8797(비어 있지 않다면 두 곳 모두에서 바꾸십시오).
"선택 사항: 실제 모델을 고객으로" 이전의 모든 단계는 오프라인이고 결정적입니다. API 키도, 제공자 계정도, 비용도 없습니다.
파일
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모든 명령은 support-desk/에서 실행하십시오.
창구는 자신의 루프를 유지합니다
# 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}창구는 고객의 첫 메시지를 요청하고, 그것에 답한 다음, 고객이 멈출 때까지 다음 메시지를 받기 위해 각 답변을 돌려줍니다. 창구는 페르소나를 결코 보지 않습니다. 둘 사이에 오간 내용은 Oloproof가 기록하며, 창구 자신은 아무것도 기록하지 않습니다. 이 기준선은 배송 주소를 바꿀 수 없고, 환불이 "guaranteed within 5 days"라고 약속합니다.
페르소나
각 케이스는 고객 한 명입니다. 12명은 환불을 원하고, 10명은 주문이 언제 도착하는지 알고 싶어 하며, 8명은 이사를 했습니다. 예를 들면 다음과 같습니다:
{"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"}}}}고객은 물어볼 때만 주문 번호와 우편번호를 알려 줍니다. 페르소나 세 개는 opening, 즉 첫 메시지를 글자 그대로 정해 둡니다.
스위트
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대역 시뮬레이션 사용자
시뮬레이션 사용자는 각 페르소나를 연기하는 모델입니다. 여기서는 user_server.py이며, 127.0.0.1에서 같은 API를 말하지만 모델은 아닙니다. Oloproof가 보내는 프롬프트에서 페르소나를 읽고, 자신의 목표를 말하고, 물어보면 주문 번호나 우편번호를 알려 주고, 창구가 도울 수 없으면 떠납니다. 그리고 목표를 이루었는지 물으면, 창구의 마지막 답변이 "Done:"으로 시작할 때 yes라고 답합니다. 두 번째 터미널에서 시작하고 계속 실행해 두십시오:
python user_server.py --port 8797정책
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어떤 답변도 약속해서는 안 됩니다. 고객의 최소 80%가 목표를 이루어야 하며, 이는 구간의 하한이 80%를 넘을 때에만 결정됩니다. 그리고 사람들이 시뮬레이션 고객의 대화 중 최소 80%를 같은 종류의 하한으로 현실적이라고 판단할 때까지, 어떤 규칙도 시뮬레이션 고객에 대해 결정하지 않습니다.
실행하기
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 │모든 고객이 답변을 받았고, 30명 중 22명이 목표를 이루었으며, 대화 12개에 약속이 들어 있습니다. 아직 아무것도 결정되지 않았습니다. 이 고객들은 시뮬레이션된 것이고, 그들이 실제 고객처럼 행동하는지 아무도 확인하지 않았기 때문입니다.
시뮬레이션 고객 읽기
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%떠난 8명은 이사한 고객들입니다. 기준선은 주소를 바꿀 수 없습니다.
대화에 현실성 레이블 붙이기
실행의 대화에서 무작위 표본을 시트로 뽑고, 사람이 각 대화를 페르소나, 그리고 대화가 어떻게 끝났는지와 함께 읽게 한 다음, 실제 고객이 고객 쪽을 쓰고 바로 그 지점에서, 그런 식으로 대화를 끝냈을 수 있는지 표시하게 합니다. 여기서는 fill_realism.py가 그 사람을 대신해 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%.그 사람은 대화 하나를 비현실적이라고 판단했습니다. 고객 c05는 "Order: F-15. Refund."로 시작하는데, 이는 사람이 아니라 양식처럼 읽힙니다. 30개 중 29개이므로 정확한 하한은 82.8%로 기준치를 넘고, 이제 규칙이 이 고객들에 대해 결정할 수 있습니다.
oloproof gate RUN_ID --policy release.yamlno-promises: FAIL (observed_failures_exceed_limit)
resolved: INSUFFICIENT_EVIDENCE (interval_overlaps_threshold)
Gate: BLOCK (exit 1)실제로 변경하기
desk_v2.py는 주문과 새 우편번호를 받으면 주소를 바꾸고, 환불을 약속하는 대신 "usually arrives within 5 days"라고 하는 환불을 시작했다고 말합니다. 이것을 desk.py 위에 복사하고, oloproof.yaml의 system 아래에 version: candidate를 설정하고, 대역을 계속 실행해 둔 채로 다음을 실행하십시오:
oloproof runGate: ALLOW (exit 0)
│ no-promises │ no_promises │ PASS │ observed_failures_within_limit │
│ resolved │ goal_reached │ PASS │ lower_bound_meets_minimum │후보는 같은 스위트에서 같은 시뮬레이션 고객을 만났으므로, 기준선의 대화에서 측정한 현실성이 후보도 다룹니다. 검증 하나는 스위트 하나에서 시뮬레이션 사용자 하나를, 그것이 어느 시스템과 대화했든 다룹니다.
후보를 기준선과 비교하기
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_zero각 고객이 두 창구를 모두 만났으므로, 비교는 고객별로 대응됩니다.
시뮬레이션과 실제를 나란히
현실성 레이블은 각 대화를 따로 판단합니다. 시뮬레이션 고객을 실제 고객과 나란히 놓으면 표본이 놓칠 수 있는 것이 보입니다. real.yaml은 가게의 사람 상담원이 실제 고객과 나눈 채팅 20개를 재생합니다:
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]실제 고객은 더 많고 더 짧은 메시지를 보냈습니다. 대역이 깔끔한 한 문장으로 말하는 것을, 실제 고객은 세 줄에 걸쳐 말합니다. 이는 어떤 시뮬레이션 사용자에 대해서든 알아 둘 만한 것이며, 여기서는 아무것도 결정하지 않습니다. 대응되지 않는 두 표본을 비교하려면 Oloproof가 아직 인정하지 않은 방법이 필요합니다.
선택 사항: 실제 모델을 고객으로
이 단계는 오프라인 경로를 벗어납니다. 채팅 모델이 필요합니다. 로컬에서는 Ollama(ollama pull qwen2.5vl, 또는 아무 채팅 모델)로 키도 비용도 없습니다. 클라우드에서는 provider: anthropic 또는 openai와 api_key_env를 쓰며, 메시지마다 청구됩니다. oloproof.yaml에서 시뮬레이션 사용자가 그 모델을 가리키게 하십시오:
simulated_user:
provider: openai_compatible
model: qwen2.5vl
base_url: http://localhost:11434/v1
max_turns: 6Ollama는 한 번에 요청 하나에만 답하므로 concurrency: {system: 1}과 몇 분짜리 timeout_s를 설정하십시오. 다른 모델은 다른 시뮬레이션 사용자입니다. 위의 현실성 레이블은 그것을 다루지 않으므로, 어떤 규칙이든 그 대화에 대해 결정하기 전에 그 대화의 별도 표본이 필요합니다.
이 머신에서 qwen2.5vl이 연기한 페르소나 10명은 후보 창구를 상대로 대화 8개에서 목표를 이루었습니다. 대역은 30명 모두에서 이루었습니다. 모델은 대역이 찾지 못한 것을 찾았습니다. 두 고객이 주문의 "status"와 "shipping status"를 물었는데, 이는 창구가 모르는 단어였고, 두 사람 모두 매번 창구의 인사말만 돌려받았습니다. 한 명은 메시지를 다 썼습니다. 다른 한 명의 세 번째 시도는 창구가 이해했지만, 그 고객은 그래도 떠났습니다. 모델의 판단인 목표 검사가 그 답변을 놓친 것입니다. 그것이 드러나는 곳이 현실성 레이블입니다. 주문이 언제 도착하는지 들은 실제 고객은 불만을 품고 떠나지 않습니다. 또한 모델은 대역과 달리 물어보기 전에 주문 번호를 알려 주는 일이 잦았고, 대역이 짧은 메시지 두 개를 쓰는 곳에서 긴 메시지 하나를 썼습니다.
문제 해결
| 증상 | 원인과 해결 |
|---|---|
| 모든 규칙이 simulated_user_not_validated와 함께 INSUFFICIENT_EVIDENCE | 이 스위트에서 이 시뮬레이션 사용자의 대화에 아무도 레이블을 붙이지 않았거나, 현실적인 비율이 기준치를 넘지 못합니다. 위와 같이 표본에 레이블을 붙이십시오. |
| a case for a simulated user needs a 'persona' object in its input | 어떤 케이스에 페르소나가 없거나 다른 키 아래에 있습니다. persona_field를 설정하십시오. |
| 모든 케이스에서 provider connection failed | 대역이 실행 중이 아니거나, base_url이 가리키는 것과 다른 포트에서 듣고 있습니다. |
| the simulated user's goal check answered ... | 모델이 목표 검사에 yes나 no로 답하지 않았습니다. 다른 모델이나 더 큰 모델을 시도하십시오. |
| conversation/v1 is written by Oloproof | 창구가 대화를 스스로 기록했습니다. 그것을 제거하십시오. Oloproof가 시뮬레이션 사용자를 거쳐 간 내용을 기록합니다. |
한계
- 대역 고객은 규칙입니다. 그 대화는 동작 방식을 보여 주며, 선택 단계가 보여 주듯 실제 모델의 대화는 더 지저분합니다.
- 목표를 이루었는지는 시뮬레이션 사용자의 모델의 판단이며, 현실성 레이블을 통해서만 확인됩니다.
- 호스팅된 워크스페이스는 아직 시뮬레이션 사용자에 대해 결정하지 않습니다.
- 시뮬레이션 고객은 실제 고객이 아닙니다. 검증된 시뮬레이션 사용자는 사람들이 그것을 현실적이라고 판단한 만큼, 그 페르소나와 같은 고객에 대한 증거일 뿐이며 그 이상은 아닙니다.