অধ্যায় 11 · প্র্যাকটিস ল্যাব ও ক্যাপস্টোন
প্র্যাকটিস ল্যাব
- পৃষ্ঠা 21 / 22
- 22 মিনিট পড়া
পুনের প্রিয়াকে প্রমাণ করতে বলা হয়েছে যে তিনি এমন একটা AI ফিচার বানাতে পারেন যার ওপর একটা টিম ভরসা করতে পারে। সার্টিফিকেট সেটা দেখায় না। দেখায় একটা গ্রেড করা রান: নির্দিষ্ট কিছু কেস, একটা স্কোর, আর এমন কিছু বিষয় যেগুলো ঘটলে যত পয়েন্টই পান পুরো কাজ বাতিল। এই পাতা পুরো টিউটোরিয়ালের জন্য সেই রান। আপনি ছয়টা চ্যালেঞ্জে একটা ছোট নলেজ অ্যাসিস্ট্যান্ট বানাবেন, আর একটা গ্রেডার সেটাকে ১০০-এর মধ্যে স্কোর দেবে।
সবকিছু অফলাইন। মডেলটা একটা বিকল্প, ভাষা মডেল নয়; আসল মডেল কম অনুমানযোগ্য, আর ঠিক সে কারণেই টেস্ট করতে হয়। গ্রেডারের কোনো API কি, নেটওয়ার্ক বা ডাউনলোড লাগে না। এই পাতা kit/lab/ ফোল্ডার আর kit/tests/test_lab.py ফাইলটা যোগ করে, আগে থেকে থাকা কিছুই বদলায় না।
যা শিখবেন
- ল্যাবটা কীভাবে গড়া: ছয়টা চ্যালেঞ্জ, ১০০ পয়েন্ট, পাস মার্ক ৭০, আর চারটা স্বয়ংক্রিয় ক্রিটিক্যাল ব্যর্থতা যা স্কোরের ওপরে।
- আগের প্রতিটা পার্টকে কীভাবে ফিক্সচার ও স্বয়ংক্রিয় পাস-মানদণ্ডে বদলাতে হয়, স্টার্টার ফাইল, ইঙ্গিত ও আদর্শ সমাধানসহ।
- একটা গ্রেডার যা ধরার দাবি করে তা সত্যিই ধরে কি না, সেটা কীভাবে প্রমাণ করবেন: পাস করা রেফারেন্স আর ইচ্ছা করে ব্যর্থ করা সাবমিশন দিয়ে।
- ফিক্সচার-গ্রেডার কী প্রমাণ করতে পারে না, আর README, উদাহরণ আউটপুট ও ডিজাইন নোটসহ কাজ কীভাবে জমা দেবেন।
ল্যাব কীভাবে কাজ করে
ল্যাবের নাম "Build a Reliable Knowledge Assistant"। আপনি submission/ নামের একটা ফোল্ডারে ছয়টা ছোট Python মডিউল লিখবেন। গ্রেডার সেগুলো ইমপোর্ট করে, রেকর্ড করা রিপ্লাই আর একটা নির্দিষ্ট কর্পাস (আবার Kestrel Cycles হেল্প সেন্টার) দেয়, এবং পয়েন্ট যোগ করে। পুরো evidencedesk প্যাকেজ আপনি ইমপোর্ট করতে পারেন: ওটা আপনারই প্রজেক্ট, আর ভালো ইঞ্জিনিয়াররা টেস্ট করা অংশ আবার কাজে লাগান।
| চ্যালেঞ্জ | পয়েন্ট | ফাইল | পার্ট |
|---|---|---|---|
| ১. প্রোভাইডার-নিরপেক্ষ ক্লায়েন্ট | ১৫ | client.py | ১ |
| ২. কাঠামোবদ্ধ স্ট্রিমিং | ১৫ | stream.py | ৩ |
| ৩. লোকাল এমবেডিং ও সার্চ | ১৫ | search.py | ৪ |
| ৪. সাইটেশন-ভিত্তিক RAG | ২৫ | rag.py | ২, ৫ |
| ৫. নিরাপদ টুল কলিং | ১৫ | tools.py | ৬, ৯ |
| ৬. মূল্যায়ন ও খরচ নিয়ন্ত্রণ | ১৫ | report.py | ৭–৮ |
পাস: কমপক্ষে ৭০ পয়েন্ট, এবং কোনো ক্রিটিক্যাল ব্যর্থতা নেই, এবং সম্পূর্ণ সাবমিশন (তালিকা নিচে)। চারটা স্বয়ংক্রিয় ক্রিটিক্যাল ব্যর্থতা স্কোর যা-ই হোক চেষ্টা বাতিল করে: কমিট করা ক্রেডেনশিয়াল, মডেলের তৈরি টেক্সট কোড হিসেবে চালানো, টুল অথরাইজেশন এড়িয়ে যাওয়া, আর বানানো সাইটেশনকে যাচাই-করা বলে ফেরত দেওয়া। API কি বা কানেকশন না থাকলে পাস আটকায় না: গ্রেডার চলার সময় নেটওয়ার্ক আর API কি-র এনভায়রনমেন্ট ভ্যারিয়েবল বন্ধ করে রাখে।
জমা দেওয়ার তালিকা (পার্ট ১০): সোর্স কোড, আপনার নিজের ফিক্সচার ডেটাসেট, টেস্ট, একটা README, উদাহরণ আউটপুট এবং ডিজাইনের ট্রেড-অফের একটা ছোট ব্যাখ্যা। গ্রেডার দেখে এগুলো আছে কি না, প্লেসহোল্ডার নয় কি না (TODO নেই, ন্যূনতম দৈর্ঘ্য আছে), আর আপনার নিজের টেস্ট পাস করছে কি না। সেগুলো ভালো কি না, তা সে বিচার করতে পারে না।
স্টার্টার থেকে শুরু করুন। প্রতিটা স্টাব NotImplementedError তোলে, আর তার ডকস্ট্রিংই চুক্তি: নাম, আর্গুমেন্টের ধরন, রিটার্নের আকার ও কোড।
cp -r lab/starter submission
ls submissionclient.py
DESIGN.md
example_output.txt
fixtures
rag.py
README.md
report.py
search.py
stream.py
tests
tools.py# excerpt of kit/lab/starter/client.py
"""Challenge 1 (15 points): one internal contract behind a fake client and two wire formats.
Read fixtures: lab/fixtures/wire.json (request and reply shape of wire A and wire B, and two error replies).
"""
def make_client(kind: str, transport=None):
"""kind is 'fake', 'wire_a' or 'wire_b'.
- 'fake': an object with complete(request) -> LLMResponse that answers from recorded text and raises LookupError for an unknown question.
- 'wire_a' / 'wire_b': an adapter. transport(payload_dict) -> reply_dict stands in for the network. Map our LLMRequest to the wire
payload, map the reply back to an LLMResponse (text, usage, stop_reason in our words: end, length, other), and turn a non-200
status into evidencedesk.errors.from_status(...). Anything else: raise ValueError.
"""
raise NotImplementedError("TODO challenge 1")অপরিবর্তিত স্টার্টারে গ্রেডার চালান। স্কোর ০, আর সেটাই সঠিক উত্তর। আরও একটা জিনিস লক্ষ করুন: কোনো ক্রিটিক্যাল চেক ব্যর্থ হয়নি, কারণ ক্ষতি করতে পারে এমন কিছু চলেইনি, আর সাবমিশনের তালিকা ঠিক বলে দিচ্ছে কী বাকি।
python -m lab.grade submission1. Provider-neutral client 0/15
2. Structured streaming 0/15
3. Local embeddings and search 0/15
4. Citation-based RAG 0/25
5. Safe tool calling 0/15
6. Evaluation and cost controls 0/15
Critical checks
[ok ] no committed credentials
[ok ] no model-run code
[ok ] tool authorization kept
[ok ] no fake citation verified
Required submission
[FAIL] README.md needs 300+ characters and no TODO
[FAIL] DESIGN.md needs 300+ characters and no TODO
[FAIL] example_output.txt needs 50+ characters and no TODO
[FAIL] fixture dataset fixtures/ needs your own data file (no TODO)
[FAIL] tests written 0 test function(s), need 3+
SCORE 0/100 | critical failures 0 | RESULT: FAILফিক্সচার ও গ্রেডার
ফিক্সচার হলো lab/fixtures/-এর খোলা ফাইল: JSON lines, প্রতি সারিতে একটা কেস। আগে সেগুলো পড়ুন; সেগুলোই স্পেসিফিকেশন। দুটো সারি ধারণাটা দেখায়: যে স্ট্যাটাস তৈরি করতেই হবে সেটাসহ একটা রেকর্ড করা স্ট্রিম, আর এমন একটা রেকর্ড করা মডেল রিপ্লাই যা কোথাও নেই এমন একটা আইডি উদ্ধৃত করে।
import json
for name, n in (("stream_cases", 0), ("rag_cases", 5)):
row = json.loads(open(f"lab/fixtures/{name}.jsonl", encoding="utf-8").read().splitlines()[n])
print(name)
for key, value in row.items():
print(f" {key}: {value}")stream_cases
id: s1
check: assemble
events: [['chunk', 'Standard deliv'], ['chunk', 'ery takes 4 '], ['chunk', 'to 7 days.'], ['done', '']]
status: complete
text: Standard delivery takes 4 to 7 days.
rag_cases
id: f1
check: fabricated
question: How should I store the e-bike battery for a long time?
reply: {'answer': 'Keep it at 50 percent in a dry place.', 'cited_ids': ['kc-battery-v9#s2'], 'confidence': 'high', 'status': 'answered'}
expect: abstainedগ্রেডার (python -m lab.grade <folder> [--verbose] [--no-tests]) একটাই ফাইল। প্রতিটা চেক একটা ছোট ফাংশন, যা চ্যালেঞ্জ, নাম ও পয়েন্টসহ নিবন্ধিত, আর বার্তাসহ একটা AssertionError দিয়ে ব্যর্থ হয়। একটা ভাঙা ফাইল শুধু তার নিজের চ্যালেঞ্জের পয়েন্ট কাটে। একটা অংশ:
# excerpt of kit/lab/grade.py
def check(challenge: int, name: str, points: int):
def register(fn):
CHECKS.append((challenge, name, points, fn))
return fn
return register
def stream_check(group: str, ctx: Ctx):
for row in (r for r in rows("stream_cases") if r["check"] == group):
events = [StreamEvent(kind, text, stop_reason="end" if kind == "done" else None) for kind, text in row["events"]]
status, text = ctx.m("stream").collect(events)
assert (status, text) == (row["status"], row["text"]), f"{row['id']}: got {(status, text[:20])}"
for _group, _points in (("assemble", 4), ("interrupted", 3), ("error", 2), ("empty", 1)):
check(2, f"stream_{_group}", _points)(lambda ctx, g=_group: stream_check(g, ctx))
def critical_checks(ctx: Ctx) -> list[tuple[str, bool, str]]:
out = []
hits = scan_secrets(ctx.folder, {})
out.append(("no committed credentials", not hits, f"{len(hits)} finding(s): {hits[:2]}" if hits else ""))দুটো সৎ সীমা। প্রথমত, গ্রেডার শুধু ফিক্সচার দেখে: একটা সমাধান ঠিক এই কেসগুলো সামলে পাস করে যেতে পারে। ফিক্সচারকে একটা আচরণের উদাহরণ ভাবুন, তারপর নিজে আরও কেস টেস্ট করুন। দ্বিতীয়ত, প্রতিটা চেকে পয়েন্ট হয় পুরোটা, নয় শূন্য; তাই কোড বদলানোর আগে ব্যর্থতার বার্তাটা পড়ুন।
চ্যালেঞ্জ ১: প্রোভাইডার-নিরপেক্ষ ক্লায়েন্ট (১৫ পয়েন্ট)
কাজ। make_client(kind, transport=None) লিখুন। fake ধরন রেকর্ড করা টেক্সট থেকে উত্তর দেওয়া একটা ক্লায়েন্ট ফেরত দেয়। wire_a ও wire_b ধরন দুটো কাল্পনিক প্রোভাইডারের অ্যাডাপ্টার ফেরত দেয়, যাদের রিকোয়েস্ট ও রিপ্লাইয়ের আকার আলাদা। তিনটাই আমাদের নিজস্ব টাইপ দিয়ে complete(request) -> response দেয়। ফিক্সচার: wire.json-এ দুটো ওয়্যার ফরম্যাট আর দুটো এরর রিপ্লাই আছে; ট্রান্সপোর্ট একটা রেকর্ড করা ফাংশন, তাই নেটওয়ার্ক লাগে না।
| চেক | পয়েন্ট | পাস হয় যখন |
|---|---|---|
fake_offline | ৩ | ফেক ক্লায়েন্ট কোনো ক্রেডেনশিয়াল ছাড়াই রেকর্ড করা টেক্সট ফেরত দেয় |
fake_never_invents | ২ | অজানা প্রশ্নে বানানো উত্তর নয়, লুকআপ এরর ওঠে |
adapters_one_contract | ৪ | দুটো অ্যাডাপ্টার একই কনটেন্ট থেকে একই টেক্সট, ইউসেজ ও স্টপ রিজন বানায় |
stop_reasons | ২ | তাদের "কেটে গেছে" ও অজানা শব্দ length ও other হয় |
errors_classified | ৩ | দুই অ্যাডাপ্টারেই স্ট্যাটাস 429 রিট্রাইযোগ্য এরর, 401 নয় |
request_mapped | ১ | প্রশ্নটা পেলোডে পৌঁছায় |
ইঙ্গিত। প্রোভাইডার-নিরপেক্ষ LLM ক্লায়েন্ট (টাইপ ও ফেক); অ্যাডাপ্টার, কনফিগারেশন ও এরর হ্যান্ডলিং (স্ট্যাটাস থেকে এরর ক্লাস)। ভালো সমাধান প্রোভাইডারের শব্দ অ্যাডাপ্টারের ভেতরেই রাখে: প্রজেক্টের বাকি অংশ finish বা done_reason কখনো দেখে না। অ্যাডাপ্টারের ভেতরে রিট্রাই করে না, আর ভেতরের তথ্য থাকতে পারে এমন রেসপন্স বডি কখনো প্রিন্ট করে না।
চ্যালেঞ্জ ২: কাঠামোবদ্ধ স্ট্রিমিং (১৫ পয়েন্ট)
কাজ। stream.py-তে (status, text) ফেরত দেওয়া collect(events) আর (ok, code) ফেরত দেওয়া read_reply(response) লিখুন। ফিক্সচার: stream_cases.jsonl (ছয়টা রেকর্ড করা স্ট্রিম, একটা বাংলায়, শব্দের মাঝখানে ভাঙা) আর reply_cases.jsonl (পাঁচটা মডেল রিপ্লাই)।
| চেক | পয়েন্ট | পাস হয় যখন |
|---|---|---|
stream_assemble | ৪ | চাঙ্ক জোড়া লেগে হুবহু টেক্সট হয়; স্ট্যাটাস complete |
stream_interrupted | ৩ | done ইভেন্ট না থাকলে interrupted হয় এবং আংশিক টেক্সট থাকে |
stream_error, stream_empty | ২ + ১ | error ইভেন্টে error হয়; শুধু done থাকলে empty |
reply_valid | ২ | বৈধ উত্তর বা বৈধ বিরত থাকা (abstention) হলে ok আসে |
reply_malformed | ৩ | সাধারণ টেক্সট, ভুল স্কিমা ও কেটে যাওয়া রিপ্লাই যথাক্রমে invalid_json, invalid_schema ও truncated দেয়, কিছু না তুলে |
ইঙ্গিত। স্ট্রিমিং ও কাঠামোবদ্ধ ফলাফল; নির্ভরযোগ্য আউটপুট চুক্তি। ভালো সমাধান পার্স করার আগে স্টপ রিজন দেখে, কারণ কেটে যাওয়া রিপ্লাইও JSON-এর মতো দেখাতে পারে, আর বৈধ JSON-কে তিনটা চেকের (সিনট্যাক্স, স্কিমা, বিজনেস রুল) প্রথমটা মাত্র ধরে।
চ্যালেঞ্জ ৩: লোকাল এমবেডিং ও সার্চ (১৫ পয়েন্ট)
কাজ। search.py-তে Search(corpus_dir) লিখুন, যার search(query, k=3, min_score=0.3) ফেরত দেয় [(passage_id, score)]। ফিক্সচার: আসল কর্পাসের ওপর search_cases.jsonl (আটটা কোয়েরি); কর্পাসে একটা বাতিল রিটার্ন পলিসি আর বাংলা পাতা আছে।
| চেক | পয়েন্ট | পাস হয় যখন |
|---|---|---|
top_delivery, top_battery | ২ + ২ | সেরা ফলাফলটা প্রত্যাশিত ডকুমেন্টের অংশ |
bangla_question | ২ | বাংলা প্রশ্ন বাংলা রিটার্ন পাতা খুঁজে পায় |
current_only | ২ | কোনো বাতিল সেকশন ফেরত আসে না, আর চলতি রিটার্ন পাতা প্রথমে |
blank_input | ২ | খালি ও শুধু-স্পেস কোয়েরি [] দেয় |
no_result | ৩ | যে প্রশ্নের উত্তর কোনো ডকুমেন্টে নেই, আর অর্থহীন কোয়েরি, [] দেয় |
order_and_k | ২ | k-এর বেশি নয়, সেরা আগে |
ইঙ্গিত। এমবেডিং ও সাদৃশ্য; লোকাল ইনডেক্স ও রিট্রিভালের মান। ভালো সমাধান র্যাংক করার আগে স্ট্যাটাস দিয়ে ছাঁকে, আর "যথেষ্ট ভালো কিছু নেই"-কে একটা খালি তালিকা হিসেবে দেয় যার ওপর কলার সিদ্ধান্ত নিতে পারে। মনে রাখবেন, উঁচু সাদৃশ্য স্কোর একটা সংকেত, প্রমাণ নয় যে অনুচ্ছেদটা প্রশ্নের উত্তর দেয়; ০.৩ এই টয় এমবেডারের জন্য একটা শুরুর আন্দাজ, ঠিক করে নেওয়া মান নয়।
চ্যালেঞ্জ ৪: সাইটেশন-ভিত্তিক RAG (২৫ পয়েন্ট)
কাজ। rag.py-তে Assistant(corpus_dir).ask(client, question) লিখুন। এটা একটা ডিকশনারি ফেরত দেয়: status (answered বা abstained), text, citations, verified, reason ও retrieved। উত্তর শুধু তখনই যাচাই-করা, যখন প্রতিটা উদ্ধৃত আইডি রিট্রিভ হয়েছে, বাস্তবে আছে, এবং উদ্ধৃত টেক্সটে দাবির সংখ্যা ও মূল শব্দগুলো আছে। বাকি সবকিছু নির্ধারিত বিরত থাকার উত্তর (abstention): প্রজেক্টের প্রমাণ-না-থাকার বার্তা, কোনো সাইটেশন নয়, verified মিথ্যা, আর মডেলের নিজের কথা কখনো দেখানো হয় না। ফিক্সচার: rag_cases.jsonl, আটটা প্রশ্ন, প্রতিটার সঙ্গে স্ট্যান্ড-ইন মডেলের রেকর্ড করা রিপ্লাই।
| চেক | পয়েন্ট | পাস হয় যখন |
|---|---|---|
answers_cite_sources | ৬ | উত্তরযোগ্য তিনটা প্রশ্নে প্রত্যাশিত সেকশন উদ্ধৃত করা যাচাই-করা উত্তর আসে |
citations_resolve | ৩ | প্রতিটা সাইটেশন কর্পাসে আছে এবং রিট্রিভ করা আইডিগুলোর মধ্যে ছিল |
unsupported_abstains | ৫ | কিস্তির প্রশ্ন ও খোলার-সময়ের প্রশ্নে বিরত থাকে, যদিও স্ট্যান্ড-ইন মডেল আত্মবিশ্বাসে উত্তর দিত |
fabricated_id_refused | ৫ | কোথাও নেই এমন আইডি উদ্ধৃত রিপ্লাই বিরত থাকার উত্তর পায় |
unretrieved_id_refused | ৩ | আসল কিন্তু কখনো না-দেখানো সেকশন উদ্ধৃত রিপ্লাই প্রত্যাখ্যাত হয় |
unsupported_claim_refused | ৩ | সঠিক আইডি কিন্তু টেক্সটে নেই এমন দাবি ("90 days") প্রত্যাখ্যাত হয় |
ইঙ্গিত। RAG ইনজেশন ও সাইটেশন প্রবাহ (রিট্রিভ, প্রশ্ন, উদ্ধৃতি); RAG-এর নির্ভরযোগ্যতা ও মূল্যায়নের ডেটা (দুর্বল রিট্রিভাল, সাইটেশনের সমর্থন); কনটেক্সট সাজানো ও বিশ্বাসের সীমানা (রিট্রিভ করা টেক্সট ডেটা)। ভালো সমাধান সাইটেশন বানায় শুধু যা রিট্রিভ করেছে তা থেকে, মডেলের তালিকা থেকে নয়; রিট্রিভাল দুর্বল হলে মডেলকে ডাকার আগেই বিরত থাকে; এবং জানে যে বৈধ আইডি মানেই সমর্থন নয়। সমর্থন-চেক শব্দ ও সংখ্যা মেলায়, অর্থ নয়, তাই অনুচ্ছেদের শব্দ দিয়ে গড়া চতুর ভুল দাবি তবু পাস করতে পারে। সব কারণে একই বিরত থাকার উত্তর ফেরত দেয়, যাতে কলার ভেতরের তথ্য জানতে না পারে; নির্দিষ্ট কারণটা যায় লগের জন্য reason-এ।
চ্যালেঞ্জ ৫: নিরাপদ টুল কলিং (১৫ পয়েন্ট)
কাজ। tools.py-তে Tools(corpus_dir).run(raw, task, user, allowed_tasks) লিখুন, যা {ok, code, data} ফেরত দেয়। তিনটা টুল নিবন্ধিত: calculator ও lookup_document (রিড) এবং send_email_draft (রাইট; মেইল সিস্টেম self.outbox-এর একটা লিস্ট)। টাস্ক তিনটা: math, faq ও reply; প্রতিটা একটা করে টুল ব্যবহার করতে পারে, আর এই ল্যাবে কোনো রাইট টুল কখনো অনুমোদিত নয়। ফিক্সচার: tool_cases.jsonl, নয়টা কল, প্রত্যাশিত ফলাফল ও গ্রহণযোগ্য এরর কোডসহ।
| চেক | পয়েন্ট | পাস হয় যখন |
|---|---|---|
read_tools | ৩ | অনুমোদিত টাস্কে একটা হিসাব ও একটা ডকুমেন্ট লুকআপ চলে |
unknown_tool | ২ | যে টুল নেই তা প্রত্যাখ্যাত হয় |
invalid_arguments | ২ | ভুল ধরনের আর্গুমেন্টে invalid_arguments আসে |
wrong_task | ২ | faq টাস্কের ভেতরে ইমেইল টুল প্রত্যাখ্যাত হয় এবং কিছু পাঠানো হয় না |
write_needs_a_person | ২ | নিজের টাস্কে ইমেইল টুল প্রত্যাখ্যাত হয় ও অনুমোদনের অপেক্ষায় থাকে; কিছু পাঠানো হয় না |
identity | ৩ | ব্যবহারকারী না থাকলে unauthenticated; যে টাস্ক ব্যবহারকারী শুরু করতে পারেন না তাতে forbidden_task |
hostile_expression | ১ | মডিউল ইমপোর্ট করতে চাওয়া ক্যালকুলেটর এক্সপ্রেশনে bad_expression আসে |
ইঙ্গিত। টুল চুক্তি ও এক্সিকিউশন; টুলের অনুমতি ও ব্যর্থতা সামলানো; প্রম্পট ইনজেকশন ও অ্যাক্সেসের সীমানা (টুলের আগে পরিচয়)। ভালো সমাধান প্রতিটা নিয়ম মডেলের বাইরে কোডে রাখে, কারণ প্রম্পট কোনো গার্ডরেইল নয়; "নেই" আর "এই টাস্কের জন্য নয়"-কে একই উত্তর দেয়; এবং eval ডাকার বদলে অঙ্কের জন্য সিনট্যাক্স ট্রি হাঁটে।
চ্যালেঞ্জ ৬: মূল্যায়ন ও খরচ নিয়ন্ত্রণ (১৫ পয়েন্ট)
কাজ। report.py-তে তিনটা জিনিস লিখুন। run_eval(cases, ask) ফেরত দেয় {total, passed, failures} এবং ক্র্যাশ করা কেসকে ব্যর্থতা গোনে। Guarded(inner, max_calls) একটা ক্লায়েন্টকে হুবহু-মিল ক্যাশ ও কল বাজেটে মোড়ে: পুনরাবৃত্তি বিনা খরচের, বাজেটের পর নতুন রিকোয়েস্ট এমন এরর তোলে যার code হলো budget_exceeded। report_md(result, limitations) সংখ্যা, প্রতিটা ব্যর্থ আইডি ও সীমাবদ্ধতা লেখে, আর খালি তালিকা প্রত্যাখ্যান করে। ফিক্সচার: eval_set.jsonl, আটটা কেস, এবং একটা স্টাব যে ইচ্ছা করে তিনটা ভুল করে (একটা এমন প্রশ্নের উত্তর দেয় যা প্রত্যাখ্যান করা উচিত, একটা এমন প্রশ্ন প্রত্যাখ্যান করে যার উত্তর দেওয়া উচিত, একটা ক্র্যাশ করে)।
| চেক | পয়েন্ট | পাস হয় যখন |
|---|---|---|
eval_counts | ৩ | মোট ৮, পাস ৫ |
eval_lists_failures, eval_survives_crash | ২ + ২ | ঠিক তিনটা ব্যর্থ আইডি তালিকায়, ক্র্যাশ তার একটা, আর দুইবার চালালে ফল এক |
cache_hit_is_free | ৩ | দুটো সমান রিকোয়েস্টে একটাই মডেল কল |
budget_stops_calls | ৩ | তৃতীয় নতুন রিকোয়েস্ট budget_exceeded-এ থামে, আর ক্যাশ করা উত্তর তবু চলে |
report_lists_failures, report_has_limitations | ১ + ১ | রিপোর্টে ব্যর্থতা ও সীমাবদ্ধতার নাম আছে; সীমাবদ্ধতা না থাকলে এরর |
ইঙ্গিত। মূল্যায়ন ডেটাসেট ও নির্ধারিত টেস্ট; রুব্রিক, রিগ্রেশন ও রিলিজ গেট (রিপোর্ট ব্যর্থতা তালিকাভুক্ত করে); খরচ ও লেটেন্সির মূল কথা; বাজেট, ক্যাশিং ও ব্যবহারের সীমা (ক্যাশ কি, কড়া থামা)। ভালো সমাধান বাজেটের আগে ক্যাশ দেখে, ব্যর্থতা কখনো ক্যাশ করে না, এবং সীমাবদ্ধতা লেখে সবার আগে: গ্রেডারের নিজের স্টাব মডেল, আটটা কেস আর একটা কর্পাস।
ক্রিটিক্যাল ব্যর্থতা
এই চারটা চেকের নিজের পয়েন্ট নেই; এরা ঠিক করে পয়েন্টগুলো গোনা হবে কি না। প্রতিটা একটা স্বয়ংক্রিয় চেক, প্রতিশ্রুতি নয়।
| ক্রিটিক্যাল চেক | গ্রেডার কীভাবে দেখে |
|---|---|
| no committed credentials (কমিট করা ক্রেডেনশিয়াল নেই) | প্রজেক্টের রিলিজ স্ক্যান (API কি-র আকার, সিক্রেট-ধরনের ফাইলের নাম, হার্ড-কোড করা সিক্রেট); ফাইল, লাইন ও ধরন জানায়, মান কখনো নয় |
| no model-run code (মডেলের চালানো কোড নেই) | eval, exec, compile, __import__, os.system ও subprocess-এর জন্য সিনট্যাক্স ট্রি স্ক্যান, সঙ্গে একটা শত্রুভাবাপন্ন ক্যালকুলেটর কল যা চললে একটা ফ্ল্যাগ সেট করত |
| tool authorization kept (টুল অথরাইজেশন বজায় আছে) | ভুল-টাস্ক, অনুমোদন-ছাড়া ও পরিচয়-ছাড়া চেষ্টার পর কোনো রাইট কল ok ফেরত দেয়নি এবং আউটবক্স খালি |
| no fake citation verified (বানানো সাইটেশন যাচাই-করা হয়নি) | বিরত থাকার (abstention) কোনো কেস উত্তর, যাচাই-করা বা সাইটেশনসহ ফেরত আসেনি, আর প্রতিটা সাইটেশন কর্পাসে আছে |
যে গ্রেডারকে ঠকানোর চেষ্টা করেননি, সে শুধু একটা মত। lab/bad.py রেফারেন্স সমাধানের একটা কপিতে ঠিক একটা করে ত্রুটি বসায়। অথরাইজেশন চেকের ত্রুটিটা এর একটা সাধারণ নমুনা: একটা "সুবিধাজনক" শর্টকাট, যা মডেলের চাওয়া ইমেইলটা পাঠিয়ে দেয়।
# excerpt of kit/lab/bad.py
"auth": ("tools.py", '''
_gated_run = Tools.run
def _run_open(self, raw, task, user, allowed_tasks): # "convenient": the model asked for an email, so send it
if isinstance(raw, dict) and raw.get("name") == "send_email_draft":
self.outbox.sent.append(dict(raw.get("arguments", {})))
return {"ok": True, "code": "ok", "data": {"queued": True}}
return _gated_run(self, raw, task, user, allowed_tasks)
Tools.run = _run_open
'''),চারটা কপি গ্রেড করুন। প্রথম কপির API কি চালানোর সময় জোড়া লাগিয়ে বানানো হয়, তাই ল্যাবের নিজের ফাইলে API কি-র আকারের কোনো টেক্সট নেই। bad/ ফোল্ডারটা পরে মুছে ফেলা হয়, কারণ ডকুমেন্টেশন, রিলিজ ও পোর্টফোলিওর মান পাতার রিলিজ চেক সেটাকে ঠিকই প্রত্যাখ্যান করত।
for flaw in secret exec auth fabricated; do
python -m lab.bad $flaw bad/$flaw > /dev/null
echo "== $flaw"
python -m lab.grade bad/$flaw --no-tests | grep -E 'FAIL|SCORE'
done
rm -rf bad== secret
[FAIL] no committed credentials 2 finding(s): ['local_settings.py:1 provider-style key', 'local_settings.py:1 hard-coded secret']
SCORE 100/100 | critical failures 1 | RESULT: FAIL
== exec
[FAIL] no model-run code tools.py:32 eval(); model text ran as code
SCORE 94/100 | critical failures 1 | RESULT: FAIL
== auth
[FAIL] tool authorization kept t5 ran; t6 ran
SCORE 93/100 | critical failures 1 | RESULT: FAIL
== fabricated
[FAIL] no fake citation verified f1 shown as an answer; o1 shown as an answer
SCORE 89/100 | critical failures 1 | RESULT: FAILপ্রতিটা কপি ঠিক একটা ক্রিটিক্যাল চেকে ব্যর্থ হয়, আর গ্রেডার ফাইল বা কেসের নাম বলে দেয়। স্কোরগুলো দেখুন: চারটাই ৮৯ বা তার বেশি। শুধু পয়েন্ট দেখলে চারটাই পাস হতো, আর সে কারণেই ক্রিটিক্যাল চেক আগে। এগুলোর পেছনের অভ্যাসের জন্য দেখুন নিরাপদ ব্যর্থতা ও নিরাপত্তা রিগ্রেশন টেস্ট।
রেফারেন্স সমাধান
lab/reference/ ছয়টা চ্যালেঞ্জেরই উত্তর দেয় প্রজেক্ট মডিউলের ওপর পাতলা স্তর দিয়ে, সঙ্গে একটা README, একটা ডিজাইন নোট, নিজের টেস্ট, নিজের কেসের একটা ফিক্সচার ফাইল, আর একটা উদাহরণ আউটপুট যা একটা টেস্ট আবার তৈরি করে মিলিয়ে দেখে। ওটা পড়ার আগে চ্যালেঞ্জগুলো চেষ্টা করুন। এই হলো তার আসল গ্রেড।
python -m lab.grade lab/reference1. Provider-neutral client 15/15
2. Structured streaming 15/15
3. Local embeddings and search 15/15
4. Citation-based RAG 25/25
5. Safe tool calling 15/15
6. Evaluation and cost controls 15/15
Critical checks
[ok ] no committed credentials
[ok ] no model-run code
[ok ] tool authorization kept
[ok ] no fake citation verified
Required submission
[ok ] README.md
[ok ] DESIGN.md
[ok ] example_output.txt
[ok ] fixture dataset
[ok ] tests written 4 test function(s), need 3+
[ok ] own tests pass
SCORE 100/100 | critical failures 0 | RESULT: PASSএটা ১০০-এর মধ্যে ১০০ পায় এবং কোনো ক্রিটিক্যাল ব্যর্থতা ও সম্পূর্ণ সাবমিশনসহ পাস করে। সংখ্যাটা সাবধানে পড়ুন। এটা ফিক্সচার জেনে লেখা, তাই এটুকুই দেখায় যে গ্রেডার ও ফিক্সচার একটা সঠিক সমাধানের সঙ্গে একমত। ফিক্সচারের বাইরের প্রশ্ন নিয়ে এটা কিছু বলে না: টয় এমবেডার "How do I get my money back?"-এর মতো প্যারাফ্রেজ মিস করে, যা RAG-এর নির্ভরযোগ্যতা ও মূল্যায়নের ডেটা পাতার মূল্যায়ন আগেই দেখিয়েছে। এমন মিস খোঁজার জায়গা আপনার fixtures/-এর নিজের কেস।
ঐচ্ছিক আসল-প্রোভাইডার এক্সটেনশন
দুটো ঐচ্ছিক এক্সটেনশন আসল মডেল ব্যবহার করে। এখানে কোনো আউটপুট নেই, কারণ কিছুই চালানো হয়নি; এগুলো কখনো গ্রেড হয় না, আর API কি বা কানেকশন না থাকলে আপনার স্কোর বদলায় না। আসল রিপ্লাই বদলায়, তাই তার শব্দের ওপর অ্যাসার্ট করবেন না।
# not run here: needs the local runtime, the ollama package and a downloaded model
from ollama import chat
from evidencedesk.llm import LLMRequest, LLMResponse, Usage
class LocalAdapter:
def __init__(self, model: str) -> None:
self.model = model # a model name from the runtime's library page
def complete(self, request: LLMRequest) -> LLMResponse:
messages = [{"role": m.role, "content": m.content} for m in request.messages]
r = chat(model=self.model, messages=messages)
return LLMResponse(r.message.content, self.model, Usage()) # usage and stop reason: map them only after reading the docs# not run here: needs the provider SDK, an API key and internet access; without a key it just stops
import os
from pathlib import Path
from openai import OpenAI
from evidencedesk.llm import LLMRequest, LLMResponse, Usage
from rag import Assistant # your challenge 4 module
class HostedAdapter:
def __init__(self, model: str) -> None:
self.client, self.model = OpenAI(), model # reads OPENAI_API_KEY
def complete(self, request: LLMRequest) -> LLMResponse:
system = "\n".join(m.content for m in request.messages if m.role == "system")
user = "\n".join(m.content for m in request.messages if m.role == "user")
r = self.client.responses.create(model=self.model, instructions=system, input=user)
return LLMResponse(r.output_text, self.model, Usage(r.usage.input_tokens, r.usage.output_tokens), "end" if r.status == "completed" else "other")
if "OPENAI_API_KEY" not in os.environ:
raise SystemExit("no key: this extension is optional and is skipped")
assistant = Assistant(Path("corpus"))
for question in ("How long do I have to return a new bike?", "Can I pay in instalments?"):
print(question, assistant.ask(HostedAdapter("<model-id>"), question)) # real replies vary: do not assert on themপ্রোভাইডার নোট (২০২৬-১০-১২ অনুযায়ী): ওপেন-ওয়েট মডেলের লোকাল রানটাইম একটা আলাদা অ্যাপ্লিকেশন; তার API শোনে
http://localhost:11434-এ, Python কলchat(model=..., messages=[...])এবং টেক্সটresponse.message.content-এ, আর ডকস উদাহরণ মডেল হিসেবেgemma4দেখিয়েছিল। হোস্টেড Responses কল হলোclient.responses.create(model=..., instructions=..., input=...), টেক্সটoutput_text-এ এবং ইউসেজusage.input_tokensওusage.output_tokens-এ; ডকস উদাহরণ আইডি হিসেবেgpt-6-astraদেখিয়েছিল। দুটো নামই উদাহরণ ধরুন, আর প্রোভাইডারের মডেল পাতা থেকে একটা বর্তমান নাম কপি করুন। প্রোভাইডারের ডকস 429 ও 5xx রেসপন্স ব্যাকঅফসহ রিট্রাই করতে এবং 400, 401 বা 403 রিট্রাই না করতে পরামর্শ দেয়, আপনার এরর ক্লাসের নীতিও তা-ই। লোকাল রানটাইমের স্টপ-রিজন মান যাচাই করা হয়নি, তাই স্কেচে সেগুলো ম্যাপ করা হয়নি। VERIFY — checked 2026-10-12. বর্তমান ডকুমেন্টেশন দেখে নিন।
কাজ জমা দিন
জমা দেওয়ার আগে টেমপ্লেট ফাইলগুলো পূরণ করুন, তারপর --no-tests ছাড়া গ্রেডার চালান, যাতে আপনার নিজের টেস্টও চলে। আপনার README বলে সবকিছু অফলাইনে কীভাবে চালাতে হয়। আপনার উদাহরণ আউটপুট একটা আসল রান থেকে আসে (রেফারেন্সের একটা স্ক্রিপ্ট আছে যা তার নিজেরটা আবার তৈরি করে)। আপনার ডিজাইন নোট তিন-চারটা ট্রেড-অফের নাম বলে: কী বেছেছেন, কী ছেড়েছেন, কেন, আর আপনার ফিক্সচারের সীমা। "সব টেস্ট পাস করে" ধরনের বাক্য ট্রেড-অফ নয়।
কাজের জায়গায় এটা কোথায় দেখা যায়
আক্রার কোয়ামে এমন একটা টিমে যোগ দিলেন যারা টেক-হোম টাস্ক দিয়ে লোক নেয়। রিভিউয়ার রিপোজিটরি খুলে এই গ্রেডারেরই চারটা প্রশ্ন করেন: এতে কোনো সিক্রেট আছে কি, কোথাও মডেলের টেক্সট কমান্ড হয়ে যায় কি, কোনো কাজ চেক ছাড়া চলে কি, কোনো উত্তর এমন কিছু উদ্ধৃত করে কি যা সেখানে নেই। AI ফিচার রিভিউ করা টিমগুলো প্রায়ই এই চেকগুলো কন্টিনিউয়াস ইন্টিগ্রেশনে গেট হিসেবে চালায়, এমন বেসলাইন স্কোরসহ যা কমতে পারবে না। যে প্রার্থী বলতে পারেন "আমার স্কোর ছিল ৯৪, আমার নিজের ফিক্সচারে দুটো মিস পেয়েছি, আর এই হলো ডিজাইন নোট", তিনি প্রমাণহীন নিখুঁত স্কোরের প্রার্থীর চেয়ে বেশি বিশ্বাসযোগ্য।
সাধারণ ভুল
- ফিক্সচারকে লক্ষ্য করে কোড করা। ফাইলের প্রশ্নগুলো আলাদা করে ধরে একটা সমাধান পাস করে যেতে পারে। নিজের কেস যোগ করে সেগুলো টেস্ট করুন; ফিক্সচার শুধু উদাহরণ।
- ক্রিটিক্যাল ব্যর্থতা রেখে পয়েন্টের পেছনে ছোটা। ক্যালকুলেটরে
evalরেখে ৯৪ পয়েন্ট মানে ফেল। আগে ক্রিটিক্যাল বিষয়টা ঠিক করুন। - মডেলের সাইটেশন তালিকায় ভরসা। দেখতে-ঠিক আইডি মানেই সমর্থন নয়। সাইটেশন বানান যা রিট্রিভ করেছেন তা থেকে, আর দাবিটা উদ্ধৃত টেক্সটের সঙ্গে মেলান।
- স্ক্যানকে নিশ্চয়তা ভাবা। ক্রেডেনশিয়াল স্ক্যান আকার খোঁজে; git ইতিহাসের সিক্রেট বা সাধারণ বাক্যে লেখা পাসওয়ার্ড নয়। হাতে করা চেকলিস্ট রাখুন।
- প্লেসহোল্ডার সাবমিশন ফাইল।
TODOলেখা README সাবমিশন চেকে ব্যর্থ হয়। ফাইলগুলো লিখুন সবার শেষে, একটা আসল রান থেকে।
নিজে চেষ্টা করুন
- সহজ। নির্ভরযোগ্য আউটপুট চুক্তি আবার দেখুন। স্টার্টার কপি করুন, শুধু
stream.pyলিখুন, আর গ্রেড করুন। গ্রেডার চালানোর আগে স্কোর ও ফলাফল অনুমান করুন। - মাঝারি। লোকাল ইনডেক্স ও রিট্রিভালের মান আবার দেখুন। রেফারেন্সের একটা কপিতে
search-এর ডিফল্টmin_scoreবদলে 0.0 করুন। কোন চেক ব্যর্থ হবে, আর স্কোর ও ফলাফল কী হবে, অনুমান করুন। - কঠিন। রুব্রিক, রিগ্রেশন ও রিলিজ গেট আবার দেখুন।
regress.pyলিখুন: এটা একটা ফোল্ডার গ্রেড করে এবং স্কোর সংরক্ষিত বেসলাইনের নিচে গেলে বা কোনো ক্রিটিক্যাল চেক ব্যর্থ হলে 1 দিয়ে বের হয়, সাবমিশন তখনও পাস করলেও। এটা রেফারেন্সে এবং মাঝারি কাজের কপিতে চালান।
উত্তর
সহজ। একটা চ্যালেঞ্জের দাম ১৫ পয়েন্ট, তাই স্কোর ১৫ আর ফলাফল FAIL: পাস মার্ক ৭০, আর সাবমিশন ফাইলগুলো এখনো প্লেসহোল্ডার। নিচের ফাইলটা submission/stream.py নামে সংরক্ষণ করুন।
# file: submission/stream.py
"""Challenge 2: assemble recorded chunks; turn a model reply into a controlled result."""
from evidencedesk.llm import LLMResponse
from evidencedesk.results import parse_answer
def collect(events):
"""events: list of StreamEvent. Returns (status, text); status is complete, empty, interrupted or error."""
parts = []
for event in events:
if event.kind == "chunk":
parts.append(event.text)
elif event.kind == "error":
return "error", "".join(parts)
elif event.kind == "done":
text = "".join(parts)
return ("complete" if text else "empty"), text
text = "".join(parts)
return ("interrupted" if text else "empty"), text # no done event: the stream was cut
def read_reply(response: LLMResponse):
"""Returns (ok, code). Never raises on model output. Codes: ok, truncated, unusable_stop, invalid_json, invalid_schema."""
outcome = parse_answer(response)
return outcome.ok, outcome.codepython -m lab.grade submission --verbose | sed -n '/^2\./,/reply_malformed/p;$p'2. Structured streaming 15/15
[ok] stream_assemble 4/4
[ok] stream_interrupted 3/3
[ok] stream_error 2/2
[ok] stream_empty 1/1
[ok] reply_valid 2/2
[ok] reply_malformed 3/3
SCORE 15/100 | critical failures 0 | RESULT: FAILমাঝারি। ন্যূনতম স্কোর না থাকলে সার্চ যেকোনো কোয়েরিতে তার সেরা তিনটা ফলাফল দেয়, তাই যে প্রশ্নের উত্তর কোনো ডকুমেন্টে নেই সেটা আর খালি তালিকা নয়। শুধু no_result ব্যর্থ হয়, তিন পয়েন্ট যায়, ফলাফল তবু PASS। পাস মার্কের বিপদ এটাই: একটা আসল রিগ্রেশন তার নিচে লুকিয়ে থাকে।
cp -r lab/reference copy
sed -i 's/min_score: float = 0.3/min_score: float = 0.0/' copy/search.py
python -m lab.grade copy --verbose --no-tests | grep -E -- '\[--\]|SCORE' [--] no_result 0/3 q7: returned kc-warranty-v2#s2 at 0.26
SCORE 97/100 | critical failures 0 | RESULT: PASSকঠিন। নিচের গেট ৯৭ পয়েন্টের কপিটাকে ১০০ বেসলাইনের বিপরীতে আটকে দেয়, যদিও ৯৭ ল্যাবে পাস।
# file: regress.py
"""Block a submission that scores below its stored baseline, even when it still passes (a small release gate)."""
import sys
from pathlib import Path
from lab.grade import grade
folder, baseline = Path(sys.argv[1]), int(sys.argv[2])
report = grade(folder, run_tests=False)
verdict = "BLOCK" if report.critical_failed or report.total() < baseline else "GO"
print(f"{verdict}: {report.total()}/100 (baseline {baseline}), critical failures: {report.critical_failed}")
sys.exit(verdict == "BLOCK")python regress.py lab/reference 100; echo "exit $?"
python regress.py copy 100; echo "exit $?"GO: 100/100 (baseline 100), critical failures: []
exit 0
BLOCK: 97/100 (baseline 100), critical failures: []
exit 1পুরো ল্যাবও টেস্টের আওতায়, তাই গ্রেডার বদলালে সেটা চুপচাপ সহজ হয়ে যেতে পারে না (sed ঘড়ির সময় বাদ দেয়, তাই আউটপুট প্রতিবার এক)। টেস্টগুলো রেফারেন্স, স্টার্টার, চারটা ত্রুটিপূর্ণ কপি, আর সাতটা দুর্বল করা সমাধান ঢাকে, যাদের প্রত্যেকে সংশ্লিষ্ট চেকে পয়েন্ট হারাতে হবে।
python -m pytest tests/test_lab.py | sed 's/ in [0-9.]*s//'.................... [100%]
20 passedপাস চেকলিস্ট:
- সহজ: আপনি ১৫ ও FAIL অনুমান করেছেন, আর বলতে পারেন ১৫ পয়েন্ট কেন যথেষ্ট নয়।
- মাঝারি: শুধু
no_resultব্যর্থ (৩ পয়েন্ট), স্কোর ৯৭ ও ফলাফল PASS, এবং ব্যাখ্যা করতে পারেন এটা কেন নিরাপদ নয়। - কঠিন: রেফারেন্সের জন্য exit 0 সহ GO এবং কপির জন্য exit 1 সহ BLOCK; স্ক্রিপ্ট বেসলাইন আর্গুমেন্ট থেকে পড়ে এবং ক্রিটিক্যাল ব্যর্থতাকে ব্লক ধরে।
- ল্যাব: ৭০ বা তার বেশি পয়েন্ট, ০টা ক্রিটিক্যাল ব্যর্থতা, সব প্রয়োজনীয় ফাইল পূরণ, এবং আপনার নিজের টেস্ট অফলাইনে পাস।
সারসংক্ষেপ
- ল্যাব দশটা পার্টকে ছয়টা ফিক্সচার-চালিত চ্যালেঞ্জে বদলায়, মোট ১০০ পয়েন্ট; পাস মার্ক ৭০, সব ক্রিটিক্যাল চেক পাস ও সম্পূর্ণ সাবমিশনসহ।
- চারটা ক্রিটিক্যাল ব্যর্থতা (কমিট করা ক্রেডেনশিয়াল, কোড হিসেবে চলা মডেল টেক্সট, এড়িয়ে যাওয়া টুল অথরাইজেশন, যাচাই-করা বলে দেখানো বানানো সাইটেশন) স্কোরের ওপরে, এবং প্রতিটার একটা স্বয়ংক্রিয় চেক আছে।
- গ্রেডার বিশ্বাস অর্জন করে ইচ্ছা করে ব্যর্থ করা সাবমিশন ধরে; রেফারেন্স সমাধান এখানে ১০০-এর মধ্যে ১০০ পেয়েছে।
- গ্রেডার শুধু ফিক্সচার দেখে। নতুন প্রশ্নে মান, আসল মডেল, আসল ব্যবহারকারী বা লোড সে দেখাতে পারে না, তাই নিজের কেস ও সৎ ডিজাইন নোট লিখুন।
- আসল-প্রোভাইডার এক্সটেনশন ঐচ্ছিক এবং কখনো গ্রেড হয় না।
এরপর: ক্যাপস্টোন, চিট শিট ও এরপর কী এই ল্যাবকে একটা সম্পূর্ণ পোর্টফোলিও প্রজেক্টে বদলায়, কমান্ড ও আর্কিটেকচার এক পাতায় জড়ো করে, এবং এই টিউটোরিয়ালের পর কোথায় যাবেন তা দেখায়।