ในบทความนี้
In this post
ตอนที่ 2 จากสิบสอง: เขียน contract ของ input คำตอบโมเดล และ gate ตรวจค่าที่หาย ชนิดผิด และ probability ก่อนเชื่อข้อมูล
คำตัดสินและร่างข้อความเริ่มต้นเป็น mock ที่ผู้เขียนกำหนด ไม่ใช่ผล Jev จริง อ่านบทเรียนคู่กับ source, tests และแบบฝึกที่ลิงก์ไปยังรุ่นตรึงใน GitHub
เป้าหมายและสิ่งที่ต้องพร้อม
เตรียม Node22.18 ขึ้นไปกับ terminal clone companion จาก GitHub ตาม README หรือดาวน์โหลด ZIP ฉบับสมบูรณ์ แตกแล้วเข้า ai-core-functional-lab ไม่ต้อง npm install ใช้ API key หรือเรียกโมเดลผ่านเครือข่าย เวลาอ่านไม่รวมการลงมือประมาณ25–45นาที เก็บชุดอ้างอิงหนึ่งชุดและทำแบบฝึกในอีกสำเนา
ดาวน์โหลด companion ZIP · README / แผนบทเรียน
ตอนนี้เน้น src/contracts.mjs โดย gate state บอกผลต่อชิ้นงานและทางไปต่อ ไม่มี state ใดส่งข้อความหรือแก้บัญชีจริง
จากโค้ดเดิมสู่ขอบเขตใหม่
signature บอกว่าสิ่งใดข้ามขอบเขตได้ แต่ type ใน TypeScript ไม่ได้ตรวจ bytes ที่บริการภายนอกส่งกลับด้วยตัวมันเอง runtime checks ใน JavaScript จึงเปลี่ยนความคาดหวังให้เป็นพฤติกรรมปฏิเสธที่รันได้
ผลลัพธ์มีสี่ state คือ accept, reject, revise และ human_review การ revise ขอร่างใหม่ภายในงบ ส่วน review หยุดการดำเนินอัตโนมัติ ทั้งสองไม่ใช่ชื่ออื่นของ parse ไม่ผ่าน และ accept ไม่ใช่สิทธิ์ส่งข้อความ
contract ตรึง jev-1.14.0 กับ answer IDs ที่ตั้งไว้ Choice มี label ชุดปิด distribution และ confidence ที่คำนวณจากการแจกแจง ส่วน Noul เป็นความน่าจะเป็นของ yes ไม่มี confidence แยก ค่าที่ขาดหรือผิดต้องไป review ไม่ใช่เดาค่าเริ่มต้น
ลงมือทีละเจ็ดขั้น
ขั้นที่ 1
เขียนรายการ model, answers, purpose และ injection ก่อนอ่าน parser แล้วเทียบกับ request ที่พิมพ์ออกมา question IDs เป็นตัวกำหนด answer IDs ที่ caller รอ
ขั้นที่ 2
ไล่ probability แล้วดู test ของ NaN, Infinity, string และค่าที่ออกนอกช่วง JavaScript รับค่าได้ไม่ได้แปลว่าเป็น probability ที่ใช้ได้ โค้ดต้องตรวจชนิด ช่วง และความเป็น finite
ขั้นที่ 3
ดู allowlist ของ Choice และบังคับ probability ครบสามตัว ตัวที่เลือกต้องเป็นค่าสูงสุดและผลรวมต้องใกล้หนึ่งตาม tolerance การเห็น label ดูสมเหตุผลอย่างเดียวไม่พอ
ขั้นที่ 4
เทียบ confidence กับสถิติการแจกแจงตามเอกสาร ด่านนี้จับ fixture หรือ adapter ที่ไม่สอดคล้อง ไม่ได้พิสูจน์ความหมายคำตอบ ต้องแยกความถูก contract กับความถูกในการจำแนก
ขั้นที่ 5
ลบ injection จาก response ใน test แล้วให้ปฏิเสธ ห้ามแปลง Noul ที่หายเป็นศูนย์ เพราะเท่ากับตีความคำตอบที่ไม่มีว่าเป็น no อย่างมั่นใจ
ขั้นที่ 6
รัน judgment ของ V005 รูปแบบข้อมูลถูก แต่ confidence ต่ำกว่าขอบเขต policy ตัวอย่าง จึงต้อง human_review การผ่าน validation ไม่ได้สัญญาว่าจะ accept
ขั้นที่ 7
อ่าน helper ของ gate state โดยไม่เพิ่ม execute การไม่ปนการกระทำภายนอกในชนิดผลลัพธ์ทำให้ประกอบและตรวจฟังก์ชันง่ายขึ้น ทดสอบ state ที่ไม่รู้จักด้วย
ไฟล์และคำสั่งที่รันได้
เปิด source จริงและอ่าน dependencies ก่อนแก้ นี่คือโค้ดอ้างอิงที่รันได้ ไม่ใช่ SDK ชื่อ AI-Core ที่สมมติขึ้น ลิงก์ชี้ source รุ่นตรึงของ companion สาธารณะใน GitHub
src/contracts.mjs · tests ของตอนนี้ · แบบฝึกตอนนี้
/** @typedef {'accept'|'reject'|'revise'|'human_review'} GateState */
/** Gate outputs describe artifacts, never permission to execute an external effect. */
export function gate(state, reasons=[], value=null) {
if(!['accept','reject','revise','human_review'].includes(state)) throw new Error('unknown gate state');
return {state,reasons,value};
}
export function plain(value) {
return value!==null && typeof value==='object' && !Array.isArray(value)
&& [Object.prototype,null].includes(Object.getPrototypeOf(value));
}
export function probability(value,name='probability') {
if(typeof value!=='number'||!Number.isFinite(value)||value<0||value>1)
throw new Error(`invalid ${name}`);
return value;
}
export const MODEL='jev-1.14.0';
export const PURPOSES=['how_to','account_action','other'];
export function parseDecision(stage,response) {
if(!plain(response)||response.model!==MODEL||!plain(response.answers))
throw new Error('missing answers or wrong pinned model');
const a=response.answers;
const names=stage==='input'?['purpose','injection']:stage==='output'
?['grounded','hiddenInstructions']:stage==='quality'
?['answersUser','stepsUsable','plainLanguage']:null;
if(!names||Object.keys(a).sort().join()!==[...names].sort().join())
throw new Error('missing or unexpected answer IDs');
const out={};
for(const name of names) {
const x=a[name];
if(name==='purpose') {
if(x?.type!=='choice'||!PURPOSES.includes(x.choice)||!plain(x.probabilities)
||Object.keys(x.probabilities).sort().join()!==[...PURPOSES].sort().join())
throw new Error('invalid Choice label or discriminant');
const values=PURPOSES.map(k=>probability(x.probabilities[k],k));
if(Math.abs(values.reduce((s,v)=>s+v,0)-1)>1e-5
||x.probabilities[x.choice]+1e-8<Math.max(...values)) throw new Error('invalid distribution');
const confidence=probability(x.confidence,'confidence');
if(Math.abs(confidence-(Math.max(...values)-1/3)/(1-1/3))>1e-5)
throw new Error('inconsistent Choice confidence');
out.purpose={choice:x.choice,confidence,probabilities:{...x.probabilities}};
} else {
if(x?.type!=='noul') throw new Error('wrong Noul discriminant');
out[name]=probability(x.noul,name); // Noul has no separate confidence field.
}
}
return out;
}
คำสั่งออฟไลน์:
node --test --test-reporter=spec test/contracts.test.mjs
node src/cli.mjs request --stage input --case V001
node src/cli.mjs judge --stage input --case V005
ผลที่ควรได้และการตรวจ
contract tests ผ่าน และ response ที่ตั้งใจแก้ให้เสียถูกปฏิเสธ V005 คืน human_review ไม่ใช่ reject หรือ accept ฟิลด์ model ใน fixture เป็นรุ่นที่ตรึง ตัวอย่างนี้จึงไม่รับ moving alias
อ่าน reasons, history และทุก attempt ไม่ดูเพียง label สุดท้าย usage ที่ไม่ทราบต้องยังไม่ทราบ test สีเขียวแสดงเส้นทางโค้ดที่กำหนด ไม่ได้พิสูจน์ความปลอดภัยเชิงความหมายทุกกรณี
ตัวอย่างที่ต้องเข้าใจเมื่อไม่ผ่าน
การเขียน answer.injection?.noul ?? 0 ทำให้ข้อมูลที่หายกลายเป็นหลักฐานว่าไม่มี injection test เรื่องคำตอบขาดมีไว้กันค่าเริ่มต้นที่ดูสะดวกแต่ตีความผิดนี้
แบบฝึกในสำเนาทดลอง
เพิ่ม test สำหรับ model ผิดรุ่นและ probability ติดลบ ให้ทั้งสองถูกปฏิเสธ แต่ Noul ที่ไม่มี confidence ยังผ่าน อย่าแก้แบบฝึกด้วยการเพิ่มฟิลด์ API ที่ไม่มีจริง
ทำตามไฟล์แบบฝึก เก็บต้นฉบับและคืนชุดอ้างอิงก่อนตอนถัดไป อย่าใช้ผล held-out เลือก threshold
ตรวจเมื่อผลไม่ตรง
ผลรวมคลาดอาจมาจาก floating point หรือคำตอบเสียจริง tolerance ถูกระบุชัด ตรวจค่าและจำนวน options ก่อนผ่อนกฎ และอย่าปรับผลรวมให้หนึ่งแบบเงียบ ๆ
จุดตรวจถัดไป
ก่อนต่อ ให้บอกคุณสมบัติที่โค้ดตรวจได้แน่นอน คำตัดสินที่ผิดได้ state ที่หยุดงาน และหลักฐานที่ต้องมีก่อนปล่อยรุ่น
แหล่งอ้างอิงและรุ่น
เอกสารต้นทางและ companion source
ตรวจเอกสาร TypeSafe วันที่24ตุลาคม2026 บทเรียนตรึงคำขอข้อความที่ jev-1.14.0 และใช้ Choice/Noul ไม่ได้อ้างว่ามีเพียงความสามารถเหล่านี้ โค้ด REST ทางเลือกยังไม่ได้ลอง live ส่วน SDK0.7.0 มีเอกสารแต่แล็บนี้ไม่ต้องติดตั้ง
สิ่งที่ควรนำไปใช้ต่อ
- เงื่อนไขแน่นอนและสิทธิ์ยังอยู่ในโค้ด
- ด่านความหมายและคุณภาพผิดพลาดได้
- accept, reject, revise และ review มีทางไปต่อคนละแบบ
Part 2 of twelve. Specify runtime contracts for inputs, model decisions and four gate states before interpreting any answer.
Default decisions and draft text are authored mocks, not live Jev results. Use the tutorial with the matching companion code, tests and exercise linked to a fixed GitHub version.
Outcome and prerequisites
Use Node22.18+ and a terminal. Clone the GitHub companion using its README, or download the complete ZIP, extract it and enter ai-core-functional-lab. No npm install, API key or network inference is needed. The reading-time label excludes about25–45 minutes of hands-on work. Keep one reference copy and make exercises in another.
Download companion ZIP · README / lesson map
This part focuses on src/contracts.mjs. Expected gate states describe content and continuation; no state sends a message or changes a real account.
Old functions and the new boundary
A function signature tells callers what may cross a boundary. At a model boundary, a TypeScript type alone cannot validate bytes returned by an external service. Our JavaScript runtime checks turn that expectation into executable refusal behavior.
The result union has four states: accept, reject, revise and human_review. Revise requests another bounded draft; review stops automated continuation. Neither is an alias for failure to parse, and accept is not permission to send.
The model contract pins jev-1.14.0 and named answer IDs. Choice includes a closed label set, distribution and distribution-derived confidence; Noul returns the probability of yes without a separate confidence field. Unsupported or missing values cause review rather than a guessed default.
Seven practical steps
Step 1
List the boundary fields before reading the parser: model, answers, purpose and injection. Compare that list with request output. The request question IDs determine which answer IDs the caller expects.
Step 2
Follow probability and test NaN, Infinity, a string and an out-of-range number. JavaScript accepting JSON-like values does not make them valid probabilities; finite numeric range checks belong in code.
Step 3
Inspect the Choice label allowlist and require all three probabilities. A selected label must be maximal and the distribution must sum to one within tolerance. A plausible-looking label alone is insufficient.
Step 4
Compare the returned confidence with the documented distribution statistic. That check catches an inconsistent fixture or adapter, not semantic truth. Keep the distinction between contract validity and classification correctness visible.
Step 5
Remove injection from a response in a test and require a refusal. Never convert a missing Noul into zero, because that silently interprets an absent answer as a confident no.
Step 6
Run the V005 input judgment. Its schema is valid but confidence is below the teaching policy boundary, so human_review is the correct continuation. Validation did not promise acceptance.
Step 7
Read the closed gate-state helper and add no execute state. Keeping external operations out of this return type makes the function easier to compose and review. Test unknown states explicitly.
Files and runnable commands
Open the exact source and read its dependencies before editing. This is runnable reference code, not a fictional AI-Core SDK. The source links point to a fixed version of the public GitHub companion.
src/contracts.mjs · Matching tests · This lesson’s exercise
/** @typedef {'accept'|'reject'|'revise'|'human_review'} GateState */
/** Gate outputs describe artifacts, never permission to execute an external effect. */
export function gate(state, reasons=[], value=null) {
if(!['accept','reject','revise','human_review'].includes(state)) throw new Error('unknown gate state');
return {state,reasons,value};
}
export function plain(value) {
return value!==null && typeof value==='object' && !Array.isArray(value)
&& [Object.prototype,null].includes(Object.getPrototypeOf(value));
}
export function probability(value,name='probability') {
if(typeof value!=='number'||!Number.isFinite(value)||value<0||value>1)
throw new Error(`invalid ${name}`);
return value;
}
export const MODEL='jev-1.14.0';
export const PURPOSES=['how_to','account_action','other'];
export function parseDecision(stage,response) {
if(!plain(response)||response.model!==MODEL||!plain(response.answers))
throw new Error('missing answers or wrong pinned model');
const a=response.answers;
const names=stage==='input'?['purpose','injection']:stage==='output'
?['grounded','hiddenInstructions']:stage==='quality'
?['answersUser','stepsUsable','plainLanguage']:null;
if(!names||Object.keys(a).sort().join()!==[...names].sort().join())
throw new Error('missing or unexpected answer IDs');
const out={};
for(const name of names) {
const x=a[name];
if(name==='purpose') {
if(x?.type!=='choice'||!PURPOSES.includes(x.choice)||!plain(x.probabilities)
||Object.keys(x.probabilities).sort().join()!==[...PURPOSES].sort().join())
throw new Error('invalid Choice label or discriminant');
const values=PURPOSES.map(k=>probability(x.probabilities[k],k));
if(Math.abs(values.reduce((s,v)=>s+v,0)-1)>1e-5
||x.probabilities[x.choice]+1e-8<Math.max(...values)) throw new Error('invalid distribution');
const confidence=probability(x.confidence,'confidence');
if(Math.abs(confidence-(Math.max(...values)-1/3)/(1-1/3))>1e-5)
throw new Error('inconsistent Choice confidence');
out.purpose={choice:x.choice,confidence,probabilities:{...x.probabilities}};
} else {
if(x?.type!=='noul') throw new Error('wrong Noul discriminant');
out[name]=probability(x.noul,name); // Noul has no separate confidence field.
}
}
return out;
}
Offline commands:
node --test --test-reporter=spec test/contracts.test.mjs
node src/cli.mjs request --stage input --case V001
node src/cli.mjs judge --stage input --case V005
Expected output and verification
All contract tests pass; deliberately mutated response variants throw. V005 returns human_review rather than reject or accept. The fixture model field is the pinned version, so a moving alias response is refused by this example.
Inspect reasons, history and every attempt rather than only the final label. Unknown usage must remain unknown. A green test demonstrates the specified code path, not universal semantic safety.
A failure to understand
A fallback such as answer.injection?.noul ?? 0 turns missing data into evidence for no injection. The missing-answer test exists to prevent that convenient-looking but unsafe default.
Exercise in a disposable copy
Add a contract test for a wrong model, then a negative probability. Confirm both are rejected while a valid Noul without confidence still passes. Do not fix the exercise by adding a fictional API field.
Follow the matching exercise file, retain the original and restore the reference before the next chapter. Do not use held-out results to choose a threshold.
Troubleshooting
A sum mismatch may be floating-point noise or genuinely malformed output. The current tolerance is explicit. Diagnose values and option count before relaxing it, and never repair a distribution by silently renormalizing it.
Next checkpoint
Before continuing, name the property that code checks exactly, the judgment that may be wrong, the state that stops work and the evidence needed before release.
← Previous lesson · Next lesson →
Sources and version notes
Primary documentation and companion source
Official TypeSafe docs checked2026-10-24. This curriculum pins text requests to jev-1.14.0 and uses Choice/Noul; it does not claim those exhaust Jev’s capabilities. Optional REST code is untested live. SDK0.7.0 is documented but unnecessary for this no-install lab.
Key takeaways
- Deterministic constraints and authorization stay in code.
- Semantic guards and quality judgments are fallible.
- Accept, reject, revise and review have distinct continuation rules.