DEV Community

Nokka
Nokka

Posted on AI-assisted

ใส่ด่านตัดสินใจใน LangChain agent ด้วย Jev พร้อมจุดที่ tutorial พลาด

ใส่ด่านตัดสินใจใน LangChain agent ด้วย Jev พร้อมจุดที่ tutorial พลาด

โดย Nokka (นก-กา) | 19 กันยายน 2026

บทความนี้เขียนโดย AI (โมเดล deepseek-v4.1-flash ของผู้ให้บริการ ollama-cloud) ผ่าน Hermes Agent จาก Nous Research ตรวจสอบและเรียบเรียงโดย Nokka ข้อความในเครื่องหมายคำพูดที่เป็นคำแปลเป็นคำแปลของผม ไม่ใช่สำเนาต้นฉบับ

LangChain agent ทำงานเป็นวงจรตายตัว คือ LLM คิด เลือก tool เรียก tool ดูผลลัพธ์ แล้ววนกลับไปคิดใหม่ จนได้คำตอบ [1]

⇒ ปัญหาของวงจรนี้คือมันปฏิบัติกับทุกคำขอเหมือนกันหมด คำถามง่าย ๆ อย่าง "นโยบายคืนสินค้าคืออะไร" ก็วิ่งเข้าโมเดลตัวแพงเท่ากับงานหลายขั้นตอนที่ซับซ้อน [1]

และที่อันตรายกว่าคือ tool อย่าง delete_user_account ทำงานด้วยความลื่นไหลพอ ๆ กับ search_faq การเรียกฟังก์ชันที่โมเดลคิดผิดเพียงครั้งเดียวจึงสร้างผลกระทบที่ย้อนกลับไม่ได้ โดยไม่มีจุดตรวจในโค้ดเลย [1]

บทความนี้จะพาไปดูวิธีวาง ชั้นตัดสินใจ ลงในวงจรนี้ด้วย Jev จาก TypeSafe และชี้จุดที่ tutorial ต้นทางเขียนผิด ซึ่งผมตรวจพบตอนลองติดตั้งจริง

ต้นทางที่ผมอ่าน และเหตุผลที่บทความนี้ต่างจากต้นทาง

SitePoint เผยแพร่ tutorial เรื่อง "Build a Safer AI Agent Harness with Jev and LangChain" เมื่อ 18 กันยายน 2026 [1]

ตัวบทความสอนสร้าง harness ที่ใช้ Jev สองจุด เป็นด่านตัดสินใจในวงจร LangChain [1]

⇒ ผมอ่านแล้วเห็นว่าแนวคิดถูกและมีประโยชน์ แต่ตอนตรวจโค้ด ผมพบว่าโค้ดในบทความเรียกใช้ API ที่ไม่มีอยู่จริงในแพ็กเกจ บทความนี้จึงยึดโครงแนวคิดจาก SitePoint และแก้โค้ดให้ตรงกับ API จริง

แผนภาพแสดงสองด่านตัดสินใจในวงจร LangChain คือ Request Router เลือกชั้นโมเดล และ Tool Gate ประเมินความเสี่ยงก่อนเรียก tool จริง

ภาพที่ 1: ตำแหน่งที่ Jev เข้าไปวางในวงจรของ agent

แนวคิด: สองด่านตัดสินใจ

SitePoint เสนอให้วาง Jev สองจุดในวงจร [1]

ด่านที่หนึ่ง · Request Router จำแนกว่าคำขอซับซ้อนแค่ไหน แล้วเลือกชั้นของโมเดลให้เหมาะ คำถาม FAQ ธรรมดาวิ่งเข้าโมเดลเล็ก ส่วนงานหลายขั้นตอนวิ่งเข้าโมเดลใหญ่ [1]

ด่านที่สอง · Tool Gate ประเมิน tool ที่ LLM เสนอ ก่อนที่จะลงมือเรียกจริง tool ความเสี่ยงต่ำผ่านได้เลย ความเสี่ยงปานกลางผ่านได้แต่ต้องบันทึก Log ไว้ ความเสี่ยงสูงถูกปฏิเสธและส่งต่อให้คน [1]

และ SitePoint ย้ำจุดสำคัญว่าทั้งสองด่าน แทนที่ if/else แบบกระจัดกระจายด้วยการตัดสินใจที่มี type ตรวจสอบได้ ทำให้บันทึกทดสอบ และรีวิวแยกจากเหตุผลของ agent ได้ [1]

จุดที่ต้องแก้: from jev import Decision ไม่มีอยู่จริง

SitePoint เขียนไว้ในหัวข้อ Prerequisites และในโค้ดตัวอย่างว่า [1]

from jev import Decision
Enter fullscreen mode Exit fullscreen mode

ผมติดตั้งแพ็กเกจแล้วเปิดซอร์สโค้ดดูโดยตรง ไม่มีคลาสชื่อ Decision อยู่ในโมดูลเลย [2]

รายการที่โมดูลส่งออกจริงคือ fn, JevFn, AsyncJevFn, BaseModel, decide, adecide, state_payload, builder และผลการค้นหาคำว่า Decision ทั้งไฟล์ได้ผลลัพธ์เป็นศูนย์ [2]

SitePoint เองก็เขียนกำกับไว้สั้น ๆ ในหัวข้อ Prerequisites ว่า "If the package is unavailable or its API differs from what is shown here" แล้วเสนอให้เปลี่ยนไปใช้ instructor หรือ marvin ถือว่าเขาระวังไว้แล้ว แต่โค้ดที่ให้มาก็ยังเรียก API ที่ไม่มี [1]

ตารางสี่แถวแสดงการแปลงชนิด field ใน Python เป็นชนิดคำถามของ Jev โดย bool เป็น Noul ตัวเลือกเป็น Choice และตัวเลขเป็น Score

ภาพที่ 2: การแปลงชนิด field ที่ดึงจากซอร์สโค้ดของแพ็กเกจ

API จริงของแพ็กเกจ jev

แพ็กเกจ jev บน PyPI อธิบายตัวเองว่าเป็น "Decorator that compiles Python function definitions into Jev (TypeSafe System One) queries" และพึ่งพา typesafe-sdk 0.6.0 ขึ้นไป อยู่เบื้องหลัง [2][3]

⇒ หัวใจของมันคือ docstring ของฟังก์ชันคือ Jinja2 template ตอนเรียก มันจะ render docstring ด้วยค่าที่ส่งเข้ามา แล้วส่งเป็น state ให้ Jev [2]

และ return annotation ต้องเป็นคลาสลูกของ pydantic.BaseModel โดยแต่ละ field กลายเป็นคำถามหนึ่งข้อ และ type ของ field เป็นตัวกำหนดชนิดคำถามอัตโนมัติ [2]

ชนิด field ใน Python กลายเป็นคำถามแบบ ได้ค่ากลับมาเป็น
bool Noul probability_yes >= 0.5
Literal[...] หรือ Enum Choice ป้ายที่ถูกเลือก
int ที่มี Field(ge, le) Score ค่าในช่วงที่กำหนด
float ที่มี Field(ge, le) Score ค่าที่ประมาณจากระดับ

และ description ของ field กลายเป็นคำสั่งของคำถามนั้น [2]

ทางเลือกที่ไม่ต้องใช้ decorator คือเรียก decide(state, Model) ตรง ๆ สำหรับกรณีที่ต้องการ input ก้อนเดียวออกมาเป็น struct เดียว [2]

ส่วนรายละเอียดของ primitive ทั้งสามแบบ มีอธิบายครบในเอกสารทางการของ TypeSafe [4]

from typing import Literal
from jev import decide, BaseModel

class TicketReview(BaseModel):
    category: Literal["billing", "technical", "sales"]
    urgent: bool

review = decide(ticket_text, TicketReview)
Enter fullscreen mode Exit fullscreen mode

⇒ หรือจะสืบทอดจาก jev.BaseModel เพื่อได้เมธอด .decide(state) ติดมา และคำถามจะถูก compile ครั้งเดียวตอนประกาศคลาส [2]

เอาไปวางในวงจรจริง

เมื่อเข้าใจ API จริงแล้ว โครงของ harness จะเขียนแบบนี้ ตรงตามแนวคิดของ SitePoint แต่ใช้ชื่อที่ถูก

from jev import decide, BaseModel
from typing import Literal

class RouteDecision(BaseModel):
    """ตัดสินว่าคำขอนี้ควรใช้โมเดลชั้นไหน"""
    tier: Literal["small", "large"]

class GateDecision(BaseModel):
    """ตัดสินว่า tool ที่เสนอควรถูกเรียกหรือไม่"""
    risk: Literal["low", "medium", "high"]

def route(user_query: str):
    return decide(user_query, RouteDecision).tier

def gate(tool_name: str, args: dict):
    state = f"tool: {tool_name}\nargs: {args}"
    return decide(state, GateDecision).risk
Enter fullscreen mode Exit fullscreen mode

จากนั้นในวงจร LangChain เอา route() ไปเลือกโมเดลตอนต้น และเอา gate() ไปคั่นก่อนเรียก tool ทุกครั้ง โค้ดส่วนที่ตัดสินใจจะกลายเป็นฟังก์ชันที่ทดสอบแยกได้ ไม่ปนกับ prompt

จุดที่ SitePoint เตือนเอง และผมเห็นด้วย

SitePoint เขียนหมายเหตุเรื่องต้นทุนไว้ตรง ๆ ว่า ทั้ง router และ gate เพิ่มการเรียก LLM หนึ่งครั้งต่อหนึ่งคำขอ การประหยัดจะเกิดขึ้นก็ต่อเมื่อส่วนที่ประหยัดจากคำขอที่ถูกเบี่ยงไปโมเดลเล็ก มากกว่าต้นทุนที่เพิ่มมา [1]

ผู้เขียนแนะนำให้วัดต้นทุนการ route ต่อคำขอ เทียบกับส่วนต่างราคาโมเดลต่อคำขอ เพื่อหาจุดคุ้มทุนของตัวเอง และระบุว่าถ้าปริมาณน้อย ค่า overhead อาจแพงกว่าที่ประหยัดได้ [1]

⇒ ผมคิดว่านี่คือคำเตือนที่ตรงที่สุดในบทความต้นทาง และหลายคู่มือไม่พูด เพราะมันบอกว่าเครื่องมือนี้ไม่ใช่คำตอบสำเร็จรูป ต้องคำนวณก่อนใช้

ค่าใช้จ่ายจริงที่ควรรู้

SitePoint ยกตัวอย่างว่า gpt-4o-mini ถูกกว่า gpt-4o ประมาณ 30 เท่าต่อโทเคน input และแนะนำให้เช็คราคาปัจจุบันจากหน้า pricing ของ OpenAI เอง เพราะราคาเปลี่ยนได้ [1]

ผมคิดว่าจุดที่ควรสังเกตคือ ตัวเลขนี้เป็นของโมเดลรุ่นก่อน การเทียบชั้นโมเดลในปี 2026 ต้องดูราคาปัจจุบันของแต่ละเจ้า ไม่ใช่ยึดตัวอย่างจากบทความ

ภาพเปรียบเทียบสองฝั่ง ฝั่งซ้ายคือโค้ดที่บทความต้นทางเขียนว่า from jev import Decision ซึ่งไม่มีอยู่จริง ฝั่งขวาคือ from jev import decide, BaseModel ซึ่งเป็น API จริง

ภาพที่ 3: จุดที่ tutorial ต้นทางเขียนผิด

สิ่งที่ผมเห็นจากเรื่องนี้

ถ้าอ่านแค่แนวคิด บทความของ SitePoint ก็ให้ประโยชน์ครบ แต่สิ่งที่ทำให้ผมต้องเขียนชิ้นนี้คือ โค้ดใน tutorial เรียก API ที่ไม่มีอยู่ [1][2]

⇒ ปัญหานี้ไม่ได้เกิดกับ SitePoint ที่เดียว แต่เกิดกับ tutorial สาย AI ทั้งวง เพราะไลบรารีเปลี่ยน API เร็วกว่าที่บทความจะตามทัน และ AI ที่ช่วยเขียนบทความก็มีแนวโน้มสร้างโค้ดที่ "ดูน่าเชื่อ" มากกว่าโค้ดที่รันได้ [2]

วิธีป้องกันที่ได้ผลที่สุดคือ เปิดซอร์สโค้ดของแพ็กเกจดู __all__ ก่อนเชื่อ tutorial ซึ่งใช้เวลาไม่ถึงหนึ่งนาที และประหยัดเวลาไล่บั๊กได้หลายชั่วโมง [2]

ส่วนแนวคิดของ SitePoint ยังใช้ได้ตามเดิม เพราะการวางชั้นตัดสินใจที่มี type ตรวจสอบได้ ระหว่าง LLM กับโค้ด เป็นรูปแบบที่แก้ปัญหาจริง ทั้งเรื่องต้นทุนและเรื่องความปลอดภัย [1]


ถ้าคุณจะลองทำตาม ลำดับที่ผมแนะนำคือ ติดตั้งแพ็กเกจด้วย pip install jev ก่อน แล้วรันคำสั่งสั้น ๆ เพื่อตรวจว่ารายชื่อที่ส่งออกตรงกับที่คุณจะเรียก ทำแบบนี้กับทุก tutorial ที่ให้โค้ดกับไลบรารีที่คุณยังไม่คุ้น [2]

จากนั้นค่อยเริ่มจากด่านเดียว คือ Tool Gate ก่อน เพราะด่านนี้ให้ผลทางความปลอดภัยที่วัดได้ทันที ส่วน Request Router ค่อยเพิ่มเมื่อคุณมีข้อมูลจริงว่าคำขอแบบไหนควรไปโมเดลไหน [1]

แหล่งอ้างอิง

[1] SitePoint — "Build a Safer AI Agent Harness with Jev and LangChain", 18 กันยายน 2026 — https://www.sitepoint.com/build-safer-ai-agent-harness-jev-langchain/

[2] PyPI — แพ็กเกจ jev 0.3.0 (ซอร์สโค้ด jev.py), กันยายน 2026 — https://pypi.org/project/jev/

[3] PyPI — แพ็กเกจ typesafe-sdk (Python SDK สำหรับ TypeSafe AI), กันยายน 2026 — https://pypi.org/project/typesafe-sdk/

[4] TypeSafe AI — เอกสารทางการ (primitives, patterns, confidence), กันยายน 2026 — https://docs.typesafe.ai/

Top comments (0)