Skip to main content

Command Palette

Search for a command to run...

MCP Optimization: ลด round-trip ของ resolver ด้วย Jev (ตอนที่ 3)

Updated
6 min readView as Markdown

ปัญหาที่ MCP เจอทุกวัน: การปิงปอง

MarketDX เปิดให้ผู้ช่วย AI (เช่น Claude) เรียกข้อมูลตลาดผ่าน MCP — ชุด "เครื่องมือ" ที่ LLM เรียกใช้ได้เอง ค้นหุ้น ดึงงบ สกรีน ฯลฯ

ฟังดูง่าย แต่มีขั้นตอนหนึ่งที่แพงเงียบ ๆ คือ การแปลงคำพูดคนให้เป็นค่าที่ระบบเข้าใจ

ลองดูคำถามจริง:

"หุ้นในกลุ่มค้าปลีกที่มีสินทรัพย์หมุนเวียนเพิ่มขึ้นก้าวกระโดด"

ก่อนจะสกรีนได้ ระบบต้องแปลง 3 ชิ้นนี้เป็น "โทเคน" ที่ฐานข้อมูลรู้จัก:

  • "สินทรัพย์หมุนเวียน" → CurrentAssets
  • "เพิ่มขึ้นก้าวกระโดด" → accelerating
  • "กลุ่มค้าปลีก" → 2550 (รหัส GICS)

งานนี้เราทำด้วยเครื่องมือตระกูล find_* (find_fsconcept, find_fspattern, find_gics ...) ปัญหาคือมันทำงานแบบนี้:

  1. LLM เรียก find_fsconcept("สินทรัพย์หมุนเวียน") → ระบบคืน รายการผู้สมัคร (top-k) กลับมาสัก 10 ตัว
  2. LLM อ่านแล้วเลือก อันที่ใช่
  3. ทำซ้ำกับ find_fspattern → คืน top-k → เลือก
  4. ทำซ้ำกับ find_gics → คืน top-k → เลือก
  5. แล้วค่อย เรียกเครื่องมือสกรีนจริงด้วยค่าที่เลือกได้

นับ round-trip ดู: สี่รอบไปกลับ กว่าจะได้เริ่มงานจริง ทุกรอบมี latency ของเน็ต บวกกับที่หนักกว่านั้น — LLM ต้องเผา reasoning token อ่าน top-k แล้วตัดสินใจเลือกทุกครั้ง

นี่คือ "การปิงปอง" ที่ MCP เจอแทบทุกคำถาม ยิ่งคำถามซับซ้อน (หลาย concept) ยิ่งปิงปองหลายรอบ ผู้ใช้ก็รอนานขึ้น

find_* ทำงานยังไงข้างใน (vector search 101)

ก่อนไปต่อ ขออธิบายว่า find_* แปลง "สินทรัพย์หมุนเวียน" เป็น CurrentAssets ได้ยังไง เพราะมันคือหัวใจของเรื่องนี้

หัวใจคือเทคนิคชื่อ vector embedding — แปลง "ความหมาย" ของข้อความให้เป็นชุดตัวเลขยาว ๆ (เวกเตอร์หลายพันมิติ) ข้อความที่ความหมายใกล้กันจะได้เวกเตอร์ที่อยู่ใกล้กันในปริภูมิ

ระบบเราทำสองอย่าง:

  1. เตรียมล่วงหน้า (ครั้งเดียว): ทุกแนวคิดใน catalog (เช่น "Total Revenue", "Current Assets", "Gross Margin") ถูกแปลงเป็นเวกเตอร์ด้วยโมเดล embedding (เราใช้ของ Gemini) แล้วเก็บไว้ในฐานข้อมูล
  2. ตอนมีคำถาม: เอาคำที่ผู้ใช้ถาม ("สินทรัพย์หมุนเวียน") มาแปลงเป็นเวกเตอร์แบบเดียวกัน แล้ว หาเพื่อนบ้านที่ใกล้ที่สุด (nearest-neighbor ด้วย cosine similarity) — เวกเตอร์ยิ่งใกล้ = ความหมายยิ่งคล้าย

เรียงผลตามความใกล้ เอา k อันบนสุด = top-k (จริง ๆ มีอีกชั้นคือจับคำตรงตัว/ชื่อพ้อง — lexical match — มาก่อน semantic แต่หลักการรวมคือ "คืนผู้สมัครที่คล้ายที่สุดมาสัก 10 ตัว")

จุดอ่อนอยู่ตรงนี้: "ใกล้ในเชิงเวกเตอร์" ไม่ได้แปลว่า "ใช่" เสมอ คำที่ความหมายเฉียด ๆ กัน — "ผลตอบแทนผู้ถือหุ้น" กับ "เงินปันผลต่อหุ้น", "ค่าเผื่อหนี้สงสัยจะสูญ" กับ "ตั้งสำรองหนี้สงสัยจะสูญ" — เวกเตอร์มันใกล้กันมากจนโผล่ปนกันใน top-k และบางทีตัวผิดดันอยู่อันดับสูงกว่าตัวถูกด้วยซ้ำ นั่นคือเหตุผลที่ระบบ ไม่กล้า ฟันธงอันเดียว

ทำไมต้องคืน top-k ตั้งแต่แรก?

คำถามที่ดีคือ — ทำไม find_* ไม่คืนคำตอบเดียวไปเลย ให้จบ?

เพราะ อันดับ 1 ไม่ได้ถูกเสมอ เราวัดจริงกับ find_fspattern (ตัวหา"รูปแบบ" ของงบ): ถ้าเชื่ออันดับ 1 ของ vector search ดื้อ ๆ จะถูกแค่ 71% แต่ถ้าดู top-10 ทั้งชุด — คำตอบที่ถูก อยู่ในนั้น 100%

find_fspattern (51 รูปแบบ):
  เชื่ออันดับ 1 ดื้อ ๆ         → ถูก 71%
  คำตอบอยู่ใน top-10          → 100%

เห็นช่องว่าง 71% → 100% ไหม? นั่นแหละคือเหตุผลที่มี top-k — ระบบไม่มั่นใจพอจะฟันธง เลยโยนตัวเลือกกลับให้ LLM ตัดสิน และนั่นคือต้นตอของการปิงปอง


ไอเดีย: ย้าย "การเลือก" ไปไว้ฝั่งเซิร์ฟเวอร์

ถ้าปัญหาคือ "ระบบไม่กล้าเลือก เลยโยนกลับให้ LLM" — แล้วถ้าเรามีตัวที่ กล้าเลือกแทน อยู่ฝั่งเซิร์ฟเวอร์ล่ะ?

นี่คือจุดที่ Jev เข้ามา จากตอน 1-2 เรารู้แล้วว่ามันเป็น "โมเดลตัดสินใจ" ที่เร็วและถูก — ให้ตัวเลือกกับคำถาม มันคืน คำตอบเดียว + ความมั่นใจ (confidence) ไม่ต้อง generate ข้อความยาว

โครงใหม่จึงเป็น:

เดิม:  user → LLM เลือก route → find_* คืน top-k → LLM เลือก → เรียก tool จริง   (ปิงปอง)
ใหม่:  user → LLM ส่งคำดิบเข้า tool → tool ให้ Jev เลือกเอง → ทำงานต่อได้เลย     (รอบเดียว)

LLM ส่ง "สินทรัพย์หมุนเวียน" เข้าเครื่องมือสกรีนตรง ๆ เครื่องมือเรียก Jev แปลงเป็น CurrentAssets ข้างในเอง แล้วสกรีนต่อได้เลย — ตัดขั้นตอนโยน top-k กลับไปให้ LLM เลือกทิ้งทั้งหมด

คำถามคือ Jev เลือกแม่นพอจะทำแบบนี้ไหม เราลองสองสถานการณ์


กรณี catalog เล็ก: ให้ Jev เลือกจากทั้งชุดทีเดียว

find_fspattern มีรูปแบบทั้งหมด 51 ตัว — เล็กพอจะยัดทั้งชุดเป็นตัวเลือกให้ Jev เลือกในครั้งเดียว

อ่านผล:

find_fspattern (51 รูปแบบ, คำถามไทย/อังกฤษ):
  Jev เลือกทีเดียว           → ถูก 92.9%
  vector อันดับ 1 (แบบเดิม)   → ถูก 71.4%

Jev ทิ้งอันดับ-1 แบบเดิมขาด และเข้าใกล้เพดาน 100% ที่ปกติต้องพึ่ง LLM มาเลือกซ้ำ — แต่ทำในครั้งเดียว ไม่ต้องปิงปอง

เซอร์ไพรส์: เคสที่ "พลาด" ส่วนใหญ่เป็นความผิดของเรา

ตอนแรกเรามีเคสที่ดูเหมือน Jev พลาด: คำถาม "มูลค่าหุ้นเพิ่มขึ้นเป็นสองเท่า" Jev ตอบผิดเป็น "สูงกว่าค่าอื่น" แทนที่จะเป็น "เติบโตทบต้น" (compound growth)

เกือบโทษ Jev แล้ว — แต่พอไปดู สิ่งที่เราป้อนให้มัน เราส่งแค่ "นิยามสั้น" ของแต่ละรูปแบบ ซึ่งนิยามของ compound growth เขียนเน้นแต่ "อัตราต่อปี (CAGR)" ไม่มีคำว่า "เป็นสองเท่า" เลย ทั้งที่ในฐานข้อมูลจริงมีคำอธิบายอีกฟิลด์ที่เขียนชัดว่า "มูลค่าเพิ่มเป็นสองเท่า/สามเท่า"

วิธีแก้: ป้อนคำอธิบายให้ครบเหมือนที่ระบบค้นหาใช้จริง — แก้ปุ๊บ Jev ตอบถูกทันที

บทเรียนนี้ย้ำสิ่งที่เราเจอตั้งแต่ตอน 2: ก่อนโทษโมเดล ให้ดูสิ่งที่เราป้อนมันก่อน เกือบทุกเคสที่ "พลาด" กลายเป็น context ที่เราให้ไม่ครบ หรือเฉลยของเราเองที่แคบเกินไป


กรณีหัวใจ: candidate เยอะเกินจะยัดหมด — vector + Jev

นี่คือกรณีที่เกิดบ่อยและสำคัญที่สุด catalog ส่วนใหญ่ใหญ่เกินกว่าจะยัดทั้งชุดให้ Jev — ตัวหาแนวคิดงบการเงิน (find_fsconcept) มีถึง 428 รายการ (บรรทัดงบ + อัตราส่วน + ตัวคูณราคา) ตัวหาหุ้นมีเป็นหมื่น ตัวหาข้อมูลเศรษฐกิจมีเป็นหลักหมื่น

โยนทั้ง 428 ตัวให้ Jev เลือกทีเดียวทำไม่ได้ (ทั้งแพงและเกินขีดจำกัด) แต่เราไม่จำเป็นต้องทิ้งของเดิม — เอา vector search ที่มีอยู่แล้วมาช่วยกรองก่อน:

คำดิบ → [vector search กรองเหลือ top-15] → [Jev เลือก 1 จาก 15] → โทเคน

vector search ทำสิ่งที่มันเก่ง (กรองจากพันเหลือสิบ) Jev ทำสิ่งที่มันเก่ง (เลือกตัวที่ใช่จากสิบ) — และการเลือกยังอยู่ฝั่งเซิร์ฟเวอร์ ไม่ต้องปิงปองกลับหา LLM

อ่านผล:

find_fsconcept (428 รายการ, คำถามอังกฤษ):
  vector อันดับ 1 (แบบเดิม)         → ถูก 68%
  vector top-15 → Jev เลือก        → ถูก 100%

Jev แก้ ทุกเคส ที่ vector จัดอันดับ 1 ผิด และเป็นเคสยากจริง เช่น:

  • "ผลตอบแทนผู้ถือหุ้น (ROE)" — vector เดาเป็น "เงินปันผลต่อหุ้น" → Jev เลือก roe ถูก
  • "หนี้ที่มีภาระดอกเบี้ยรวม" — vector เดาเป็น "ดอกเบี้ยค้างจ่าย" → Jev เลือก total_debt ถูก
  • "งานระหว่างก่อสร้าง", "เงินลงทุนที่ถือจนครบกำหนด", "กำไรขาดทุนเบ็ดเสร็จอื่นสะสม" — ศัพท์บัญชีลึกที่ vector ลอยหมด แต่ Jev เลือกถูก

ที่น่าทึ่งกว่าคือมันแยกแยะ "ประเภท" ได้ถูก: "กำไรขั้นต้น" → บรรทัดในงบ (gross_profit) ส่วน "อัตรากำไรขั้นต้น" → อัตราส่วน (gross_margin) — คนละตัวกัน Jev ไม่สับสน


แล้วต้นทุนล่ะ? — vector + Jev ยังถูกกว่า

หลายคนคงคิดว่าเพิ่ม Jev เข้าไปก็ต้องแพงขึ้นสิ ตรงกันข้าม

วิธี Jev อ่านกี่ตัวเลือก ต้นทุน / 1,000 ครั้ง
ยัดทั้ง catalog (51 ตัว) 51 $0.35
vector กรองก่อน → Jev (10-15 ตัว) ~10 $0.08

การกรองด้วย vector ก่อน ถูกลง ~4 เท่า เพราะต้นทุนหลักของ Jev คือความยาวของตัวเลือกที่ป้อนเข้าไป — อ่าน 10 ตัวย่อมถูกกว่าอ่าน 400 ตัว

แถมค่า embedding ของ vector แทบเป็นศูนย์ เพราะเราเก็บ vector ของคำค้นที่เคยเจอไว้ถาวรอยู่แล้ว (คำค้นซ้ำ = ไม่ต้องคำนวณใหม่) ส่วนตัว Jev เอง — คิดเงินเฉพาะ input, output ฟรี, และถูกกว่า LLM ทั่วไปหลายเท่า

เทียบเป็น round-trip: จาก 4 รอบ → 1 รอบ ผู้ใช้ได้คำตอบไวขึ้นชัดเจน และ LLM ฝั่ง client ไม่ต้องเสียสมาธิมานั่งเลือก top-k ทุกคำถาม — มันเอา token ไปคิดเรื่องที่สำคัญกว่าได้ นี่คือ UX ของ MCP ที่ดีขึ้นจริง


บทเรียนที่ต้องจำ (มีจริง)

การจะเอาไปใช้จริงต้องซื่อสัตย์กับข้อจำกัด — เราเจอ 4 เรื่อง

1. context ที่ป้อนสำคัญกว่าที่คิด เกือบทุกเคสที่ Jev "พลาด" คือเราป้อนคำอธิบายไม่ครบ หรือเฉลยของเราเองแคบเกิน แก้คำอธิบายในฐานข้อมูล = ดีขึ้นทั้งระบบ (ทั้ง vector และ Jev กินคำอธิบายชุดเดียวกัน)

2. "ถามยืนยัน" ต้องฉลาด ไม่งั้นทำลาย UX ที่เพิ่งแก้มา บางครั้ง Jev ก็ไม่มั่นใจจริง ๆ — คำถามกำกวมเอง หรือมีตัวเลือกสูสีสองตัว เราออกแบบให้เครื่องมือ ดู "รูปทรง" ของความมั่นใจ ไม่ใช่แค่ตัวเลขเดียว:

  • มั่นใจชัด → ใช้เลย
  • ก้ำกึ่งแต่พอได้ → ตอบไปก่อน พร้อมแนบหมายเหตุว่าตีความยังไง (ไม่บล็อก)
  • สองตัวสูสีจริง + ต่างกันจริง → ค่อยถามผู้ใช้ยืนยัน (เกิดน้อยมาก)

หลักคือ: ถามยืนยันเฉพาะตอนที่ "คำตอบที่ถูกมีอยู่จริงแต่ Jev เลือกไม่ขาด" เท่านั้น ไม่ใช่ถามทุกครั้งที่ไม่มั่นใจ — ไม่งั้นก็กลับไปปิงปองเหมือนเดิม

3. หลายภาษา = ให้แปลเป็นอังกฤษก่อน (และนี่คือข้อจำกัดของ vector ไม่ใช่ Jev) เราลองศัพท์บัญชีลึกหลายภาษา เจอว่า "ค่าความนิยม" (goodwill) ภาษาไทย ตกไปอยู่อันดับ 45 ของ vector — หลุด top-15 ไป Jev เลยไม่เห็นให้เลือก ดูเผิน ๆ เหมือน Jev พัง

แต่เราพิสูจน์แล้วว่า เป็นความอ่อนของ vector (embedding) ข้ามภาษา ไม่ใช่ Jev — ถ้าถามคำเดียวกันเป็นภาษาอังกฤษ ("goodwill") มันขึ้นอันดับ 1 ทันที Jev เองก็รู้ว่า のれん (ญี่ปุ่น) = goodwill ด้วยซ้ำ

โชคดีที่เครื่องมือ MCP ของเรากำหนดให้ client แปลเป็นอังกฤษก่อนส่งอยู่แล้ว — ปัญหานี้จึงไม่เกิดในทางปฏิบัติ

4. เส้นแบ่งบาง ๆ ทางบัญชี — Jev ผ่านสวย เราจัดชุดทดสอบโหดสุด: คู่ศัพท์ที่ต่างกันนิดเดียวแต่คนละความหมาย

คู่ที่ต้องแยก Jev แยกออกไหม
หุ้นทุนซื้อคืน (treasury stock) ↔ การซื้อหุ้นคืน (buybacks)
ตั้งสำรอง (P&L) ↔ ค่าเผื่อ (งบดุล)
สินทรัพย์ภาษีรอตัดบัญชี ↔ หนี้สินภาษีรอตัดบัญชี
หมุนเวียน ↔ ไม่หมุนเวียน
หนี้มีดอกเบี้ย ↔ หนี้สินรวม ↔ เงินกู้ (ไม่รวมลีส)

25 คู่โหด — Jev ถูก 100% ด้วยความมั่นใจเฉลี่ย 0.998 เทียบกับ vector อันดับ 1 ที่ถูกแค่ 48% นี่คือจุดที่ห่วงที่สุด (ตอบผิดแบบมั่นใจ) แต่กลับผ่านสบายในภาษาอังกฤษ


สรุป: เมื่อไหร่ควรยุบ resolver ด้วย Jev

จากที่ลองมา แนวทางที่ชัดคือ:

  • catalog เล็ก (ไม่กี่สิบ): ให้ Jev เลือกจากทั้งชุดทีเดียว
  • catalog ใหญ่ (ร้อย/พัน/หมื่น): ใช้ vector search กรองก่อนเหลือ ~10-15 → ให้ Jev เลือก — ยังยุบ round-trip และถูกลงพร้อมกัน
  • มีทางลัด cache: คำที่เคยแปลแล้ว (เช่น "current assets" → CurrentAssets) เก็บผลไว้ ครั้งต่อไปคืนเลย ไม่ต้องเรียก Jev ซ้ำ

ทั้งหมดนี้ทำให้ MCP จาก "ปิงปองสี่รอบต่อคำถาม" เหลือ "รอบเดียว" — เร็วขึ้น ถูกลง และ LLM ฝั่ง client เบาลง

ข้อควรระวัง: อย่าเพิ่งสลับทั้งหมดในวันเดียว วิธีที่ปลอดภัยคือรัน Jev คู่ขนานกับของเดิมบน traffic จริงสักพัก เทียบผล ดู distribution ของความมั่นใจ แล้วค่อยเปิดทีละส่วน — และเตรียม fallback ไว้เผื่อ Jev ช้าหรือล่ม (เราวัดได้ว่าปกติ ~1 วินาที แต่มี tail ที่พุ่งได้เป็นสิบวินาที)

แต่ทิศทางชัด: resolver คือ "ภาษี round-trip" ที่ MCP จ่ายทุกคำถาม — Jev ช่วยจ่ายภาษีนั้นแทน และแม้ candidate จะเยอะแค่ไหน การจับคู่ vector + Jev ก็ยังยุบมันได้ ทั้งเร็วขึ้นและถูกลง

More from this blog

เอา Jev มาทำงานจริง — ผล ต้นทุน และบทเรียน (ตอนที่ 2)

งานที่เราเลือกลอง MarketDX แปลงข่าวการเงินเป็นกราฟผลกระทบ หัวใจคือขั้นตอน "อ่านข่าวแล้วจัดประเภท + ประเมิน" ที่ยิง ทุกข่าว ที่ไหลเข้ามา — เป็นงาน LLM ที่ volume สูงสุดในระบบ ถ้าแทนได้คือประหยัดก้อนใหญ

Sep 18, 20264 min read63

MarketDX

6 posts