AI-Core + Jev Hands-on Lab Offline First

Baseline — วาง baseline ที่ตรวจได้Establish a Measurable Baseline — AI-Core + Jev in Practice

ตรึงนิยามงานสำเร็จและ baseline รูปแบบ JSON เก็บ attempts และงานที่ยังไม่จบให้ครบ ก่อนเปรียบเทียบกับเส้นทาง JevFreeze the success definition and JSON baseline; retain attempts and unresolved work before comparing the Jev path.

By Anirach Mingkhwan AI-Core + Jev in Practice 2026 • Post #2 4 min read
Baseline — วาง baseline ที่ตรวจได้
ในบทความนี้
  1. เป้าหมายและสิ่งที่ต้องพร้อม
  2. ทำความเข้าใจขอบเขตนี้
  3. ลงมือทีละเจ็ดขั้น
  4. ไฟล์และคำสั่งที่ใช้
  5. ตรวจผลที่ควรได้
  6. แก้ปัญหาเมื่อผลไม่ตรง
  7. เปลี่ยนหนึ่งอย่างแล้วทดสอบ
  8. จุดตรวจถัดไป
  9. แหล่งอ้างอิงและหลักฐาน
  10. สรุปที่นำไปใช้ต่อ
In this post
  1. Outcome and prerequisites
  2. How this boundary works
  3. Seven practical steps
  4. Files and commands
  5. Verify the expected behavior
  6. Troubleshooting
  7. Change one thing, then test
  8. Next checkpoint
  9. References and evidence
  10. Key takeaways

ตอนที่ 2 จากสิบตอน: ตรึงนิยามงานสำเร็จและ baseline รูปแบบ JSON เก็บ attempts และงานที่ยังไม่จบให้ครบ ก่อนเปรียบเทียบกับเส้นทาง Jev

ฉบับร่างสำหรับตรวจในเครื่อง • โค้ดเริ่มต้นใช้ 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/contracts.mjs และใช้ผลจาก fixture เพื่อทดสอบการประกอบระบบเท่านั้น ยังไม่ได้ติดตั้ง SDK หรือใช้ key ในการจัดทำบทเรียน เส้นทาง live ที่แยกไว้ในตอนท้ายยังไม่ได้ทดสอบผ่าน API จริง

ทำความเข้าใจขอบเขตนี้

baseline คือกระบวนการที่ตรึงไว้พร้อมหลักฐาน ไม่ใช่การเลือกโมเดลอ่อนมาให้ระบบใหม่ดูดี ในแล็บนี้เราใช้ตัวแทนออฟไลน์ของ generative classifier ที่คืน JSON string การทดลองจริงต้องใช้โมเดล prompt กติกาซ่อมคำตอบ และ trace ค่าใช้จ่ายของระบบที่ใช้งานอยู่ โครงการดาวน์โหลดนี้ยังไม่เชื่อม generative provider จริง

นิยามความสำเร็จก่อนอ่านตัวเลขเปรียบเทียบ งานอัตโนมัติที่สำเร็จต้องส่งไปคิวถูกและไม่มองข้ามคำขอคุยกับคน งานที่ส่ง review ยังถือว่ายังไม่จบใน harness นี้ ไม่ใช่คำตอบถูกโดยอัตโนมัติ เรารายงาน coverage คู่กับ selective accuracy เพราะการปฏิเสธเคสมากขึ้นอาจทำให้เคสที่เหลือดูแม่นขึ้น

parser ของ baseline ป้องกันได้เพียงรูปแบบข้อมูล label billing ที่ถูก schema ยังอาจผิดความหมาย แยกการวินิจฉัยเป็น JSON เสีย label ไม่อยู่ในชุด และคำตอบถูก schema แต่จำแนกผิด มิฉะนั้นการปรับ parser อาจถูกเข้าใจว่าเป็นการใช้เหตุผลดีขึ้น และ retry ที่ซ่อมรูปแบบอาจหายไปจากการคิดต้นทุน

ลงมือทีละเจ็ดขั้น

ขั้นที่ 1

เขียนเกณฑ์รับงาน ใช้ชื่อคิวสามค่ากับเงื่อนไขคำขอคุยกับมนุษย์ตามแล็บ และใช้เกณฑ์เดียวกันกับ baseline, Jev-only และ hybrid อย่าให้โมเดลใหม่เปลี่ยนเป้าหมายจากส่งคิวถูกไปเป็นเพียงคืน JSON ที่อ่านได้

ขั้นที่ 2

อ่าน parseBaseline ใน contracts.mjs ฟังก์ชันรับ route จากชุดปิดและ wantsHuman แบบ boolean แล้วแปลง boolean เป็น 0 หรือ 1 ภายในเพื่อให้ policy กลางอ่านได้ การแปลงนี้เป็นโค้ดของแอป ไม่ใช่ฟิลด์ตอบกลับเพิ่มเติมของ Jev

ขั้นที่ 3

รัน baseline ในเคสชัดเจน T-001 จะเสนอ billing โดยมี baseline attempt หนึ่งครั้ง อ่านจำนวน input/output tokens สมมติพร้อมป้ายหลักฐานจำลองเสมอ นี่คือการสอนรูปแบบบัญชี ไม่ใช่การวัดบริการ generative รายใดจริง

ขั้นที่ 4

ดู fixture ที่ตั้งใจตอบผิด T-005 เป็นปัญหา dashboard โหลดไม่ขึ้น แต่คำตอบ baseline ที่เตรียมไว้เลือกอีกคิวหนึ่งและยังผ่าน schema ให้อ่านข้อความเทียบป้ายเฉลยแยกต่างหาก ห้ามส่งเฉลยกลับไปให้ provider เพื่อซ่อมคำทำนาย

ขั้นที่ 5

ทดสอบเส้นทาง parser ล้ม contract test ส่ง JSON เสียและ route นอกชุดให้ parser ซึ่งต้องปฏิเสธก่อนข้อเสนอไปถึง effect guard เมื่อใช้ baseline จริง การเรียกซ่อมคำตอบต้องเป็น inference attempt ใหม่ใน log ไม่ใช่เขียนทับคำตอบแรก

ขั้นที่ 6

รัน validation split แล้วบันทึกรายงานเป็นหลักฐาน baseline ก่อนเปลี่ยนระบบ ticket สมมติสิบสองรายการพอทดสอบการเดินสาย harness แต่เล็กและสร้างขึ้นเกินกว่าจะใช้ประมาณความแม่นเมื่อใช้งานจริง เก็บ test split ไว้นอกกระบวนการเลือก threshold

ขั้นที่ 7

ดู coverage ก่อนต้นทุน วิธีที่ดูราคาถูกแต่ส่งทุกเรื่องให้คนตรวจยังไม่ได้ทำงานชุดเดียวกันให้เสร็จ กำหนดขั้นต่ำของ selective accuracy และ coverage สำหรับการทดลองจริงล่วงหน้า แล้วจึงเทียบราคาและเลือก configuration

ไฟล์และคำสั่งที่ใช้

โค้ดประกอบบน GitHub: ai-core-jev-tutorial · README. ใช้ repo เป็นทางหลักในการทำแบบฝึก ไฟล์ต่อไปนี้ตรึงกับรุ่นที่ทดสอบแล้ว src/contracts.mjs · test/contracts.test.mjs

เปิดไฟล์นี้เทียบกับ source ใน ZIP ก่อนแก้ ข้อความต่อไปนี้คือโค้ดที่ใช้ในโครงการจริง ไม่ใช่ pseudocode ผลโมเดลเป็น input ให้โค้ดตรวจและวางเส้นทางต่อ ไม่ใช่สิทธิ์ให้ข้ามด่านอนุมัติ

src/contracts.mjs

export const QUEUES = Object.freeze(['billing', 'technical', 'other']);
export function probability(value, name) {
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1)
    throw new Error(`invalid ${name}: expected a finite number in [0,1]`);
  return value;
}
export function validateTicket(value) {
  if (!value || typeof value.id !== 'string' || !/^T-\d{3}$/.test(value.id)) throw new Error('invalid ticket id');
  if (typeof value.text !== 'string' || !value.text.trim() || value.text.length > 8000) throw new Error('invalid ticket text');
  return Object.freeze({id:value.id, text:value.text, language:value.language ?? 'en'});
}
export function parseJev(response) {
  if (!response || response.model !== 'jev-1.13.0') throw new Error('unexpected or missing pinned model');
  const route = response.answers?.route, human = response.answers?.wantsHuman;
  if (route?.type !== 'choice' || !QUEUES.includes(route.choice)) throw new Error('invalid route label/type');
  const confidence = probability(route.confidence, 'choice confidence');
  const probabilities = route.probabilities;
  if (!probabilities || Object.keys(probabilities).sort().join() !== [...QUEUES].sort().join()) throw new Error('incomplete option distribution');
  const values = QUEUES.map(k => probability(probabilities[k], `probability ${k}`));
  if (Math.abs(values.reduce((a,b)=>a+b,0)-1) > 1e-5) throw new Error('probabilities do not sum to one');
  if (probabilities[route.choice] + 1e-8 < Math.max(...values)) throw new Error('selected choice is not maximal');
  const expected=(Math.max(...values)-1/QUEUES.length)/(1-1/QUEUES.length);
  if(Math.abs(confidence-expected)>1e-5)throw new Error('inconsistent Choice confidence');
  if (human?.type !== 'noul') throw new Error('missing Noul answer');
  return Object.freeze({route:route.choice, confidence, probabilities:{...probabilities}, wantsHuman:probability(human.noul,'Noul')});
}
export function parseBaseline(raw) {
  const value=JSON.parse(raw);
  if (!QUEUES.includes(value.route) || typeof value.wantsHuman !== 'boolean') throw new Error('invalid baseline schema');
  return Object.freeze({route:value.route,wantsHuman:value.wantsHuman?1:0,confidence:null,probabilities:null});
}
export function tokenUsage(response) {
  const input=response?.usage?.input_tokens, output=response?.usage?.output_tokens;
  const valid=x=>Number.isSafeInteger(x)&&x>=0;
  return valid(input)&&valid(output) ? {input,output} : null;
}

คำสั่งออฟไลน์ต่อไปนี้รันได้โดยไม่ใช้ SDK หรือ API key:

node src/cli.mjs demo --mode baseline --ticket T-001
node src/cli.mjs demo --mode baseline --ticket T-005
node --test --test-reporter=spec test/contracts.test.mjs
node src/cli.mjs evaluate --split validation --save

ตรวจผลที่ควรได้

T-005 ควรแสดงคำตอบที่ผ่าน schema แต่ route สมมติผิด ไม่ใช่ parser exception รายงานต้องแสดง coverage และ unresolvedReviewTasks ผลในตอนนี้ยังสรุปคุณภาพเปรียบเทียบระหว่าง Jev กับ generative model จริงไม่ได้

ตรวจชื่อ test และเส้นทางปฏิเสธด้วย อย่าดูเพียง exit code หรือ label สุดท้าย ข้อมูลที่ไม่ทราบต้องยังระบุว่าไม่ทราบ และรายงานทุกชิ้นต้องแยกผลจำลองออกจากข้อมูลที่วัดจริง

แก้ปัญหาเมื่อผลไม่ตรง

ถ้าทั้งสาม pipeline รายงานเหมือนกัน ให้ตรวจชื่อ provider ในแต่ละ attempt ถ้า JSON เสียถูกเปลี่ยนเป็น other เงียบ ๆ ให้ยกเลิก default นั้น เพราะมันซ่อนความล้มเหลว ถ้าตัวหารเปลี่ยนระหว่างรัน ให้หยุดและคืนคำนิยามความสำเร็จให้ตรงกันก่อนแปลผล

หากเปิดบทเรียนผ่าน Python http.server ให้ใช้ลิงก์ .html ที่ท้ายตอนเมื่อจำเป็น ลิงก์ chip แบบไม่มีนามสกุลในแถบซีรีส์อาศัยการ resolve ของ GitHub Pages และเป็นข้อจำกัด preview ที่มีอยู่เดิม ไม่ใช่ปัญหาในโค้ด Node

เปลี่ยนหนึ่งอย่างแล้วทดสอบ

ในสำเนาทดลอง ทำ JSON string ของ baseline T-001 ให้เสีย ตรวจว่า parser tests หรือการสาธิต baseline ปฏิเสธแทนการเลือก route เริ่มต้น คืน fixture ก่อนเทียบโหมด แล้วจดแยกว่าคำตอบเสียรูปกับคำตอบถูก schema แต่ผิดความหมายเป็นคนละข้อค้นพบ

ทำการแก้ไขในสำเนาทดลอง เก็บไฟล์ต้นฉบับไว้เทียบ แล้วคืนค่าก่อนเริ่มตอนถัดไป หากทดลองกับ provider จริงภายหลัง ให้สร้าง experiment record ใหม่แทนเขียนทับผลเดิม

จุดตรวจถัดไป

ก่อนขยับต่อ ให้บอกได้ว่าไฟล์ใดเป็น contract ไฟล์ใดเป็น policy หลักฐานใดได้จากการรัน และข้อใดยังไม่ได้ทดสอบกับโมเดลจริง การแยกสี่อย่างนี้เป็นงานวิศวกรรมที่สำคัญกว่าการได้เดโมที่ดูไหลลื่น

← ตอนก่อนหน้า · ตอนถัดไป →

แหล่งอ้างอิงและหลักฐาน

เอกสารและ source ที่ใช้

ตรวจเอกสาร Jev วันที่ 23 ตุลาคม 2026 ข้อเท็จจริง API และราคาอาจเปลี่ยน ให้ตรวจใหม่ก่อนใช้ live ส่วนผลของ fixture เป็นข้อมูลสอนที่ระบุไว้ในโครงการ

  1. Lab contracts source
  2. Lab metrics source
  3. AI-Core validation principles

สรุปที่นำไปใช้ต่อ

  • contract จำกัดโครงสร้าง ส่วน evaluation ตรวจพฤติกรรม
  • อ่าน coverage คุณภาพ เวลา และต้นทุนรวมควบคู่กัน
  • ข้อเสนอไม่ใช่ผลที่อนุมัติแล้ว และหลักฐาน mock ไม่ใช่หลักฐานโมเดล live

Part 2 of ten: Freeze the success definition and JSON baseline; retain attempts and unresolved work before comparing the Jev path.

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/contracts.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

A baseline is a frozen procedure plus evidence, not a weaker model selected to make the new system look good. Here it is an offline stand-in for a generative classifier that returns a JSON string. In a real experiment, use your current production-quality generative model, prompt, repair rules and complete billing trace. We do not connect a live generative provider in this download.

Define success before reading comparative numbers. A successful automatic task routes to the correct queue and does not ignore a request for a human. A task sent for review is unresolved in this harness, not a magically correct answer. We report coverage and selective accuracy together because refusing more cases can make the remaining cases look better.

The baseline parser protects structure only. A valid billing label can still be semantically wrong. Separate invalid JSON, invalid label and valid-but-wrong classification in your diagnosis. Otherwise a parser improvement can be mistaken for a reasoning improvement, and a retry that fixes formatting can disappear from the cost calculation.

Seven practical steps

Step 1 — Write the acceptance rule

Write the acceptance rule. Use the three queue labels and the explicit-human-request condition in the lab. Keep the same rule for baseline, Jev-only and hybrid. Do not let a later model change the goal from correct routing to merely returning parseable JSON.

Step 2 — Read parseBaseline in contracts.mjs

Read parseBaseline in contracts.mjs. It accepts a closed route set and a boolean wantsHuman. The internal normalized object uses a numeric 0 or 1 for that boolean so the shared policy can read it; this conversion is application code, not an extra Jev response field.

Step 3 — Run a clear baseline case

Run a clear baseline case. T-001 produces a billing proposal with one baseline attempt. Read its artificial input/output token counts and keep the fictional-evidence label attached. This shows the accounting shape without pretending to measure any named generative service.

Step 4 — Inspect a deliberately wrong fixture

Inspect a deliberately wrong fixture. T-005 is a dashboard-loading problem but the canned baseline answer chooses another queue. The schema still passes. Compare the ticket text with the evaluation label separately; never send the label to the provider to repair its prediction.

Step 5 — Test the parser failure path

Test the parser failure path. The contract test passes malformed JSON and an out-of-set route to the parser. Both must fail before a proposal can reach an effect guard. In a live baseline, log a repair attempt as a second inference attempt rather than overwriting the first response.

Step 6 — Run the validation split once

Run the validation split once. Save its report as your pre-change baseline artifact. The twelve synthetic validation tickets are enough to exercise the harness but too small and artificial to estimate deployment accuracy. Keep the held-out test split out of threshold selection.

Step 7 — Review coverage before cost

Review coverage before cost. An apparently cheap method that sends everything to review has not completed the same workload. Record an acceptable selective-accuracy floor and coverage floor for your future real experiment before comparing prices or choosing the winning configuration.

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/contracts.mjs · test/contracts.test.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.

src/contracts.mjs

export const QUEUES = Object.freeze(['billing', 'technical', 'other']);
export function probability(value, name) {
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 1)
    throw new Error(`invalid ${name}: expected a finite number in [0,1]`);
  return value;
}
export function validateTicket(value) {
  if (!value || typeof value.id !== 'string' || !/^T-\d{3}$/.test(value.id)) throw new Error('invalid ticket id');
  if (typeof value.text !== 'string' || !value.text.trim() || value.text.length > 8000) throw new Error('invalid ticket text');
  return Object.freeze({id:value.id, text:value.text, language:value.language ?? 'en'});
}
export function parseJev(response) {
  if (!response || response.model !== 'jev-1.13.0') throw new Error('unexpected or missing pinned model');
  const route = response.answers?.route, human = response.answers?.wantsHuman;
  if (route?.type !== 'choice' || !QUEUES.includes(route.choice)) throw new Error('invalid route label/type');
  const confidence = probability(route.confidence, 'choice confidence');
  const probabilities = route.probabilities;
  if (!probabilities || Object.keys(probabilities).sort().join() !== [...QUEUES].sort().join()) throw new Error('incomplete option distribution');
  const values = QUEUES.map(k => probability(probabilities[k], `probability ${k}`));
  if (Math.abs(values.reduce((a,b)=>a+b,0)-1) > 1e-5) throw new Error('probabilities do not sum to one');
  if (probabilities[route.choice] + 1e-8 < Math.max(...values)) throw new Error('selected choice is not maximal');
  const expected=(Math.max(...values)-1/QUEUES.length)/(1-1/QUEUES.length);
  if(Math.abs(confidence-expected)>1e-5)throw new Error('inconsistent Choice confidence');
  if (human?.type !== 'noul') throw new Error('missing Noul answer');
  return Object.freeze({route:route.choice, confidence, probabilities:{...probabilities}, wantsHuman:probability(human.noul,'Noul')});
}
export function parseBaseline(raw) {
  const value=JSON.parse(raw);
  if (!QUEUES.includes(value.route) || typeof value.wantsHuman !== 'boolean') throw new Error('invalid baseline schema');
  return Object.freeze({route:value.route,wantsHuman:value.wantsHuman?1:0,confidence:null,probabilities:null});
}
export function tokenUsage(response) {
  const input=response?.usage?.input_tokens, output=response?.usage?.output_tokens;
  const valid=x=>Number.isSafeInteger(x)&&x>=0;
  return valid(input)&&valid(output) ? {input,output} : null;
}

Run these offline commands without an SDK installation or API key:

node src/cli.mjs demo --mode baseline --ticket T-001
node src/cli.mjs demo --mode baseline --ticket T-005
node --test --test-reporter=spec test/contracts.test.mjs
node src/cli.mjs evaluate --split validation --save

Verify the expected behavior

T-005 should demonstrate a valid schema with a wrong canned route, not a parser exception. The report must expose coverage and unresolvedReviewTasks. No result in this chapter establishes the relative quality of Jev and a real generative model.

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 all three pipelines report the same results, check which provider each attempt names. If malformed JSON silently becomes other, remove that default: it hides a failure. If the denominator changes between runs, stop and restore one success definition before interpreting any difference.

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

In a disposable copy, make the T-001 baseline string malformed. Confirm that parser tests or the baseline demonstration refuse it rather than choosing a default route. Restore the fixture before comparing modes. A formatting failure and a valid wrong answer should remain separate findings in your notebook.

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.

  1. Lab contracts source
  2. Lab metrics source
  3. AI-Core validation principles

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.
บทปฏิบัติจากซีรีส์ AI-Core + Jev in Practice 2026A lab from AI-Core + Jev in Practice 2026