অধ্যায় 10 · প্রোডাকশন ক্যাপস্টোন ইন্টিগ্রেশন ও রিলিজ
ক্যাপস্টোন আর্কিটেকচার ও ইন্টিগ্রেশন
- পৃষ্ঠা 19 / 22
- 23 মিনিট পড়া
ঢাকার নাদিয়ার হাতে ছয়টা কাজ করা অংশ: ইনজেশন, অনুমতি-সচেতন রিট্রিভার, রিলিজ গেট, ট্রেসার, নিরাপত্তা স্তর আর একটা স্ট্রিমিং সার্ভিস। ম্যানেজার জিজ্ঞেস করলেন, "এটা কি একটাই সিস্টেম?" প্রতিটা অংশ নিজের টেস্ট পাস করেছে। কিন্তু যে বাগ ক্ষতি করে তা থাকে জোড়ায়: ভুল পরিচয় থেকে বানানো ফিল্টার, এক মডিউলে আছে অথচ আরেকটায় নেই এমন একটা স্ট্যাটাস শব্দ, কিংবা র্যাপারের সঙ্গে চলে আসা একটা রুট। (এটা একটা বোঝানোর গল্প।)
এই পাতা ক্যাপস্টোন অধ্যায় শুরু করে। এটা একটাই নতুন সাবপ্যাকেজ, evidencedesk/knowledgeops/, যোগ করে; আর কিছু বদলায় না। ইনজেশন ও অনুমতি-সচেতন রিট্রিভাল, রিট্রিভাল অপারেশন ও ডেটার সতেজতা, মজবুত মেট্রিক ও রিলিজ গেট, গার্ডরেইল ও নিরাপত্তা রিগ্রেশন, স্ট্রাকচার্ড লগিং ও ট্রেসিং এবং কন্টেইনারাইজেশন ও স্ট্রিমিং ডিপ্লয়মেন্ট-এর অংশগুলো এখানে জোড়া লাগে, নতুন করে শেখানো হয় না। পরের পাতা রিলিজের প্রমাণ জড়ো করে, প্যাকেজিং, কনফিগারেশন ও পুনরুৎপাদনযোগ্যতা এবং ডকুমেন্টেশন, রিলিজ ও পোর্টফোলিওর মান-এর অভ্যাস কাজে লাগিয়ে। সৎ সীমা: এখানে সব মডেলই নকল (আসল মডেল কম অনুমানযোগ্য, আর ঠিক সে কারণেই টেস্ট করতে হয়), ঘড়িও নকল, প্রশ্নের সেটে মাত্র ১৯টা কেস, আর এই পাতার শেষে রিলিজ ক্যান্ডিডেট প্রস্তুত নয়। এটাই সঠিক ফলাফল, আর পাতা দেখায় কেন।
যা শিখবেন
- কীভাবে একটা সংকীর্ণ ইউজ কেস ঠিক করবেন এবং যে কোড সেটা পূরণ করবে তার আগেই মেশিনে-যাচাইযোগ্য গ্রহণযোগ্যতার মানদণ্ড লিখবেন।
- ডিসিশন রেকর্ড দিয়ে কীভাবে RAG ও এজেন্টের মধ্যে বাছবেন, এবং চলমান সিস্টেম থেকেই কীভাবে আর্কিটেকচার ও বিশ্বাসের সীমানা বানাবেন।
- কীভাবে মডেল ও টুল চুক্তি স্পষ্ট করবেন, এবং প্রোভাইডারকে একটা ইন্টারফেসের পেছনে রেখে নকল ক্লায়েন্ট ডিফল্ট রাখবেন।
- যাচাইকৃত রিট্রিভাল, একটা অফলাইন গেট আর ক্রিটিক্যাল পাথের ইনস্ট্রুমেন্টেশন কীভাবে একটা কম্পোজিশন রুটের পেছনে জোড়া লাগাবেন।
- কীভাবে পুরো সিস্টেমের অফলাইন রিলিজ ক্যান্ডিডেট চালাবেন, আর কোন ইন্টিগ্রেশন জোড়া মেলেনি।
একটা সংকীর্ণ প্রোডাকশন ইউজ কেস বাছুন
ইউজ কেস সংকীর্ণ তখনই, যখন আপনি ব্যবহারকারী, কাজ, ডেটার উৎস আর ব্যর্থতার সীমা নাম ধরে বলতে পারেন: উত্তর দিতে না পারলে, মডেল বন্ধ থাকলে, আর মডেলের আউটপুট ভাঙা হলে সিস্টেম আগে থেকেই কী করবে। KnowledgeOps হলো দুটো কোম্পানির সাইন-ইন করা স্টাফের সহায়তা সহকারী, যারা একই প্ল্যাটফর্ম ভাগ করে। এটা শুধু তাদের নথি থেকে উত্তর দেয় এবং প্রতিটা দাবির সাইটেশন দেয়। সংজ্ঞাটা ডেটা, একটা Pydantic মডেল দিয়ে যাচাই করা; আর problems() চেক বলে দেয় কোন অংশ এত অস্পষ্ট যে তার ওপর বানানো যায় না।
from evidencedesk.knowledgeops.usecase import load_usecase
uc = load_usecase()
print("task:", uc.task[:95] + "...")
print("out of scope:", "; ".join(uc.out_of_scope[:3]) + "; ...")
print("allowed tools:", uc.allowed_tools, "| tenants:", uc.tenants)
for name, rule in uc.failure_boundary.items():
print(f" {name}: {rule}")
print("problems:", uc.problems())
vague = uc.model_copy(update={"user": " ", "out_of_scope": ["x"], "failure_boundary": {}})
print("vague draft:", len(vague.problems()), "problems, first:", vague.problems()[:2])task: Answer policy and product questions from the company's own documents. Every claim carries a cit...
out of scope: issuing refunds or credits; sending email; answering from the open web; ...
allowed tools: ['search_knowledge', 'read_chunk'] | tenants: ['kestrel', 'harbor']
unsupported_question: abstain with the standard sentence; never guess
forbidden_document: behave exactly as if the document did not exist
provider_down: a plain failure message and a retryable code; no partial answer
malformed_model_output: reject the whole reply; do not repair it
problems: []
vague draft: 6 problems, first: ['user is empty', 'name at least two things that are out of scope']সীমার-বাইরের তালিকা একটা নকশার হাতিয়ার: "ক্রেডিট দেওয়া" আর "ইমেইল পাঠানো" এই সহকারীকে শুধু-পড়ার রাখে, আর নিচের সিদ্ধান্ত এর ওপর নির্ভর করে।
কোডের আগে গ্রহণযোগ্যতার মানদণ্ড লিখুন
data/knowledgeops/acceptance.json ফাইলে পাঁচটা বিভাগে ১৯টা মানদণ্ড আছে। প্রতিটা একটা মেজার (measure), একটা তুলনা আর একটা সংখ্যার নাম দেয়, যাতে প্রোগ্রাম তা ঠিক করতে পারে। ধারণাটা কন্টেইনারাইজেশন ও স্ট্রিমিং ডিপ্লয়মেন্ট-এর ডিপ্লয়মেন্ট গ্রহণযোগ্যতার ফাইল থেকে নেওয়া, যেখানে প্রতি মানদণ্ডে একটা টেস্টের নাম থাকে; এখানে বেশির ভাগ মানদণ্ড মাপজোখ, তাই একটা সীমা (threshold) থাকে। check মাপা মানের একটা ডিক্ট নিয়ে প্রতি মানদণ্ডের একটা রায় দেয়, আর যে মেজার কেউ তৈরি করেনি সেটা ব্যর্থ: অনুপস্থিতি পাস নয়।
from collections import Counter
from evidencedesk.knowledgeops import criteria as crit
criteria = crit.load_criteria()
print(dict(Counter(c.category for c in criteria)))
for c in criteria[:2] + criteria[5:6] + criteria[11:12]:
print(f"{c.id} {c.measure} {c.op} {c.value} # {c.requirement[:68]}")
verdicts = crit.check(criteria[:3], {"answer_correct": 0.80, "abstain_correct": 0.95})
print([(v.id, v.ok, v.reason) for v in verdicts]){'functionality': 5, 'security': 6, 'latency': 1, 'observability': 4, 'recovery': 3}
F1 answer_correct >= 0.85 # A signed-in caller gets the stated fact, with a citation, for at lea
F2 abstain_correct >= 0.9 # A question the caller's documents do not answer is abstained or clar
S1 cross_tenant_leaks == 0.0 # No answer or citation carries text from another tenant's document.
L1 p95_fake_s <= 0.5 # p95 request latency on the fixed set stays within 0.5 s of the fake
[('F1', False, 'answer_correct 0.8 is not >= 0.85'), ('F2', True, ''), ('F3', False, 'not measured')]দুটো সততার নিয়ম। অফলাইনে লেটেন্সির মানদণ্ড শুধু নকল ঘড়িতে একটা কাঠামোগত বাজেট হতে পারে, আর ফাইল সেটা বলেই দেয়। আর সীমাগুলো আসে প্রয়োজন থেকে এবং আগের বেসলাইন থেকে (Beginner সহকারী তার নির্দিষ্ট কেসের ০.৮৫২ পাস করেছিল), প্রথম রান থেকে নয়: ফলের সঙ্গে মেলাতে সংখ্যা সরানো ঠিক সেই ভুল, যে বিষয়ে মজবুত মেট্রিক ও রিলিজ গেট সাবধান করেছিল। গেট তার সীমা এই ফাইল থেকেই নেয়, তাই দুটো কখনো ভিন্ন কথা বলতে পারে না।
RAG নাকি এজেন্ট, বাছুন
প্রয়োজন মেটায় এমন সহজতর আর্কিটেকচারটা বেছে নিন। এখানকার নিয়মটা যান্ত্রিক: এজেন্ট বাছা হয় কেবল তখন, যখন কোনো প্রয়োজন এজেন্ট ছাড়া মেটে না (লেখার ক্রিয়া, ধাপের সংখ্যা আগে অজানা এমন পরিকল্পনা, খোলা-প্রান্ত টুল বাছাই)। রেকর্ডটা তার ফিল্ড আর Markdown-এর জন্য ফাইন-টিউনিংয়ের সিদ্ধান্ত ও LoRA-র ধারণা-র DecisionRecord ব্যবহার করে, কিন্তু নিজের problems() নিয়ে, কারণ মূলটা শুধু ফাইন-টিউনিংয়ের বিকল্প মানে।
from evidencedesk.knowledgeops.decision import decide_architecture
from evidencedesk.knowledgeops.usecase import Needs
record = decide_architecture(uc)
print(record.to_markdown())
agentic = uc.model_copy(update={"needs": Needs(**{**uc.needs.model_dump(), "write_actions": True})})
r2 = decide_architecture(agentic)
print("if the task also had to issue credits:", r2.decision, r2.triggers)# Decision AR-1 (2026-10-13)
Problem: Answer policy and product questions from the company's own documents. Every claim carries a citation; a question the documents do not answer gets an abstention, not a guess.
Symptom: answers must follow documents that change
Options considered: rag, workflow, agent
Decision: rag
Evidence:
- no requirement needs an agent: write_actions, multi_step_planning, open_ended_tool_choice are all false
- facts change (changing_facts is true), so the documents are retrieved at question time, not trained in
- allowed tools are read-only: search_knowledge, read_chunk
Risks:
- a fixed pipeline cannot handle a question that needs two lookups in sequence
- retrieval quality caps answer quality
- the simpler design has fewer places to hold a permission bug, but the ones it has are in retrieval
Baseline: none
Review when: a requirement adds an action that changes data, or the fixed question set shows multi-step failures
if the task also had to issue credits: agent ['write_actions']এজেন্ট আনত সেসবই, যা এজেন্ট লুপ, স্টেট ও মেমরি এবং মানুষের অনুমোদন ও মাল্টি-এজেন্টের সীমা-কে আনতে হয়েছিল: ধাপের বাজেট, সংরক্ষিত স্টেট, অনুমোদন। এই কাজে এগুলোর কোনোটাই লাগে না, আর রেকর্ড বলে দেয় কখন সিদ্ধান্ত নতুন করে দেখতে হবে।
ডেটা প্রবাহ আর বিশ্বাসের সীমানা আঁকুন
হাতে আঁকা ডায়াগ্রাম সরে যায়। এটা পড়া হয় কম্পোজিশন রুটের বানানো অবজেক্ট থেকে (নিচে পুরোটা দেখানো), আর প্রতিটা তীরে থ্রেট মডেলিং ও ইনজেকশন টেস্টিং-এর থ্রেট মডেলের একটা বাউন্ডারি আইডি এবং যে কোড সেটা প্রয়োগ করে তার নাম থাকে। problems() প্রতিটা প্রয়োগকারী ফাংশন ইমপোর্ট করে, দেখে কোনো প্রবাহ প্রতিটা বাউন্ডারি পার হয়েছে কি না, আর প্রতিটা টেন্যান্টের পার্টিশন আছে কি না।
from evidencedesk.knowledgeops.system import build_system
system = build_system() # the composition root, read in full further down
print(system.ingest_report.text().splitlines()[0])
print(system.ingest_report.counts())run-001 epoch 1 embedded 21 verify findings 0
{'added': 12, 'quarantined': 1}from evidencedesk.knowledgeops import architecture
print(architecture.describe(system))
print("problems:", architecture.problems(system))
print("surfaces the shipped threat model does not list:", architecture.threat_gaps())components
callers untrusted people with a bearer token; 2 tenants share the platform
edge edge stream route: bearer auth, size limit, quota, admission; the inherited /ask route is removed
pipeline core retrieve -> context -> model -> validate; one span per stage; identity from the directory, never from the text
knowledge data 12 sources, epoch 1, 1 quarantined; partitions: harbor 2 chunks, kestrel 19 chunks
model provider LLMClient protocol only (ExtractiveModel offline); no tools, JSON reply contract
tools core read-only contracts: search_knowledge (read), read_chunk (read)
telemetry telemetry events + spans; 13 field names logged as length only; content capture off
flows (boundary of the threat model, enforcing code)
callers --B1--> edge create_stream_app
edge --B1--> pipeline Directory
pipeline --B5--> knowledge security_allowed
pipeline --B2--> model SecretGuard
model --B3--> pipeline check_output
pipeline --B4--> tools ToolBox
pipeline --B7--> telemetry EventLog
problems: []
surfaces the shipped threat model does not list: ['tool:search_knowledge', 'tool:read_chunk', 'input:model_reply', 'route:/v1/stream']ব্যবহারকারী, API, রিট্রিভাল, মডেল, টুল, স্টোরেজ আর লগ সবই আছে। শেষ লাইনটাই সৎ অংশ: পাঠানো থ্রেট মডেলে এই প্যাকেজের যোগ করা চারটা সারফেসের কোনো এন্ট্রি পয়েন্ট নেই। পরের পাতা সেগুলো বন্ধ করে।
নিরাপত্তা নোট (২০২৬-১০-১২ অনুযায়ী): প্রকল্পের মূল পাতায় OWASP Top 10 for LLM Applications-এর ২০২৫ সংস্করণ দেখানো আছে। ২০২৬-০৮-০৩ তারিখের একটা ২০২৬ সংস্করণ আছে, কিন্তু তার তালিকা কোনো অফিশিয়াল পাতা থেকে নিশ্চিত করা যায়নি। যে সংস্করণ ব্যবহার করছেন তার নাম বলুন এবং বর্তমান ডকুমেন্টেশন দেখে নিন।
অভ্যন্তরীণ মডেল ও টুল চুক্তি ঠিক করুন
চারটা চুক্তি সীমানা পার হয়। AskRequest-এ টেন্যান্ট, ব্যবহারকারী বা গ্রুপের কোনো ফিল্ড নেই, আর অচেনা ফিল্ড মানেই এরর, তাই অনুরোধ কোনো পরিচয় দাবি করতে পারে না। ModelReply মডেলের ফেরত দেওয়ার একমাত্র আকার; টুল অনুরোধ বা বাড়তি ফিল্ড থাকলে পুরো উত্তর বাতিল। Answer সিস্টেম থেকে যা বেরোয়। টুলগুলো স্পষ্ট শুধু-পড়ার চুক্তি, আর ইউজ কেস বলে দেয় কোনগুলো অনুমোদিত।
# excerpt of kit/evidencedesk/knowledgeops/contracts.py
class SearchArgs(BaseModel):
model_config = ConfigDict(extra="forbid", frozen=True)
query: str = Field(min_length=1, max_length=500)
filters: Filters | None = None # may only narrow what the caller can already read
k: int = Field(3, ge=1, le=5)টুল বক্স আর্গুমেন্ট যাচাই করে, অনুমোদিত তালিকা দেখে, আর আগের অধ্যায়ের অনুমতি-সচেতন সার্চ কলারের পরিচয় দিয়ে চালায়। সে শুধু একটা কোড ফেরত দেয়, কখনো এরর বার্তা নয়, কারণ Pydantic-এর বার্তা ইনপুট প্রতিধ্বনি করতে পারে।
from evidencedesk.knowledgeops.contracts import boundary_problems
from evidencedesk.knowledgeops.directory import Directory
people = Directory.load()
calls = [("kwame", "search_knowledge", {"query": "goodwill credit limit"}), ("cust-7", "search_knowledge", {"query": "goodwill credit limit"}),
("aiko", "search_knowledge", {"query": "x", "tenant": "harbor"}), ("aiko", "delete_everything", {}),
("cust-7", "read_chunk", {"chunk_id": "kc-payroll-note:c0"}), ("cust-7", "read_chunk", {"chunk_id": "kc-nothing-here:c0"}),
("lena", "read_chunk", {"chunk_id": "kc-payroll-note:c0"})]
for who, tool, args in calls:
out = system.tools.run(people.principal(who), tool, args)
print(f"{who:<7}{tool:<18}{out.code:<18}", [h.source_id for h in out.data.hits] if out.ok and hasattr(out.data, "hits") else "")
print("provider imports in the package:", boundary_problems())kwame search_knowledge ok ['kc-goodwill', 'kc-goodwill', 'kc-fleet-policy']
cust-7 search_knowledge ok ['kc-returns', 'kc-warranty', 'kc-shipping']
aiko search_knowledge invalid_arguments
aiko delete_everything unknown_tool
cust-7 read_chunk not_found
cust-7 read_chunk not_found
lena read_chunk ok
provider imports in the package: []সারিগুলো পড়ুন: কোয়ামের সার্চ সেই গুডউইল নোট পায়, যা কাস্টমার ৭-এর সার্চ পায় না; নিষিদ্ধ চাঙ্ক আর অনুপস্থিত চাঙ্ক একই কোড দেয়; লেনা (HR গ্রুপে) যা খুলতে পারেন, কাস্টমার ৭ পারেন না। প্রোভাইডারের সীমানাও কোডে যাচাই হয়: boundary_problems() প্যাকেজের প্রতিটা মডিউলে প্রোভাইডার SDK ইমপোর্ট খোঁজে এবং কিছু পায় না। একটা টেস্ট নিশ্চিত করে, একই কলারের জন্য টুল পথ আর পাইপলাইন পথ একই চাঙ্ক ফেরত দেয়। RAG পাইপলাইন নিজে টুল বক্স ডাকে না; চুক্তিগুলো এজেন্ট এক্সটেনশন বা ক্লায়েন্টের জন্য।
প্রোভাইডার-নিরপেক্ষ LLM স্তর জোড়া লাগান
llmlayer.py কোনো SDK ইমপোর্ট করে না। এটা একটা অনুরোধ বানায় (নিয়ম, এস্কেপ করা এভিডেন্স যাতে trust="untrusted" চিহ্ন, আর প্রশ্ন), উত্তরের চুক্তি ঠিক করে, আর বদলি মডেল দেয়। ডিফল্ট ExtractiveModel ভাষা মডেল নয়; সে এভিডেন্সের সবচেয়ে মিলে যাওয়া বাক্যটা তুলে দেয়। ScriptedModel দেওয়া উত্তর বা দেওয়া এরর রিপ্লে করে, এভাবেই প্রোভাইডার বিভ্রাট ও শত্রুভাবাপন্ন আউটপুটের মহড়া হয়। আসল অ্যাডাপ্টার শুধু complete(request) বানায়, আর কিছু না। রিট্রাই অ্যাডাপ্টার, কনফিগারেশন ও এরর হ্যান্ডলিং-এর এরর ক্লাস ব্যবহার করে, এবং অপেক্ষা চলে নকল ঘড়িতে।
প্রোভাইডার নোট (২০২৬-১০-১২ অনুযায়ী): একটা প্রোভাইডারের ডকুমেন্টেশন বলে আগে স্টপ রিজন দেখুন: প্রত্যাখ্যান (refusal) একটা স্বাভাবিক সফল রেসপন্স হিসেবে আসে যা স্কিমার সঙ্গে নাও মিলতে পারে, আর টোকেন সীমায় কেটে যাওয়া উত্তর অবৈধ হতে পারে। তাই পাইপলাইন পার্স করার আগেই
stop_reasonদেখে। আপনার প্রোভাইডারের বর্তমান ডকুমেন্টেশন দেখে নিন।
from evidencedesk.knowledgeops import llmlayer, scenarios
block = llmlayer.evidence_block("kc-returns:c0", 1, "Customers may return a new bike within 21 days of delivery.")
request = llmlayer.build_request("How long do I have to return a bike?", [block])
print(request.messages[-1].content)
print(f"{'scenario':<20}{'status':<10}{'code'}")
for sid, (answer, as_expected) in scenarios.run_all().items():
print(f"{sid:<20}{answer.status:<10}{answer.code}{'' if as_expected else ' <-- NOT AS EXPECTED'}")<evidence source="kc-returns:c0" version="1" trust="untrusted">
Customers may return a new bike within 21 days of delivery.
</evidence>
<question source="user-input">
How long do I have to return a bike?
</question>
scenario status code
provider_down failed server
rate_limited failed rate_limited
not_json failed invalid_reply
extra_field failed invalid_reply
truncated failed truncated
no_citation failed unknown_citation
foreign_citation failed unknown_citation
claim_not_in_source abstained unsupported_claim
image_exfil blocked unsafe_output
foreign_link blocked unsafe_output
secret_in_answer blocked unsafe_outputএগারোটা মহড়া ঠিক যেখানে থামার কথা সেখানেই থামে। চেকের ক্রমটা লক্ষ করুন: লিংক, ছবি আর সিক্রেটের আউটপুট চেক সাপোর্ট চেকের আগে চলে। প্রথম সংস্করণে উল্টো ছিল, আর ডেটা বের করে নেওয়ার চেষ্টা নিরীহ "অসমর্থিত দাবি" হিসেবে রিপোর্ট হয়েছিল; নিরাপত্তার চেক আগে, মানের চেক পরে।
যাচাইকৃত রিট্রিভাল জোড়া লাগান
কম্পোজিশন রুট একটাই ফাংশন। সবকিছু এতেই ইনজেক্ট হয় বা ডিফল্ট পায়, তাই টেস্ট একটা অংশ বদলে বাকিটা রাখতে পারে।
# excerpt of kit/evidencedesk/knowledgeops/system.py
def build_system(model: LLMClient | None = None, config: Config | None = None, root: Path = INGEST_DATA, usecase: UseCase | None = None) -> System:
"""The composition root. Everything is injected or defaulted here, so a test can swap one part and keep the rest."""
config, usecase = config or Config(), usecase or load_usecase(DATA / "usecase.json")
clock = ManualClock(1000.0)
guard = SecretGuard({"api_key": SIMULATED_SECRET})
log = EventLog(clock, guard)
kb = KnowledgeBase(root, clock=clock, log=log)
report = kb.ingest(snapshot(config.snapshot))
tracer, ledger = Tracer(clock, CountingIds(), log), Ledger()
base = model or llmlayer.ExtractiveModel(config.min_share, corpus_idf(kb))
retrying = RetryingClient(base, RetryPolicy(max_attempts=config.max_attempts), sleep=clock.advance, rng=random.Random(7))
traced = TracedModel(Relabel(SlowClient(retrying, clock, MODEL_BASE_S, MODEL_PER_TOKEN_S), config.pricing_name), tracer, ledger)
return System(usecase, kb, clock, log, tracer, ledger, traced, guard, ToolBox(kb, usecase.allowed_tools), config, report)রিট্রিভাল নতুন করে বানানো হয়নি। ইনজেশন ও অনুমতি-সচেতন রিট্রিভাল-এর KnowledgeBase প্রতি টেন্যান্টে একটা হাইব্রিড ইনডেক্স রাখে এবং র্যাংকিংয়ের আগে পরিচয় দিয়ে ফিল্টার করে; রিট্রিভাল অপারেশন ও ডেটার সতেজতা-র fallback.decide উত্তর দেয়, জিজ্ঞেস করে, সতর্কতা জুড়ে দেয় বা বিরত থাকে। পাইপলাইন এগুলোর চারপাশে স্প্যান, মডেল কল আর যাচাই যোগ করে। প্যাকেজটা তেরোটা ছোট মডিউল: সংজ্ঞা (usecase, criteria, decision), চুক্তি (contracts, tools, directory), মডেল স্তর (llmlayer, scenarios), রুট (system) আর বাইরের অংশ (gate, architecture, app, rc)।
Q = "How long do I have to return a bike?"
for name in ("cust-7", "omar", "aiko"):
a = system.ask(people.principal(name), Q, f"demo-{name}")
print(f"{name:<7}{a.status:<10}{a.code:<6}{a.text}")
a = system.ask(people.principal("aiko"), "How much goodwill credit can an agent give?", "demo-restricted")
print("aiko, goodwill:", a.status, a.code, "|", a.text[:52])cust-7 answered ok Customers may return a new bike within 21 days of delivery. [kc-returns:c0]
omar answered ok Harbor Bikes accepts returns within 30 days of delivery. [hb-returns:c0]
aiko answered ok Customers may return a new bike within 21 days of delivery. [kc-returns:c0]
aiko, goodwill: abstained low_coverage | Not enough evidence in the help centre to answer thiকাস্টমার ৭ আর আইকো একই টেন্যান্টের, দুজনেই ২১ দিনের সময়সীমা পান; ওমরের টোকেন অন্য টেন্যান্টে যায়, তাই ৩০ দিন পান। আইকো গুডউইল সীমা জিজ্ঞেস করেন, যেটা শুধু টিম লিডরা পড়তে পারেন, আর পান নির্ধারিত বিরত থাকার বাক্য: নথিটা এমন আচরণ করে যেন তার অস্তিত্বই নেই। এবার সেই একটা সংখ্যা, যা গ্রহণযোগ্যতার মানদণ্ড পরীক্ষা করবে, মডেলের বিরত-থাকার সীমা:
from evidencedesk.knowledgeops.gate import answer_report
from evidencedesk.knowledgeops.system import Config
print("min_share answer_correct abstain_correct wrong answers on unanswerable cases")
for share in (0.3, 0.4, 0.5, 0.6):
r = answer_report(build_system(config=Config(min_share=share)))
c = r.counts
print(f"{share:<11}{c['answer_correct'].k}/{c['answer_correct'].n:<14}{c['abstain_correct'].k}/{c['abstain_correct'].n:<15}{c['abstain_correct'].n - c['abstain_correct'].k}")min_share answer_correct abstain_correct wrong answers on unanswerable cases
0.3 9/12 6/7 1
0.4 8/12 7/7 0
0.5 8/12 7/7 0
0.6 6/12 7/7 0কোনো সেটিং জেতে না। শিথিল মডেল বেশি উত্তর দেয় কিন্তু নথিতে নেই এমন একটা প্রশ্নে ভুল উত্তর দেয়; কড়া মডেল ঠিকভাবে বিরত থাকে আর কম উত্তর দেয়। এখানে ভুল উত্তরের দাম বিরত থাকার চেয়ে বেশি, তাই ডিফল্ট ০.৫। সংখ্যাগুলো ১২ ও ৭টা কেসের, তাই একটা কেসের তফাত গোলমালের মধ্যেই পড়ে; টেবিলটা একটা বাছাইয়ের প্রমাণ, কোনো র্যাংকিং নয়।
একটা অফলাইন মূল্যায়ন গেট যোগ করুন
একটা কমান্ড তিন ধরনের প্রমাণ চালিয়ে একটাই রায় ছাপে, অংশগুলোর মধ্যে সবচেয়ে খারাপটা। প্রতিটা অংশ একটা evalkit রিপোর্ট, যা মজবুত মেট্রিক ও রিলিজ গেট-এর গেট বিচার করে: শুরু-থেকে-শেষ উত্তর (নির্দিষ্ট কেস, সাইটেশন ও লিক চেক), রিট্রিভাল অপারেশন ও ডেটার সতেজতা-র রিট্রিভাল রিগ্রেশন স্যুট, আর গার্ডরেইল ও নিরাপত্তা রিগ্রেশন-এর আক্রমণ-সংগ্রহ। উত্তরের অংশ সীমা নেয় গ্রহণযোগ্যতার ফাইল থেকে, আর ক্রিটিক্যাল কেস নেয় কেস ফাইল থেকে। শুধু block-এ এক্সিট কোড ১ হয়।
from evidencedesk.knowledgeops.gate import run_gate
result = run_gate(system)
print(result.text())
print("exit code:", result.exit_code())part verdict reasons that are not notes
answers block floor: answer_correct 0.667 (8/12) is below the floor 0.85; floor: citation_support 0.8 (8/10) is below the floor 0.9
retrieval go -
security go -
GATE: BLOCK
exit code: 1গেট উত্তরের মান আর সাইটেশন সাপোর্টে block করে; বাকি দুই অংশ পরিষ্কার। দুটো সংখ্যারই কারণ পড়া যায়: প্যারাফ্রেজ (২১ দিনের জায়গায় "তিন সপ্তাহ", damaged-এর জায়গায় "broken") আর একটা টেবিল সারি, যা কাঠামো-সচেতন চাঙ্কার তার হেডার থেকে আলাদা করে ফেলেছে। আরও দুটো তথ্য জরুরি। রিট্রিভাল অংশের জন্য সঠিক স্ন্যাপশট লাগে: পুরোনোটা পুরোনো রিটার্ন সময়সীমা নিয়ে block করে। আর উত্তরের অংশ একটা সংরক্ষিত বেসলাইনের সঙ্গে তুলনা হয়, যা প্রথম রানের রেকর্ড, অনুমোদন নয়।
গেট কি অনুমতির বাগ দেখতে পায়? এখানে ফিল্টারটা ইচ্ছা করে ভুল পরিচয় থেকে বানানো হয়েছে, একটা অস্থায়ী প্যাচে, আর কেসগুলো আবার চালানো হয়েছে।
from unittest import mock
from evidencedesk.ingest.contract import Principal
from evidencedesk.ingest.fallback import decide as real_decide
from evidencedesk.knowledgeops import system as system_module
from evidencedesk.knowledgeops.criteria import load_criteria
from evidencedesk.knowledgeops.gate import answer_report, evaluate, load_cases, policy_from_criteria
boss = lambda p: Principal(p.user_id, "kestrel", ("staff", "team-leads", "hr")) # the bug: the filter is built from the wrong identity
with mock.patch.object(system_module, "decide", lambda kb, p, q, *a, **k: real_decide(kb, boss(p), q, *a, **k)):
bad = answer_report(build_system())
decision = evaluate(policy_from_criteria(load_criteria(), bad, load_cases()), bad)
print(decision.verdict, {k: bad.scalars[k] for k in ("cross_tenant_leaks", "restricted_leaks")})
print([r.text for r in decision.reasons if r.rule == "critical"][:3], "...")block {'cross_tenant_leaks': 2.0, 'restricted_leaks': 4.0}
['case k12 failed', 'case k13 failed', 'case k14 failed'] ...পাইপলাইন কিছুই রিপোর্ট করেনি; সে নিজের ফিল্টারকে বিশ্বাস করে। গেট বাগটা ধরেছে একটা স্বাধীন স্ক্যানে: leaks উত্তরের ভেতরে এমন চাঙ্কের বাক্য খোঁজে যেটা কলার পড়তে পারেন না, আর কোনো পাবলিক পাতায়ও থাকা বাক্যকে উপেক্ষা করে (প্রথম সংস্করণ "Clearance items and gift cards cannot be returned" ধরেছিল, যা দুই জায়গাতেই আছে)। ক্রিটিক্যাল কেস একটা লিককেই block বানায়, গড় যাই বলুক।
ক্রিটিক্যাল পাথ ইনস্ট্রুমেন্ট করুন
প্রতিটা অনুরোধ একটা ট্রেস রেখে যায়: একটা রুট স্প্যান আর প্রতি ধাপে একটা স্প্যান, শুধু নিরাপদ অ্যাট্রিবিউট নিয়ে। ব্যবহারকারীর আইডি ছদ্মনাম, কনটেন্ট ক্যাপচার বন্ধ (দৈর্ঘ্য রাখা হয়), আর স্প্যান বহন করে একটা স্থির কোড, কখনো বার্তা নয়। একই ডেটা খাওয়ায় ইউসেজ লেজার এবং মনিটরিং, বাজেট ও ইনসিডেন্ট-এর অবজার্ভেশন।
from collections import Counter
from evidencedesk.observe import viewer
print(viewer.tree(system.tracer.traces()[0]))
print("codes:", dict(sorted(Counter(o.code for o in system.observations).items())))
t = system.ledger.summary()
print(f"model calls {t.calls}, tokens {t.input_tokens}+{t.output_tokens}, invented cost {t.cost}")
import json
print(json.dumps(json.loads(system.log.lines[-1])["fields"]), json.loads(system.log.lines[-1])["event"])request +0.000s 0.296s code=ok model_calls=1 outcome=answered status=answered tenant=kestrel
├─ retrieval +0.000s 0.030s action=answer coverage=0.5 hits=3 reason=ok top_score=0.0328
├─ context +0.030s 0.004s evidence=3 flagged=0
├─ model +0.034s 0.260s cost=0.000852 model=fake-model tokens_in=164 tokens_out=30 usage_estimated=False
└─ validation +0.294s 0.002s code=ok
codes: {'clarify': 1, 'low_coverage': 6, 'model_abstained': 3, 'ok': 13}
model calls 16, tokens 2503+429, invented cost 0.012657
{"hits": 3, "k": 3, "late_denied": 0, "query_chars": 28} search.queryএরর ক্যাটাগরি হলো code কলাম: রিট্রিভালের বিরত থাকা (low_coverage) মডেল পর্যন্ত পৌঁছায় না এবং কিছুই খরচ করে না, আর model_abstained টোকেন খরচ করে। লেটেন্সি নকল ঘড়ির: এখানে ০.২৯৬ সেকেন্ড মানে ধাপের ধ্রুবক আর একটা ভান-করা মডেল, তাই এটা কাঠামো প্রমাণ করে, গতি নয়। আসল লেটেন্সির সংখ্যার জন্য লাগে আসল মডেল আর লোড টেস্ট (স্কেলিং, রেজিলিয়েন্স ও রোলব্যাক)।
অনুশীলনী: পুরো সিস্টেমের অফলাইন রিলিজ ক্যান্ডিডেট
একটা কমান্ড ফিক্সচার ও নকল দিয়ে প্রতিটা ধাপ চালায়: সংজ্ঞা, বিল্ড, আর্কিটেকচার চেক, গেট, HTTP অ্যাপ, ব্যর্থতার মহড়া আর টেলিমেট্রি চেক। সে প্রতিটা মানদণ্ড মাপে এবং একটা সংক্ষিপ্ত টেবিল ছাপে। সে রিপোর্ট করে; go বা no-go ঠিক করা একজন মানুষের কাজ, এই প্রমাণ নিয়ে।
python -m evidencedesk.knowledgeops --criteria
echo "exit code: $?"step ok detail
use case yes Kestrel staff help assistant: 5 out-of-scope items, 2 tenants
decision yes rag over workflow, agent
criteria yes 19 criteria in 5 categories, all measurable
ingest yes {'added': 12, 'quarantined': 1}; verify findings 0
contracts yes 2 tool contracts, provider imports in package: 0
architecture yes 7 flows, threat-model gaps to close later: 4
gate NO answers=block retrieval=go security=go -> block
app yes routes /health, /ready, /v1/stream; 5 streams; no token -> 401
failures yes 11/11 scenarios ended where they must; stale snapshot blocked: True
telemetry yes 24 traces, 192 log lines, 18 model calls, 3362 tokens
id measure value result
F1 answer_correct 0.667 FAIL
F2 abstain_correct 1.0 ok
F3 citation_resolves 1.0 ok
F4 citation_support 0.8 FAIL
F5 stream_complete 1.0 ok
S1 cross_tenant_leaks 0.0 ok
S2 restricted_leaks 0.0 ok
S3 unsafe_outputs_served 0.0 ok
S4 attacks_stopped 0.97 ok
S5 unauthenticated_http 401.0 ok
S6 legacy_route_open 0.0 ok
L1 p95_fake_s 0.31 ok
O1 traces_well_formed 1.0 ok
O2 error_codes_present 1.0 ok
O3 usage_per_model_call 1.0 ok
O4 log_content_leaks 0.0 ok
R1 provider_failure_clean 1.0 ok
R2 malformed_reply_rejected 1.0 ok
R3 bad_snapshot_blocked 1.0 ok
criteria passing 17/19; failing: F1, F4
RESULT: NOT READY
exit code: 1দশটা ধাপ, ১৯টির মধ্যে ১৭টা মানদণ্ড পূরণ, একটা রায়। দুটো ব্যর্থতা সেগুলোই, যা গেট নাম ধরে বলেছিল; নিরাপত্তা রক্ষা করে এমন সবকিছু পাস করেছে: কোনো লিক নেই, কোনো অনিরাপদ আউটপুট পরিবেশিত হয়নি, টোকেন ছাড়া ৪০১, উত্তরাধিকারে পাওয়া রুট নেই, প্রোভাইডারের পরিষ্কার ব্যর্থতা, ভাঙা উত্তর পুরোটাই বাতিল, পুরোনো স্ন্যাপশট block। মানের জন্য "প্রস্তুত নয়" আর নিরাপত্তার জন্য "প্রস্তুত" একটা কাজে-লাগার মতো উত্তর; একটা গড় এটা লুকিয়ে ফেলত।
HTTP জোড়া, কন্টেইনারাইজেশন ও স্ট্রিমিং ডিপ্লয়মেন্ট-এর স্ট্রিমিং সার্ভিসের মধ্য দিয়ে, মোড়ানো কিন্তু অসম্পাদিত:
from fastapi.testclient import TestClient
from evidencedesk.knowledgeops import app as ops_app
with TestClient(ops_app.create_ops_app(build_system())) as http:
print(ops_app.route_paths(http.app), http.get("/ready").json())
for name, q in (("cust-7", "How long do I have to return a bike?"), ("omar", "How long do I have to return a bike?"),
("cust-7", "When are salary reviews held?"), ("", "hello")):
code, kinds = ops_app.smoke(http.app, name, q)
print(f"{name or '(no token)':<11}HTTP {code} {' '.join(kinds)}")['/health', '/ready', '/v1/stream'] {'ready': True, 'reason': '', 'chunks': 22, 'offline': True}
cust-7 HTTP 200 start token token token token token done
omar HTTP 200 start token token token token done
cust-7 HTTP 200 start token token token token token token done
(no token) HTTP 401 বেয়ারার টোকেনের নাম পরিচয় ঠিক করে (বডি পরিচয় বহন করতে পারে না), স্ট্রিম done দিয়ে শেষ হয়, সীমাবদ্ধ প্রশ্ন বিরত থাকার বাক্য পায়, আর টোকেন না থাকলে কোনো কাজের আগেই ৪০১। প্রতিটা জোড়ার টেস্ট:
python -m pytest tests/test_knowledgeops_definition.py tests/test_knowledgeops_contracts.py tests/test_knowledgeops_system.py \
tests/test_knowledgeops_gate.py tests/test_knowledgeops_app.py tests/test_knowledgeops_rc.py 2>&1 | tail -1 | sed 's/ in [0-9.]*s//'86 passedযে জোড়া মেলেনি, আর যা ইন্টিগ্রেট হয়নি
| কোথায় | অমিল | কী করেছি |
|---|---|---|
DecisionRecord | শুধু ফাইন-টিউনিংয়ের বিকল্প মানে | নিজের problems() সহ সাবক্লাস |
create_stream_app | Beginner-এর /ask নিয়ে আসে, আর রেডিনেসের জন্য রিট্রিভার চায় | /ready-র জন্য শুধু-দৈর্ঘ্যের একটা ভিউ; কলের পরে রাউটার থেকে রুটটা সরানো |
Upstream.start | টেন্যান্ট বা গ্রুপ নয়, একটা টোকেনের নাম পায় | একটা ডিরেক্টরি নামকে প্রিন্সিপালে ম্যাপ করে |
observe মেট্রিক | শুধু answered/refused/blocked/failed স্ট্যাটাস | abstained ও clarify refused-এ ম্যাপ করে; পুরো ফলাফল স্প্যান অ্যাট্রিবিউটে থাকে |
threat.egress.check_output | Beginner-এর উত্তরের আকার আশা করে | চেকের আগে উত্তরটা আবার মুড়ে দেওয়া |
ingest.access.Hit | শুধু স্নিপেট, ১৬০ অক্ষর | নলেজ বেস থেকে পুরো চাঙ্ক পড়া (এর রেকর্ডে হাত দেওয়া) |
observe.pipeline.Desk, দামের টেবিল | Beginner রিট্রিভারের জন্য বানানো; শুধু নকল মডেলের নাম জানে | স্প্যান আবার লেখা (পুনরাবৃত্তি); Relabel |
evalkit.ci স্যুট | এর বেসলাইন Beginner কর্পাসের, আর নিজের বেসলাইনেই block করে | এই গেটের অংশ করা হয়নি |
requirements ফাইলে কোনো নির্ভরতা যোগ হয়নি: প্যাকেজের শুধু কিট যা ইনস্টল করে তাই লাগে, আর anyio FastAPI-এর সঙ্গেই আসে।
ইন্টিগ্রেট হয়নি: MCP সার্ভার (টুল বক্সই চুক্তি; তার ওয়্যার ব্যবহার হয়নি), এজেন্ট লুপ, রিরাংকিং (কড়া মডেলের ভুলগুলো যেখানে এটা মাপা হতো), রিট্রিভাল ক্যাশ, ট্রেস এক্সপোর্ট, জাজ, আসল প্রোভাইডার অ্যাডাপ্টার আর আসল কন্টেইনার রান। স্ট্রিমিং একটা শেষ-হওয়া উত্তর থেকে টুকরো কাটে; এটা মডেল থেকে টোকেন স্ট্রিমিং নয়।
কাজের জায়গায় এটা কোথায় দেখা যায়
প্রথম রিভিউ মডেলের আগেই জিজ্ঞেস করে "এটা কীসের জন্য, আর উত্তর দিতে না পারলে কী হয়"। ইউজ কেস, গ্রহণযোগ্যতার ফাইল, ডিসিশন রেকর্ড আর একটা গেট কমান্ড রিভিউয়ারকে দেওয়ার জিনিস, আর এগুলোই দ্বিতীয় ইঞ্জিনিয়ারকে একটা অংশ বদলে এক মিনিটের মধ্যে দেখতে দেয় পুরোটা ঠিক আছে কি না।
সাধারণ ভুল
- প্রথম রানের পরে মানদণ্ড লেখা। তখন সীমাগুলো সিস্টেমকেই বর্ণনা করে। প্রয়োজন থেকে লিখুন, বেসলাইন রেকর্ড করুন, আর অনুপস্থিত মেজারকে ব্যর্থ ধরুন।
- অনুরোধ থেকে পরিচয় নেওয়া। টেন্যান্ট আর গ্রুপ আসে লগইন থেকে, কোডে ম্যাপ করা। একই নামের বডি ফিল্ড একটা আক্রমণের সারফেস।
- ফিল্টার তার নিজের বাগ জানাবে ধরে নেওয়া। ফিল্টার ব্যবহার করে না এমন স্ক্যান দিয়ে উত্তর যাচাই করুন, আর লিক কেসকে ক্রিটিক্যাল চিহ্নিত করুন।
- নিরাপত্তার চেকের আগে মানের চেক। তখন লিক-করা উত্তর "অসমর্থিত" হিসেবে দেখা দেয়। আউটপুট চেক আগে চালান।
- রিলিজের জন্য একটাই গড়। প্রতিটা অংশ ও প্রতিটা মানদণ্ড আলাদা রিপোর্ট করুন; ভালো গড় একটা ব্যর্থতা লুকিয়ে ফেলে।
নিজে চেষ্টা করুন
- সহজ (রুব্রিক, রিগ্রেশন ও রিলিজ গেট ফিরে দেখে): পুরোনো স্ন্যাপশট দিয়ে সিস্টেম বানিয়ে গেট চালান। অনুমান করুন কোন অংশ এমন কারণে block করে যার সঙ্গে মডেলের সম্পর্ক নেই।
- মাঝারি (বাজেট, ক্যাশিং ও ব্যবহারের সীমা ফিরে দেখে): এ পর্যন্ত পরিবেশিত অনুরোধগুলো এরর কোড ধরে গ্রুপ করুন এবং গড় কাল্পনিক খরচ তুলনা করুন। অনুমান করুন কোন বিরত থাকাগুলো বিনা খরচের।
- কঠিন (অনুশীলনী, প্রয়োগে): বিরত-থাকার সীমা ০.৩ ও ০.৫ দিয়ে রিলিজ ক্যান্ডিডেট চালিয়ে ব্যর্থ মানদণ্ড তুলনা করুন। অনুমান করুন শিথিল মডেল বেশি পাস করে না কম, তারপর বলুন কোন সেটিং পাঠাবেন এবং কেন।
উত্তর
সহজ। রিট্রিভালের অংশ: পুরোনো স্ন্যাপশটে বাংলা রিটার্ন পাতা এখনো ১৪ দিনে আছে, তাই পুরোনো-মান চেক আর একটা ক্রিটিক্যাল কেস ব্যর্থ হয়, মডেল যা-ই করুক।
from evidencedesk.knowledgeops.system import Config
stale = run_gate(build_system(config=Config(snapshot="v2")))
print({p.name: p.decision.verdict for p in stale.parts})
print([r.text for r in stale.parts[1].decision.reasons if r.level == "block"]){'answers': 'block', 'retrieval': 'block', 'security': 'go'}
['case r16 failed', 'evidence_at_3 0.812 (13/16) is below the floor 0.9', 'stale_free 0.667 (2/3) is below the floor 1.0']পাস চেকলিস্ট: রিট্রিভাল block, নিরাপত্তা go, আর কারণগুলো একটা পুরোনো মানের নাম বলে, মডেলের ব্যর্থতার নয়।
মাঝারি। রিট্রিভালের বিরত থাকা কখনো মডেল ডাকে না, তাই বিনা খরচ; মডেলের বিরত থাকা টোকেন খরচ করেছে, উত্তর-দেওয়া অনুরোধের প্রায় তিন-চতুর্থাংশ।
from collections import defaultdict
cost = defaultdict(list)
for o in system.observations:
cost[o.code].append(o.cost)
for code, rows in sorted(cost.items()):
print(f"{code:<16}requests {len(rows):<3}mean invented cost {sum(rows) / len(rows):.6f}")clarify requests 1 mean invented cost 0.000000
low_coverage requests 6 mean invented cost 0.000000
model_abstained requests 3 mean invented cost 0.000640
ok requests 13 mean invented cost 0.000826পাস চেকলিস্ট: low_coverage আর clarify ঠিক শূন্য; model_abstained নয়; আপনি স্ট্যাটাস ধরে নয়, কোড ধরে গ্রুপ করেছেন (মেট্রিকে দুটোই "refused")।
কঠিন। শিথিল মডেল বেশি মানদণ্ডে ব্যর্থ হয়: F1 ও F4-এর সঙ্গে F2-ও যোগ হয়, কারণ নথিতে নেই এমন প্রশ্নেও সে উত্তর দেয়।
from evidencedesk.knowledgeops import rc
from evidencedesk.knowledgeops.system import Config
for share in (0.3, 0.5):
steps, verdicts, gate = rc.run_rc(Config(min_share=share))
print(share, "failing:", [v.id for v in verdicts if not v.ok], "| gate", gate.verdict)0.3 failing: ['F1', 'F2', 'F4'] | gate block
0.5 failing: ['F1', 'F4'] | gate blockপাস চেকলিস্ট: চালানোর আগে অনুমান করেছেন; F2-কে নতুন ব্যর্থতা বলেছেন; ০.৫ পাঠাচ্ছেন, কারণ উত্তরহীন প্রশ্নে ভুল উত্তর বাড়তি বিরত থাকার চেয়ে খারাপ; আর লিখেছেন কোনো সেটিংই F1 পূরণ করে না, তাই আসল সমাধান ভালো রিট্রিভাল বা শক্তিশালী মডেল, সীমা নয়।
সারসংক্ষেপ
- কোডের আগে ইউজ কেস, ব্যর্থতার সীমা আর গ্রহণযোগ্যতার মানদণ্ড ডেটা হিসেবে ঠিক করুন; অনুপস্থিত মেজার ব্যর্থ।
- কোনো প্রয়োজন এজেন্ট না চাইলে RAG বাছুন, এমন ডিসিশন রেকর্ডে যা বলে কখন নতুন করে দেখতে হবে।
- চলমান সিস্টেম থেকে আর্কিটেকচার ও বিশ্বাসের সীমানা বানান, আর থ্রেট মডেল যা ধরেনি সেই সারফেসগুলোর তালিকা করুন।
- পরিচয় অনুরোধের বাইরে রাখুন, প্রোভাইডারকে একটা ইন্টারফেসের পেছনে নকল ডিফল্টসহ রাখুন, আর নিরাপত্তার চেক মানের চেকের আগে রাখুন।
- একটা কম্পোজিশন রুট, উত্তর, রিট্রিভাল ও নিরাপত্তার ওপর একটা গেট, প্রতি অনুরোধে একটা ট্রেস; সব অফলাইনে চালান এবং ব্যর্থতাসহ টেবিলটা পড়ুন।
এরপর: রিলিজের প্রমাণ ও প্রোডাকশন প্রস্তুতি। পরের পাতা এই রিলিজ ক্যান্ডিডেটকে চাপে ফেলে, এর ভার্সন ও সীমা রেকর্ড করে, থ্রেট মডেল, রানবুক ও রোলব্যাক পরিকল্পনা লেখে, এবং শেষ হয় একটা স্পষ্ট go বা no-go দিয়ে।