Typed Functions Jev Decisions Offline First

Budgets — จำกัดการลองใหม่และทางสำรองBound Retries, Repairs, and Fallback

จำกัด transport retries การแก้ร่าง timeout และ fallback ด้วยงบรวมหนึ่งชุด พร้อมทางหยุดให้มนุษย์Bound transport retries and semantic revisions with shared budgets, cancellation and a deterministic fallback.

By Anirach Mingkhwan AI-Core Functional Workflows 2026 • Post #10 3 min read
Budgets — จำกัดการลองใหม่และทางสำรอง
ในบทความนี้
  1. เป้าหมายและสิ่งที่ต้องพร้อม
  2. จากโค้ดเดิมสู่ขอบเขตใหม่
  3. ลงมือทีละเจ็ดขั้น
  4. ไฟล์และคำสั่งที่รันได้
  5. ผลที่ควรได้และการตรวจ
  6. ตัวอย่างที่ต้องเข้าใจเมื่อไม่ผ่าน
  7. แบบฝึกในสำเนาทดลอง
  8. ตรวจเมื่อผลไม่ตรง
  9. จุดตรวจถัดไป
  10. แหล่งอ้างอิงและรุ่น
  11. สิ่งที่ควรนำไปใช้ต่อ
In this post
  1. Outcome and prerequisites
  2. Old functions and the new boundary
  3. Seven practical steps
  4. Files and runnable commands
  5. Expected output and verification
  6. A failure to understand
  7. Exercise in a disposable copy
  8. Troubleshooting
  9. Next checkpoint
  10. Sources and version notes
  11. Key takeaways

ตอนที่ 10 จากสิบสอง: จำกัด transport retries การแก้ร่าง timeout และ fallback ด้วยงบรวมหนึ่งชุด พร้อมทางหยุดให้มนุษย์

คำตัดสินและร่างข้อความเริ่มต้นเป็น 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/budget.mjs โดย gate state บอกผลต่อชิ้นงานและทางไปต่อ ไม่มี state ใดส่งข้อความหรือแก้บัญชีจริง

จากโค้ดเดิมสู่ขอบเขตใหม่

transport retry กับ semantic revision แก้คนละเรื่อง การเชื่อมต่อหรือ HTTP บางแบบล้มอาจลอง attempt ใหม่ แต่ร่างไร้หลักฐานต้องสร้าง candidate ใหม่หรือใช้ fallback ทั้งสองใช้ทรัพยากร จึงมีงบ call รวมที่นับทุก provider attempt

ตัวอย่างกำหนด provider ไม่เกิน8ครั้ง ร่างไม่เกิน2ครั้ง และ transport retry อีก1หลังความล้มที่เข้าเงื่อนไข timeout100มิลลิวินาทีต่อ call ใช้กับ fixture ตัวเลขสอนเล็ก ๆ นี้ไม่ใช่เป้า latency ระบบจริงหรืองบเงินที่รับประกัน

retry ใน SDK อาจคูณวงนอกที่ดูเล็ก ค่า default maxRetries2 ของ SDK ปัจจุบันหมายถึงสาม attempts ไม่ใช่สอง แล็บเลือก REST adapter แบบหนึ่ง attempt แทน SDK หากต่อ live ภายหลังต้องตรวจ backoff, retry-after และการนับบิลให้ครบ

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

ขั้นที่ 1

อ่านการตรวจงบรวมก่อนเรียก provider มีการใช้หน่วยก่อนเริ่มงานและบันทึก attempt แม้ call ล้ม error ต้องไม่ลบงานที่อาจถูกคิดเงินไปแล้ว

ขั้นที่ 2

รัน V009 หา input สอง attempts ครั้งแรกเป็น transient จำลองที่ retry ได้และ usage ไม่ทราบ ครั้งที่สองสำเร็จ แต่ความสำเร็จสุดท้ายไม่ได้ทำครั้งแรกฟรี

ขั้นที่ 3

รัน V001 กับ max-calls3 input, generation และ semantic output ใช้งบหมด quality จึงเริ่มไม่ได้ ต้อง review ด้วย call_budget_exhausted ไม่รับร่างที่ตรวจไม่ครบ

ขั้นที่ 4

อ่าน timed กับ cancellation signal timeout สั่ง abort งานที่กำลังทำและหยุดไปต่ออัตโนมัติ แต่ provider ที่ไม่สน cancellation อาจทำเสร็จบนบริการ จึงไม่ใช่หลักฐานว่าค่าใช้จ่ายเป็นศูนย์

ขั้นที่ 5

รัน V007 แยก semantic revise ออกจาก transport retry candidate ที่สองเป็น template fallback ธรรมดา ไม่ใช่ข้อความที่ Jev สร้าง ด่าน semantic และ quality ของร่างใหม่ยังใช้งบที่เหลือ

ขั้นที่ 6

รัน tests ของ cancellation ก่อนเริ่ม ระหว่างงาน timeout adapter เสีย และ retry ที่ชนงบรวม retry ที่เข้าเงื่อนไขก็ห้ามเกินเพดานรวม

ขั้นที่ 7

เขียนนโยบายจบก่อนเพิ่ม fallback ในตัวอย่าง provider ล้มถาวรหรือไม่แน่ใจต้อง review งบ revise หมดก็ review อย่าเพิ่ม retry-until-pass หรือถือการตรวจโมเดลซ้ำเป็นหลักฐานอิสระ

ไฟล์และคำสั่งที่รันได้

เปิด source จริงและอ่าน dependencies ก่อนแก้ นี่คือโค้ดอ้างอิงที่รันได้ ไม่ใช่ SDK ชื่อ AI-Core ที่สมมติขึ้น ลิงก์ชี้ source รุ่นตรึงของ companion สาธารณะใน GitHub

src/budget.mjs · tests ของตอนนี้ · แบบฝึกตอนนี้

export class BudgetError extends Error {constructor(code){super(code);this.code=code}}
export async function timed(run,{signal,timeoutMs}) {
  if(signal?.aborted) throw new BudgetError('cancelled');
  const controller=new AbortController();
  let timer,listener;
  const stop=new Promise((_,reject)=>{
    timer=setTimeout(()=>{controller.abort();reject(new BudgetError('timeout'))},timeoutMs);
    listener=()=>{controller.abort();reject(new BudgetError('cancelled'))};
    signal?.addEventListener('abort',listener,{once:true});
  });
  try {return await Promise.race([Promise.resolve().then(()=>run(controller.signal)),stop])}
  finally {clearTimeout(timer);signal?.removeEventListener('abort',listener)}
}
export class CallBudget {
  constructor(policy,{signal}={}){this.policy=policy;this.signal=signal;this.attempts=[];this.used=0}
  async call(stage,request,provider) {
    for(let retry=0;retry<=this.policy.transportRetries;retry++) {
      if(this.signal?.aborted) throw new BudgetError('cancelled');
      if(this.used>=this.policy.maxCalls) throw new BudgetError('call_budget_exhausted');
      this.used++;
      const attempt={stage,transportAttempt:retry+1,evidence:'OFFLINE AUTHORED FIXTURE',usage:null,scenarioMs:null,status:'started'};
      this.attempts.push(attempt);
      try {
        const r=await timed(s=>provider(stage,request,{signal:s}),{signal:this.signal,timeoutMs:this.policy.timeoutMs});
        if(!r||!Object.hasOwn(r,'value')) throw new BudgetError('malformed_adapter');
        const u=r.usage;
        attempt.usage=u&&Number.isSafeInteger(u.input)&&u.input>=0&&Number.isSafeInteger(u.output)&&u.output>=0?{...u}:null;
        attempt.scenarioMs=Number.isFinite(r.scenarioMs)&&r.scenarioMs>=0?r.scenarioMs:null;
        attempt.evidence=r.evidence;attempt.value=structuredClone(r.value);attempt.returnedModel=r.value?.model??null;attempt.status='returned';return r.value;
      } catch(e) {
        attempt.status=e.code??'provider_error';
        if(e.code!=='E_TRANSIENT'||retry===this.policy.transportRetries) throw e;
      }
    }
  }
}

คำสั่งออฟไลน์:

node src/cli.mjs run --case V009
node src/cli.mjs run --case V001 --max-calls 3
node src/cli.mjs run --case V007
node --test --test-reporter=spec test/budget.test.mjs

ผลที่ควรได้และการตรวจ

V009 accept หลัง transport retry ที่นับไว้หนึ่งครั้ง V001 งบสาม call ต้อง review ก่อน quality ส่วน V007 มี revise แล้ว fallback แน่นอน attempt ที่ล้มยังมี usage ไม่ทราบ ทำให้ต้นทุนรวมอาจยังไม่ทราบแม้ชิ้นงานผ่าน

อ่าน reasons, history และทุก attempt ไม่ดูเพียง label สุดท้าย usage ที่ไม่ทราบต้องยังไม่ทราบ test สีเขียวแสดงเส้นทางโค้ดที่กำหนด ไม่ได้พิสูจน์ความปลอดภัยเชิงความหมายทุกกรณี

ตัวอย่างที่ต้องเข้าใจเมื่อไม่ผ่าน

semantic สองรอบคูณ transport ของ SDK สาม attempts อาจเกินสิ่งที่ wrapper เห็นมาก ตัวอย่างเลี่ยง SDK ชั้นที่ซ่อนอยู่ หากปรับไปใช้ SDK ต้องนับชั้นนั้นอย่างชัดเจน

แบบฝึกในสำเนาทดลอง

ตั้งงบ call รวมเป็น1 แล้วจำลอง transient failure ใน test ต้องเรียก provider เพียงครั้งเดียว จากนั้นส่ง signal ที่ abort แล้ว ต้องไม่เรียกเลยและคืน human_review

ทำตามไฟล์แบบฝึก เก็บต้นฉบับและคืนชุดอ้างอิงก่อนตอนถัดไป อย่าใช้ผล held-out เลือก threshold

ตรวจเมื่อผลไม่ตรง

หาก test ค้าง ให้ provider ที่จำลองรับ abort signal หรือให้ timeout race จบได้ อย่าเพิ่มเวลาแบบเดา ค่า100ms ใช้กับ fixture เร็ว ไม่ใช่ SLA ของ live

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

ก่อนต่อ ให้บอกคุณสมบัติที่โค้ดตรวจได้แน่นอน คำตัดสินที่ผิดได้ state ที่หยุดงาน และหลักฐานที่ต้องมีก่อนปล่อยรุ่น

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

แหล่งอ้างอิงและรุ่น

เอกสารต้นทางและ companion source

ตรวจเอกสาร TypeSafe วันที่24ตุลาคม2026 บทเรียนตรึงคำขอข้อความที่ jev-1.14.0 และใช้ Choice/Noul ไม่ได้อ้างว่ามีเพียงความสามารถเหล่านี้ โค้ด REST ทางเลือกยังไม่ได้ลอง live ส่วน SDK0.7.0 มีเอกสารแต่แล็บนี้ไม่ต้องติดตั้ง

  1. Existing AI-Core assurance cycle
  2. Jev and coding agents
  3. Models and aliases
  4. Choice
  5. Noul
  6. Confidence statistic
  7. System One REST API
  8. SDK retry policy

สิ่งที่ควรนำไปใช้ต่อ

  • เงื่อนไขแน่นอนและสิทธิ์ยังอยู่ในโค้ด
  • ด่านความหมายและคุณภาพผิดพลาดได้
  • accept, reject, revise และ review มีทางไปต่อคนละแบบ

Part 10 of twelve. Bound transport retries and semantic revisions with shared budgets, cancellation and a deterministic fallback.

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/budget.mjs. Expected gate states describe content and continuation; no state sends a message or changes a real account.

Old functions and the new boundary

Transport retry and semantic revision solve different problems. A transient connection or selected HTTP error may justify retrying one attempt; an unsupported draft needs a new candidate or a fallback. Both spend resources, so a shared aggregate call budget owns every provider attempt.

The reference allows at most8provider calls,2drafts and1transport retry after an eligible initial failure. It uses100milliseconds per-call timeout for offline fixtures. These tiny teaching settings are not production latency targets or a guaranteed monetary spending cap.

SDK retries can multiply a seemingly small outer loop. The current official JavaScript SDK default maxRetries2 means three attempts, not two. This lab uses an optional one-attempt REST adapter instead of the SDK; wiring a live transport later requires reviewed backoff, retry-after and billing accounting.

Seven practical steps

Step 1

Read the aggregate budget check before the provider call. It consumes a unit before starting work and records an attempt even if the call fails. An error response must not erase potentially billable work.

Step 2

Run V009 and find two input attempts. The first simulated transient error is retryable; its usage is unknown. The second succeeds, but final success does not make the first attempt free.

Step 3

Run V001 with max-calls3. Input, generation and semantic output consume the available calls. Quality cannot start; the result must review with call_budget_exhausted rather than accepting an incompletely checked draft.

Step 4

Read timed and inspect its cancellation signal. Timeout aborts the in-flight operation and stops automatic continuation. A provider that ignores cancellation may still complete remotely, so cancellation is not a proof of zero charges.

Step 5

Run V007 and distinguish semantic revise from transport retry. The second candidate is an ordinary template fallback, not a new Jev-generated response. Its semantic and quality checks still consume remaining calls.

Step 6

Run tests for pre-start cancellation, in-flight cancellation, timeout, malformed adapter and aggregate-budget exhaustion during retries. No eligible retry is allowed to exceed the shared cap.

Step 7

Write the terminal policy before adding another fallback. In the reference, a permanent provider failure or uncertainty reviews; an exhausted revision budget also reviews. Do not add retry-until-pass behavior or treat repeated model checks as independent evidence.

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/budget.mjs · Matching tests · This lesson’s exercise

export class BudgetError extends Error {constructor(code){super(code);this.code=code}}
export async function timed(run,{signal,timeoutMs}) {
  if(signal?.aborted) throw new BudgetError('cancelled');
  const controller=new AbortController();
  let timer,listener;
  const stop=new Promise((_,reject)=>{
    timer=setTimeout(()=>{controller.abort();reject(new BudgetError('timeout'))},timeoutMs);
    listener=()=>{controller.abort();reject(new BudgetError('cancelled'))};
    signal?.addEventListener('abort',listener,{once:true});
  });
  try {return await Promise.race([Promise.resolve().then(()=>run(controller.signal)),stop])}
  finally {clearTimeout(timer);signal?.removeEventListener('abort',listener)}
}
export class CallBudget {
  constructor(policy,{signal}={}){this.policy=policy;this.signal=signal;this.attempts=[];this.used=0}
  async call(stage,request,provider) {
    for(let retry=0;retry<=this.policy.transportRetries;retry++) {
      if(this.signal?.aborted) throw new BudgetError('cancelled');
      if(this.used>=this.policy.maxCalls) throw new BudgetError('call_budget_exhausted');
      this.used++;
      const attempt={stage,transportAttempt:retry+1,evidence:'OFFLINE AUTHORED FIXTURE',usage:null,scenarioMs:null,status:'started'};
      this.attempts.push(attempt);
      try {
        const r=await timed(s=>provider(stage,request,{signal:s}),{signal:this.signal,timeoutMs:this.policy.timeoutMs});
        if(!r||!Object.hasOwn(r,'value')) throw new BudgetError('malformed_adapter');
        const u=r.usage;
        attempt.usage=u&&Number.isSafeInteger(u.input)&&u.input>=0&&Number.isSafeInteger(u.output)&&u.output>=0?{...u}:null;
        attempt.scenarioMs=Number.isFinite(r.scenarioMs)&&r.scenarioMs>=0?r.scenarioMs:null;
        attempt.evidence=r.evidence;attempt.value=structuredClone(r.value);attempt.returnedModel=r.value?.model??null;attempt.status='returned';return r.value;
      } catch(e) {
        attempt.status=e.code??'provider_error';
        if(e.code!=='E_TRANSIENT'||retry===this.policy.transportRetries) throw e;
      }
    }
  }
}

Offline commands:

node src/cli.mjs run --case V009
node src/cli.mjs run --case V001 --max-calls 3
node src/cli.mjs run --case V007
node --test --test-reporter=spec test/budget.test.mjs

Expected output and verification

V009 accepts after one counted transport retry. V001 with three calls reviews before quality. V007 shows a revise event followed by deterministic fallback. Failed attempts retain unknown usage, so complete cost may remain unknown despite a final accepted artifact.

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

Two outer semantic rounds multiplied by three SDK transport attempts can far exceed the calls visible to an application wrapper. This example avoids a hidden SDK layer; adapting it to an SDK requires counting that layer explicitly.

Exercise in a disposable copy

Set the aggregate call cap to1 and trigger a transient failure in a test. Verify only one provider invocation occurs. Then set an already-aborted signal and verify zero invocations and a human_review result.

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

If a test hangs, make the injected provider observe its abort signal or ensure the timeout race can settle. Do not increase timeouts blindly. The100ms reference setting is for fast canned fixtures, not a live SLA.

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.

  1. Existing AI-Core assurance cycle
  2. Jev and coding agents
  3. Models and aliases
  4. Choice
  5. Noul
  6. Confidence statistic
  7. System One REST API
  8. SDK retry policy

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