Hướng dẫn
Hướng dẫn: một cuộc hội thoại nhiều lượt
Một bài hướng dẫn có thể chạy được để đánh giá một trợ lý hội thoại bằng Python SDK: ứng dụng của bạn phát lại một cuộc hội thoại có kịch bản trên một phiên mới, ghi lại những gì nó đã trả lời dưới dạng một artifact conversation/v1, và hai bộ đánh giá chấm mỗi cuộc hội thoại trọn vẹn. Nó chạy ngoại tuyến với một bản thay thế có kịch bản cho mô hình giám khảo, rồi so sánh một bản sửa ứng viên, và kết thúc với phương án thay thế là đánh giá các lượt như những trường hợp theo cụm.
Các thuật ngữ như khoảng, trạng thái quyết định và hành động phát hành được định nghĩa trong Khái niệm; Ghi lại những gì một hệ thống đã làm trình bày về artifact.
Oloproof làm gì và không làm gì ở đây
| Oloproof làm | Ứng dụng của bạn làm |
|---|---|
| lưu các cuộc hội thoại có kịch bản làm tập dữ liệu, giống hệt nhau cho mọi hệ thống | điều khiển cuộc hội thoại: hỏi từng lượt theo kịch bản theo thứ tự |
| kiểm tra rằng mọi lượt theo kịch bản đều được trả lời (ConversationCompleted) | sở hữu trạng thái phiên, bắt đầu một phiên mới cho mỗi trường hợp, và đặt lại nó |
| chấm toàn bộ bản ghi hội thoại bằng một mô hình (ConversationJudge) | quyết định điều gì xảy ra khi nó không thể tiếp tục, và ghi lại rằng nó đã dừng |
| tính các khoảng, so sánh hai hệ thống, và quyết định theo một chính sách | ghi lại artifact conversation/v1 |
Oloproof không có bộ mô phỏng người dùng: nó không bao giờ viết một lượt của người dùng, nên phía người dùng là bất cứ thứ gì tập dữ liệu viết sẵn. Nó không có chỉ số cấp lượt bên trong một cuộc hội thoại đã ghi, và nó không thể phát lại một cuộc hội thoại đã ghi trên một hệ thống mới. Các bộ đánh giá hội thoại chỉ có trong Python SDK: ConversationCompleted và ConversationJudge không phải là các loại bộ đánh giá trong oloproof.yaml, nên bài hướng dẫn này dùng một script thay vì oloproof run.
Điều kiện tiên quyết
- Python 3.11 trở lên và pip install oloproof, như trong phần bắt đầu nhanh.
- Các tệp ví dụ, đi kèm với gói. Sao chép chúng vào một thư mục mới để kho lưu trữ của lần chạy nằm ở đó:
oloproof init --example conversation ~/oloproof-conversation
cd ~/oloproof-conversation| Tệp | Nó là gì |
|---|---|
| assistant.py | ứng dụng được kiểm thử: một trợ lý gói dịch vụ có trạng thái phiên |
| systems.py | adapter: phát lại một kịch bản, ghi lại conversation/v1 |
| judge_offline.py | bản thay thế có kịch bản cho mô hình giám khảo |
| evaluate.py | chạy việc đánh giá, phép so sánh và phương án theo lượt |
| release.yaml | chính sách cho một lần chạy |
| comparison.yaml | chính sách cho ứng viên so với đường cơ sở |
| turns_release.yaml | chính sách cho phương án theo lượt |
| data/conversations.jsonl | 40 cuộc hội thoại có kịch bản |
| data/turns.jsonl | cùng các cuộc hội thoại đó, mỗi lượt một trường hợp |
Không khóa, không mạng và không chi phí nhà cung cấp, cho tới bước trực tiếp tùy chọn ở cuối.
Ứng dụng
assistant.py trả lời các câu hỏi về ba gói giá. Nó giữ một mẩu trạng thái, gói mà cuộc hội thoại đang nói tới, để một câu hỏi tiếp nối như "Does that include SSO?" có thể hiểu được "that". Đường cơ sở có một lỗi cố ý: nó không nhớ gói, nên một câu hỏi tiếp nối được trả lời về gói mặc định. Một người dùng yêu cầu gặp người thật sẽ kết thúc cuộc hội thoại với HandoffRequested.
class PlanAssistant:
def __init__(self, *, remembers_plan):
self.remembers_plan = remembers_plan
self.reset()
def reset(self):
"""Forget everything, so one conversation never leaks into the next."""
self.current_plan = None
def ask(self, question): ...Đây là phần bạn thay bằng ứng dụng của chính mình: một client chatbot, một phiên agent, một phiên HTTP tới dịch vụ của bạn. Dù là gì, nó sở hữu trạng thái và việc đặt lại của nó; Oloproof chỉ thấy những gì adapter ghi lại.
Tập dữ liệu: kịch bản là đầu vào
Một dòng của data/conversations.jsonl là một cuộc hội thoại:
{"expected": {"plan": "enterprise"}, "id": "conv_00", "input": {"turns": ["What does the enterprise plan cost?", "Does that include SSO?"]}, "metadata": {"pattern": "pronoun_followup"}}Các lượt của người dùng là nội dung của tập dữ liệu, được bao bởi digest của bộ kiểm thử và giống hệt nhau cho mọi hệ thống được đo trên chúng; đó là điều khiến hai hệ thống so sánh được với nhau. expected là tham chiếu được cho giám khảo xem. 40 cuộc hội thoại gồm 24 cuộc có một câu hỏi tiếp nối không nêu gói nào, 12 cuộc nêu gói ở mọi lượt, và 4 cuộc yêu cầu gặp người thật ở lượt thứ hai trong ba lượt.
Adapter
systems.py bắt đầu một phiên mới cho mỗi trường hợp, hỏi từng lượt theo kịch bản theo thứ tự, và ghi lại những gì nhận được:
from oloproof import CONVERSATION, current_case, system
def replay(case, *, remembers_plan):
script = [str(turn) for turn in case["turns"]]
session = PlanAssistant(remembers_plan=remembers_plan) # a new session per case
turns = []
truncated = False
for index, question in enumerate(script, start=1):
try:
reply = session.ask(question)
except HandoffRequested:
truncated = True # the recording stops here and says so
break
turns.append({"index": index, "asked": question, "answer": reply["answer"]})
current_case().artifact(
CONVERSATION,
{"turns": turns, "declared_turns": len(script), "truncated": truncated},
)
last = turns[-1]["answer"] if turns else None
return {"answer": last, "turns_answered": len(turns)}
@system(name="plan-assistant", version="baseline", records=(CONVERSATION,))
def baseline(case):
return replay(case, remembers_plan=False)
@system(name="plan-assistant", version="candidate-remembers-plan", records=(CONVERSATION,))
def candidate(case):
return replay(case, remembers_plan=True)Một phiên mới cho mỗi trường hợp là quan trọng: Oloproof chạy các trường hợp đồng thời và không theo thứ tự cố định, và một phiên dùng chung giữa các trường hợp sẽ để trạng thái của một cuộc hội thoại rò rỉ sang cuộc khác. records= khai báo rằng hệ thống ghi lại artifact; không có nó, các bộ đánh giá hội thoại bị từ chối trước khi bất cứ thứ gì chạy, thay vì tính mọi trường hợp là bị thiếu.
Artifact conversation/v1
Những gì đường cơ sở đã ghi cho conv_00, từ oloproof export RUN_ID (tệp cases.jsonl của gói):
{"declared_turns": 2, "truncated": false, "turns": [{"answer": "The enterprise plan costs a price agreed per contract.", "asked": "What does the enterprise plan cost?", "index": 1, ...}, {"answer": "The starter plan does not include SSO.", "asked": "Does that include SSO?", "index": 2, ...}]}Và cho một cuộc hội thoại đã yêu cầu gặp người thật:
{"declared_turns": 3, "truncated": true, "turns": [{"answer": "The team plan costs $20 a month.", "asked": "What does the team plan cost?", "index": 1, ...}]}| Trường | Ý nghĩa |
|---|---|
| turns[].index | lượt theo kịch bản nào mà mục này trả lời; liên tục từ 1 |
| turns[].answer | những gì trợ lý đã trả về, bất kỳ JSON nào |
| turns[].asked | tùy chọn, chỉ để đọc; engine khớp theo index |
| turns[].retrieval | tùy chọn, những gì lượt đó đã truy xuất, theo hình dạng retrieval/v1 |
| declared_turns | kịch bản đã khai báo bao nhiêu lượt |
| truncated | bản ghi dừng trước khi hết kịch bản, bất kể điều gì đã cắt ngắn nó |
Artifact được kiểm tra khi nó được ghi: một bản ghi có ít lượt hơn số đã khai báo phải nói truncated: true, các chỉ số phải liên tục, và một bản ghi không thể trả lời nhiều lượt hơn số được hỏi. Một artifact sai dạng làm dừng lần chạy với một SystemContractError.
Hai bộ đánh giá, và vì sao cần cả hai
- ConversationCompleted là tất định: trợ lý có trả lời mọi lượt theo kịch bản không? Nó chạy trước vì mọi khẳng định khác về một cuộc hội thoại đã dừng ở lượt một trong ba là một khẳng định về một cuộc hội thoại khác. Một cuộc hội thoại bị cắt ngắn thì không đạt nó; đó là một kết quả, không phải một trường hợp bị thiếu.
- ConversationJudge là một giám khảo mô hình trên toàn bộ bản ghi hội thoại, mọi lượt của người dùng và trợ lý, vì những thất bại mà một sản phẩm hội thoại bị trách là mang tính quan hệ: một câu trả lời mâu thuẫn với câu ở lượt trước chỉ sai khi đặt cạnh câu đó. Một cuộc hội thoại bị cắt ngắn được chấm trên những gì đã ghi, và bản ghi cho giám khảo biết nó đã dừng ở đâu.
evaluators = [
ConversationCompleted(),
ConversationJudge(criterion="plan_coherent", provider=..., model=..., rubric_text=RUBRIC),
]Chấm ngoại tuyến
Một giám khảo cần một mô hình. Để chạy không cần mạng, judge_offline.py truyền vào một nhà cung cấp có kịch bản, cùng công cụ hỗ trợ mà chính các bài kiểm thử của Oloproof dùng (FakeProvider từ phần nội bộ của engine, không phải API công khai). Nó trả lời mọi prompt giám khảo bằng một quy tắc cố định: đạt khi mọi lượt của trợ lý đều nêu gói mà tham chiếu nêu. Điều đó làm cho các phán quyết tất định và bài hướng dẫn có thể tái lập. Nó không đo được gì về cách một mô hình giám khảo thật hành xử.
Chính sách
release.yaml:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- {id: completion-floor, metric: conversation_completed, min: 0.75}
- {id: coherence-floor, metric: plan_coherent, min: 0.80}Chạy
python evaluate.pybaseline run run_...
conversation_completed: 0.900 [0.763, 0.972] over 40 conversations
plan_coherent: 0.400 [0.249, 0.567] over 40 conversations
gate BLOCK (exit 3)
completion-floor: PASS (lower_bound_meets_minimum)
coherence-floor: INSUFFICIENT_EVIDENCE (evaluator_not_validated)
first failing conversation:
conversation_completed: passed=True {'turns_recorded': 2, 'turns_declared': 2, 'truncated': False}
plan_coherent: passed=False {'provider_model': 'offline-rule'}Cách đọc:
- Mức hoàn thành là 36 trên 40, bốn lần chuyển giao. Cận dưới của nó vượt qua 0.75, nên quy tắc đó là PASS.
- Mức mạch lạc là 40%, và quy tắc của nó ghi INSUFFICIENT_EVIDENCE với evaluator_not_validated, không phải FAIL. Các quy tắc của một giám khảo không quyết định cho tới khi giám khảo đã được đo so với nhãn của con người (require_validated_evaluators được bật theo mặc định; Giám khảo giải thích điều này). Ước lượng vẫn được hiển thị, và nó vẫn là bằng chứng: chỉ là nó không thể tự mình cho phát hành hay chặn.
- gate BLOCK (exit 3): chính sách chặn trên INSUFFICIENT_EVIDENCE. Mã thoát 3 là trạng thái đó, không phải một thất bại.
Việc xác thực bản thay thế ngoại tuyến sẽ vô nghĩa, vì nó là một quy tắc được viết cho ví dụ này. Với một giám khảo thật, hãy gán nhãn cho một mẫu của lần chạy bằng oloproof review RUN_ID --criterion plan_coherent --by YOU --sample 20 rồi chạy oloproof evaluators validate EVALUATOR_ID --by YOU.
Xem xét một cuộc hội thoại thất bại
SDK ghi vào cùng kho lưu trữ mà CLI đọc, .oloproof/ trong thư mục bạn đã chạy:
oloproof inspect RUN_ID --case conv_00output: {
"answer": "The starter plan does not include SSO.",
"turns_answered": 2
}
judgments:
conversation_completed: passed
plan_coherent: failed
judge text, not verified:
every answer is about enterprise: FalseNgười dùng hỏi về gói enterprise và câu hỏi tiếp nối được trả lời về gói starter. oloproof inspect RUN_ID --failures liệt kê mọi cuộc hội thoại thất bại; cả 24 câu hỏi tiếp nối không có tên gói đều thất bại theo cùng một cách. Hành động tiếp theo nằm trong ứng dụng: giữ gói trong trạng thái phiên.
Một thay đổi ứng viên, và phép so sánh
candidate trong systems.py đặt remembers_plan=True. evaluate.py chạy cả hai hệ thống trên cùng các kịch bản và so sánh chúng từng trường hợp một theo comparison.yaml:
rules:
- {id: coherence-better, kind: superiority, metric: plan_coherent}
- {id: completion-no-worse, kind: non_inferiority, metric: conversation_completed, margin: 0.05}comparison, candidate minus baseline
conversation_completed: +0.000 [-0.127, +0.127] over 40 pairs
plan_coherent: +0.600 [+0.337, +0.817] over 40 pairs
gate BLOCK (exit 3)
coherence-better: INSUFFICIENT_EVIDENCE (evaluator_not_validated)
completion-no-worse: INSUFFICIENT_EVIDENCE (interval_overlaps_margin)Khác biệt về mức mạch lạc là lớn và khoảng của nó loại trừ 0, nhưng giám khảo chưa được xác thực, nên quy tắc của nó vẫn không quyết định. Mức hoàn thành không thay đổi, và 40 cặp không thể cho thấy nó nằm trong năm điểm: khoảng chạm tới 12,7 điểm theo cả hai hướng. Cả hai đều chỉ tới cùng một bước tiếp theo: xác thực giám khảo và thêm hội thoại.
Phương án thay thế: các lượt như trường hợp, được phân tích theo cụm
Một phán quyết cấp hội thoại cho biết một cuộc hội thoại diễn ra tốt thường xuyên đến đâu, không cho biết lượt nào đã sai. Vì Oloproof không có chỉ số cấp lượt bên trong một cuộc hội thoại đã ghi, lộ trình còn lại là biến mỗi lượt thành một trường hợp riêng và gắn các lượt của một cuộc hội thoại lại với nhau bằng group_id:
{"expected": {"plan": "enterprise"}, "group_id": "conv_00", "id": "conv_00_t2", "input": {"history": ["What does the enterprise plan cost?"], "question": "Does that include SSO?"}}Hệ thống phát lại lịch sử theo kịch bản vào một phiên mới, rồi trả lời lượt đó:
@system(name="plan-assistant-turns", version="baseline")
def turn_baseline(case):
session = PlanAssistant(remembers_plan=False)
for earlier in case["history"]:
session.ask(str(earlier))
return session.ask(str(case["question"]))Các lượt của một cuộc hội thoại không độc lập với nhau, nên một khi bất kỳ trường hợp nào có group_id, bộ kiểm thử được phân tích theo cụm bằng một phương pháp xấp xỉ mà một chính sách phải chấp nhận (turns_release.yaml đặt allow_approximate_methods: true; Trường hợp theo cụm giải thích điều này):
turns as cases: turn_plan 0.667 [0.588, 0.749] over 72 turns
gate BLOCK (exit 1)
turn-plan-floor: FAIL (upper_bound_below_minimum)Sự đánh đổi:
| Một trường hợp cho mỗi cuộc hội thoại | Một trường hợp cho mỗi lượt, theo cụm | |
|---|---|---|
| Đơn vị của tỷ lệ | các cuộc hội thoại diễn ra tốt | các lượt được trả lời đúng |
| Cỡ mẫu hiệu dụng | số cuộc hội thoại | vẫn là số cuộc hội thoại, không phải số lượt |
| Lượt nào đã thất bại | đọc bản ghi hội thoại | mỗi lượt có phán quyết riêng |
| Lịch sử mà mỗi lượt thấy | các câu trả lời trước đó của chính trợ lý | các lượt người dùng trước đó của kịch bản, được phát lại |
| Bắt được sự trôi lệch do chính các câu trả lời trước đó của nó gây ra | có | không, mỗi lượt bắt đầu từ một lịch sử theo kịch bản |
| Bộ đánh giá | ConversationCompleted, ConversationJudge (chỉ SDK) | bất kỳ bộ đánh giá nào, trong YAML hoặc SDK |
Phương án theo lượt loại trừ bốn cuộc hội thoại có chuyển giao, nên 72 lượt của nó đến từ 36 cuộc hội thoại. Ở đây nó có thể quyết định khi giám khảo không thể, vì ExactMatch là tất định và không cần xác thực.
Tùy chọn: một mô hình giám khảo trực tiếp
Bước này cần một mô hình được phục vụ trên máy của bạn. Nó không được chạy bởi bài hướng dẫn ngoại tuyến hay bài kiểm thử của nó. Với Ollama đang chạy và llama3.1 đã được tải:
python evaluate.py --liveGiám khảo khi đó gọi http://localhost:11434/v1 với provider="openai_compatible". Một máy chủ loopback không cần khóa và không gửi gì ra khỏi máy. Một nhà cung cấp đám mây cần khóa của nó trong môi trường, gửi từng bản ghi hội thoại tới nhà cung cấp đó và tốn tiền cho mỗi phán quyết. Các phán quyết của một mô hình thật khác với của bản thay thế, nên các con số ở trên sẽ thay đổi, và các quy tắc của nó vẫn ghi evaluator_not_validated cho tới khi bạn xác thực nó.
Khắc phục sự cố
| Triệu chứng | Nguyên nhân và cách sửa |
|---|---|
| this evaluator needs exactly one conversation/v1 artifact; the case recorded 0 | Adapter đã không gọi current_case().artifact(CONVERSATION, ...), hoặc ném lỗi trước đó. Hãy ghi lại cả khi cuộc hội thoại dừng sớm. |
| malformed conversation/v1 artifact: ... 0 of 2 turns recorded and truncated is false | Lần chạy dừng với một SystemContractError. Một bản ghi có ít lượt hơn declared_turns phải đặt truncated: true. |
| conversation turn indexes must be contiguous starting at 1 | Đánh số các lượt 1, 2, 3 theo lượt kịch bản mà chúng trả lời. |
| evaluator 'conversation_completed' needs conversation/v1 artifacts, but system ... does not declare that it records them | Thêm records=(CONVERSATION,) vào decorator @system. |
| Input tag 'conversation_completed' found using 'type' does not match any of the expected tags từ oloproof run | Các bộ đánh giá hội thoại chỉ có trong SDK. Hãy dùng một script như ở đây. |
| Câu trả lời rò rỉ giữa các cuộc hội thoại | Một phiên được dùng chung giữa các trường hợp. Hãy tạo một phiên cho mỗi trường hợp. |
| Các quy tắc về mức mạch lạc không bao giờ quyết định | Giám khảo chưa được xác thực. Hãy xác thực nó, hoặc đặt require_validated_evaluators: false một cách có ý thức. |
Giới hạn
- Không có bộ mô phỏng người dùng: mọi lượt của người dùng đến từ kịch bản của tập dữ liệu, nên cuộc hội thoại không thể rẽ nhánh theo những gì trợ lý đã nói.
- Không có chỉ số cấp lượt bên trong một cuộc hội thoại đã ghi; thay vào đó hãy dùng các lượt như trường hợp theo cụm, với sự đánh đổi nêu trên.
- Không phát lại hội thoại: một cuộc hội thoại đã ghi không thể được chạy lại trên một hệ thống khác. So sánh hai hệ thống nghĩa là mỗi hệ thống phát lại cùng một kịch bản.
- ConversationCompleted và ConversationJudge chỉ có trong Python SDK.
- Chỉ văn bản: một giám khảo được cho xem văn bản JSON, không bao giờ là hình ảnh hay âm thanh.
- Giám khảo ngoại tuyến là một quy tắc có kịch bản. Các phán quyết của nó cho thấy cơ chế, không phải độ chính xác của một giám khảo thật.
Tiếp theo
- Giám khảo trình bày nhà cung cấp, xác thực và hiệu chuẩn lại.
- Trường hợp theo cụm trình bày group_id và việc chọn dùng phương pháp xấp xỉ.
- API Python trình bày evaluate và evaluate_comparison.
- Agent trình bày cùng ranh giới đó cho vòng lặp công cụ của một agent.