วิธีสร้างระบบประเมินผล AI Agent แบบ Self-hosted ด้วยตนเอง
สร้างระบบทดสอบ AI Agent ของคุณเองด้วยการเก็บ Golden cases จาก Trace จริง ใช้การตรวจสอบแบบ Deterministic ร่วมกับ LLM judge เพื่อวัดผลความแม่นยำและติดตาม Pass rate ในทุก Commit
การประเมินผล AI agent แบบ self-hosted คืออะไร
การประเมินผล AI agent แบบ self-hosted ประกอบด้วย 4 สิ่งที่คุณเก็บไว้ใน repository ของคุณเอง ได้แก่ ไฟล์กรณีทดสอบที่บันทึกไว้, สคริปต์สำหรับรัน agent เพื่อทดสอบกรณีเหล่านั้น, ชุดการตรวจสอบเพื่อประเมินคำตอบ และตารางผลลัพธ์ที่คุณสามารถสืบค้นได้ ทั้งหมดนี้ไม่จำเป็นต้องพึ่งพาผู้ให้บริการภายนอก วงจรการทำงานทั้งหมดใช้โค้ด Python เพียงไม่กี่ร้อยบรรทัดและไฟล์ SQLite หนึ่งไฟล์เท่านั้น
Agent ทำงานได้ในตัวอย่างสาธิตเพราะคุณเลือกข้อมูลนำเข้า 5 รายการด้วยตนเอง แต่ระบบกลับล้มเหลวในสัปดาห์ที่สองเนื่องจากมีการเปลี่ยนบรรทัดใน prompt, การเปลี่ยนโมเดล หรือการเปลี่ยนคำอธิบายเครื่องมือ โดยไม่มีการวัดผลใดๆ ครอบคลุมถึงการเปลี่ยนแปลงเหล่านั้น วงจรการประเมินผลจะเปลี่ยนความรู้สึกที่ว่า "ตอนนี้มันแย่ลง" ให้กลายเป็นตัวเลขที่ชัดเจน เช่น "อัตราการผ่านลดลงจาก 58 ใน 60 เหลือ 51 ใน 60 ที่ commit 4f1c9ab"
วงจรนี้มี 4 ขั้นตอน และคู่มือนี้จะแบ่งเนื้อหาออกเป็นส่วนละหนึ่งขั้นตอน ได้แก่ การรวบรวม trace จริง, การคัดเลือกกรณีที่น่าสนใจมาเป็นกรณีทดสอบ, การประเมินทุกกรณีในการเปลี่ยนแปลงทุกครั้ง และการจัดเก็บอัตราการผ่านไว้คู่กับ commit ที่สร้างผลลัพธ์นั้น วงจรเดียวกันนี้สามารถใช้ได้กับทุกสิ่งที่คุณใช้รัน agent และ เฟรมเวิร์กสำหรับ agent แบบ self-hosted ที่น่าใช้งาน ส่วนใหญ่จะแตกต่างกันที่ปริมาณข้อมูล trace ที่เฟรมเวิร์กนั้นเตรียมไว้ให้คุณโดยอัตโนมัติ
เหตุใด agent ถึงทำงานผิดพลาดในสัปดาห์ที่สอง
Agent ประกอบด้วย prompt, โมเดล, ชุดคำจำกัดความของเครื่องมือ และบริบทที่ถูกดึงมาใช้ในขณะรันไทม์ ทั้งสี่ส่วนนี้สามารถเปลี่ยนแปลงได้โดยที่โค้ดแอปพลิเคชันของคุณไม่ได้เปลี่ยนไปเลย ดังนั้นการรีวิวโค้ดตามปกติจึงไม่พบสิ่งผิดปกติใดๆ
สาเหตุที่พบบ่อยที่สุดคือการแก้ไข prompt คุณเพิ่มประโยคหนึ่งเข้าไปเพื่อป้องกันการตอบกลับที่หยาบคาย ประโยคนั้นไปเปลี่ยนพฤติกรรมของอินพุตที่ไม่มีใครทดสอบซ้ำ และใน trace ก็แสดงให้เห็นชัดเจน: trace ของสัปดาห์ที่แล้วสำหรับคำถามเดียวกันมีการเรียกใช้ create_refund tool call แต่ของสัปดาห์นี้กลับไม่มี และคำตอบที่ได้กลายเป็นการขอโทษอย่างสุภาพแทน ไม่มีข้อผิดพลาดใดๆ เกิดขึ้น จึงไม่มีการแจ้งเตือนใดๆ ทำงาน
สาเหตุที่สองคือโมเดล ให้บันทึกสตริงของโมเดลที่แน่นอนที่คุณส่งไปในทุกการรัน claude-haiku-4-5-20251001 แทนการใช้ชื่อย่อที่คุณจำไว้ในหัว เพราะอัตราความสำเร็จที่ลดลงในวันที่คุณเปลี่ยนโมเดลจะสามารถวิเคราะห์ได้ก็ต่อเมื่อมีข้อมูลโมเดลระบุอยู่ในแถวนั้นเท่านั้น
สาเหตุที่สามคือเครื่องมือ การปรับเปลี่ยนคำอธิบายของเครื่องมือจะส่งผลต่อการตัดสินใจของโมเดลว่าจะเรียกใช้เครื่องมือนั้นเมื่อใด หากเครื่องมือของคุณถูกส่งผ่าน MCP servers running on a VPS ตัว schema จะอยู่ในอีก process หนึ่ง ดังนั้นมันจึงสามารถเปลี่ยนแปลงได้โดยที่คุณไม่รู้ตัวและไม่มี diff ใดๆ ใน repository ของคุณเลย สาเหตุที่สี่คือการดึงข้อมูล (retrieval): คำถามเดิมไปกระทบกับ index ที่ถูกสร้างใหม่ในช่วงข้ามคืน และคำตอบก็เปลี่ยนไปตามเอกสารชุดใหม่นั้น
สร้างชุดข้อมูลมาตรฐาน (golden set) จากร่องรอย (traces) ที่คุณเก็บรวบรวมไว้แล้ว
อย่าสร้างกรณีทดสอบขึ้นมาเอง ให้ดึงข้อมูลมาจากทราฟฟิกจริง หากคุณใช้งาน การทำ tracing สำหรับเอเจนต์ด้วย Langfuse แบบ self-hosted อยู่แล้ว ทุกคำขอจะถูกจัดเก็บพร้อมอินพุต การเรียกใช้เครื่องมือ และเอาต์พุต ซึ่งเป็นวัตถุดิบที่จำเป็นสำหรับกรณีทดสอบโดยตรง
ให้ส่งออกข้อมูลช่วงเวลาของ root observations ผ่านทาง public API โดยใช้การยืนยันตัวตนแบบ basic authentication ซึ่งใช้ public key ของคุณเป็นชื่อผู้ใช้ และ secret key เป็นรหัสผ่าน
export LF_HOST="https://langfuse.example.com"
curl -sS -u "$LF_PUBLIC_KEY:$LF_SECRET_KEY" \
"$LF_HOST/api/public/v2/observations?limit=50&isRootObservation=true&fromStartTime=2026-07-01T00:00:00Z" \
| jq '.data[0]'ให้อ่านข้อมูลหนึ่งระเบียนก่อนเริ่มเขียนโค้ดแยกวิเคราะห์ (parsing) ข้อมูลจะถูกส่งกลับมาภายใต้ data แต่ชื่อฟิลด์ที่เก็บคำถามและคำตอบจะขึ้นอยู่กับวิธีการที่เอเจนต์ของคุณทำ instrumentation ในแต่ละ span ดังนั้นให้แมปข้อมูลตามที่คุณเห็นจริงแทนที่จะยึดตามที่คาดการณ์ไว้ จากนั้นจึงเขียนกรณีทดสอบด้วยตนเอง โดยใช้รูปแบบหนึ่ง JSON object ต่อหนึ่งบรรทัดในไฟล์ evals/cases.jsonl:
{"id": "refund-double-charge", "tags": ["smoke"], "input": "I was charged twice for order 41822.", "must_call": ["lookup_order", "create_refund"], "must_not_include": ["I cannot help"], "rubric": "The reply confirms exactly one refund for order 41822 and states the amount."}กฎ 5 ข้อที่จะช่วยให้ชุดข้อมูลนี้มีประสิทธิภาพในการใช้งาน:
- จำนวน 40 ถึง 80 กรณีทดสอบเพียงพอสำหรับการเริ่มต้น หากน้อยกว่า 20 กรณีทดสอบที่ทำงานไม่เสถียรเพียงรายการเดียวจะส่งผลต่ออัตราความสำเร็จถึง 5 จุด และตัวเลขที่เปลี่ยนแปลงโดยไม่มีเหตุผลจะถูกละเลยในที่สุด
- ทุกบั๊กในระบบผลิต (production bug) ที่คุณแก้ไข จะต้องกลายเป็นกรณีทดสอบในวันที่คุณแก้ไขบั๊กนั้น นิสัยนี้คือสิ่งที่ทำให้ชุดข้อมูลเติบโตไปในทิศทางที่ถูกต้อง
- หนึ่งพฤติกรรมต่อหนึ่งกรณีทดสอบ กรณีทดสอบที่ตรวจสอบทั้งจำนวนเงินคืนและน้ำเสียงไปพร้อมกัน จะไม่สามารถบอกอะไรคุณได้เลยเมื่อการทดสอบล้มเหลว
- ค่า
idต้องไม่มีการเปลี่ยนแปลง เพราะ id คือสิ่งที่ใช้เปรียบเทียบผลการรันของวันนี้กับเดือนที่แล้ว - ทำการปกปิดข้อมูลส่วนบุคคล (redact) ก่อนทำการ commit เนื่องจากไฟล์นี้จะถูกเก็บไว้ใน git ดังนั้นให้ลบชื่อลูกค้าและหมายเลขคำสั่งซื้อใดๆ ที่ไม่ใช่ของคุณออกให้หมด
ให้คะแนนด้วยการตรวจสอบแบบกำหนดผลลัพธ์ได้ (deterministic checks) ก่อน เพราะไม่มีค่าใช้จ่าย
สิ่งใดที่มีคำตอบที่ถูกต้องชัดเจน ให้ใช้การยืนยัน (assertion) แบบตรงไปตรงมา ไม่ต้องเรียกใช้โมเดล ไม่เสียค่าใช้จ่าย และไม่มีความคลุมเครือ การตรวจสอบแบบกำหนดผลลัพธ์ได้จะช่วยตรวจจับความถดถอยเชิงโครงสร้าง ซึ่งเป็นสิ่งที่ทำให้ระบบรอบตัวเอเจนต์ของคุณเสียหาย เช่น JSON ไม่สามารถ parse ได้, ไม่มีการเรียกใช้เครื่องมือ, มีการใช้คำต้องห้าม หรือคำตอบไม่มีการอ้างอิงแหล่งที่มา
มีเพียงฟังก์ชันเดียวเท่านั้นที่รู้จักเอเจนต์ของคุณ ส่วนประกอบอื่นทั้งหมดในชุดทดสอบ (harness) ควรเป็นแบบทั่วไป
import json, os, urllib.request
def run_agent(case):
req = urllib.request.Request(
os.environ["AGENT_URL"],
data=json.dumps({"input": case["input"]}).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=120) as resp:
return json.load(resp)
def deterministic(case, result):
text = result.get("output", "")
called = [c["name"] for c in result.get("tool_calls", [])]
failures = []
for tool in case.get("must_call", []):
if tool not in called:
failures.append(f"tool not called: {tool}")
for phrase in case.get("must_not_include", []):
if phrase.lower() in text.lower():
failures.append(f"forbidden phrase: {phrase}")
if len(called) > case.get("max_tool_calls", 12):
failures.append(f"too many tool calls: {len(called)}")
return failuresให้คงงบประมาณการใช้เครื่องมือไว้ในรายการนั้น เอเจนต์ที่แก้เคสได้โดยใช้การเรียก 3 ครั้งในวันนี้ แต่ต้องใช้ 11 ครั้งในวันพรุ่งนี้ ถือว่ามีความถดถอยเกิดขึ้นแม้ว่าคำตอบสุดท้ายจะถูกต้องก็ตาม เนื่องจากคุณต้องเสียค่าใช้จ่ายในทุกครั้งที่มีการเรียกใช้งาน
การใช้ LLM เป็นผู้ประเมินและข้อผิดพลาด 4 ประการที่มักเกิดขึ้น
สิ่งที่ผ่านการตรวจสอบด้วย assertion แล้ว จำเป็นต้องมีตัวประเมินที่สามารถอ่านเนื้อหาได้ การใช้ LLM เป็นผู้ประเมินคือการเรียกใช้โมเดลอีกตัวหนึ่ง โดยให้รับคำถาม คำตอบของเอเจนต์ และเกณฑ์การประเมินหนึ่งข้อ จากนั้นจึงส่งผลการตัดสินกลับมา นี่เป็นวิธีเดียวที่ใช้งานได้จริงในการประเมินว่า "คำตอบนั้นตอบสิ่งที่ผู้ใช้ถามหรือไม่"
กฎ 4 ข้อที่ทำให้ผู้ประเมินใช้งานได้จริงมีดังนี้:
- ใช้ผลตัดสินแบบไบนารี (ผ่าน/ไม่ผ่าน) ห้ามใช้คะแนน 1 ถึง 10 เพราะสเกลคะแนนมักจะให้ค่า 7 หรือ 8 กับเกือบทุกอย่าง ทำให้ตัวเลขไม่เปลี่ยนแปลงและคุณจะไม่ได้รับข้อมูลที่เป็นประโยชน์
- ใช้เกณฑ์เดียวต่อการเรียกหนึ่งครั้ง ให้ถามเรื่องจำนวนเงินคืนหรือเรื่องน้ำเสียงอย่างใดอย่างหนึ่ง ไม่ควรถามทั้งสองอย่างพร้อมกัน
- ให้คำตอบที่คาดหวังแก่ผู้ประเมินเสมอในกรณีที่มีคำตอบที่ถูกต้อง การประเมินเทียบกับข้อมูลอ้างอิงนั้นง่ายกว่าการประเมินแบบลอยๆ มาก
- บังคับรูปแบบผลลัพธ์และตรวจสอบ (parse) อย่างเคร่งครัด
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
def judge_prompt(case, output):
return (
"You grade one answer against one criterion.\n"
"Reply with JSON only, in this exact shape:\n"
'{"verdict": "pass", "confidence": "high", "reason": "one short sentence"}\n'
f"Criterion: {case['rubric']}\n"
f"Question: {case['input']}\n"
f"Answer: {output}\n"
"Length is not a criterion. Judge only the criterion above."
)
def judge(case, output, model):
msg = client.messages.create(
model=model,
max_tokens=200,
messages=[{"role": "user", "content": judge_prompt(case, output)}],
)
return json.loads(msg.content[0].text)ต่อไปคือรูปแบบความล้มเหลว แต่ละข้อมีวิธีทดสอบที่คุณสามารถทำได้ในบ่ายวันนี้ และการทดสอบนั้นสำคัญมาก เพราะผู้ประเมินที่ไม่มีการตรวจสอบจะสร้างตัวเลขที่ดูแม่นยำแต่ไม่มีความหมายใดๆ
อคติจากความยาว (Length bias): คำตอบที่ยาวกว่ามักจะผ่านการประเมินบ่อยกว่า ให้ทดสอบโดยนำคำตอบ 10 ข้อที่ผู้ประเมินเคยให้สอบตก มาเพิ่มเนื้อหาด้วยย่อหน้าที่มีความมั่นใจแต่ไม่มีข้อเท็จจริงใหม่เข้าไป แล้วให้ผู้ประเมินตัดสินอีกครั้ง หากผลตัดสินเปลี่ยนเป็นผ่าน แสดงว่าเกิดอคติจากความยาว และคุณต้องแก้ไขเกณฑ์การประเมิน (rubric)
อคติเข้าข้างตัวเอง (Self-preference): ผู้ประเมินมักให้คะแนนผลลัพธ์จากโมเดลตระกูลเดียวกันดีกว่าผลลัพธ์จากโมเดลอื่น ให้ทดสอบโดยการประเมินคำตอบชุดเดียวกัน 30 ข้อด้วยผู้ประเมินจากสองตระกูลที่ต่างกัน แล้วเปรียบเทียบผลตัดสินทีละกรณี ในกรณีที่ผลไม่ตรงกัน ให้คุณอ่านกรณีนั้นด้วยตัวเอง
อคติจากตำแหน่ง (Position bias): หากคุณใช้ผู้ประเมินเพื่อเปรียบเทียบคำตอบสองชุดคือ A และ B ให้ลองสลับตำแหน่งแล้วรันการประเมินอีกครั้ง หากผลตัดสินเปลี่ยนไปเมื่อสลับตำแหน่ง แสดงว่าการเปรียบเทียบแบบจับคู่ยังไม่ปลอดภัยสำหรับเกณฑ์นั้น
เกณฑ์การประเมินคลาดเคลื่อน (Rubric drift): เกณฑ์ที่คลุมเครือจะทำให้ได้ผู้ประเมินที่ใจดีเกินไป คำถามว่า "คำตอบมีประโยชน์หรือไม่" จะทำให้เกือบทุกอย่างผ่าน แต่คำถามว่า "คำตอบระบุจำนวนเงินคืนเป็นดอลลาร์หรือไม่" จะผ่านเฉพาะสิ่งที่ตรงประเด็นเท่านั้น ให้เขียนเกณฑ์ใหม่ทุกข้อจนกว่าจะระบุข้อเท็จจริงที่ต้องการตรวจสอบได้อย่างชัดเจน
มีมาตรการป้องกันหนึ่งข้อที่ครอบคลุมทั้ง 4 ปัญหา คือการเก็บชุดข้อมูล 30 กรณีที่คุณติดป้ายกำกับด้วยมือไว้ และให้คะแนนผู้ประเมินเทียบกับป้ายกำกับของคุณทุกครั้งที่มีการเปลี่ยนโมเดลผู้ประเมินหรือเปลี่ยน prompt ของผู้ประเมิน หากผู้ประเมินให้ผลไม่ตรงกับคุณเกิน 1 ใน 10 กรณี ให้แก้ไขเกณฑ์การประเมินก่อนที่จะเชื่อถืออัตราการผ่านที่ผู้ประเมินสร้างขึ้น ผู้ประเมินถือเป็นโค้ดชนิดหนึ่ง ดังนั้นจึงต้องมีการทำ version control และตรวจสอบเช่นเดียวกับโค้ดทั่วไป
จัดลำดับความสำคัญของโมเดลราคาประหยัด และยกระดับสู่โมเดลระดับแนวหน้าเมื่อจำเป็น
การใช้โมเดลที่แพงที่สุดประเมินทุกกรณีในทุก commit จะทำให้ค่าใช้จ่ายในการประเมินผลสูงเกินกว่าตัว agent ที่กำลังทดสอบ ให้จัดลำดับโมเดลผู้ประเมินตามราคา และหยุดการประเมินทันทีที่ได้คำตอบที่ชัดเจน
The data behind this chart
[
{
"label": "Haiku 4.5, Batch API",
"usd_per_1000_judge_calls": "0.90"
},
{
"label": "Haiku 4.5",
"usd_per_1000_judge_calls": "1.80"
},
{
"label": "Sonnet 5",
"usd_per_1000_judge_calls": "3.60"
},
{
"label": "Opus 5",
"usd_per_1000_judge_calls": "9.00"
}
]ตัวเลขเหล่านี้คำนวณโดยประมาณจาก input token 1,200 token และ output token 120 token ต่อการเรียกใช้งานผู้ประเมินหนึ่งครั้ง ซึ่งเป็นขนาดที่สมเหตุสมผลสำหรับหนึ่งคำถาม หนึ่งคำตอบ และหนึ่งเกณฑ์การตัดสิน การประเมิน 1,000 กรณีจะมีค่าใช้จ่าย 1.80 ดอลลาร์สหรัฐบน Claude Haiku 4.5 และ 9.00 บน Claude Opus 5 ส่วนต่างนี้อาจดูเล็กน้อยจนกว่าคุณจะคูณจำนวนครั้งที่ใช้งานจริง ชุดทดสอบ 60 กรณีที่ประเมินทุก commit โดยมีการ commit 40 ครั้งต่อสัปดาห์ จะเท่ากับการเรียกใช้งานผู้ประเมิน 2,400 ครั้งต่อสัปดาห์ ก่อนที่จะเริ่มรันงาน nightly job เสียอีก
มีส่วนลดสองประเภทที่ใช้ได้กับงานประเมินผลและสามารถใช้ร่วมกันได้ การรันประเมินผลไม่จำเป็นต้องโต้ตอบแบบเรียลไทม์ ดังนั้น Batch API จึงลดราคา input และ output ลงครึ่งหนึ่งเพื่อแลกกับการส่งผลลัพธ์แบบไม่พร้อมกัน ซึ่งแสดงไว้ในแถวแรกของตาราง ส่วน rubric และคำสั่งนั้นเหมือนกันทุกประการในทุกการเรียกใช้งาน ดังนั้น prompt caching จึงเหมาะสมที่สุด: การอ่านจาก cache มีค่าใช้จ่ายเพียงหนึ่งในสิบของราคา input ปกติ และการเขียน cache 5 นาทีมีค่าใช้จ่าย 1.25 เท่าของราคา input พื้นฐาน ดังนั้น cache จะคุ้มทุนหลังจากการเรียกใช้งานเพียงครั้งเดียว นี่คือราคาตามรายการของ Anthropic ณ เดือนสิงหาคม 2026 และ Sonnet 5 อยู่ในราคาช่วงแนะนำจนถึงวันที่ 31 สิงหาคม 2026 ดังนั้นแถบที่สามจะปรับราคาสูงขึ้นหลังจากวันที่ดังกล่าว
ลำดับขั้นการประเมินมีดังนี้:
- ตรวจสอบด้วยวิธี Deterministic ในทุกกรณี ซึ่งไม่มีค่าใช้จ่าย API เลย
- ใช้โมเดลขนาดเล็กประเมินในกรณีที่ผ่านการตรวจสอบขั้นแรกมาแล้ว
- ใช้โมเดลระดับแนวหน้าประเมินเฉพาะกรณีที่โมเดลขนาดเล็กระบุว่าไม่ผ่าน หรือระบุว่าผ่านแต่มีความมั่นใจต่ำ
- ให้มนุษย์ตรวจสอบจากกลุ่มตัวอย่างขนาดเล็กสัปดาห์ละหนึ่งครั้ง
CHEAP = "claude-haiku-4-5-20251001"
STRICT = "claude-opus-5"
def grade(case, result):
hard = deterministic(case, result)
if hard:
return False, "deterministic", "; ".join(hard)
first = judge(case, result["output"], CHEAP)
if first["verdict"] == "pass" and first["confidence"] == "high":
return True, CHEAP, first["reason"]
second = judge(case, result["output"], STRICT)
return second["verdict"] == "pass", STRICT, second["reason"]วิธีนี้เป็นการแลกเปลี่ยนความแม่นยำในการประเมินกับค่าใช้จ่าย ดังนั้นควรวัดผลการแลกเปลี่ยนนี้แทนการคาดเดา เดือนละหนึ่งครั้ง ให้ประเมินชุดข้อมูลทั้งหมดด้วยโมเดลที่เข้มงวดแล้วเปรียบเทียบผลลัพธ์ทั้งสองคอลัมน์ หากผลลัพธ์ไม่ตรงกันในหลายกรณี แสดงว่า rubric ของคุณหลวมเกินไปสำหรับโมเดลขนาดเล็ก และสิ่งที่คุณต้องแก้ไขคือตัว rubric เอง การควบคุมค่าใช้จ่ายที่ตัว agent ใช้เองนั้นเป็นอีกงานหนึ่ง ซึ่งครอบคลุมอยู่ใน การควบคุมค่าใช้จ่ายสำหรับ AI agent บน VPS
ติดตามอัตราความสำเร็จเมื่อเวลาผ่านไปในระบบที่คุณดูแล
อัตราความสำเร็จที่คุณไม่สามารถเชื่อมโยงกับ commit ได้นั้นเป็นเพียงความรู้สึก ให้จัดเก็บข้อมูลหนึ่งแถวต่อหนึ่งกรณีทดสอบต่อการรันหนึ่งครั้ง โดยระบุ commit และ model ไว้ภายในแถวนั้นด้วย
CREATE TABLE IF NOT EXISTS results (
run_id TEXT NOT NULL,
ran_at TEXT NOT NULL,
git_sha TEXT NOT NULL,
agent_model TEXT NOT NULL,
case_id TEXT NOT NULL,
passed INTEGER NOT NULL,
graded_by TEXT NOT NULL,
reason TEXT
);SELECT run_id, git_sha, agent_model,
count(*) AS cases,
round(100.0 * sum(passed) / count(*), 1) AS pass_pct
FROM results
GROUP BY run_id
ORDER BY ran_at DESC
LIMIT 10;โหลด schema ด้วย sqlite3 evals/results.db < evals/schema.sql จากนั้นอ่านแนวโน้มด้วย sqlite3 -box evals/results.db < evals/passrate.sql การรันรายวันตลอดหนึ่งปีสำหรับ 60 กรณีทดสอบ จะมีข้อมูลประมาณ 22,000 แถว ดังนั้นการจัดเก็บข้อมูลนี้จึงไม่กลายเป็นโปรเจกต์ที่ซับซ้อนด้วยตัวมันเอง การใช้งาน SQLite ในสภาพแวดล้อม production บน VPS ครอบคลุมถึงการตั้งค่าที่เริ่มมีความสำคัญหากไฟล์นี้ถูกใช้งานร่วมกันระหว่างหลายเครื่อง
ตัวรันโปรแกรมจะแสดงข้อมูลเดียวกันนี้สำหรับผู้ใช้งาน:
run 2026-08-05T09:14:22Z sha 4f1c9ab model claude-sonnet-5 58/60 pass (96.7%)
FAIL refund-double-charge deterministic: tool not called: create_refund
FAIL pto-policy-question judge(opus): reply gives no dollar amountให้รันชุดทดสอบกับการเปลี่ยนแปลงที่อาจส่งผลกระทบต่อ agent ซึ่งหมายถึงการแก้ไข prompt, การเปลี่ยน model และการเปลี่ยนเครื่องมือ แทนที่จะรันทุก commit ใน repository โดยใช้ hook pre-push เพื่อครอบคลุมชุดทดสอบย่อยที่รวดเร็ว:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
python3 evals/run.py --set smoke || exit 1
EOF
chmod +x .git/hooks/pre-pushการรันแบบเต็มรูปแบบจะใช้เวลานานกว่าและควรตั้งเวลาไว้ โดยใช้ systemd service และ timer บน VPS รันชุดทดสอบทั้งหมดเทียบกับ prompt ที่ใช้งานจริง ซึ่งเป็นวิธีที่ช่วยตรวจจับการเปลี่ยนแปลงที่มาจากภายนอก repository ของคุณ เช่น เครื่องมือที่โฮสต์ไว้ภายนอกซึ่งมีการเปลี่ยนแปลงพฤติกรรมไป
การตรวจสอบโดยมนุษย์: ใช้วิธีสุ่มตัวอย่างแทนการตรวจสอบทั้งหมด
ตัวตัดสิน (judge) จะถูกปรับเทียบโดยอ้างอิงจากป้ายกำกับ (labels) ของมนุษย์ ดังนั้นจึงจำเป็นต้องมีผู้สร้างป้ายกำกับเหล่านั้นขึ้นมา ให้อ่านตัวอย่างสัปดาห์ละหนึ่งครั้ง โดยเลือกทุกกรณีที่ตัวตัดสินประมวลผลพลาด รวมกับกรณีที่ผ่านการตรวจสอบแล้วอีก 10 รายการที่เลือกมาแบบสุ่ม กรณีที่ผ่านการตรวจสอบแบบสุ่มถือเป็นส่วนที่สำคัญ เพราะตัวตัดสินที่เริ่มปล่อยคำตอบที่ผิดผ่านไปอย่างเงียบๆ จะดูสมบูรณ์แบบบนแดชบอร์ดใดก็ตามที่สร้างขึ้นจากคำตัดสินของตัวมันเอง
การตรวจสอบ 15 กรณีโดยใช้เวลากรณีละ 3 นาที จะใช้เวลารวม 45 นาทีต่อสัปดาห์ ซึ่งจะช่วยให้สามารถแก้ไขเกณฑ์การประเมิน (rubric) ในส่วนที่คุณและตัวตัดสินมีความเห็นไม่ตรงกัน รวมถึงได้กรณีใหม่ๆ สำหรับรูปแบบความผิดพลาดที่ไม่มีใครคาดคิดมาก่อน ให้บันทึกคำตัดสินของมนุษย์ลงในตารางเดียวกันโดยตั้งค่า graded_by เป็น human เพื่อให้การตรวจสอบความสอดคล้องระหว่างตัวตัดสินและมนุษย์กลายเป็นการสืบค้นข้อมูล (query) แทนที่จะต้องอาศัยความจำ
สิ่งที่ทำให้ระบบทดสอบ (eval harness) ทำงานผิดพลาด
anthropic.RateLimitError ในการรันเต็มรูปแบบครั้งแรก การส่งเคสทดสอบ 60 รายการพร้อมกันเกินขีดจำกัดของจำนวนคำขอหรือโทเค็นในระดับแพ็กเกจของคุณ ให้จำกัดการทำงานแบบขนานไว้ที่ 4 เวิร์กเกอร์ และย้ายการรันรอบกลางคืนไปใช้ Batch API แทน
json.JSONDecodeError: Expecting value: line 1 column 1 (char 0) จากตัวตัดสิน (judge) โมเดลตอบกลับเป็นข้อความปกติ หรือครอบ JSON ไว้ในบล็อกโค้ด ให้ลองใหม่หนึ่งครั้ง หากยังผิดพลาดให้บันทึกเคสนั้นเป็นข้อผิดพลาด ห้ามถือว่าการ parse ล้มเหลวเป็นผลผ่านเด็ดขาด เพราะชุดทดสอบที่เปลี่ยนข้อผิดพลาดให้เป็นผลผ่านจะทำให้ค่าความสำเร็จพุ่งเข้าใกล้ 100% ในขณะที่ประสิทธิภาพของเอเจนต์กลับแย่ลง
เคสที่ไม่เสถียร (flaky cases) อินพุตเดียวกันผ่านในการรันครั้งหนึ่งแต่ล้มเหลวในครั้งถัดไป เนื่องจากเอเจนต์มีการสุ่มผลลัพธ์ ให้รันเคสที่ไม่เสถียรนั้น 3 ครั้งแล้วบันทึกเป็นสัดส่วนแทนที่จะลบเคสนั้นทิ้ง เคสที่ผ่าน 2 ใน 3 ครั้งคือบั๊กด้านความทนทานของระบบที่แท้จริง และลูกค้าจะพบปัญหานี้อย่างแน่นอน
ชุดคำตอบมาตรฐาน (golden set) ล้าสมัย มีคนแก้ไขคำตอบที่คาดหวังเพื่อให้ชุดทดสอบผ่าน ให้ตรวจสอบ diff ของ evals/cases.jsonl อย่างละเอียดเช่นเดียวกับ diff ของตัวเอเจนต์ เพราะไฟล์นั้นคือคำจำกัดความของความถูกต้องที่คุณเขียนไว้
ชุดทดสอบที่ไม่เคยล้มเหลว อัตราการผ่านที่ค้างอยู่ที่ 100% เป็นเวลาหนึ่งเดือนหมายความว่าชุดทดสอบไม่ได้ติดตามการเปลี่ยนแปลงของผลิตภัณฑ์อีกต่อไป ให้ดึง trace ล่าสุดมา 10 รายการ ค้นหาเคสที่เอเจนต์จัดการได้ไม่ดี แล้วเพิ่มเคสเหล่านั้นเข้าไปในชุดทดสอบ
FAQ
ชุดข้อมูลสำหรับประเมินผล AI agent ต้องมีกี่กรณี?
ให้เริ่มต้นที่ 40 ถึง 80 กรณี แล้วค่อยขยายชุดข้อมูลจากความล้มเหลวที่เกิดขึ้นจริง หากมีน้อยกว่า 20 กรณี ผลลัพธ์ที่แปรปรวนเพียงครั้งเดียวจะทำให้ค่า pass rate แกว่งไปถึง 5 จุด ซึ่งจะทำให้ตัวเลขดังกล่าวขาดความน่าเชื่อถือ หากเกินกว่าไม่กี่ร้อยกรณี การรันแต่ละครั้งจะสิ้นเปลืองทั้งเงินและเวลาจริง ในขณะที่กรณีทดสอบส่วนเพิ่มให้ความครอบคลุมเพิ่มขึ้นเพียงเล็กน้อย สิ่งที่สำคัญไม่ใช่จำนวน แต่คือสัดส่วนของประเภทความล้มเหลวใน production ที่คุณรู้จัก ซึ่งควรปรากฏอยู่ในชุดข้อมูลทดสอบอย่างน้อยหนึ่งครั้ง
ฉันจะเชื่อใจ LLM judge ในการให้คะแนน agent ของฉันได้หรือไม่?
เชื่อได้ก็ต่อเมื่อคุณได้วัดผลเทียบกับชุดข้อมูลที่คุณติดป้ายกำกับ (label) ไว้เองแล้วเท่านั้น ให้เก็บกรณีทดสอบ 30 กรณีที่คุณตรวจด้วยตนเองไว้ และใช้ประเมินตัว judge ทุกครั้งที่คุณเปลี่ยนโมเดลหรือเปลี่ยน prompt ของ judge ตัว judge มักมีอคติเรื่องความยาว (length bias) โดยคำตอบที่ยืดยาวมักจะผ่านการประเมินบ่อยกว่า และมีอคติเรื่องความชอบในตัวเอง (self-preference) โดยจะให้คะแนนผลลัพธ์จากโมเดลตระกูลเดียวกันสูงกว่าปกติ ทั้งสองกรณีนี้สามารถทดสอบได้ด้วยการเพิ่มความยาวให้คำตอบที่ล้มเหลวแล้วให้ judge ตรวจใหม่ หรือใช้ judge จากตระกูลอื่นมาตรวจคำตอบชุดเดียวกัน หาก judge ให้คะแนนไม่ตรงกับป้ายกำกับของคุณเกิน 1 ใน 10 กรณี แสดงว่าเกณฑ์การให้คะแนน (rubric) นั้นคลุมเครือเกินกว่าจะนำไปใช้งาน
ควรใช้โมเดลใดในการตรวจประเมินผล?
ให้ตรวจด้วยวิธีที่ประหยัดและค่อยขยับขยาย การตรวจสอบแบบกำหนดเงื่อนไข (deterministic assertions) ไม่มีค่าใช้จ่าย จึงควรให้รันเป็นอันดับแรกในทุกกรณี ส่วนโมเดลขนาดเล็กสามารถจัดการกับกรณีที่ผ่านเกณฑ์ชัดเจนได้ และให้ส่งเฉพาะกรณีที่ล้มเหลวหรือกรณีที่โมเดลมีความมั่นใจต่ำไปยังโมเดลระดับ frontier เท่านั้น ณ ราคาตลาดในเดือนสิงหาคม 2026 การตรวจประเมิน 1,000 กรณีจะมีค่าใช้จ่ายประมาณ 1.80 ดอลลาร์สหรัฐเมื่อใช้ Claude Haiku 4.5 และประมาณ 9.00 เมื่อใช้ Claude Opus 5 และเนื่องจากการรัน eval เป็นแบบ asynchronous การใช้ Batch API จะช่วยลดค่าใช้จ่ายทั้งสองกรณีลงได้ครึ่งหนึ่ง
การประเมินผล (evals) สามารถทดแทนการตรวจสอบใน production ได้หรือไม่?
ไม่ได้ เพราะทั้งสองอย่างตอบคำถามที่แตกต่างกัน ชุดการประเมินผลจะบอกคุณว่าการเปลี่ยนแปลงที่คุณกำลังจะนำไปใช้งานจริงนั้น ทำให้ชุดกรณีทดสอบที่กำหนดไว้ดีขึ้นหรือแย่ลง ส่วนการทำ tracing และ monitoring จะบอกคุณว่าผู้ใช้จริงกำลังประสบปัญหาอะไรอยู่ในขณะนี้ รวมถึง input รูปแบบที่ไม่มีในกรณีทดสอบใดๆ ทั้งสองส่วนนี้จะส่งเสริมซึ่งกันและกัน โดย traces จะช่วยจัดหาชุดกรณีทดสอบใหม่ๆ และชุดการประเมินผลจะช่วยตัดสินว่าการแก้ไขของคุณได้ผลจริงหรือไม่