অধ্যায় 5 · সিকিউরিটি ও অপারেশন
টিম হ্যান্ডওভার: প্রম্পট লাইব্রেরি, চেঞ্জ লগ, হ্যান্ডওভার ডকুমেন্ট ও প্রস্তুতি
- পৃষ্ঠা 22 / 24
- 18 মিনিট পড়া
আগের পাতা, প্রম্পট অপারেশন: মালিক, ভার্সন, গ্রহণযোগ্যতার শর্ত ও রোলব্যাক, একটি প্রম্পটকে মালিক, ভার্সন, নির্দিষ্ট রিগ্রেশন সেট ও গ্রহণযোগ্যতার শর্ত দিয়েছে। এই পাতায় দেখা যাবে আরও মানুষ ও আরও মডেল জড়িত হলে কী হয়: দ্বিতীয় একটি মডেল, দ্বিতীয় জোড়া চোখ লাগে এমন পরিবর্তন, প্রোডাকশনে খারাপ হয়ে যাওয়া প্রম্পট, আর দায়িত্ব নেওয়া একজন সহকর্মী। ব্যাকএন্ডের দিক (কোন অনুরোধে কোন প্রম্পট ও মডেল ভার্সন ব্যবহার হয়েছে) আছে AI-এর জন্য Git, API ও ব্যাকএন্ড: অ্যাডভান্সড-এ; এখানে বিষয় হলো অ্যাসেটের জীবনচক্র।
পাতাটি নিজে থেকেই চলে: প্রথম দুটি ব্লক আগের পাতা যেখানে শেষ হয়েছিল সেই অবস্থা আবার তৈরি করে (ছোট করা promptops.py, সঙ্গে চেঞ্জ লগ ও রোলব্যাক)। এটি আগের পাতার সংস্করণের চেয়ে ছোট (lint, diff বা টোকেন বাজেট নেই; run_set কোনো runs/ ফাইল লেখে না), তাই পুরনো ফোল্ডার না ব্যবহার করে নতুন ফোল্ডারে চালান। লাগবে Python ৩.১২+ এবং promptlab.py। প্রতিটি "মডেল" একটি টয় প্রোভাইডার (toy provider), বারবার একই ফল পাওয়ার জন্য নির্ধারিত আচরণের বদলি; আসল ও কম অনুমানযোগ্য মডেল সম্পর্কে টয়ের ফল কিছু বলে না।
যা শিখবেন
- কখন ভ্যারিয়েন্ট (variant) যুক্তিসংগত, এবং কীভাবে পরিবর্তন রিভিউ, রোলব্যাক ও লগ করবেন।
- কীভাবে খোঁজযোগ্য লাইব্রেরি রাখবেন এবং মূল্যায়ন ডেটাকে অপ্রয়োজনীয় ব্যক্তিগত তথ্য থেকে মুক্ত রাখবেন।
- কীভাবে সীমাবদ্ধতা লিখবেন, পণ্যে ব্যবহারের প্রস্তুতি যাচাই করবেন ও হ্যান্ডওভার ডকুমেন্ট লিখবেন।
শুরু করুন: টুলকিট ও একটি ছোট প্রকল্প
প্রথমে এই মডিউলটি সংরক্ষণ করুন।
# file: promptops.py
"""promptops, trimmed, plus a change log and rollback."""
import hashlib, json
from pathlib import Path
from promptlab import Client, render
ROOT = Path("prompts")
def _read():
return json.loads((ROOT / "registry.json").read_text(encoding="utf-8"))
def _save(reg):
text = json.dumps(reg, indent=1, sort_keys=True, ensure_ascii=False)
(ROOT / "registry.json").write_text(text + "\n", encoding="utf-8")
def update(name, **fields):
reg = _read()
reg[name].update(fields)
_save(reg)
def load(name, version=None):
asset = _read()[name]
version = version or asset["current"]
entry = asset["versions"][version]
return {"version": version, "entry": entry, "asset": asset,
"text": (ROOT / name / entry["file"]).read_text(encoding="utf-8")}
def add_version(name, text, reason, date):
if not reason.strip():
raise ValueError("a new version needs a change reason")
reg = _read()
version = f"v1.{len(reg[name]['versions'])}"
(ROOT / name / f"{version}.txt").write_text(text, encoding="utf-8")
reg[name]["versions"][version] = {"file": f"{version}.txt", "reason": reason, "date": date, "status": "candidate"}
_save(reg)
return version
def set_status(name, version, status):
reg = _read()
reg[name]["versions"][version]["status"] = status
_save(reg)
def set_current(name, version, date, why):
asset = _read()[name]
history = asset.get("pointer_history", []) + [{"from": asset["current"], "to": version, "date": date, "why": why}]
update(name, current=version, pointer_history=history)
set_status(name, version, "released")
KEYWORDS = {"en": {"billing": ("charged", "invoice"), "bug": ("crash", "error")},
"es": {"billing": ("cobr", "factura"), "bug": ("falla", "falló")},
"bn": {"billing": ("চার্জ",), "bug": ("ক্র্যাশ",)}}
LANGUAGES = {"en": "English", "es": "Spanish", "bn": "Bangla"}
def toy_triage(messages, **settings):
"""STAND-IN, not a model: reads only the languages the prompt names; breaks ties as told.
Planted weakness: without the context line, it loses tickets longer than 12 words."""
rules, ticket = messages[-1]["content"].split("Ticket:\n", 1)
if "software shop" not in rules and len(ticket.split()) > 12:
return "other"
langs = [code for code, word in LANGUAGES.items() if word in rules]
hits = [label for label in ("billing", "bug")
if any(w in ticket.lower() for c in langs for w in KEYWORDS[c][label])]
if len(hits) > 1 and "choose bug" in rules:
return "bug"
return hits[0] if hits else "other"
def run_set(name, version, provider=toy_triage, text=None):
"""Run the fixed set; `text` tries an unsaved draft instead."""
raw = (ROOT / name / "cases.json").read_bytes()
prompt, cases, client = text or load(name, version)["text"], json.loads(raw), Client(provider)
ok = {c["id"]: client.complete([{"role": "user", "content": render(prompt, ticket=c["ticket"])}]).text.strip()
== c["expected"] for c in cases}
langs = {}
for c in cases:
langs.setdefault(c["language"], []).append(ok[c["id"]])
return {"cases": hashlib.sha256(raw).hexdigest()[:8], "accuracy": round(sum(ok.values()) / len(ok), 3),
"by_language": {k: round(sum(v) / len(v), 2) for k, v in sorted(langs.items())},
"failed": [i for i, good in ok.items() if not good]}
def check(report, baseline, criteria):
"""Judge a run by the criteria. Empty list = accepted."""
if baseline["cases"] != report["cases"]:
return ["different case set: results are not comparable"]
problems = []
if report["accuracy"] < criteria["min_accuracy"]:
problems.append(f"accuracy {report['accuracy']} is below {criteria['min_accuracy']}")
for lang, acc in report["by_language"].items():
if acc < criteria["min_accuracy_per_language"]:
problems.append(f"{lang} accuracy {acc} is below {criteria['min_accuracy_per_language']}")
new = sorted(set(report["failed"]) - set(baseline["failed"]))
if len(new) > criteria["max_new_failures"]:
problems.append(f"new failures: {new}")
return problems
def log_change(kind, name, version, who, date, summary):
"""Append one line to prompts/changelog.jsonl: append-only, one event per line."""
event = {"date": date, "kind": kind, "asset": name, "version": version, "who": who, "summary": summary}
with (ROOT / "changelog.jsonl").open("a", encoding="utf-8") as handle:
handle.write(json.dumps(event, sort_keys=True, ensure_ascii=False) + "\n")
def changelog(name=None):
lines = (ROOT / "changelog.jsonl").read_text(encoding="utf-8").splitlines()
events = [e for e in map(json.loads, lines) if name in (None, e["asset"])]
return "\n".join(f"{e['date']} {e['version']:<5} {e['kind']:<11} {e['who']}: {e['summary']}"
for e in reversed(events))
def rollback(name, to_version, date, who, why):
"""Undo by pointer: nothing is deleted. Refuses a target that was never released."""
asset = load(name)["asset"]
if asset["versions"][to_version]["status"] != "released":
raise ValueError(f"{to_version} was never released; choose a released version")
bad = asset["current"]
set_current(name, to_version, date, why)
set_status(name, bad, "withdrawn")
log_change("rolled-back", name, bad, who, date, f"back to {to_version}: {why}")এই ব্লকটি অ্যাসেটটি তৈরি করে: v1.1 current, v1.2 বাতিল, সঙ্গে বারোটি কেস, শর্ত, একটি চেঞ্জ লগ এবং লাইব্রেরির আরও তিনটি এন্ট্রি।
import json
from pathlib import Path
from promptops import *
V10 = """You label customer support tickets for a software shop.
Tickets are written in English.
Labels: billing, bug, other.
Reply with the label only.
Ticket:
{ticket}
"""
V11 = V10.replace("are written in English.", "may be written in English, Spanish or Bangla.").replace(
"Reply", "If a ticket fits more than one label, choose bug.\nReply")
V12 = V11.replace("If a ticket fits more than one label, choose bug.\n", "")
TEXT = {"billing": ["I was charged twice for my March invoice.", "Me cobraron dos veces la factura de marzo.",
"আমার কার্ডে দুইবার চার্জ করা হয়েছে।"],
"bug": ["The app crashes when I upload a photo.", "La aplicación falla al subir una foto.",
"অ্যাপটি বারবার বন্ধ হয়ে যায়।"],
"other": ["How do I change my account email?", "¿Cómo cambio mi correo?", "আমার পাসওয়ার্ড কীভাবে বদলাব?"],
"mixed": ["Charged twice and then the app crashed.", "Me cobraron dos veces y luego la app falló.",
"দুইবার চার্জ হয়েছে আর অ্যাপ ক্র্যাশ করেছে।"]}
cases = [{"id": f"{lang}-{use}", "language": lang, "use_case": use, "expected": "bug" if use == "mixed" else use,
"ticket": TEXT[use][i]} for use in TEXT for i, lang in enumerate(("en", "es", "bn"))]
folder = Path("prompts/ticket-triage")
folder.mkdir(parents=True)
(folder / "cases.json").write_text(json.dumps(cases, ensure_ascii=False, indent=1), encoding="utf-8")
(folder / "criteria.json").write_text(json.dumps({"min_accuracy": 0.85, "min_accuracy_per_language": 0.75,
"max_new_failures": 0}), encoding="utf-8")
(folder / "v1.0.txt").write_text(V10, encoding="utf-8")
asset = {"purpose": "Label a support ticket as billing, bug or other.", "current": "v1.0",
"owner": "Nadia Rahman, support tools (Dhaka)", "reviewer": "Mateo Silva, support operations (Bogotá)",
"assumptions": {"model": "a stand-in; write the real model name", "checked_on": "2026-10-12"},
"tags": {"category": "classification", "languages": ["bn", "en", "es"], "stage": "production"},
"limitations": ["Only English, Spanish and Bangla were tested.",
"Bangla bug reports without the word for 'crash' are labelled other (bn-bug); route to a person."],
"versions": {"v1.0": {"file": "v1.0.txt", "reason": "First release.", "date": "2026-09-01",
"status": "released"}}}
library = {"reply-draft": ("Draft a customer reply.", "writing", ["en"], "pilot"),
"invoice-extract": ("Extract invoice fields.", "extraction", ["en", "es"], "production"),
"meeting-summary": ("Summarise meeting notes.", "writing", ["en", "bn"], "draft")}
registry = {"ticket-triage": asset, **{k: {"purpose": p, "tags": {"category": c, "languages": l, "stage": s}}
for k, (p, c, l, s) in library.items()}}
(ROOT / "registry.json").write_text(json.dumps(registry, ensure_ascii=False), encoding="utf-8")
add_version("ticket-triage", V11, "Spanish and Bangla tickets were labelled other.", "2026-09-14")
add_version("ticket-triage", V12, "Shorter prompt to cut input tokens.", "2026-09-20")
set_current("ticket-triage", "v1.1", "2026-09-21", "Passed all criteria.")
set_status("ticket-triage", "v1.2", "rejected")
log_change("added", "ticket-triage", "v1.0", "Nadia Rahman", "2026-09-01", "First release.")
log_change("rejected", "ticket-triage", "v1.2", "Nadia Rahman", "2026-09-20", "Lost the three mixed tickets.")
log_change("released", "ticket-triage", "v1.1", "Nadia Rahman", "2026-09-21", "Three languages and a tie rule.")কখন একটি প্রম্পটের মডেল-নির্দিষ্ট ভ্যারিয়েন্ট লাগে
আপনি যত মডেল সমর্থন করেন, সবগুলোতে একই শর্ত পূরণ করলে অ্যাসেটপ্রতি একটি প্রম্পটই রাখুন। ভ্যারিয়েন্ট বানান শুধু তখনই, যখন প্রমাণ বলে একটি লেখা যথেষ্ট নয়: একই নির্দিষ্ট সেটে দুই মডেলের স্কোর স্পষ্টভাবে আলাদা, একটি মডেল এমন আউটপুট ফরম্যাট ভাঙে যা অন্যটি মানে, অথবা প্রোভাইডারের গাইড কোনো মডেল-পরিবারের জন্য ভিন্ন ধরন সুপারিশ করে। ভ্যারিয়েন্টকে একই ভেরিয়েবল রাখতে হয়, একই শর্ত পূরণ করতে হয়, আর মূল প্রম্পট বদলালে আবার পরীক্ষা করতে হয়।
প্রোভাইডার নোট (২০২৬-১০-১২ অনুযায়ী): OpenAI-এর রিজনিং গাইড রিজনিং মডেলের জন্য স্পষ্ট লক্ষ্য, শক্ত সীমা ও সুনির্দিষ্ট আউটপুট চুক্তি, আর নন-রিজনিং মডেলের জন্য বেশি স্পষ্ট ও নির্ভুল নির্দেশ সুপারিশ করে। Anthropic হাতে লেখা ধাপের তালিকার চেয়ে সাধারণ নির্দেশ পছন্দ করে। মডেল-পরিবার অনুযায়ী ধরন আলাদা, তাই পরীক্ষা করুন; বর্তমান ডকুমেন্টেশন দেখে নিন (আরও দেখুন প্রোভাইডারের পার্থক্য ও পোর্টেবিলিটি: গাইড পড়া ও একাধিক মডেলে টেস্ট)।
একটি দ্বিতীয় টয় মডেল (বদলি) প্রম্পটে "no other words" না থাকলে লেবেলটিকে একটি বাক্যে মুড়ে দেয়। তাই এর সঙ্গে মূল প্রম্পট শূন্য পায়, কারণ প্রতিটি উত্তর একটি বাক্য। ভ্যারিয়েন্ট শুধু ওই একটি জিনিস বদলায়।
def toy_chatty(messages, **settings):
"""STAND-IN for a second model that wraps the label in a sentence unless told not to."""
label = toy_triage(messages)
return label if "no other words" in messages[-1]["content"] else f"Label: {label}."
base = load("ticket-triage", "v1.1")["text"]
variant = base.replace("Reply with the label only.", "Reply with the label only, with no other words.")
for text in (base, variant):
r = run_set("ticket-triage", None, provider=toy_chatty, text=text)
print(r["accuracy"], len(r["failed"]), "failing")
Path("prompts/ticket-triage/v1.1.chatty.txt").write_text(variant, encoding="utf-8")
update("ticket-triage", variants={"v1.1:chatty": "v1.1.chatty.txt"})0.0 12 failing
0.917 1 failingভ্যারিয়েন্ট প্রথম মডেলে মূল প্রম্পটের মতোই ০.৯১৭ পায়; রেজিস্ট্রি রেখে দেয় এটি কোন ভার্সনের।
বড় পরিবর্তনের জন্য একটি রিভিউ প্রক্রিয়া
কোনো পরিবর্তন বড় (material) হয়, যদি তা প্রম্পটের কাজ বদলে দিতে পারে: নির্দেশ, লেবেল, ভেরিয়েবল, ভাষা, উদাহরণ, মডেল বা সেটিংস। শুধু স্পেসিং ঠিক করা বড় পরিবর্তন নয়; সন্দেহ হলে বড় ধরুন। প্রক্রিয়া: লেখক কারণসহ ভার্সন লেখেন ও নির্দিষ্ট সেট চালান; লেখক নন এমন একজন রিভিউয়ার ডিফ, কারণ ও রিপোর্ট পড়েন এবং দেখেন একই পরিবর্তনে শর্তের ফাইল বদলানো হয়নি; গেট পাস করে, মালিক পয়েন্টার সরান, রিলিজ লগ হয়। v1.3 একটি সংক্ষিপ্ত পুনর্লিখন, যা প্রসঙ্গের লাইনটি বাদ দেয়; gate শর্ত ও লেখক/অনুমোদনকারী নিয়ম প্রয়োগ করে:
V13 = """Label the support ticket: billing, bug or other.
Tickets may be in English, Spanish or Bangla. Mixed ticket: choose bug.
Reply with the label only.
Ticket:
{ticket}
"""
def gate(name, version, baseline, author, approver):
criteria = json.loads((ROOT / name / "criteria.json").read_text(encoding="utf-8"))
problems = check(run_set(name, version), baseline, criteria)
if approver == author:
problems.append("the approver must not be the author")
return problems
add_version("ticket-triage", V13, "Same labels in fewer tokens: drop the context line.", "2026-10-01")
baseline = run_set("ticket-triage", "v1.1")
print(gate("ticket-triage", "v1.3", baseline, "Nadia Rahman", "Nadia Rahman"))
print(gate("ticket-triage", "v1.3", baseline, "Nadia Rahman", "Mateo Silva") or "approved")
set_current("ticket-triage", "v1.3", "2026-10-01", "Gate passed; approved by Mateo Silva.")
log_change("released", "ticket-triage", "v1.3", "Mateo Silva", "2026-10-01", "Compact prompt; gate passed.")['the approver must not be the author']
approvedগেট নিজে-নিজে অনুমোদন থামিয়েছে, তারপর v1.3 পাস করিয়েছে, কারণ সংক্ষিপ্ত প্রম্পট যেখানে ভুল করে তা কোনো কেসে নেই। গেট ততটাই ভালো, যতটা ভালো তার সেট।
রিগ্রেস করা প্রম্পটের জন্য রোলব্যাক পরিকল্পনা
তিন দিন পর সাপোর্ট লিড জানালেন, লম্বা টিকিট other-এ যাচ্ছে। রোলব্যাক পরিকল্পনা এমন ঘটনার আগে লেখা থাকে এবং বলে: কী রিগ্রেশন ধরা হবে, কে সিদ্ধান্ত নেবেন, কীভাবে ফেরানো হবে, কীভাবে যাচাই হবে, এরপর কী। রোলব্যাক সস্তা, কারণ রিলিজ হওয়া ফাইল কখনো বদলায় না: পয়েন্টারটি সর্বশেষ রিলিজ করা ভার্সনে সরান, ব্যর্থ কেসটি সেটে যোগ করুন যাতে তা নীরবে ফিরে না আসে, আর সামনের দিকে ঠিক করুন।
PLAN = """Trigger: a failing case in the set, or a confirmed report from support.
Who: the owner or the reviewer; tell the other within the day.
How: rollback() moves the pointer; no file is deleted. Then re-run the set.
Next: add the failing ticket to cases.json, fix forward, pass the gate."""
update("ticket-triage", rollback_plan=PLAN)
LONG = "Charged twice for my March invoice, and then the app crashed while I uploaded the receipt photo."
path = Path("prompts/ticket-triage/cases.json")
cases = json.loads(path.read_text(encoding="utf-8"))
cases.append({"id": "en-long", "language": "en", "use_case": "mixed", "expected": "bug", "ticket": LONG})
path.write_text(json.dumps(cases, ensure_ascii=False, indent=1), encoding="utf-8")
crit = json.loads(Path("prompts/ticket-triage/criteria.json").read_text(encoding="utf-8"))
r11, r13 = run_set("ticket-triage", "v1.1"), run_set("ticket-triage", "v1.3")
print(r13["failed"], check(r13, r11, crit))
rollback("ticket-triage", "v1.1", "2026-10-04", "Nadia Rahman", "v1.3 loses long tickets (en-long)")
print(load("ticket-triage")["version"], load("ticket-triage", "v1.3")["entry"]["status"],
run_set("ticket-triage", "v1.1")["failed"])['bn-bug', 'en-long'] ['accuracy 0.846 is below 0.85', "new failures: ['en-long']"]
v1.1 withdrawn ['bn-bug']ফিরিয়ে আনা v1.1 আগের মতোই শুধু bn-bug-এ ব্যর্থ। রোলব্যাক পুরনো প্রম্পট ফিরিয়ে আনে; প্রোভাইডার মডেল বদলালে সমাধান হতে পারে মডেল ভার্সন পিন করা বা ভ্যারিয়েন্ট যোগ করা, যা অনুমানের ব্লক থেকে বোঝা যায়।
প্রম্পটের চেঞ্জ লগ
রেজিস্ট্রি বলে কী আছে; চেঞ্জ লগ বলে কী ঘটেছে ও কে সিদ্ধান্ত নিয়েছে। প্রতিটি ঘটনার জন্য একটি লাইন, শুধু যোগ করা যায়, কখনো সম্পাদনা হয় না। ফিল্ড: তারিখ, অ্যাসেট, ভার্সন, ধরন (added, released, rejected, variant, rolled-back), কে, এবং প্রমাণসহ সারসংক্ষেপ (একটি স্কোর, একটি কেস আইডি)। বাতিল ও রোলব্যাকও লগ করুন। Git দেখায় কোন লাইন বদলেছে; লগ দেখায় টিম কী সিদ্ধান্ত নিয়েছে।
print(changelog("ticket-triage"))2026-10-04 v1.3 rolled-back Nadia Rahman: back to v1.1: v1.3 loses long tickets (en-long)
2026-10-01 v1.3 released Mateo Silva: Compact prompt; gate passed.
2026-09-21 v1.1 released Nadia Rahman: Three languages and a tie rule.
2026-09-20 v1.2 rejected Nadia Rahman: Lost the three mixed tickets.
2026-09-01 v1.0 added Nadia Rahman: First release.খোঁজযোগ্য ক্যাটাগরিসহ প্রম্পট লাইব্রেরি
খোঁজার উপায় না থাকলে মানুষ একই জিনিস আবার লেখে। লাইব্রেরি হলো রেজিস্ট্রি আর নিয়ন্ত্রিত তালিকা থেকে নেওয়া ট্যাগ: category (classification, extraction, writing), languages ও stage (draft, pilot, production)। মুক্ত ট্যাগ এলোমেলো হয়ে যায় (summarise, summarize), তাই তালিকার সঙ্গে মিলিয়ে দেখুন। খোঁজ নাম বা উদ্দেশ্যের শব্দ আর ট্যাগের সঠিক মান একসঙ্গে মেলায়।
def find(text="", **tags):
registry = json.loads(Path("prompts/registry.json").read_text(encoding="utf-8"))
def has(asset, key, wanted):
value = asset.get("tags", {}).get(key)
return wanted in value if isinstance(value, list) else value == wanted
return sorted(name for name, asset in registry.items()
if text.lower() in f"{name} {asset['purpose']}".lower()
and all(has(asset, key, wanted) for key, wanted in tags.items()))
print(find(category="writing"), find("invoice"), find(languages="bn", stage="production"))['meeting-summary', 'reply-draft'] ['invoice-extract'] ['ticket-triage']মূল্যায়ন ডেটাকে অপ্রয়োজনীয় সংবেদনশীল তথ্য থেকে মুক্ত রাখুন
টেস্ট সেট রিপোজিটরি, রিপোর্ট ও চ্যাটে কপি হয়ে ছড়ায়। কমিয়ে রাখুন: একটি কেসের দরকার আসল টিকিটের আকার, আসল ইমেইল ঠিকানা বা কার্ড নম্বর প্রায় কখনোই নয়। বানানো বা মুছে দেওয়া (redacted) লেখা পছন্দ করুন; আসল টিকিট ব্যবহার করতেই হলে আপনার এলাকার নিয়ম ও প্রতিষ্ঠানের নীতি দেখে নিন এবং প্রবেশাধিকার সীমিত রাখুন। প্যাটার্ন স্ক্যান ইমেইল ও লম্বা সংখ্যা ধরে, কিন্তু নাম আর পরিচয় ফাঁস করা গল্প ধরে না, তাই একজন মানুষকে তবু নমুনা পড়তে হয়। বিস্তৃত রিভিউ আছে প্রাইভেসি ও সিকিউরিটি রিভিউ: সিক্রেট, ব্যক্তিগত ডেটা, লগিং ও আউটপুট হ্যান্ডলিং-এ।
import re
EMAIL = re.compile(r"[\w.+-]+@[\w-]+(?:\.[\w-]+)+")
NUMBER = re.compile(r"\+?\d(?:[ -]?\d){8,}") # 9 or more digits
def redact(text):
return NUMBER.sub("[NUMBER]", EMAIL.sub("[EMAIL]", text))
export = [{"id": "x1", "ticket": "Charged twice, please call me: +49 30 1234 5678."},
{"id": "x2", "ticket": "Refund to card 4111 1111 1111 1111 or write to lena@example.org."}]
for row in export:
print(redact(row["ticket"]))Charged twice, please call me: [NUMBER].
Refund to card [NUMBER] or write to [EMAIL].সীমাবদ্ধতা ও জানা ব্যর্থতার ধরন সংক্ষেপে লিখুন
সীমাবদ্ধতা হলো এমন বিবৃতি, যা সহকর্মী পরীক্ষা করতে পারেন: প্রম্পট কী সামলায় না, প্রমাণ কী, এবং বদলে কী করতে হবে। পরিধি ("শুধু তিনটি ভাষায় পরীক্ষা হয়েছে"), কেস আইডি ও প্রতিকার ("মানুষের কাছে পাঠান") লিখুন; "ভুল করতে পারে" কোনো কাজের কথা নয়। মানুষের লেখা আর সর্বশেষ রান যা দেখায়, দুটো মিলিয়ে রাখুন, যাতে তালিকা পুরনো না হয়।
দুর্বল: "ভুল করতে পারে"। পরীক্ষাযোগ্য: "যে বাংলা বাগ রিপোর্টে 'crash'-এর শব্দ নেই, তা other লেবেল পায় (bn-bug); মানুষের কাছে পাঠান।" সেটআপ ব্লক এমন দুটি এন্ট্রি রেখেছে; নিচের হ্যান্ডওভার ডকুমেন্ট সেগুলো ব্যর্থ কেসের পাশে ছাপে।
কখন একটি প্রম্পট পণ্যে সংযোজনের জন্য প্রস্তুত
"প্রস্তুত" হলো একটি চেকলিস্ট, যা স্ক্রিপ্ট আংশিক যাচাই করতে পারে আর একজন মানুষ স্বাক্ষর করেন। ready খোলা আইটেমগুলো ফেরত দেয়; শর্তগুলো সঠিক ছিল কি না তা সে বিচার করতে পারে না। আপনার কোডকেও উত্তরটিকে অবিশ্বস্ত লেখা ধরে অনুমোদিত লেবেলের সঙ্গে যাচাই করতে হবে, যেমন ভ্যালিডেশন ও রিপেয়ার: jsonschema, বিজনেস রুল ও সীমিত রিট্রাই দেখিয়েছে, আর ভুল লেবেলের দাম বেশি হলে একজন মানুষ থাকবেন।
def ready(name):
current = load(name)
asset, version = current["asset"], current["version"]
criteria = json.loads((ROOT / name / "criteria.json").read_text(encoding="utf-8"))
report = run_set(name, version)
cases = json.loads((ROOT / name / "cases.json").read_text(encoding="utf-8"))
checks = {
"owner, reviewer and assumptions recorded": all(asset.get(k) for k in ("owner", "reviewer", "assumptions")),
"criteria met on the fixed set": not check(report, report, criteria),
"limitations written": bool(asset.get("limitations")),
"rollback plan written": bool(asset.get("rollback_plan")),
"an earlier released version exists": any(v != version and e["status"] == "released"
for v, e in asset["versions"].items()),
"current version is in the change log": f" {version} " in changelog(name),
"no personal data in the cases": all(redact(c["ticket"]) == c["ticket"] for c in cases)}
return [item for item, ok in checks.items() if not ok]
print(ready("ticket-triage") or "no open items")no open itemsসহকর্মীদের জন্য প্রম্পট হ্যান্ডওভার ডকুমেন্ট
হ্যান্ডওভার ডকুমেন্ট একজন সহকর্মীকে মিটিং ছাড়াই দায়িত্ব নিতে দেয়। তথ্যগুলো রেজিস্ট্রি ও সর্বশেষ রান থেকে তৈরি করুন, যাতে তা সত্য থাকে; যে বিচার শুধু মানুষ লিখতে পারে তার জন্য চিহ্নিত লাইন রাখুন। এক পাতার মধ্যে রাখুন।
HANDOVER = """Handover: {name}
Owner: {owner} | Reviewer: {reviewer}
Purpose: {purpose}
Current version: {version} | Fixed set: {n} cases (digest {digest}) | Quality: {quality}
Failing today: {failing}
Known limitations:
{limits}
Recent changes:
{recent}
If it regresses:
{plan}
Open readiness items: {open_items}
To write by hand (a script cannot know these):
- Who asks for changes, and how urgent ones are handled:
- Which downstream code reads the label, and who owns it:
- Ideas we rejected, and why:"""
def handover(name):
current = load(name)
asset, report = current["asset"], run_set(name, current["version"])
n = len(json.loads((ROOT / name / "cases.json").read_text(encoding="utf-8")))
return render(HANDOVER, name=name, owner=asset["owner"], reviewer=asset["reviewer"], purpose=asset["purpose"],
version=current["version"], n=n, digest=report["cases"], failing=", ".join(report["failed"]) or "none",
quality=", ".join(f"{k} {v}" for k, v in report["by_language"].items()),
limits="\n".join("- " + text for text in asset["limitations"]),
recent="\n".join(changelog(name).splitlines()[:2]), plan=asset["rollback_plan"],
open_items=", ".join(ready(name)) or "none")
print(handover("ticket-triage"))Handover: ticket-triage
Owner: Nadia Rahman, support tools (Dhaka) | Reviewer: Mateo Silva, support operations (Bogotá)
Purpose: Label a support ticket as billing, bug or other.
Current version: v1.1 | Fixed set: 13 cases (digest e8191325) | Quality: bn 0.75, en 1.0, es 1.0
Failing today: bn-bug
Known limitations:
- Only English, Spanish and Bangla were tested.
- Bangla bug reports without the word for 'crash' are labelled other (bn-bug); route to a person.
Recent changes:
2026-10-04 v1.3 rolled-back Nadia Rahman: back to v1.1: v1.3 loses long tickets (en-long)
2026-10-01 v1.3 released Mateo Silva: Compact prompt; gate passed.
If it regresses:
Trigger: a failing case in the set, or a confirmed report from support.
Who: the owner or the reviewer; tell the other within the day.
How: rollback() moves the pointer; no file is deleted. Then re-run the set.
Next: add the failing ticket to cases.json, fix forward, pass the gate.
Open readiness items: none
To write by hand (a script cannot know these):
- Who asks for changes, and how urgent ones are handled:
- Which downstream code reads the label, and who owns it:
- Ideas we rejected, and why:তথ্য আসে ফাইল থেকে, তাই ডকুমেন্ট রেজিস্ট্রির সঙ্গে অমিল হতে পারে না।
কাজের জায়গায় এটি কোথায় দেখা যায়
কোনো সহকর্মী চলে গেলে বা অডিট জানতে চাইলে কে একটি প্রম্পট অনুমোদন করেছেন ও কী যাচাই করেছেন, তখন রেজিস্ট্রি, লগ ও হ্যান্ডওভার ডকুমেন্টই উত্তর।
সাধারণ ভুল
- প্রতিটি মডেলের জন্য ভ্যারিয়েন্ট। পরীক্ষা দ্বিগুণ হয়। সমাধান: সেট ফাঁক দেখালেই কেবল।
- নিজে-নিজে অনুমোদিত পরিবর্তন। লেখক নিজের অন্ধ জায়গা দেখতে পান না। সমাধান: ভিন্ন রিভিউয়ার; গেট তা বাধ্য করে।
- ঘটনার সময় রোলব্যাক পরিকল্পনা লেখা। সমাধান: রিলিজের সময়ই লিখুন।
- টেস্ট কেসে আসল গ্রাহকের ডেটা। প্রতিটি কপিতে ছড়ায়। সমাধান: বানানো বা মুছে দেওয়া কেস, স্ক্যান, মানুষের নমুনা যাচাই।
- সীমাবদ্ধতা একবার লিখে ফেলে রাখা। সমাধান: সর্বশেষ ব্যর্থ কেসের সঙ্গে মিলিয়ে রাখুন।
নিজে চেষ্টা করুন
- সহজ। স্প্যানিশ সামলায় এমন প্রোডাকশন অ্যাসেট খুঁজে বের করার কল লিখুন।
- মাঝারি। বাতিল হওয়া
v1.2-তে রোলব্যাক করার চেষ্টা করুন। কী হবে আগে অনুমান করুন, তারপর চালান। - কঠিন।
v1.4লিখুন:v1.3, তবে প্রসঙ্গের লাইনটি ফিরিয়ে দিয়ে। ভিন্ন রিভিউয়ারসহ গেট পাস করান, রিলিজ করুন, লগ করুন এবং সর্বশেষ লগ লাইন ছাপুন।
উত্তর
সহজ।
print(find(languages="es", stage="production"))['invoice-extract', 'ticket-triage']পাস চেকলিস্ট: এটি টেক্সট সার্চ নয়, ট্যাগ ব্যবহার করে; মিলে যাওয়া দুটি অ্যাসেটই আসে।
মাঝারি। ভার্সনটি বাতিল হয়েছিল, তাই rollback তা প্রত্যাখ্যান করে।
try:
rollback("ticket-triage", "v1.2", "2026-10-05", "Nadia Rahman", "test")
except ValueError as exc:
print(exc)v1.2 was never released; choose a released versionপাস চেকলিস্ট: পয়েন্টার সরেনি; কারণ বলতে পারেন (বাতিল ভার্সন শর্ত পূরণে ব্যর্থ হয়েছিল)।
কঠিন।
V14 = V13.replace("Label the support ticket:", "You label customer support tickets for a software shop.\nLabels:")
add_version("ticket-triage", V14, "v1.3 plus the context line; fixes long tickets.", "2026-10-06")
baseline = run_set("ticket-triage", "v1.1")
print(gate("ticket-triage", "v1.4", baseline, "Nadia Rahman", "Mateo Silva") or "approved")
set_current("ticket-triage", "v1.4", "2026-10-06", "Gate passed; en-long now covered.")
log_change("released", "ticket-triage", "v1.4", "Mateo Silva", "2026-10-06", "Compact prompt with context line.")
print(changelog("ticket-triage").splitlines()[0])approved
2026-10-06 v1.4 released Mateo Silva: Compact prompt with context line.পাস চেকলিস্ট: বেসলাইন হলো হালনাগাদ সেটে current ভার্সন; অনুমোদনকারী লেখক থেকে ভিন্ন; লগে নতুন ভার্সনের নাম আছে।
সারসংক্ষেপ
- মডেল-নির্দিষ্ট ভ্যারিয়েন্ট শুধু তখনই বানান, যখন নির্দিষ্ট সেট এমন ফাঁক দেখায় যা একটি লেখায় ঘোচে না।
- বড় পরিবর্তনে চাই লেখক নন এমন রিভিউয়ার, পাস করা গেট এবং লগ করা রিলিজ।
- রোলব্যাক পরিকল্পনা রিলিজের সময়ই লিখুন; রোলব্যাক মানে পয়েন্টার সরানো, তারপর ব্যর্থ কেস সেটে যোগ হয়।
- শুধু-যোগ-করা চেঞ্জ লগ, নিয়ন্ত্রিত ট্যাগের লাইব্রেরি এবং অপ্রয়োজনীয় ব্যক্তিগত তথ্যবিহীন টেস্ট ডেটা রাখুন।
- সীমাবদ্ধতা প্রমাণসহ লিখুন, প্রস্তুতি যাচাই করুন, আর বিচারের জন্য চিহ্নিত লাইনসহ তৈরি করা ডকুমেন্টে হ্যান্ডওভার করুন।
এরপর: অ্যাডভান্সড প্র্যাকটিস ল্যাব: তুলনা, ভ্যালিডেশন, হার্ডেনিং, রিগ্রেশন ও রেড-টিম, যেখানে তুলনা, ভ্যালিডেশন, হার্ডেনিং ও রিগ্রেশন টেস্টিং একসঙ্গে করবেন।