Tool: web_search — ค้นเว็บแล้วคืนแค่สรุป ไม่ยัด context เต็มก้อน

ภาพประกอบ: Tool: web_search — ค้นเว็บแล้วคืนแค่สรุป ไม่ยัด context เต็มก้อน

web_search ค้นหาข้อมูลบนเว็บและคืนผลลัพธ์ที่ย่อมาให้ agent ใช้คัดเลือกแหล่งข้อมูลก่อน เหมาะกับการหาข้อมูลปัจจุบันโดยไม่ต้องเปิดทุกหน้าเองตั้งแต่ต้น

Tool นี้คืออะไร

ค้น DuckDuckGo หา URL ที่เกี่ยวข้อง แล้วดึงเนื้อหาเต็มหน้าจริง (ไม่ใช่แค่ snippet จาก search engine เหมือน AGENT LITE) ผ่าน trafilatura มาสรุปเป็นภาษาไทยสั้นๆ ด้วยโมเดล 35B ตัวเดียวกับที่คุยอยู่ (ปิด thinking เพื่อความเร็ว) ผลลัพธ์ที่ผู้ใช้/context เห็นมีแค่:

[web:https://example.com/article] สรุปสั้นๆ ภาษาไทย...

ส่วนเนื้อหาเต็มถูกเก็บแยกไว้ใน web cache ระดับ process (ไม่ใช่ไฟล์ ไม่ persist ข้าม session) — ถ้าต้องการรายละเอียดมากกว่าสรุป ต้องเรียก recall_web แยกต่างหาก

ทำไมต้องแยก “สรุปใน context” ออกจาก “เนื้อหาเต็มใน cache”

โมเดล 35B มี context window ใหญ่กว่าโมเดล 2B ของ AGENT LITE มาก แต่ก็ยังมีขีดจำกัด — ถ้าดึงเนื้อหาเต็มหน้าเว็บ 5 หน้ามาใส่ตรงๆ ในบทสนทนา context จะเต็มเร็วมากโดยเฉพาะงานค้นคว้าหลายรอบ สถาปัตยกรรมนี้แก้ปัญหาด้วยการให้ context เห็นแค่สรุป ส่วนเนื้อหาดิบเก็บใน cache ที่ agent เลือกเรียกกลับมาดูเพิ่มได้เมื่อจำเป็นจริงๆ เท่านั้น — ประหยัด context แต่ไม่ทิ้งข้อมูลไปเลย

Cache แบบ query-aware สองชั้น

web_cache.py แยกเก็บ 2 อย่าง: raw body (ต่อ URL) และ summary (ต่อคู่ URL+query) — เหตุผลของการแยกคู่ query: URL เดียวกันถูกถามด้วยคำถามคนละมุมในคนละ turn อาจต้องการสรุปคนละแบบ (เช่นหน้าเดียวกันถูกถามเรื่อง “ราคา” ครั้งหนึ่ง แล้วถามเรื่อง “ฟีเจอร์” อีกครั้ง) ระบบจึง hash url|query เป็น key แยกกัน ไม่ใช้สรุปเก่าที่ไม่ตรงประเด็นซ้ำ

Cache มี TTL 30 นาที (ตั้งได้ผ่าน env) และมีการ evict แบบ LRU ทั้งจำนวน entry และขนาดรวมเป็น byte — เมื่อ raw entry หมดอายุหรือถูก evict สรุปที่ผูกกับ URL นั้นก็ถูกลบตามไปด้วยทั้งหมด กันสรุปเก่าค้างอยู่โดยไม่มี raw รองรับ

จำกัดจำนวนครั้งค้นเว็บต่อ turn

มี counter ต่อ turn (_WEB_COUNT/_WEB_LIMIT, ดีฟอลต์ 20 ครั้ง) ที่ web_search, browse_url, browser_use, batch_browse ใช้ร่วมกัน — ป้องกัน agent ค้นเว็บวนไม่จบในคำถามเดียว ถ้าครบโควตาจะได้ข้อความ [web_limit] บอกให้หยุดค้นและสรุปจากสิ่งที่มีแทน (graph.py ลดเพดานนี้ลงอีกสำหรับ turn ที่จัดว่าเป็นการค้นแบบง่าย แล้วรีเซ็ตกลับทุก turn ใหม่)

กรองเนื้อหาสำหรับผู้ใหญ่/พนัน

ก่อนจัดอันดับผลลัพธ์ มีการกรองโดเมน/คำที่เข้าข่ายเนื้อหาผู้ใหญ่หรือการพนันออกก่อนเสมอ (รายชื่อโดเมนและคำภาษาไทย/อังกฤษที่รู้จัก) — จัดอันดับที่เหลือด้วยคะแนนคำที่ overlap กับ query บวกความยาว snippet แล้วดึงเนื้อหาเต็มของ top URLs (ค่าเริ่มต้นตาม config) มาสรุปแบบขนาน (ThreadPoolExecutor) เพราะการดึง HTTP เป็นคอขวดที่ทำพร้อมกันได้ปลอดภัย

สรุปแบบเรียงลำดับ ไม่ใช่ขนาน

ต่างจากขั้นดึง HTTP ที่ทำพร้อมกันได้ ขั้นสรุปด้วย LLM ทำทีละ URL เรียงลำดับเสมอ — เหตุผลตรงไปตรงมา: MLX server ที่รันโมเดลในเครื่องรับคำขอได้ทีละคำขอ ถ้ายิงสรุปพร้อมกันหลาย URL คำขอจะแค่ต่อคิวรอกันอยู่ดี ไม่ได้เร็วขึ้นจริง แถมเพิ่ม overhead — สถาปัตยกรรมจึงแยกเฟสชัดเจน: ดึงข้อมูล = ขนาน, ใช้โมเดล = เรียงคิว

ทำไมเรื่องนี้ถึงสำคัญ

web_search ของ LOCAL AGENT TH แสดงให้เห็นการออกแบบที่ต่างจาก AGENT LITE โดยสิ้นเชิงแม้จะแก้ปัญหาเดียวกัน — LITE เลือกไม่ดึงเนื้อหาเต็มเลย (กัน SSRF, ประหยัด RAM), TH เลือกดึงเต็มแต่จัดการ context ด้วยการแยกสรุปกับ raw ออกจากกันผ่าน cache เพราะมีโมเดลใหญ่กว่าและ RAM มากกว่าให้ใช้ — เป็นตัวอย่างที่ดีว่า “tool เดียวกันในชื่อ” ถูกออกแบบต่างกันได้มากตามข้อจำกัดจริงของแต่ละผลิตภัณฑ์

อ่านเพิ่มเติม

ความคิดเห็น

กำลังโหลดความคิดเห็น...