ข้ามไปยังเนื้อหา
KoishiAI
EN
← สารบัญ

บทที่ 6 จาก 12 · Agentic Engineering — คู่มือปฏิบัติเขียนซอฟต์แวร์ระดับ Production ด้วย AI Agent

A/B Test, QA Test และการวัด User Harm

ชั้นของการทดสอบฉบับ agentic ว่าแต่ละชั้นจับ bug ประเภทไหนและ agent ทำได้แค่ไหน A/B สองความหมายที่ต้องแยกให้ชัด ลำดับชั้นความเสียหายห้าระดับ guardrail metric ที่ผูกกับ rollback อัตโนมัติ และ shadow mode

6.1 ชั้นของการทดสอบ

agent เขียน test ได้เร็วมาก ซึ่งอันตราย เพราะมันจะผลิต test จำนวนมากที่ทดสอบ สิ่งที่โค้ดทำอยู่แล้ว แทนที่จะทดสอบสิ่งที่ spec ต้องการ ต้องกำหนดชั้นให้ชัดว่าแต่ละชั้นมีหน้าที่จับ bug ประเภทไหน

ชั้นจับ bug ประเภทไหนสัดส่วนและเวลาagent ทำได้แค่ไหน
Unitตรรกะผิดในฟังก์ชันเดียว~70% / ต่ำกว่า 10 วิทำได้ดีมาก แต่ต้องเขียนจาก spec ไม่ใช่จากโค้ด
Propertyเคสที่คนคิดไม่ถึง — invariant ที่ต้องจริงเสมอ~10% / ต่ำกว่า 30 วิทำได้ดี ถ้าคนเป็นคนบอกว่า invariant คืออะไร
ContractAPI เปลี่ยนรูปแบบโดยไม่ตั้งใจ~5% / ต่ำกว่า 60 วิทำได้ดี generate จาก OpenAPI ได้
Integrationการประกอบกับ DB คิว หรือบริการนอก~10% / 1–5 นาทีต้องมี fixture ที่คนวางไว้ให้ก่อน
E2Eเส้นทางผู้ใช้จริงพัง~5% / 5–20 นาทีเปราะ ให้คนเลือกเองว่าเส้นทางไหนคุ้มค่าดูแล
Goldenoutput เปลี่ยนจากเดิมโดยไม่ตั้งใจเสริมดีมากกับ pipeline ข้อมูล prompt หรือ renderer

Property test — ชั้นที่ agent สร้างมูลค่าสูงสุด

from decimal import Decimal
from hypothesis import given, strategies as st

money = st.decimals(min_value=0, max_value=1_000_000, places=2)

@given(subtotal=money, pct=st.integers(min_value=0, max_value=100))
def test_percent_discount_invariants(subtotal, pct):
    result = apply_percent_coupon(subtotal, pct)

    # invariant 1: ยอดสุทธิห้ามติดลบ ไม่ว่าอินพุตจะเป็นอะไร
    assert result.total >= Decimal("0")

    # invariant 2: ยอดสุทธิห้ามมากกว่ายอดตั้งต้น
    assert result.total <= subtotal

    # invariant 3: ส่วนลด + ยอดสุทธิ = ยอดตั้งต้นเสมอ (ไม่มีเงินหายจากการปัดเศษ)
    assert result.discount + result.total == subtotal

    # invariant 4: ทศนิยมไม่เกิน 2 ตำแหน่ง
    assert result.total.as_tuple().exponent >= -2

คนเป็นคนบอกว่า invariant สี่ข้อนี้คืออะไร ซึ่งคือความรู้เชิงธุรกิจ ส่วน agent เป็นคนหาอินพุตที่ทำให้มันพัง ซึ่ง hypothesis ทำได้ดีกว่าคนมาก งานแบบนี้คือจุดที่การแบ่งงานคนกับ agent ชัดที่สุด

LLM-as-judge ใน gate

อย่าใช้ LLM ตัดสินผ่านหรือไม่ผ่านใน gate ที่บล็อก merge เพราะผลไม่คงที่ ทำให้ CI แดงสุ่ม ๆ และทีมจะเรียนรู้ที่จะกด re-run จนเขียว ซึ่งทำลายความน่าเชื่อถือของ gate ทั้งระบบ ใช้ LLM-as-judge ได้ในฐานะ รายงานเสริม ที่โพสต์ลง PR comment เช่น “จุดที่น่าตรวจเพิ่ม 3 ข้อ” แต่ห้ามให้มันมีสิทธิบล็อก

6.2 A/B Test

ในบริบท agentic คำว่า A/B มีสองความหมายที่ต่างกันมาก ต้องแยกให้ชัด

แบบที่ 1: A/B ระหว่างสองคำตอบของ agent (offline)

ใช้ตอนที่ทางเลือกในการ implement มีมากกว่าหนึ่งทางที่สมเหตุสมผล ให้ agent ทำทั้งสองทางใน worktree แยกกัน แล้ววัดด้วยเกณฑ์ที่กำหนดล่วงหน้า

# สร้าง worktree สองอัน ทำงานขนานกัน
git worktree add ../impl-a -b exp/coupon-strategy-a
git worktree add ../impl-b -b exp/coupon-strategy-b

# แต่ละอันรัน agent ด้วย spec เดียวกัน แต่ constraint ต่างกัน
(cd ../impl-a && claude -p "implement ตาม docs/specs/coupon.md \
  ใช้ strategy pattern แยก class ต่อชนิดคูปอง")
(cd ../impl-b && claude -p "implement ตาม docs/specs/coupon.md \
  ใช้ฟังก์ชันเดียวกับ match-case ไม่สร้าง class ใหม่")

# วัดด้วยเกณฑ์ที่ตั้งไว้ "ก่อน" เห็นผล — สำคัญมาก
./scripts/compare.sh ../impl-a ../impl-b

เกณฑ์เปรียบเทียบที่วัดได้ ตั้งก่อนรัน ห้ามเปลี่ยนหลังเห็นผล:

เกณฑ์วัดด้วย
test ชุดเดียวกันผ่านทั้งคู่หรือไม่ถ้าไม่ ตัดทิ้งทันที
จำนวนบรรทัดที่เพิ่มน้อยกว่าดีกว่า
cyclomatic complexity สูงสุดต่อฟังก์ชันradon cc
จำนวนไฟล์ที่ถูกแตะblast radius
p95 latency จาก benchmark เดียวกันpytest-benchmark
เพิ่มฟีเจอร์ที่ 3 ต้องแก้กี่ที่ทดสอบด้วยการสั่ง agent ทำจริง

เกณฑ์ข้อสุดท้ายมีค่ามากที่สุดและคนมักข้าม คำถามคือ “ออกแบบไหนรองรับการเปลี่ยนแปลงถัดไปได้ดีกว่า” ซึ่งทดสอบได้จริงด้วยการให้ agent ทำ requirement ถัดไปบนทั้งสอง branch แล้วดูว่าอันไหน diff เล็กกว่า

แบบที่ 2: A/B กับผู้ใช้จริง (online)

คือการทดสอบสมมติฐานทางธุรกิจ ไม่ใช่การทดสอบว่าโค้ดถูก โค้ดต้องผ่าน gate ครบก่อนแล้วเสมอ A/B ไม่ใช่วิธีทดแทน QA

ลำดับที่ถูกต้อง:
gate เขียว → เข้า staging → canary 5% → A/B 50/50 → rollout 100%

                     ตรงนี้คือจุดวัด user harm ไม่ใช่ตอน A/B

สิ่งที่ต้องตั้งก่อนเปิด A/B:

  • primary metric 1 ตัวเท่านั้น เช่น conversion rate
  • guardrail metrics 3–5 ตัวที่ห้ามแย่ลง
  • minimum sample size คำนวณล่วงหน้า ห้าม peek แล้วหยุดตอนสวย
  • ระยะเวลาขั้นต่ำ อย่างน้อย 1 สัปดาห์เต็ม เพื่อกันผลจากวันในสัปดาห์
  • เกณฑ์หยุดฉุกเฉิน guardrail แตะเส้นคือหยุดทันที ไม่รอครบ sample

6.3 User Harm — การวัดว่าเราทำร้ายผู้ใช้หรือยัง

นี่คือส่วนที่ขาดบ่อยที่สุดเมื่อทีมเร่งความเร็วด้วย AI เพราะความเร็วในการ ship เพิ่มขึ้น แต่ความเร็วในการ ตรวจจับความเสียหาย เท่าเดิม ช่องว่างตรงนี้คือที่ที่ผู้ใช้เจ็บ

ระดับตัวอย่างตรวจเจอด้วยเวลาที่ยอมรับได้
L1 รำคาญหน้าโหลดช้าลง 300ms, ปุ่มขยับRUM, p95 latencyแก้ในสปรินต์
L2 ทำงานไม่ได้กดสั่งซื้อไม่ได้, upload ล้มerror rate, funnel dropแก้ใน 24 ชม.
L3 ข้อมูลผิดยอดเงินผิด, ส่วนลดคำนวณเกินreconciliation jobrollback ทันที
L4 ข้อมูลหายเขียนทับ record, migration พลาดrow count drift, backup diffป้องกันเท่านั้น กู้ยาก
L5 ข้อมูลรั่วเห็นข้อมูลข้าม tenant, log มี PIItenancy test, log scannerต้องกันตั้งแต่ gate

จุดที่ AI เพิ่มความเสี่ยงมากที่สุด

คือ L4 และ L5 เพราะเป็นความเสียหายที่ ไม่แสดงอาการทันที agent เขียน migration ที่ backfill ผิด หรือใส่ user object ทั้งก้อนลง log โดยที่ test ยังเขียวหมด นี่คือเหตุผลที่ต้องมี test เชิงโครงสร้าง เช่น tenancy scan และ PII scan ไม่ใช่พึ่งแค่ test เชิงพฤติกรรม

Guardrail metrics — ตัวเลขที่ห้ามแย่ลง

ตั้งไว้ในระบบ monitoring และผูกกับ automated rollback:

Metricเกณฑ์หน้าต่าง
error_rate_5xx< 0.5%5 นาที
p95_latency< baseline+20%5 นาที
checkout_success> baseline−2%15 นาที
payment_mismatch= 0ทุก 1 นาที (L3 ห้ามมีเลย)
cross_tenant_reads= 0ทุก 1 นาที (L5 ห้ามมีเลย)
row_count_drift< 0.1%เทียบกับ 24 ชม.ก่อน (L4)
support_ticket_rate< baseline+30%1 ชม. (สัญญาณที่คนบอก)

กฎคือ guardrail ใดแตะเส้นให้ rollback อัตโนมัติ ไม่ต้องรอมนุษย์อนุมัติ แล้วค่อยมาสืบสาเหตุทีหลัง เพราะต้นทุนของ rollback ที่ไม่จำเป็นน้อยกว่าต้นทุนของการปล่อยให้เสียหายต่ออีก 30 นาทีมาก

Blast radius — จำกัดขนาดความเสียหายก่อนที่มันจะเกิด

ทุก PR ต้องตอบ 4 คำถามนี้ใน description โดยเป็นช่องบังคับใน template:

  1. ถ้าโค้ดนี้ผิดแบบเลวร้ายที่สุด ผู้ใช้กี่คนได้รับผลกระทบ
  2. ความเสียหายอยู่ระดับไหน (L1–L5)
  3. ตรวจเจอได้ภายในกี่นาที และเจอด้วยอะไร
  4. rollback ทำอย่างไร ใช้เวลาเท่าไร ต้องแตะข้อมูลไหม

ถ้าข้อ 2 เป็น L3 ขึ้นไป และข้อ 3 ตอบว่า “ไม่รู้” ห้าม merge ให้กลับไปเพิ่มการตรวจจับก่อน

เครื่องมือลด blast radius เรียงตามความแรง:

feature flag แบบปิดได้ทันที        ← ควรมีทุกฟีเจอร์ที่แตะเงินหรือข้อมูล
canary ตาม % ผู้ใช้
canary ตาม tenant (ทดสอบกับ tenant ภายในก่อน)
shadow mode (รันคู่ขนาน เทียบผล ไม่ใช้ผลจริง)   ← ดีที่สุดสำหรับ L3
dual-write + reconciliation                      ← สำหรับ migration

Shadow mode — วิธีที่ปลอดภัยที่สุดสำหรับตรรกะที่แตะเงิน

async def calculate_total(order: Order) -> Money:
    old = legacy_calculate(order)                 # ของเดิม — ใช้ผลจริง

    if flags.enabled("coupon_v2_shadow", order.tenant_id):
        try:
            new = await coupon_v2_calculate(order)  # ของใหม่ — ไม่ใช้ผล
            if new != old:
                metrics.increment("coupon_v2.mismatch")
                log.warning("shadow mismatch",
                            order_id=order.id, old=str(old), new=str(new))
        except Exception:
            metrics.increment("coupon_v2.error")
            log.exception("shadow failed")          # ห้าม raise ออกไป

    return old                                      # ผู้ใช้ยังได้ผลจากของเดิมเสมอ

เปลี่ยนมาใช้ของใหม่เมื่อ mismatch เป็น 0 ติดต่อกัน 7 วันบนทราฟฟิกจริง 100%

สำหรับงานที่ agent เป็นคนเขียนตรรกะการเงินหรือการคำนวณสำคัญ shadow mode คุ้มค่าเสมอ เพราะมันเปลี่ยนคำถาม “โค้ดนี้ถูกไหม” ให้กลายเป็นข้อมูลจริงจากทราฟฟิกจริง โดยไม่มีใครเจ็บระหว่างทาง

สรุปบทนี้

แบ่งชั้น test ให้ชัดว่าชั้นไหนจับ bug อะไร และให้คนเป็นคนบอก invariant ส่วน agent เป็นคนหาอินพุตที่ทำให้พัง อย่าให้ LLM มีสิทธิบล็อก merge แยก A/B แบบ offline กับ online ออกจากกัน และวัด user harm ด้วย guardrail ที่ผูกกับ rollback อัตโนมัติ เพราะ AI เพิ่มความเร็วในการ ship แต่ไม่ได้เพิ่มความเร็วในการตรวจจับความเสียหายให้เอง

บทถัดไปเป็น walkthrough เส้นทาง 9 ขั้นจากสเปกถึง rollout พร้อมระบุว่าขั้นไหนที่มนุษย์ต้องทำเอง