অধ্যায় 4 · এক্সপেরিমেন্ট ও মূল্যায়ন
Python প্রম্পট এক্সপেরিমেন্ট: টেস্ট কেস, রানার, JSONL লগ ও মক মোড
- পৃষ্ঠা 15 / 24
- 14 মিনিট পড়া
চ্যাট উইন্ডোতে একটি প্রম্পট চেষ্টা করাকে এক্সপেরিমেন্ট মনে হয়, কিন্তু তা পুনরাবৃত্তিযোগ্য নয়: কোন শব্দবিন্যাসে কোন উত্তর এসেছিল তা বলা যায় না, আর বিশটি ইনপুটে আবার চালানোও যায় না। Python-এ প্রম্পট এক্সপেরিমেন্ট এই সমস্যা মেটায়। প্রম্পটের লেখা থাকে এক ফাইলে, টেস্ট ইনপুট আরেক ফাইলে; প্রতিটি ইনপুট একই ফাংশনের মধ্য দিয়ে চলে, আর প্রতি রানে একটি কাঠামোবদ্ধ রেকর্ড লেখা হয়। তখন দুটি প্রম্পট ভার্সনকে হুবহু একই ইনপুটে তুলনা করা যায়।
এই পাতায় একটি ছোট মূল্যায়ন টুলকিটের প্রথম অর্ধেক তৈরি হবে, তিনটি ছোট ফাইলে: runner.py, toys.py এবং কয়েকটি ডেটা ফাইল। পরের পাতা মেট্রিক ও এক্সপেরিমেন্ট রিপোর্ট: accuracy, precision, recall, ল্যাটেন্সি ও CSV এর ওপর স্কোরিং যোগ করে, আর তার পরের পাতা টেস্ট ডেটা ডিজাইন করে। সবকিছু স্ট্যান্ডার্ড লাইব্রেরি এবং প্রম্পট চেইনিং: ধাপ, নির্দিষ্ট স্কিমা ও promptlab হেল্পার পাতার promptlab হেল্পার দিয়ে চলে।
যা শিখবেন
- প্রম্পটকে স্ট্রিং ও টেমপ্লেট ফাইলে রাখা, টেস্ট ইনপুট থেকে আলাদা রাখা, আর JSON বা CSV থেকে কেস লোড করা।
- বিদ্যমান ক্লায়েন্টকে ডাকে এমন একটি পুনর্ব্যবহারযোগ্য রানার লেখা, যা প্রতি কেসের জন্য মেটাডেটাসহ একটি JSONL রেকর্ড লেখে।
- API এরর এমনভাবে সামলানো যাতে ব্যর্থ কল লগ নষ্ট না করে রেকর্ড হয়ে যায়।
- মক (টয়) প্রোভাইডার দিয়ে অফলাইনে অনুশীলন করা, একই টেস্ট সেটে দুটি প্রম্পট ভার্সন চালানো এবং পাশাপাশি তুলনা করা।
প্রম্পট হলো Python স্ট্রিং
প্রম্পট একটি লেখা, তাই Python স্ট্রিং তাকে ধরে রাখতে পারে। ট্রিপল-কোটেড স্ট্রিং লাইন ব্রেক ধরে রাখে, আর promptlab-এর render {text} ফিল্ড পূরণ করে। কোনো ভেরিয়েবল না থাকলে বা অব্যবহৃত থাকলে render এরর দেয়, ফলে কল করার আগেই টাইপো ধরা পড়ে। যে প্রম্পট সংরক্ষণ ও পুনরায় ব্যবহার করবেন, তার জন্য f-string এড়িয়ে চলুন: f-string লাইনটি চলার মুহূর্তেই ফিল্ড ভরে ফেলে, ফলে টেমপ্লেট ও ইনপুট মিশে যায় এবং আলাদাভাবে সেভ বা ভার্সন করা যায় না। প্রম্পটে আক্ষরিক ব্রেস দরকার হলে দুবার লিখুন ({{ এবং }}), render প্রতিটি জোড়াকে একটি ব্রেসে বদলে দেবে।
from promptlab import render
V1 = """Classify the support message as billing, delivery or other.
### MESSAGE
{text}"""
print(render(V1, text="Where is my box?"))
print(len(V1), "characters in the template")Classify the support message as billing, delivery or other.
### MESSAGE
Where is my box?
78 characters in the templateএকটি স্ক্রিপ্টে একটি প্রম্পটের জন্য এটি ঠিক আছে। কিন্তু দুটি ভার্সন আর দশটি ইনপুট হলেই কোডের ভেতরের স্ট্রিং তুলনা ও রিভিউ করা কঠিন হয়ে যায়। পরের অংশগুলোতে টেমপ্লেট ও ইনপুট ফাইলে সরানো হয়েছে।
JSON বা CSV থেকে টেস্ট কেস
টেস্ট কেস হলো একটি ইনপুট ও একটি আইডি। কেস রাখুন ডেটা ফাইলে, কখনো কোডের ভেতরে নয়, যাতে সহকর্মী Python না ছুঁয়েই একটি কেস যোগ করতে পারেন। একাধিক ফিল্ডের রেকর্ডের জন্য JSON ভালো; স্প্রেডশিটে সম্পাদনার জন্য CSV ভালো। নিচের ছয়টি কাল্পনিক সাপোর্ট মেসেজ এই পাতার টেস্ট সেট।
# file: cases.json
[
{"id": "c1", "text": "I was charged twice for invoice 3318."},
{"id": "c2", "text": "My parcel has not arrived after two weeks."},
{"id": "c3", "text": "Please send me a refund for the broken lamp that I bought last month."},
{"id": "c4", "text": "How do I change my profile photo?"},
{"id": "c5", "text": "Tracking shows nothing for my package."},
{"id": "c6", "text": "The driver left the box outside in the rain, and the invoice total looks wrong too."}
]লোড করতে স্ট্যান্ডার্ড লাইব্রেরির json ও csv ব্যবহার হয়। line.split(",") নয়, csv মডিউল ব্যবহার করুন: লেখার ভেতরে কমা ও কোটেশন থাকে, মডিউল সেগুলো সামলায়। মনে রাখবেন, CSV সব মান স্ট্রিং হিসেবে ফেরত দেয়, তাই সংখ্যার কলাম রূপান্তর করে নিতে হয়।
import csv, json
cases = json.load(open("cases.json", encoding="utf-8"))
with open("cases.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["id", "text"])
writer.writeheader()
writer.writerows(cases)
with open("cases.csv", newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
print(len(cases), len(rows), rows == cases)
print(rows[2])6 6 True
{'id': 'c3', 'text': 'Please send me a refund for the broken lamp that I bought last month.'}টেস্ট ইনপুট থেকে আলাদা প্রম্পট টেমপ্লেট
দুটি ফাইল, একটি প্রম্পটের দুটি ভার্সন। ভার্সন ২ রিফান্ডের একটি নিয়ম যোগ করে এবং শুধু লেবেল চায়। প্রতিটি ফাইল একটি ফিল্ডসহ টেমপ্লেট, {text}; ইনপুট থাকে cases.json-এ। এখন প্রম্পট বদলানো মানে একটি ছোট ফাইল বদলানো, আর নতুন টেস্ট কেস প্রম্পট স্পর্শই করে না।
# file: prompts/v1.txt
Classify the support message as billing, delivery or other.
### MESSAGE
{text}# file: prompts/v2.txt
Classify the support message as billing, delivery or other.
A refund request is billing. Reply with the label only.
### MESSAGE
{text}| টেমপ্লেট ফাইলে রাখুন | কেস ফাইলে রাখুন | কোডে রাখুন |
|---|---|---|
| নির্দেশ, নিয়ম, আউটপুট ফরম্যাট, ডিলিমিটার | ইনপুট, আইডি, পরে প্রত্যাশিত উত্তর | লোড, কল, লগ, স্কোরিং |
| প্রম্পট উন্নত করলে বদলায় | নতুন কেস পেলে বদলায় | কদাচিৎ বদলায় |
টেমপ্লেটে এমন ভেরিয়েবল থাকলে যা ইনপুট দেয়নি, বা উল্টোটা হলে render তা জানিয়ে দেয়:
from promptlab import render
v1 = open("prompts/v1.txt", encoding="utf-8").read()
for variables in ({}, {"text": "hi", "extra": 1}):
try:
render(v1, **variables)
except ValueError as error:
print(error)missing variables: ['text']
unused variables: ['extra']ক্লায়েন্টকে ডাকে এমন একটি পুনর্ব্যবহারযোগ্য ফাংশন
এবার মূল ফাইল। run_experiment যেকোনো Client (promptlab-এর ক্লাস; ভেতরে কোন প্রোভাইডার আছে তাতে কিছু আসে যায় না), একটি প্রম্পট ভার্সন, কেসগুলো এবং একটি লগ পাথ নেয়। উত্তর আসল মডেল থেকে আসছে নাকি টয় থেকে, ফাংশন তা জানে না, তাই একই ফাংশন অনুশীলন ও আসল এক্সপেরিমেন্ট দুটোতেই কাজ করে। ফলাফল ব্যাখ্যা করতে পরে যে মেটাডেটা লাগবে তাও এটি রেকর্ড করে: কোন মডেল, কোন প্রম্পট ভার্সন, কোন হুবহু প্রম্পট লেখা (টেমপ্লেটের ছোট হ্যাশ), কোন সেটিংস এবং কোন কেস। ফাইলটি একবার পড়ুন; অংশগুলো এর পরে ব্যাখ্যা করা হয়েছে।
# file: runner.py
"""runner.py: load cases, run one prompt template over them, log one JSONL record per case."""
import csv, hashlib, json
from pathlib import Path
from promptlab import ProviderError, render
def load_cases(path):
"""Read cases from a .json list or a .csv with a header row; every case needs a unique 'id'."""
p = Path(path)
if p.suffix == ".csv":
with p.open(newline="", encoding="utf-8") as f:
cases = list(csv.DictReader(f))
else:
cases = json.loads(p.read_text(encoding="utf-8"))
if len({c["id"] for c in cases}) != len(cases):
raise ValueError("duplicate case ids")
return cases
def load_prompt(version):
return Path("prompts", f"{version}.txt").read_text(encoding="utf-8")
def run_experiment(client, version, cases, log, **settings):
"""Run prompts/<version>.txt over cases. Each record is written to the log at once; an API error
becomes a record with status 'error' instead of stopping the run."""
template = load_prompt(version)
sha = hashlib.sha256(template.encode()).hexdigest()[:8]
records = []
Path(log).parent.mkdir(parents=True, exist_ok=True)
with open(log, "a", encoding="utf-8") as out:
for case in cases:
rec = {"case": case["id"], "version": version, "prompt_sha": sha,
"model": client.model, "settings": settings}
try:
r = client.complete([{"role": "user", "content": render(template, text=case["text"])}], **settings)
rec.update(status="ok", text=r.text.strip(), input_tokens=r.usage.input_tokens,
output_tokens=r.usage.output_tokens, latency_ms=r.latency_ms)
except ProviderError as exc:
rec.update(status="error", error=str(exc))
out.write(json.dumps(rec, sort_keys=True) + "\n")
out.flush()
records.append(rec)
return records
def read_jsonl(path):
with open(path, encoding="utf-8") as f:
return [json.loads(line) for line in f if line.strip()]
def side_by_side(cases, a, b):
"""Print case id, text, both outputs and a marker where they differ."""
for case, ra, rb in zip(cases, a, b):
x, y = ra.get("text", "ERROR"), rb.get("text", "ERROR")
print(f"{case['id']:<3}{case['text'][:28]:<30}{x[:24]:<26}{y[:24]:<26}{'' if x == y else '<-- differs'}")- মেটাডেটা (প্রথম
rec): কেস আইডি, প্রম্পট ভার্সন,prompt_sha, মডেলের নাম ও সেটিংস। ভার্সনের নাম মানুষ বদলাতে ভুলে যেতে পারে; টেমপ্লেটের লেখা বদলালেই হ্যাশ বদলে যায়, তাই রেকর্ড সবসময় হুবহু সেই লেখার সঙ্গে মেলানো যায়। (আপনার ফাইলের শেষে বাড়তি নিউলাইন থাকলে আপনার হ্যাশ এখানে ছাপা হ্যাশ থেকে আলাদা হবে; গুরুত্বপূর্ণ হলো লেখা বদলালে হ্যাশ বদলায়।) - JSONL মানে প্রতি লাইনে একটি JSON অবজেক্ট। ফাইল অ্যাপেন্ড মোডে খোলা হয় এবং প্রতিটি রেকর্ড সঙ্গে সঙ্গে লিখে ফ্লাশ করা হয়, তাই ৪০ নম্বর কেসে ক্র্যাশ হলেও ১ থেকে ৩৯ ডিস্কে থাকে, আর দুটি রান একই ফাইল ভাগ করতে পারে।
sort_keys=Trueলাইনগুলো স্থিতিশীল রাখে এবং diff-এ তুলনা সহজ করে। - দুটি লগ: রানারের লগে প্রতি কেসে একটি কাঠামোবদ্ধ রেকর্ড থাকে (যা আপনি বিশ্লেষণ করবেন); ক্লায়েন্টের নিজস্ব
log_path-এ প্রতিটি চেষ্টার জন্য পূর্ণ মেসেজসহ একটি লাইন থাকে (কিছু অদ্ভুত লাগলে যা পড়বেন)। - ব্যবহার ও ল্যাটেন্সি আসে
Responseথেকে। এখানে এগুলো মোটামুটি, নকল সংখ্যা; পরের পাতা এগুলো সততার সঙ্গে সামলায়।
মক মোড: টয় প্রোভাইডার দিয়ে অফলাইনে অনুশীলন
নেটওয়ার্ক, অ্যাকাউন্ট বা কোনো খরচ ছাড়াই পুরো এক্সপেরিমেন্ট লুপ তৈরি ও পরীক্ষা করতে পারা উচিত। একেই বলে মক মোড: ক্লায়েন্ট আসল প্রোভাইডারের বদলে একটি টয় প্রোভাইডার মোড়ে। নিচের টয়টি একটি স্ট্যান্ড-ইন, মডেল নয়। এটি কীওয়ার্ড দিয়ে শ্রেণিবদ্ধ করে; প্রম্পট পড়ে (তাই ভার্সন ২ সত্যিই আলাদা আচরণ করে); প্রম্পটে "label only" না থাকলে লম্বা মেসেজে বাচাল হয়ে ওঠে; আর temperature ০-এর বেশি হলে মাঝেমধ্যে সিড-করা ভুল উত্তর দেয়, যাতে পরের পাতাগুলোতে পরিবর্তনশীলতা নিয়ে অনুশীলন করা যায়। আসল মডেল কম পূর্বানুমেয়, আর ঠিক সে কারণেই পরীক্ষা করতে হয়; মক মোড প্রমাণ করে শুধু আপনার হারনেস কাজ করছে।
# file: toys.py
"""toys.py: stand-in providers (NOT real models) so experiments can be repeated exactly."""
import random
BILLING = ["invoice", "charged", "payment"]
DELIVERY = ["parcel", "package", "delivery", "arrived", "tracking", "driver"]
def make_triage(seed=7, noise=0.3):
"""Toy classifier. It reads the prompt: a 'refund' hint adds a billing word, 'label only' stops wordy
replies on long messages. With temperature above 0 it sometimes answers 'other' (seeded)."""
rng = random.Random(seed)
def provider(messages, temperature=0, **settings):
head, text = messages[-1]["content"].lower().split("### message", 1)
billing = BILLING + (["refund"] if "refund" in head else [])
label = ("billing" if any(w in text for w in billing)
else "delivery" if any(w in text for w in DELIVERY) else "other")
if temperature > 0 and rng.random() < noise:
label = "other"
if "label only" not in head and len(text.split()) > 9:
return f"I think it is {label}."
return label
return providerএকটি ছোট ফ্যাক্টরি প্রোভাইডার বেছে নেয়। মক মোডে সে টয় ফেরত দেয়; আসল রানে আপনি নিজের SDK কলকে provider(messages, **settings) ফাংশনে মুড়ে অন্য শাখায় রাখবেন। রানার বদলায় না।
from promptlab import Client
from runner import load_cases, run_experiment, read_jsonl, side_by_side
from toys import make_triage
def make_client(mode="mock", log="logs/calls.jsonl"):
if mode == "mock":
return Client(make_triage(), model="toy-triage", log_path=log)
raise NotImplementedError("wrap your SDK call in provider(messages, **settings) and return a Client")
client = make_client()
cases = load_cases("cases.json")
v1 = run_experiment(client, "v1", cases, "logs/v1.jsonl")
print(v1[0])
print(len(read_jsonl("logs/v1.jsonl")), "records in logs/v1.jsonl;", len(read_jsonl("logs/calls.jsonl")), "lines in logs/calls.jsonl"){'case': 'c1', 'version': 'v1', 'prompt_sha': '55f857e1', 'model': 'toy-triage', 'settings': {}, 'status': 'ok', 'text': 'billing', 'input_tokens': 28, 'output_tokens': 2, 'latency_ms': 60}
6 records in logs/v1.jsonl; 6 lines in logs/calls.jsonlএক্সপেরিমেন্ট লগ না হারিয়ে API এরর সামলানো
আসল কল ব্যর্থ হয়: টাইম-আউট, রেট লিমিট, নেটওয়ার্ক বিচ্ছিন্ন। একটি ব্যর্থতা যদি আপনার লুপের ভেতর দিয়ে এক্সেপশন ছুড়ে দেয়, রানের বাকি অংশ হারিয়ে যায়, আর যদি লিখতে লিখতে না এগোন তবে এ পর্যন্ত পাওয়া ফলাফলও যায়। ওপরের রানার ProviderError ধরে (ক্লায়েন্ট রিট্রাই শেষ হলে এটি ছোড়ে) এবং একটি error রেকর্ড রাখে। পরের ব্লকে এমন একটি প্রোভাইডার বানানো হয়েছে যা "tracking" সংক্রান্ত মেসেজে সবসময় ব্যর্থ হয়, একটি রিট্রাইসহ।
def fail_on(word, provider):
"""Stand-in for an unreliable API: raises a time-out whenever the message contains word."""
def wrapped(messages, **settings):
if word in messages[-1]["content"].lower():
raise TimeoutError("provider timed out")
return provider(messages, **settings)
return wrapped
flaky = Client(fail_on("tracking", make_triage()), model="toy-triage", log_path="logs/calls_flaky.jsonl", retries=1)
flaky_recs = run_experiment(flaky, "v2", cases, "logs/v2_flaky.jsonl")
print([r["status"] for r in flaky_recs])
print(flaky_recs[4]["error"])
print(len(read_jsonl("logs/v2_flaky.jsonl")), "experiment records;", len(read_jsonl("logs/calls_flaky.jsonl")), "attempts logged")['ok', 'ok', 'ok', 'ok', 'error', 'ok']
TimeoutError: provider timed out (after 2 attempt(s))
6 experiment records; 7 attempts loggedরান শেষ হয়েছে, কেস c5 কারণসহ error হিসেবে চিহ্নিত, আর ক্লায়েন্ট লগে ছয় কেসের জন্য সাতটি চেষ্টা দেখা যাচ্ছে: ব্যর্থ কেসটি দুবার চেষ্টা হয়েছে। ব্যর্থ কেস ডেটা, গোলমাল নয়: পরে আপনি ঠিক করবেন আবার চালাবেন কি না, আর অনুপস্থিত উত্তরকে কখনো ভুল বা ঠিক হিসেবে গোনেন না।
একই টেস্ট সেট, দুটি প্রম্পট ভার্সন
ন্যায্য তুলনায় একবারে একটি জিনিস বদলায়। নিচে দুটি ভার্সনই একই ক্লায়েন্টের মধ্য দিয়ে, একই ক্রমে সাজানো কেসে চলে; শুধু টেমপ্লেট আলাদা। অ্যাসার্শন যাচাই করে যে দুটি ফলাফল তালিকা সত্যিই একই কেস আইডি একই ক্রমে ঢাকছে।
v2 = run_experiment(client, "v2", cases, "logs/v2.jsonl")
assert [r["case"] for r in v1] == [r["case"] for r in v2]
LABELS = {"billing", "delivery", "other"}
for name, records in (("v1", v1), ("v2", v2)):
clean = sum(r["text"] in LABELS for r in records)
print(name, records[0]["prompt_sha"], "bare labels:", clean, "of", len(records))v1 55f857e1 bare labels: 4 of 6
v2 fbece8b2 bare labels: 6 of 6পাশাপাশি আউটপুট তুলনা
সংখ্যা খুঁটিনাটি লুকিয়ে রাখে; সারিগুলো পড়ুন। side_by_side প্রতিটি ইনপুটের পাশে দুটি আউটপুট ছাপে এবং পার্থক্য চিহ্নিত করে।
side_by_side(cases, v1, v2)c1 I was charged twice for invo billing billing
c2 My parcel has not arrived af delivery delivery
c3 Please send me a refund for I think it is other. billing <-- differs
c4 How do I change my profile p other other
c5 Tracking shows nothing for m delivery delivery
c6 The driver left the box outs I think it is billing. billing <-- differsভার্সন ২ দুটি সারি বদলেছে। c3-তে (রিফান্ডের অনুরোধ) ভার্সন ১ একটি বাক্যে "other" বলেছে ("I think it is other."), আর ভার্সন ২ শুধু লেবেল billing দিয়েছে, যা দুই দিক থেকেই ভালো। c6-তে মেসেজে দুটি সমস্যা, দেরি করা ড্রাইভার আর ভুল ইনভয়েস; ভার্সন ১ উত্তরকে বাক্যে মুড়েছে আর ভার্সন ২ শুধু লেবেল দিয়েছে, কিন্তু ভেতরের লেবেল একই, এবং কেউ যুক্তি দিতে পারেন যে এটি delivery হওয়া উচিত। সারি পড়ে আপনি এমন কিছু দেখলেন যা সংখ্যা দেখাতে পারত না: ভার্সন ২ বেশি পরিচ্ছন্ন, কিন্তু প্রতিটি কেসে প্রত্যাশিত উত্তর না থাকা পর্যন্ত কোনটি বেশি সঠিক তা বলা যায় না। সেটি পরের পাতার কাজ।
কাজের জায়গায় এটি কোথায় দেখা যায়
একটি সাপোর্ট টিম ট্রায়াজ প্রম্পট বদলায় এবং পুরনোটি সরানোর আগে জানতে চায় নতুনটি নিরাপদ কি না। একটি ডেটা টিম মডেল বা প্রম্পট বদলালেই একই দুইশো ইনপুট আবার চালায়। একটি রানার, ভার্সন করা টেমপ্লেটের একটি ফোল্ডার আর একটি JSONL লগ থাকলে ছয় মাস পরে অন্য কেউ রানটি পুনরাবৃত্তি করতে এবং অদ্ভুত কোনো সারি বুঝতে পারেন।
সাধারণ ভুল
- প্রম্পট ও ইনপুট একই স্ট্রিংয়ে মেশানো। নতুন কেসে প্রম্পট পুনরায় ব্যবহার করা বা ভার্সনের diff দেখা যায় না। টেমপ্লেট ও কেস আলাদা ফাইলে রাখুন।
- শুধু শেষে লগ করা। ক্র্যাশ বা থামানো রানে সব হারায়। প্রতিটি কেস শেষ হওয়ামাত্র একটি রেকর্ড অ্যাপেন্ড করুন।
- একটি API এররে রান থামানো, বা এরর লুকানো। স্ট্যাটাসসহ একটি error রেকর্ড রাখুন, আর অনুপস্থিত উত্তরকে কখনো উত্তর ধরে স্কোর করবেন না।
- ভিন্ন ইনপুটে ভার্সন তুলনা। একই কেস একই ক্রমে ব্যবহার করুন, আর একবারে একটি জিনিস বদলান।
- মকের ফলাফলকে আসল মডেলের প্রমাণ ভাবা। টয় প্রোভাইডার আপনার হারনেস পরীক্ষা করে। আসল আচরণ সম্পর্কে কিছু বলে শুধু আসল প্রোভাইডারে চালানো রান।
নিজে চেষ্টা করুন
এই পাতার ফাইল ও নেমস্পেস ব্যবহার করুন, promptlab.py-সহ একই ফোল্ডারে।
- সহজ। "Why was my card debited two times?" লেখাসহ সপ্তম কেস
c7যোগ করুন এবং তাতে ভার্সন ২ চালান। আগে লেবেল অনুমান করুন, আর বলুন একজন মানুষ একমত হতেন কি না। - মাঝারি।
failed_ids(records)লিখুন, যা statuserrorএমন কেস আইডিগুলো ফেরত দেয়, এবং ফ্ল্যাকি রানে ডাকুন। - কঠিন।
rerun_failed(client, version, cases, records, log)লিখুন, যা আপনার দেওয়া ক্লায়েন্ট দিয়ে শুধু ব্যর্থ কেসগুলো আবার চালায় এবং মূল ক্রমে পূর্ণ রেকর্ড তালিকা ফেরত দেয়। সুস্থclientদিয়ে ফ্ল্যাকি রানে পরীক্ষা করুন।
উত্তর
সহজ। "debited"-এর জন্য টয়ের কোনো কীওয়ার্ড নেই, তাই সে other বলে; একজন মানুষ বলতেন billing। এটি স্ট্যান্ড-ইনের কীওয়ার্ড-না-থাকার ব্যর্থতা, আর এমন সারিই আপনার টেস্ট সেটে নতুন কেস হয়ে ওঠে।
c7 = {"id": "c7", "text": "Why was my card debited two times?"}
print([r["text"] for r in run_experiment(client, "v2", [c7], "logs/c7.jsonl")])['other']মাঝারি।
def failed_ids(records):
return [r["case"] for r in records if r["status"] == "error"]
print(failed_ids(flaky_recs))['c5']কঠিন।
def rerun_failed(client, version, cases, records, log):
bad = {r["case"] for r in records if r["status"] == "error"}
again = {r["case"]: r for r in run_experiment(client, version, [c for c in cases if c["id"] in bad], log)}
return [again.get(r["case"], r) for r in records]
fixed = rerun_failed(client, "v2", cases, flaky_recs, "logs/v2_flaky.jsonl")
print([r["status"] for r in fixed], failed_ids(fixed))['ok', 'ok', 'ok', 'ok', 'ok', 'ok'] []পাসের চেকলিস্ট: সহজ উত্তরে শুধু লেবেল নয়, কারণও আছে; failed_ids একটি ফিল্ড পড়ে এবং কখনো প্রোভাইডার ডাকে না; rerun_failed শুধু ব্যর্থ কেসের জন্য প্রোভাইডার ডাকে, মূল তালিকার ক্রম রাখে এবং একই লগে অ্যাপেন্ড করে, ফলে লগে এখন ব্যর্থ রেকর্ড ও তার মেরামত দুটোই আছে (ব্যর্থতা মুছবেন না)। এটি প্রম্পট চেইনিং: ধাপ, নির্দিষ্ট স্কিমা ও promptlab হেল্পার-এর থামো-আর-ধাপের-নাম-বলো অভ্যাসের পুনরাবৃত্তি।
সারসংক্ষেপ
- প্রম্পট টেমপ্লেট, টেস্ট কেস ও কোড আলাদা ফাইলে রাখুন;
renderঅনুপস্থিত ও অব্যবহৃত ভেরিয়েবল ধরে। - যেকোনো
Client-এ কাজ করে এমন একটি রানার লিখুন, এবং প্রতি কেসে মডেল, প্রম্পট ভার্সন, প্রম্পট হ্যাশ, সেটিংস, স্ট্যাটাস, টেক্সট, ব্যবহার ও ল্যাটেন্সি রেকর্ড করুন। - JSONL রেকর্ড লিখতে লিখতে অ্যাপেন্ড করুন; API এরর error রেকর্ড করুন এবং স্কোর থেকে বাইরে রাখুন।
- অফলাইনে হারনেস পরীক্ষা করতে মক মোড হিসেবে টয় প্রোভাইডার ব্যবহার করুন; এটি স্ট্যান্ড-ইন, আসল মডেলের প্রমাণ নয়।
- একই কেস একই ক্রমে ভার্সন তুলনা করুন, এবং সারিগুলো পাশাপাশি পড়ুন।
এরপর: মেট্রিক ও এক্সপেরিমেন্ট রিপোর্ট: accuracy, precision, recall, ল্যাটেন্সি ও CSV, যেখানে প্রত্যাশিত উত্তর এই আউটপুটগুলোকে স্কোর ও একটি CSV রিপোর্টে রূপ দেয়।