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

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

ปัญหาของ LangChain Agent ปัจจุบัน

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 จริง

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

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

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

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

ด่านที่สอง · Tool Gate - ประเมิน tool ที่ LLM เสนอ ก่อนที่จะลงมือเรียกจริง [1]

  • ความเสี่ยงต่ำ: ผ่านได้เลย
  • ความเสี่ยงปานกลาง: ผ่านได้แต่ต้องบันทึก Log ไว้
  • ความเสี่ยงสูง: ถูกปฏิเสธและส่งต่อให้คน

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

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

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

from jev import Decision

เมื่อติดตั้งแพ็กเกจแล้วเปิดซอร์สโค้ดดูโดยตรง ไม่มีคลาสชื่อ 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]

ภาพที่ 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:

ชนิด 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)

หรือจะสืบทอดจาก 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

จากนั้นในวงจร 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 ต้องดูราคาปัจจุบันของแต่ละเจ้า ไม่ใช่ยึดตัวอย่างจากบทความ

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

สิ่งที่ทำให้ต้องเขียนบทความนี้คือ โค้ดใน tutorial เรียก API ที่ไม่มีอยู่ [1][2]

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

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

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

ลำดับที่แนะนำถ้าจะลองทำตาม

  1. ติดตั้งแพ็กเกจด้วย pip install jev ก่อน
  2. รันคำสั่งสั้น ๆ เพื่อตรวจว่ารายชื่อที่ส่งออกตรงกับที่จะเรียก ทำแบบนี้กับทุก tutorial ที่ให้โค้ดกับไลบรารีที่ยังไม่คุ้น [2]
  3. เริ่มจากด่านเดียว คือ Tool Gate ก่อน เพราะด่านนี้ให้ผลทางความปลอดภัยที่วัดได้ทันที
  4. เพิ่ม 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/
Read on DEV Community ↗ ← Back to News

Comments

No comments yet. Start the discussion.