ในบทความนี้
In this post
ตอนที่ 3 จากสิบตอน: ประกอบ Choice และ Noul ใน request เดียว ตรวจฟิลด์ตามเอกสาร ตรึงรุ่นโมเดล และอ่าน response โดยไม่เดา API
ฉบับร่างสำหรับตรวจในเครื่อง • โค้ดเริ่มต้นใช้ offline fixtures เท่านั้น ตัวเลขเป็นสถานการณ์สมมติ ไม่ใช่ผลวัด Jev จริง และยังไม่มีการพิสูจน์ว่าระบบเร็วขึ้นหรือประหยัดลง
เป้าหมายและสิ่งที่ต้องพร้อม
แนะนำให้เริ่มจาก companion repo และคู่มือ README บน GitHub ด้านล่าง ส่วน ZIP ของเว็บไซต์เป็นสำเนาออฟไลน์อีกทางหนึ่ง คำสั่ง Node ใช้เหมือนกันเมื่อรันจากรากโครงการ GitHub repository · README / Quickstart
ใช้โครงการ Support Ticket Decision Lab เดิมตลอดสิบตอน เตรียม Node 22.18 ขึ้นไป terminal และโฟลเดอร์ที่เขียนไฟล์ได้ รันคำสั่งจากราก repo ai-core-jev-tutorial หรือจากไดเรกทอรี ai-core-jev-lab หากใช้ ZIP ของเว็บไซต์ ไม่ต้อง npm install สำหรับออฟไลน์ เผื่อเวลาลงมือและทดลองเปลี่ยนโค้ดประมาณ 20–40 นาที โดยเวลาที่แสดงบนหัวบทความเป็นเวลาอ่านเนื้อหา ไม่รวมเวลาลองทำ
ดาวน์โหลดโครงการพร้อมโค้ดและ tests (ZIP) · อ่านคู่มือโครงการ
ตอนนี้เน้นไฟล์ src/providers.mjs และใช้ผลจาก fixture เพื่อทดสอบการประกอบระบบเท่านั้น ยังไม่ได้ติดตั้ง SDK หรือใช้ key ในการจัดทำบทเรียน เส้นทาง live ที่แยกไว้ในตอนท้ายยังไม่ได้ทดสอบผ่าน API จริง
ทำความเข้าใจขอบเขตนี้
เลือกคำตอบที่เล็กพอกับสิ่งที่โค้ดต้องใช้ การส่งคิวต้องการ Choice จากชุดปิด ไม่ต้องการย่อหน้าอธิบายแผนก การตรวจคำขอคุยกับคนต้องการความน่าจะเป็นแบบ Noul ไม่ต้องการประโยค yes/no ที่สร้างขึ้น คำถามสองข้อเห็น ticket เดียวกันแต่ประเมินอย่างอิสระ การรวมใน request เดียวลดการส่ง state ซ้ำในสถาปัตยกรรมนี้ แต่ยังไม่พิสูจน์ว่าระบบของคุณจะเร็วหรือถูกลง
ชื่อคำถามเป็น handle ให้แอปใช้อ้างอิง จึงต้องเขียน instructions ให้ครบ ไม่หวังให้ชื่อ route หรือ wantsHuman สอนงานโมเดลเอง criteria ควรแยกใบแจ้งหนี้และการชำระเงินออกจากซอฟต์แวร์เสีย มี other ไว้ด้วย แต่ยังต้องทดสอบข้อความกำกวมและนอกขอบเขต เพราะการเพิ่ม label นี้ไม่ได้รับประกันว่าจะปฏิเสธได้ดี
ผลแบบมีชนิดข้อมูลเป็นข้อเสนอที่เครื่องอ่านได้ ไม่ใช่ใบรับรองความจริง adapter อ่านฟิลด์ Choice กับค่า Noul ตามเอกสารโดยไม่สร้าง confidence ของ Noul เพิ่มเอง ตรึง jev-1.13.0 เพื่อให้การประเมินตั้งต้นอ้างโมเดลรุ่นเดียว และบันทึก model ที่ตอบกลับด้วย เพราะ alias ไม่ใช่ขอบเขตที่ทำให้ผลทำซ้ำได้
ลงมือทีละเจ็ดขั้น
ขั้นที่ 1
ตรวจ requestShape ซึ่งใส่เพียงข้อความ ticket ลงใน state helper นี้แยกจาก SDK เพื่อให้ทดสอบการประกอบ request แบบออฟไลน์ และดูข้อมูลที่จะส่งได้ครบโดยไม่โหลดแพ็กเกจหรือเรียก API ที่ยืนยันตัวตน
ขั้นที่ 2
เขียนเกณฑ์ route ให้แยกกัน อ่านคำอธิบายแต่ละข้อแล้วถามว่าเพื่อนร่วมงานทั่วไปจะใช้ได้ตรงกันไหม ถ้า billing กับ technical ทับซ้อน ให้ทำ policy ให้ชัดหรือแยกคำถามเพิ่ม ไม่ซ่อนความไม่ตรงกันทางธุรกิจไว้ใน threshold
ขั้นที่ 3
รักษาคำถามเรื่องคนให้เป็นข้อเดียวที่ชัด มันถามว่าลูกค้าร้องขอมนุษย์อย่างชัดแจ้งหรือไม่ ไม่ใช่ถามว่าโมเดลคิดว่าควรใช้คนไหม การแยกความหมายทำให้ evaluator มีป้ายที่ติด ตรวจ และโต้แย้งได้จริง
ขั้นที่ 4
พิมพ์ request ของ T-004 เทียบกับ T-001 ทั้งคู่เกี่ยวกับ billing แต่มีเพียงหนึ่งรายการที่ขอคุยกับคน ใน fixture ความต่างนี้เปลี่ยน wantsHuman.noul แล้วโปรแกรมของเราเป็นผู้รวมฟิลด์เข้ากับนโยบาย ไม่ใช่ประโยคที่โมเดลสร้างขึ้น
ขั้นที่ 5
ไล่เส้นทาง import สำหรับ live โดยยังไม่รัน providers.mjs จะ import choice, noul และ TypeSafeClient หลังตรวจ flag live และ API key เท่านั้น ส่วนอื่นยังใช้ได้โดยไม่มี node_modules adapter live เป็นโค้ดตามเอกสารที่ยังไม่ได้ยืนยันด้วยการเรียกจริง
ขั้นที่ 6
อ่าน response fixture จริง Choice มี choice, probabilities และ confidence ส่วน Noul มี type และ noul อย่าอ่าน questions.route หรือ route ที่ระดับบนสุดแทนคำตอบ เพราะเป็นคนละตำแหน่ง การยอมรับแบบไหนก็ได้เงียบ ๆ อาจซ่อนรุ่นข้อมูลที่ไม่ตรงกัน
ขั้นที่ 7
รัน contract tests แล้วแก้ criterion หนึ่งข้อในสำเนาทดลองและพิมพ์ request ใหม่ คำทำนายแบบออฟไลน์จะไม่เปลี่ยนตาม criteria เพราะมันเป็น fixture ที่เตรียมไว้ ข้อจำกัดนี้ตั้งใจให้เห็น และเป็นเหตุผลสำคัญที่ไม่เรียกการทดลองนี้ว่า benchmark โมเดลจริง
ไฟล์และคำสั่งที่ใช้
โค้ดประกอบบน GitHub: ai-core-jev-tutorial · README. ใช้ repo เป็นทางหลักในการทำแบบฝึก ไฟล์ต่อไปนี้ตรึงกับรุ่นที่ทดสอบแล้ว src/providers.mjs · src/questions.mjs
เปิดไฟล์นี้เทียบกับ source ใน ZIP ก่อนแก้ ข้อความต่อไปนี้คือโค้ดที่ใช้ในโครงการจริง ไม่ใช่ pseudocode ผลโมเดลเป็น input ให้โค้ดตรวจและวางเส้นทางต่อ ไม่ใช่สิทธิ์ให้ข้ามด่านอนุมัติ
import {readFile} from 'node:fs/promises';
import {performance} from 'node:perf_hooks';
import {requestShape,ROUTES,ROUTE_QUESTION,HUMAN_QUESTION} from './questions.mjs';
import {tokenUsage} from './contracts.mjs';
export async function fixtureProviders() {
const fixtures=JSON.parse(await readFile(new URL('../data/predictions.mock.json',import.meta.url),'utf8'));
async function call(kind,ticket) {
const f=fixtures[ticket.id]?.[kind];if(!f)throw new Error('fixture not found');
return {value:structuredClone(f.value),attempt:{provider:kind,mode:'MOCK / FICTIONAL SCENARIO',usage:f.usage,scenarioLatencyMs:f.scenarioLatencyMs,wallMs:null,httpAttempts:1,costComplete:true}};
}
return {jev:t=>call('jev',t),baseline:t=>call('baseline',t)};
}
export async function liveJev(ticket,{live=false}={}) {
if(!live)throw new Error('Live API requires explicit --live');
if(!process.env.TYPESAFE_API_KEY)throw new Error('TYPESAFE_API_KEY is not set');
// Offline mode never resolves the SDK or reads a secret.
const {choice,noul,TypeSafeClient}=await import('@typesafe-ai/sdk');
const client=new TypeSafeClient({defaultModel:'jev-1.13.0'});
const started=performance.now();
const value=await client.systemOne({state:requestShape(ticket).state,questions:{route:choice(ROUTE_QUESTION,{...ROUTES}),wantsHuman:noul(HUMAN_QUESTION)}});
return {value,attempt:{provider:'jev',mode:'LIVE / NOT VERIFIED BY THE AUTHOR',usage:tokenUsage(value),wallMs:performance.now()-started,scenarioLatencyMs:null,httpAttempts:null,costComplete:false}};
// SDK retries may occur internally. No outer retry loop is added. Final
// response usage alone cannot establish the total bill across failed attempts.
}
คำสั่งออฟไลน์ต่อไปนี้รันได้โดยไม่ใช้ SDK หรือ API key:
node src/cli.mjs request --ticket T-001
node src/cli.mjs request --ticket T-004
node src/cli.mjs demo --mode jev --ticket T-004
node --test --test-reporter=spec test/contracts.test.mjs
ตรวจผลที่ควรได้
T-004 ควรจบที่ needs_review แม้ route จะเป็น billing ตรวจว่า fixture ไม่อ้าง confidence แยกของ Noul และการพิมพ์ request ไม่ต้องมี key ถ้าต้องการคำตอบหลังเปลี่ยน criteria ต้องใช้ provider จริง mock นี้ตั้งใจตอบคำถามวิจัยนั้นไม่ได้
ตรวจชื่อ test และเส้นทางปฏิเสธด้วย อย่าดูเพียง exit code หรือ label สุดท้าย ข้อมูลที่ไม่ทราบต้องยังระบุว่าไม่ทราบ และรายงานทุกชิ้นต้องแยกผลจำลองออกจากข้อมูลที่วัดจริง
แก้ปัญหาเมื่อผลไม่ตรง
ถ้าต้องใช้คำตอบก่อนหน้าเพื่อประกอบ state ของคำถามถัดไป การใส่ทั้งสองคำถามใน object เดียวไม่ได้ทำให้มันเป็นอิสระ ให้แยกการเรียกและนับต้นทุนทั้งคู่ ถ้ายังใช้แพ็กเกจ live ไม่ได้ ให้ทำออฟไลน์ต่อ ไม่แทนชื่อ helper ตามเอกสารด้วย API ที่เดาเอง
หากเปิดบทเรียนผ่าน Python http.server ให้ใช้ลิงก์ .html ที่ท้ายตอนเมื่อจำเป็น ลิงก์ chip แบบไม่มีนามสกุลในแถบซีรีส์อาศัยการ resolve ของ GitHub Pages และเป็นข้อจำกัด preview ที่มีอยู่เดิม ไม่ใช่ปัญหาในโค้ด Node
เปลี่ยนหนึ่งอย่างแล้วทดสอบ
แก้ criterion ของ billing เพื่อทำขอบเขตที่คุณไม่เห็นด้วยให้ชัด แล้วพิมพ์ requestShape เพื่อตรวจ diff fixture จะยังตอบเหมือนเดิมเพราะเตรียมคำตอบไว้ ให้เขียนแผนทดลองโมเดลจริงสำหรับ criterion ใหม่ ระบุชุด ticket ที่ตรึงและเกณฑ์ความสำเร็จเดิม
ทำการแก้ไขในสำเนาทดลอง เก็บไฟล์ต้นฉบับไว้เทียบ แล้วคืนค่าก่อนเริ่มตอนถัดไป หากทดลองกับ provider จริงภายหลัง ให้สร้าง experiment record ใหม่แทนเขียนทับผลเดิม
จุดตรวจถัดไป
ก่อนขยับต่อ ให้บอกได้ว่าไฟล์ใดเป็น contract ไฟล์ใดเป็น policy หลักฐานใดได้จากการรัน และข้อใดยังไม่ได้ทดสอบกับโมเดลจริง การแยกสี่อย่างนี้เป็นงานวิศวกรรมที่สำคัญกว่าการได้เดโมที่ดูไหลลื่น
แหล่งอ้างอิงและหลักฐาน
เอกสารและ source ที่ใช้
ตรวจเอกสาร Jev วันที่ 23 ตุลาคม 2026 ข้อเท็จจริง API และราคาอาจเปลี่ยน ให้ตรวจใหม่ก่อนใช้ live ส่วนผลของ fixture เป็นข้อมูลสอนที่ระบุไว้ในโครงการ
สรุปที่นำไปใช้ต่อ
- contract จำกัดโครงสร้าง ส่วน evaluation ตรวจพฤติกรรม
- อ่าน coverage คุณภาพ เวลา และต้นทุนรวมควบคู่กัน
- ข้อเสนอไม่ใช่ผลที่อนุมัติแล้ว และหลักฐาน mock ไม่ใช่หลักฐานโมเดล live
Part 3 of ten: Build a single request with Choice and Noul, pin the model, and read documented response fields without inventing an API.
Local review draft • Default code uses offline fixtures. Numbers are fictional scenarios, not measured Jev results. Faster performance and lower cost remain hypotheses.
Outcome and prerequisites
Start with the companion repository and its detailed README. The website ZIP remains an alternative offline snapshot; run the same Node commands from either project root. GitHub repository · README / Quickstart
Use the same Support Ticket Decision Lab throughout the series. Have Node 22.18+, a terminal and a writable directory. Run commands from the cloned ai-core-jev-tutorial root, or the extracted ai-core-jev-lab folder if using the website ZIP. Offline work needs no npm install. Allow roughly 20–40 minutes for experiments; the article reading-time label excludes hands-on work.
Download the complete code and tests (ZIP) · Project README
This chapter focuses on src/providers.mjs. Fixture outputs test the integration mechanics only. The author did not install the SDK or use an API key while preparing the lab. The separately documented live path has not been tested against the API.
How this boundary works
Choose the smallest output your code needs. Routing needs a closed Choice, not a paragraph explaining a department. A human-request check needs a Noul probability, not a generated yes/no sentence. The two questions see the same ticket independently. Combining them in one request reduces duplicated state transmission in this architecture; it does not prove a latency or cost benefit for your workload.
The question ID is an application handle, so make the instructions complete rather than relying on the name route or wantsHuman to teach the model the task. Criteria should distinguish invoices and payments from software failures. Keep an other option, but evaluate ambiguous and out-of-domain tickets: adding that label does not guarantee graceful rejection.
A typed result is a machine-readable proposal, not a certificate of truth. The adapter returns Choice fields and the Noul value exactly as documented. It must not invent a separate Noul confidence. Pin jev-1.13.0 so the initial evaluation targets one model version; log the returned model too, since an alias is not a reproducibility boundary.
Seven practical steps
Step 1 — Inspect requestShape
Inspect requestShape. Its state contains only the ticket text. The helper exists separately from the SDK so you can test request construction offline and inspect every byte that would be sent, without resolving the package or making an authenticated call.
Step 2 — Write distinct route criteria
Write distinct route criteria. Read each description and ask whether an ordinary colleague could use it consistently. If billing and technical overlap, clarify the policy or add a separate decision; do not hide a business-policy disagreement inside a confidence threshold.
Step 3 — Keep the human-request question atomic
Keep the human-request question atomic. It asks whether the customer explicitly wants a human, not whether the model thinks a person would be useful. Distinguishing these meanings gives the evaluator a label it can actually annotate, review and contest.
Step 4 — Print T-004 and compare it with T-001
Print T-004 and compare it with T-001. Both concern billing, but only one explicitly requests a human. In the fixtures that distinction changes wantsHuman.noul. Your program, not a generated sentence, combines the fields into a routing policy.
Step 5 — Trace the live-only import without executing it
Trace the live-only import without executing it. providers.mjs imports choice, noul and TypeSafeClient only after the explicit live flag and an API-key check. The rest of the project stays usable without node_modules. The live adapter is documented code, not a verified live integration.
Step 6 — Read an actual fixture response
Read an actual fixture response. Choice contains choice, probabilities and confidence; Noul contains type and noul. Do not use questions.route or a top-level route as an answer. Those are different locations and accepting either silently can hide a version mismatch.
Step 7 — Run the contract tests
Run the contract tests. Then rewrite one criterion in a disposable copy and print the request again. Offline canned predictions will not change when criteria change; that is an intentional limit of the mock, and an important reason not to call this experiment a live model benchmark.
Files and commands
Companion code on GitHub: ai-core-jev-tutorial · README. Use the companion repository as the default working copy. These file links pin the tested revision. src/providers.mjs · src/questions.mjs
Compare this file with the source in the ZIP before editing. The listing is the implementation used by the lab, not pseudocode. Model output is input to validation and routing code; it grants no permission to bypass approval.
import {readFile} from 'node:fs/promises';
import {performance} from 'node:perf_hooks';
import {requestShape,ROUTES,ROUTE_QUESTION,HUMAN_QUESTION} from './questions.mjs';
import {tokenUsage} from './contracts.mjs';
export async function fixtureProviders() {
const fixtures=JSON.parse(await readFile(new URL('../data/predictions.mock.json',import.meta.url),'utf8'));
async function call(kind,ticket) {
const f=fixtures[ticket.id]?.[kind];if(!f)throw new Error('fixture not found');
return {value:structuredClone(f.value),attempt:{provider:kind,mode:'MOCK / FICTIONAL SCENARIO',usage:f.usage,scenarioLatencyMs:f.scenarioLatencyMs,wallMs:null,httpAttempts:1,costComplete:true}};
}
return {jev:t=>call('jev',t),baseline:t=>call('baseline',t)};
}
export async function liveJev(ticket,{live=false}={}) {
if(!live)throw new Error('Live API requires explicit --live');
if(!process.env.TYPESAFE_API_KEY)throw new Error('TYPESAFE_API_KEY is not set');
// Offline mode never resolves the SDK or reads a secret.
const {choice,noul,TypeSafeClient}=await import('@typesafe-ai/sdk');
const client=new TypeSafeClient({defaultModel:'jev-1.13.0'});
const started=performance.now();
const value=await client.systemOne({state:requestShape(ticket).state,questions:{route:choice(ROUTE_QUESTION,{...ROUTES}),wantsHuman:noul(HUMAN_QUESTION)}});
return {value,attempt:{provider:'jev',mode:'LIVE / NOT VERIFIED BY THE AUTHOR',usage:tokenUsage(value),wallMs:performance.now()-started,scenarioLatencyMs:null,httpAttempts:null,costComplete:false}};
// SDK retries may occur internally. No outer retry loop is added. Final
// response usage alone cannot establish the total bill across failed attempts.
}
Run these offline commands without an SDK installation or API key:
node src/cli.mjs request --ticket T-001
node src/cli.mjs request --ticket T-004
node src/cli.mjs demo --mode jev --ticket T-004
node --test --test-reporter=spec test/contracts.test.mjs
Verify the expected behavior
T-004 should end in needs_review even though its route is billing. Confirm that no fixture claims a separate Noul confidence and that request printing does not require a key. A response to changed criteria requires a real provider; our mock deliberately cannot answer that research question.
Read the named tests and refusal paths, not just the exit code or final label. Unknown values must remain unknown, and every report must distinguish simulation inputs from real measurements.
Troubleshooting
If a request depends on an earlier answer to construct its state, it cannot be made independent by putting both questions into one object. Split the calls and count both. If the live package is unavailable, keep working offline; do not replace documented helper names with guessed APIs.
When previewing with Python http.server, use the .html continuation links below when needed. Extensionless series chips rely on GitHub Pages resolution; that documented preview limitation is separate from the Node example.
Change one thing, then test
Rewrite the billing criterion to clarify a boundary you disagree with, then print requestShape and review the change. The fixture response will remain unchanged because it is canned. Record the real model experiment you would need to test the revised criterion, including the frozen ticket set and unchanged success rule.
Make changes in a disposable copy, retain the reference files for comparison, and restore them before the next chapter. If you later use a real provider, create a new experiment record rather than overwriting the fixture transcript.
Next checkpoint
Before moving on, identify the contract file, the policy file, what was observed in a run, and what remains untested against a real model. Keeping those four things separate matters more than making the demo look effortless.
← Previous chapter · Next chapter →
References and evidence
Documentation and implementation
Jev documentation checked on 23 October 2026. API and pricing details can change; recheck before live use. Fixture results are explicitly authored teaching data in the project.
Key takeaways
- Contracts bound structure; evaluation checks behavior.
- Coverage, quality, latency and total cost must be read together.
- A proposal is not an approved effect; mock evidence is not live model evidence.