الأدلة الإرشادية
درس تعليمي: مكتب دعم مقابل عملاء محاكَين
قيّم مساعدًا محادِثًا مقابل مستخدمين يتفاعلون مع ما يقوله. ثلاثون عميلًا محاكى، لكلٍّ منهم هدف والمعلومات التي يعرفها، يتحدثون مع مكتب الدعم في متجر؛ ويقيس 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 وليس نموذجًا: فهو يقرأ الشخصية من الموجِّه الذي يرسله Oloproof، ويذكر هدفه، ويعطي رقم طلبه أو رمزه البريدي حين يُسأل، ويغادر حين لا يستطيع المكتب المساعدة، وحين يُسأل هل تحقق هدفه يقول نعم إذا بدأت آخر إجابة للمكتب بـ "Done:". شغّله في طرفية ثانية واتركه يعمل:
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 │تلقّى كل عميل إجابات، وبلغ 22 من 30 هدفهم، و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%الثمانية الذين غادروا هم العملاء الذين انتقلوا إلى عنوان جديد: فخط الأساس لا يستطيع تغيير العنوان.
ضع وسوم الواقعية على المحادثات
اسحب عيّنة عشوائية من محادثات التشغيل إلى ورقة، واجعل شخصًا يقرأ كل واحدة منها، مع الشخصية وكيف انتهت، ويعلّم هل كان بوسع عميل حقيقي أن يكتب جانب العميل وأن ينهي المحادثة هناك، بتلك الطريقة. وهنا ينوب 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."، وهذا يُقرأ كاستمارة لا كإنسان. ومع 29 من 30، يبلغ الحد الأدنى الدقيق 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، واضبط version: candidate تحت system في oloproof.yaml، وأبقِ البديل يعمل، ثم:
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: 6يجيب Ollama عن طلب واحد في كل مرة، لذا اضبط concurrency: {system: 1} وtimeout_s من بضع دقائق. والنموذج المختلف مستخدم محاكى مختلف: فوسوم الواقعية أعلاه لا تغطيه، لذا تحتاج محادثاته إلى عيّنة خاصة بها قبل أن تحسم أي قاعدة عليها.
عشر من الشخصيات، أدّاها qwen2.5vl على هذا الجهاز مقابل المكتب المرشَّح، بلغت هدفها في 8 محادثات، حيث بلغه البديل في الثلاثين كلها. ووجد النموذج ما لم يستطع البديل إيجاده: سأل عميلان عن "status" و"shipping status" طلبهما، وهي كلمات لا يعرفها المكتب، فتلقّيا تحيته في كل مرة. ونفدت رسائل أحدهما. وفهم المكتب المحاولة الثالثة للآخر، ومع ذلك غادر ذلك العميل: ففحص الهدف، وهو حكم نموذج، فاتته الإجابة. ووسم الواقعية هو حيث يظهر ذلك: فالعميل الحقيقي الذي يُخبَر بموعد وصول طلبه لا يغادر مستاءً. وعلى خلاف البديل، كثيرًا ما أعطى النموذج أيضًا رقم طلبه قبل أن يُسأل، وكتب رسالة واحدة أطول حيث كتب البديل رسالتين قصيرتين.
استكشاف الأخطاء وإصلاحها
| العَرَض | السبب والإصلاح |
|---|---|
| كل قاعدة INSUFFICIENT_EVIDENCE مع simulated_user_not_validated | لم يضع أحد وسومًا على محادثات هذا المستخدم المحاكى على مجموعة الاختبار هذه، أو أن نسبة المحادثات الواقعية لا تبلغ المستوى. ضع وسومًا على عيّنة كما سبق. |
| 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 ... | لم يُجب النموذج عن فحص الهدف بنعم أو لا. جرّب نموذجًا آخر، أو نموذجًا أكبر. |
| conversation/v1 is written by Oloproof | سجّل المكتب المحادثة بنفسه. أزل ذلك: فـ Oloproof يسجّل ما مرّ عبر المستخدم المحاكى. |
القيود
- العميل البديل قاعدة مكتوبة. ومحادثاته تُظهر الآلية؛ أما محادثات النموذج الحقيقي فأكثر فوضى، كما تُظهر الخطوة الاختيارية.
- تحقُّق الهدف من عدمه حكمُ نموذج المستخدم المحاكى، ولا يُتحقق منه إلا عبر وسوم الواقعية.
- لا تحسم مساحة العمل المستضافة على مستخدمين محاكَين بعد.
- العملاء المحاكَون ليسوا حقيقيين: فالمستخدم المحاكى الذي تُحقِّق منه دليل على عملاء مثل شخصياته، بقدر ما وجده الأشخاص واقعيًا، ولا أبعد من ذلك.