Evaluator-optimizer: generate → evaluate → refine — และเมื่อไรไม่ควร reflect
สามบทที่ผ่านมาเราไล่ workflow pattern ตามแนว BEA ทีละตัว — prompt chaining กับ routing (บท3), parallelization (บท4), orchestrator-workers (บท5) ทั้งหมดคือ predefined code path ที่ code คุมทาง ไม่ใช่ loop ที่ model ขับเอง บทนี้คือ pattern สุดท้ายของชุดนั้นก่อนเราขึ้นเรื่องความทนทาน (บท7) และมันมีลักษณะพิเศษ: มันคือรูป workflow ของสิ่งที่คนชอบเรียกกันลอยๆ ว่า “ให้ agent ตรวจงานตัวเอง” — generate → evaluate → refine และเพราะมันฟังดูน่าเชื่อ บทนี้จึงต้องถ่วงมันด้วยความจริงตั้งแต่ต้น: การให้ model สะท้อนงานตัวเองไม่ใช่ชนะเสมอไป
บทนี้ต่อยอด agent Order ตัวเดิม (repo kaen-food-ordering — กำลังจัดทำ) เคสที่เราจะใช้ทั้งบท: ร่างข้อความอธิบายการคืนเงินให้ลูกค้า แล้วเอามาตรวจกับ rubric ด้านนโยบาย/โทนก่อนส่ง — งานที่มีเกณฑ์ชัดและการขัดเกลาซ้ำมีคุณค่าที่วัดได้ code ในบทนี้ใช้ hand-rolled loop (แบบที่ 1 จากบท1) ไม่ใช่ middleware — เพราะจุดแทรก reflection เป็นสิ่งที่ knob ของ FunctionInvokingChatClient แสดงออกไม่ได้ ทุกกติกา API ฝั่งแชตจากบท1 ยังยึดเดิมทุกข้อ กับ ทั้งสอง การเรียก (ทั้ง generator และ evaluator)
Evaluator-optimizer คือ generate → evaluate → refine ที่วนเป็น loop
หัวข้อที่มีชื่อว่า “Evaluator-optimizer คือ generate → evaluate → refine ที่วนเป็น loop”Evaluator-optimizerEvaluator-Optimizerworkflow ตาม BEA: 'one LLM call generates a response while another provides evaluation and feedback in a loop' — คือรูปแบบ workflow ของ reflection: draft → ประเมินกับ rubric → revise โดย cap จำนวนรอบและหยุดทันทีเมื่อ evaluator ตอบ pass เหมาะเมื่อมีเกณฑ์ประเมินชัดและการขัดเกลาซ้ำมีคุณค่าที่วัดได้ evaluator call คือ eval-at-runtime (เชื่อมไป #13 ไม่สอนซ้ำ)Process คือ workflow ที่ BEA นิยามไว้ตรงๆ — “In the evaluator-optimizer workflow, one LLM call generates a response while another provides evaluation and feedback in a loop.” — หนึ่งการเรียกทำหน้าที่ generate อีกหนึ่งทำหน้าที่ evaluate + feedback แล้ววนขัดเกลากันไป จุดที่ต้องเน้น: evaluator ไม่ใช่ agent ตัวที่สอง มันคือการเรียก model อีกครั้งที่ codeคุมวงอยู่ — เป็น predefined code path เต็มตัว ไม่ใช่ loop อิสระที่ model ขับเอง (นี่คือเหตุผลที่มันยังนับเป็น workflow ไม่ใช่ autonomous agent)
BEA ยังให้ litmus test ว่าเมื่อไร pattern นี้คุ้ม — “This workflow is particularly effective when we have clear evaluation criteria, and when iterative refinement provides measurable value.” — และให้สองสัญญาณของความ fit ไว้ชัด: “LLM responses can be demonstrably improved when a human articulates their feedback; and second, that the LLM can provide such feedback.” อ่านสองประโยคนี้เป็นด่านคัดกรอง: ถ้า คน เขียน feedback แล้วคำตอบดีขึ้นได้จริง และ model เองก็ผลิต feedback แบบนั้นได้ — pattern นี้ถึงจะจ่ายคืน ถ้าข้อใดข้อหนึ่งไม่จริง คุณกำลังจะเผา token ฟรี
map ลงบน agent Order: generate คือให้ generator ร่างข้อความคืนเงิน evaluate คือให้ evaluator ตรวจ draft กับ rubric (“อ้างนโยบายถูกไหม · โทนสุภาพไหม · ระบุจำนวน/กรอบเวลาชัดไหม”) แล้วคืน verdict + feedback refine คือให้ generator แก้ตาม feedback — วนจนกว่า evaluator จะตอบ pass หรือ ครบจำนวนรอบที่ตั้งเพดานไว้ ตัวเพดานนั้นสำคัญ และเราจะกลับมาที่มันหลายรอบในบทนี้
reflection ไม่ใช่ชนะเสมอไป — มันช่วยเป็นสัดส่วนกับความ grounded ของสัญญาณ
หัวข้อที่มีชื่อว่า “reflection ไม่ใช่ชนะเสมอไป — มันช่วยเป็นสัดส่วนกับความ grounded ของสัญญาณ”รากทางความคิดของ evaluator-optimizer คือ reflectionReflectionการที่ model สะท้อนบน feedback แล้ววางแผนใหม่ (Reflexion, Self-Refine) — ⚠️ ไม่ใช่ชนะเสมอไป: ช่วยเป็นสัดส่วนกับความ grounded ของสัญญาณ reflection บนสัญญาณจริงจาก tool/oracle (เช่น unit test) เชื่อถือได้ แต่ intrinsic self-correction ล้วน ๆ บน reasoning มักไม่ดีขึ้นและบางครั้งแย่ลง (Huang et al., 'LLMs cannot self-correct reasoning yet') รู้ว่า 'เมื่อไรไม่ควร reflect' สำคัญพอ ๆ กับรู้วิธี reflect และทุกรอบคือ ≥1 model call เต็ม ๆ จึงต้อง cap เสมอProcess — การที่ model สะท้อนบน feedback แล้ววางแผนใหม่ งานวิจัยสองชิ้นที่มักถูกอ้างคือ Reflexion กับ Self-Refine และนี่คือแกนของบทนี้: ทั้งคู่ได้ผลจริง แต่ได้ผลด้วยเหตุผลที่ต่างกัน และเหตุผลนั้นคือสิ่งที่ตัดสินว่าเทคนิคนี้จะช่วย agent ของคุณหรือไม่
Reflexion ให้ agent สะท้อนบนสัญญาณ feedback ของงาน แล้วเก็บข้อความสะท้อนนั้นไว้ในหน่วยความจำ — “Reflexion agents verbally reflect on task feedback signals, then maintain their own reflective text in an episodic memory buffer to induce better decision-making in subsequent trials.” — Shinn et al. รายงานว่า “Reflexion achieves a 91% pass@1 accuracy on the HumanEval coding benchmark, surpassing the previous state-of-the-art GPT-4 that achieves 80%.” ⚠️ ตัวเลข 91% นี้เป็นผลลงวันที่ เฉพาะ benchmark (HumanEval, งานเขียน code) — ที่สำคัญกว่าตัวเลขคือ มันได้ผลเพราะ Reflexion ขี่อยู่บนสัญญาณจริง: unit test บอกได้ว่า code ผ่านหรือไม่ผ่าน การสะท้อนจึงมี ground truth ให้เกาะ
Self-Refine คือรูปที่ถูกที่สุด — modelตัวเดียวเล่นสามบทบาท — “Self-Refine does not require any supervised training data, additional training, or reinforcement learning, and instead uses a single LLM as the generator, refiner, and feedback provider.” — และรายงานว่า “improving by ~20% absolute on average in task performance.” ⚠️ ~20% นี้เป็นค่าเฉลี่ยที่ซ่อนความแปรปรวนสูงมากระหว่างชนิดงาน บางงานขึ้นเยอะ บางงานแทบไม่ขยับ อย่าอ่านมันเป็นกฎ
แล้วด้านมืดล่ะ? Huang et al. (Google DeepMind) ในงานชื่อตรงประเด็นว่า “Large Language Models Cannot Self-Correct Reasoning Yet” ชี้ผลที่ต้องจำ — “In the context of reasoning, our research indicates that LLMs struggle to self-correct their responses without external feedback, and at times, their performance even degrades after self-correction.” — คำที่ต้องขีดเส้นใต้คือ degrades: บางครั้งการให้ model ตรวจงานเหตุผลของตัวเอง ทำให้คำตอบแย่ลง เขาแยกเคสที่อันตรายไว้ชัด — “Central to our investigation is the notion of intrinsic self-correction, whereby an LLM attempts to correct its initial responses based solely on its inherent capabilities, without the crutch of external feedback.”
เอาสามชิ้นนี้มาต่อกันจะได้กฎเดียวที่ทั้งบทหมุนรอบ:
reflection ช่วยเป็นสัดส่วนกับความ grounded ของสัญญาณที่มันสะท้อน
Reflexion ได้ผลเพราะ unit test คือ external signal ที่จริง Self-Refine ได้ผลตรงงานที่ evaluator มีเกณฑ์เกาะ แต่ intrinsic self-correction ล้วนๆ บน reasoning — model ตรวจเหตุผลตัวเองโดยไม่มีสัญญาณจากภายนอก — คือเคสที่ Huang บอกว่าอาจแย่ลง เส้นแบ่งนี้คือทุกอย่าง: reflection บนสัญญาณจาก tool/oracle เชื่อถือได้ · reflection ที่ model เดาเอาเองว่าตัวเองผิดตรงไหน เชื่อไม่ได้
เมื่อไร ไม่ควร reflect
หัวข้อที่มีชื่อว่า “เมื่อไร ไม่ควร reflect”รู้ว่าเมื่อไรไม่ควร reflect สำคัญพอๆ กับรู้วิธี reflect นี่คือรายการตรวจก่อนคุณจะเสียบ evaluator เข้าไป:
- ไม่มีเกณฑ์ตรวจที่ชัด → อย่า reflect ถ้าคุณเขียน rubric ที่ checkable ไม่ออก evaluator ก็ไม่มีอะไรให้ยึด คุณจะได้แค่ความเห็นสุ่มๆ ที่แพงขึ้นทุกรอบ
- reflection แก้ คุณภาพ/รูปแบบ/การอิงนโยบาย ไม่ได้แก้ ข้อเท็จจริงที่ขาด ถ้าข้อความคืนเงินผิดเพราะ agent ไม่เคยเรียก
getOrderเลย — ข้อมูลจริงมันหายไปตั้งแต่ต้น การให้ model สะท้อนถ้อยคำของตัวเองซ้ำสิบรอบก็ไม่ทำให้ข้อเท็จจริงที่ไม่มีอยู่โผล่มา สิ่งที่ต้องทำคือ เรียก tool (กลับไปที่ loop ปกติของบท1) ไม่ใช่ reflection pass การสับสนสองอย่างนี้คือกับดักที่แพงที่สุด - ทุกรอบ reflection คือ ≥1 การเรียก model เต็มๆ และมันส่ง transcript ทั้งก้อนกลับเข้าไปใหม่ — ต้นทุนโตทุกรอบ จึงต้อง cap จำนวนรอบเสมอ ไม่มี evaluator-optimizer ที่วนแบบ open-ended เพดานนี้ผูกตรงกับ cost meter ที่เราจะประกอบใน 🔁 บท8 — autonomous capstone
- ระวัง diminishing returns และการแกว่ง บางครั้ง draft รอบ 3 ไม่ได้ดีกว่ารอบ 2 มันแค่ต่าง — evaluator เปลี่ยนใจไปมา หยุดทันทีที่ผ่านเกณฑ์ อย่า reflect เพื่อ reflect
- ถ้าเลือกได้ ให้ ground evaluator ดีกว่าให้มัน introspect evaluator ที่ตรวจกับผลของ tool จริง (เช่น “จำนวนเงินใน draft ตรงกับ
getOrderไหม”) เชื่อได้กว่า evaluator ที่แค่ “รู้สึกว่าโทนโอเค”
reflection นั่งตรงไหนใน loop — และทำไมต้อง hand-roll
หัวข้อที่มีชื่อว่า “reflection นั่งตรงไหนใน loop — และทำไมต้อง hand-roll”ในรูปทรง loop จากบท1 evaluator-optimizer แทรกเป็นขั้น ทางเลือก ระหว่าง “ได้ผลลัพธ์” กับ “จบ”: generate ผลออกมา → evaluate → ถ้าไม่ผ่านและยังไม่ครบเพดาน ก็ refine แล้ววน evaluate ใหม่ นี่คือเหตุผลที่ต้องใช้ hand-rolled loop: middleware .UseFunctionInvocation() วน call→execute→append→repeat ให้อัตโนมัติก็จริง แต่มันไม่มี knobให้แทรกขั้น “เอาผลไปตรวจกับ rubric แล้วตัดสินใจ refine” คุณต้องเป็นเจ้าของ while เองถึงจะแทรกงานที่ไม่ใช่ tool เข้ากลางวงได้ (ตรงกับตารางเลือก loop ในบท1: hand-roll เมื่อต้องแทรกงานที่ไม่ใช่ tool ระหว่างเทิร์น)
และมีจุดต่อสวยๆ กลับไปคอร์สก่อน: ข้อความสะท้อนที่ evaluator ผลิต ไม่จำเป็นต้องตายไปกับ request นี้ — Reflexion เก็บมันไว้ใน episodic memory buffer ซึ่งคือ 🔁 memory layer ที่คุณสร้างใน คอร์ส #16 — context engineering reflection ที่ดีในรอบนี้กลายเป็น context ที่ดีในรอบหน้าได้
flowchart TD
T["งาน: ร่างข้อความคืนเงิน"] --> G["generate<br/>generator ร่าง draft"]
G --> E{"evaluate<br/>evaluator ตรวจกับ rubric"}
E -->|"pass — ผ่านเกณฑ์"| DONE["หยุดสำเร็จ<br/>ส่ง draft ให้ลูกค้า"]
E -->|"ไม่ผ่าน · ยังไม่ครบเพดาน"| R["refine<br/>generator แก้ตาม feedback"]
R --> E
E -->|"ครบเพดานรอบ"| STOP["หยุดแบบมีขอบ<br/>ส่งดีที่สุด หรือ escalate"]
classDef eval fill:#7c3aed,stroke:#4c1d95,color:#f8fafc;
class E eval;
คำบรรยายภาพ: วง generate → evaluate → refine มีทางออกสองทาง ไม่ใช่ทางเดียว — ช่องม่วงคือ evaluator ที่ตัดสินทุกรอบ: pass พาออกทางซ้าย (หยุดสำเร็จ) ส่วนการครบเพดานรอบพาออกทางขวา (หยุดแบบมีขอบ แล้ว escalate) — ไม่มีทางที่วนไม่รู้จบ เพดานรอบคือ stop clause ที่ทำให้ reflection ไม่กลายเป็นหลุมเผา token
C# — hand-rolled generate → evaluate → refine
หัวข้อที่มีชื่อว่า “C# — hand-rolled generate → evaluate → refine”generator คือ IChatClient ตัว1 evaluator คือ อีกตัวหนึ่งที่ system prompt ของมันคือ rubric — เราอ่าน verdict แบบมีโครงสร้างด้วย overload GetResponseAsync<T> แล้วป้อน feedback กลับ โดย cap รอบไว้แน่นตั้งแต่ต้น:
// generator กับ evaluator เป็น IChatClient คนละตัว (คนละ system prompt) แต่กติกา API เดียวกันทุกข้อ:// bare id "claude-opus-4-8" (ตัวอย่าง ณ 2026-07) · ไม่ส่ง temperature · อ่านผ่าน Contents[] ไม่ใช่ top-level .text
string draft = (await generator.GetResponseAsync(task, cancellationToken: ct)).Text; // .Text = convenience concat
for (int i = 0; i < 3; i++) { // เพดานรอบตายตัว — ห้าม open-ended var eval = await evaluator.GetResponseAsync<Critique>( // T-overload = structured output; system prompt ของ evaluator คือ rubric $"ตรวจ draft นี้กับ rubric\nงาน: {task}\nDraft: {draft}", cancellationToken: ct);
if (eval.Result.Passes) break; // ผ่าน → หยุดทันที อย่า reflect เพื่อ reflect
draft = (await generator.GetResponseAsync( // refine: ป้อน feedback ที่ ground แล้วกลับให้ generator $"แก้ให้ผ่านประเด็นนี้: {eval.Result.Feedback}\nDraft เดิม: {draft}", cancellationToken: ct)).Text;}// ออกจาก loop ด้วย: ผ่านเกณฑ์ (break) หรือ ครบ 3 รอบ — ทั้งสองทางมี draft ล่าสุดในมือเสมอrecord Critique(bool Passes, string Feedback); // verdict ของ evaluator แบบมีโครงสร้างจุดที่ต้องยึด:
- กติกา API เดิมของบท1 ใช้กับ ทั้งสอง การเรียก — generator และ evaluator ต่างเป็น Claude หลัง NuGet
Anthropicตัวทางการ (beta) ผ่านAsIChatClientid เปล่าclaude-opus-4-8· ไม่ส่งtemperature/top_p/top_k· คำตอบดิบคือContents[]ไม่ใช่.textระดับบน (.Textที่เห็นใน code คือ convenience concatenation สำหรับหยิบข้อความล้วนๆ ของ draft) - เพดาน
i < 3ไม่ใช่ของประดับ มันคือ stop clause แท้ๆ — ทุกรอบคือการเรียก model อย่างน้อยสองครั้ง (evaluate + refine) เพดานนี้ต่อตรงเข้ากับ cost meter ของบท8 if (eval.Result.Passes) break;คือการหยุดเชิงบวกที่ประหยัดที่สุด — ถ้ารอบแรกผ่าน ก็ไม่ refine เลย นี่คือวินัย “อย่า reflect เพื่อ reflect” ที่เขียนเป็น code
while (true) { draft = refine(draft); } — reflection ที่วนไม่มีเพดาน ไม่มี evaluator ที่ ground และไม่มีทางออกเชิงบวก คือหลุมเผา token ที่ Huang et al. เตือน: ยิ่งวน คำตอบไม่ได้ยิ่งดี บางทีแย่ลง — และบิลค่า API ขึ้นทุกรอบแน่นอน reflection ต้องมี เกณฑ์ checkable + เพดานรอบ + ทางออก pass ครบทั้งสามเสมอ
evaluator step คือ eval-at-runtime — ต่อไป #13 ไม่สอนซ้ำที่นี่
หัวข้อที่มีชื่อว่า “evaluator step คือ eval-at-runtime — ต่อไป #13 ไม่สอนซ้ำที่นี่”สังเกตว่า evaluator ที่คุณเพิ่งเขียน ก็คือ eval — มันให้คะแนนผลของ model กับ rubric เพียงแต่ทำ ตอน runtime ต่อ request แทนที่จะทำ offline เป็นชุด นี่คือวินัยเดียวกับที่ 🔁 คอร์ส #13 — evals for AI agents สอนเต็มๆ: การเขียน rubric ที่ดี การเลือกเกณฑ์ที่ checkable การระวัง evaluator ที่ลำเอียง
บทนี้จึงไม่สอนซ้ำวิธีเขียน rubric — ไปเอาจาก #13 สิ่งที่ #17 ทำคือ นำ rubric ตัวนั้นมาใช้เป็นสัญญาณหยุดของ loop evaluator ที่ #13 สอนให้คุณสร้าง คือ Critique ที่ป้อน Passes/Feedback เข้า loop พูดอีกอย่าง: #13 สอนให้คุณ สร้างไม้บรรทัด — #17 เอาไม้บรรทัดนั้นมาวางเป็น stop clause ในเครื่องยนต์
สรุปก่อนไปต่อ
หัวข้อที่มีชื่อว่า “สรุปก่อนไปต่อ”evaluator-optimizer คือ workflow ปิดชุดของเรา — generate → evaluate → refine ที่ code คุมวง เป็นรูป workflow ของ reflection แต่บทนี้ยืนอยู่บนความจริงที่ต้องถือติดตัว: reflection ช่วยเป็นสัดส่วนกับความ grounded ของสัญญาณ Reflexion (91% บน HumanEval) และ Self-Refine (~20% เฉลี่ย) เป็นผลลงวันที่ เฉพาะงาน ไม่ใช่กฎ ส่วน intrinsic self-correction บน reasoning ล้วนๆ Huang เตือนว่าอาจ แย่ลง — รู้ว่าเมื่อไรไม่ควร reflect (ไม่มีเกณฑ์ checkable · ข้อเท็จจริงขาด · ไม่มีเพดาน) สำคัญพอๆ กับรู้วิธี reflect
ทั้ง workflow pattern ห้าตัว (บท3–6) มีจุดร่วมหนึ่งอย่างที่ยังไม่ได้แตะ: มันสมมติว่า tool ทำงานสำเร็จ แต่ในโลกจริง network ล่ม rate limit ชน issueRefund อาจถูกยิงซ้ำจนคืนเงินสองรอบ บทหน้าเราเติม clause “error ที่กู้ไม่ได้” ให้ stop policy และสอนให้ loop รักษา ตัวเองไว้เมื่อของพัง — 🔁 บท7 — ความทนทาน: tool-error recovery, retries, timeouts, idempotency
บทนี้อิงต้นทางที่ลงวันที่กำกับ อ่านต่อได้โดยตรง:
- Anthropic, “Building Effective Agents” (2024-12-19) — นิยาม evaluator-optimizer (“one LLM call generates a response while another provides evaluation and feedback in a loop”), litmus test “clear evaluation criteria… iterative refinement provides measurable value” และสองสัญญาณของความ fit
- Shinn et al., “Reflexion: Language Agents with Verbal Reinforcement Learning” (arXiv:2303.11366, 2023; NeurIPS 2023) — reflection บน feedback signal เก็บใน “episodic memory buffer” และผล 91% pass@1 บน HumanEval (ผลลงวันที่ เฉพาะ benchmark)
- Madaan et al., “Self-Refine: Iterative Refinement with Self-Feedback” (arXiv:2303.17651, 2023; NeurIPS 2023) — model ตัวเดียวเป็น generator/refiner/feedback provider และผล ~20% absolute เฉลี่ย (ค่าเฉลี่ยที่ซ่อนความแปรปรวนสูงระหว่างงาน)
- Huang et al. (Google DeepMind), “Large Language Models Cannot Self-Correct Reasoning Yet” (arXiv:2310.01798, 2023; ICLR 2024) — intrinsic self-correction บน reasoning ล้วนๆ อาจ “degrades after self-correction” — เหตุผลที่ reflection ต้องมีสัญญาณ external
- Microsoft Learn, “Use the IChatClient interface” —
GetResponseAsync, overloadGetResponseAsync<T>(structured output),ChatResponse.Text(convenience concatenation)
เช็กความเข้าใจ — บทที่ 6
ข้อ 1 / 3ในกรอบ BEA 'evaluator-optimizer' คืออะไร และทำไมมันยังนับเป็น workflow ไม่ใช่ autonomous agent?