ENDEAVOR LOCAL AGENT TH ทำงานอย่างไร — เจาะลึกสถาปัตยกรรม

บทความ เริ่มต้นใช้งาน บอกวิธีติดตั้งและใช้งาน บทความนี้เจาะลึกว่าข้างในทำงานอย่างไร — LOCAL AGENT TH ใช้โมเดลใหญ่กว่า AGENT LITE มาก (35B เทียบกับ 2B) จึงพึ่งพาความสามารถของโมเดลเองได้มากกว่า แต่จุดที่น่าสนใจคือทีมพัฒนายังคงไม่ยอมฝากการตัดสินใจสำคัญไว้กับโมเดลทั้งหมด โดยเฉพาะเรื่องการวางแผนงานและความปลอดภัย
วงจรหลัก: ยังเป็น ReAct node เดียว แต่มีชั้นก่อนหน้าเพิ่มขึ้น
โครงกราฟหลักยังเรียบง่าย — START → react → END ผ่าน LangGraph node เดียว เหมือน AGENT LITE — แต่ก่อนถึง node นั้น มีชั้น planner คั่นอยู่ ที่ทำหน้าที่ตัดสินใจว่าคำถามนี้ “ง่าย” พอจะให้โมเดลตอบตรงๆ หรือ “ซับซ้อน” พอที่ต้องบังคับให้วางแผนก่อน
START ──▶ [ planner node ] ──▶ [ react node ] ──┐
│ ▲ │
จัดหมวดคำถาม └── วน tool ─┘
simple / complex │
▼
END
(รายละเอียดกลไก react node แบบเต็ม — วิธี LangGraph วนลูป, state, tool_calls — อยู่ใน ReAct Agent ทำงานอย่างไร)
Planner: จัดหมวดคำถามก่อนคิดคำตอบ
planner.py เรียกโมเดลเบาๆ 1 ครั้งต่อคำถาม (ปิด thinking, temperature=0.0 เพื่อผลลัพธ์นิ่ง, cache client instance ไว้ไม่สร้างใหม่ทุกครั้ง) ให้จัดหมวดเป็น JSON ล้วนๆ 2 แบบ:
{"mode": "simple"}— ตอบตรงได้เลย ไม่ต้องวางแผน{"mode": "complex", "plan": [...2-6 ขั้นตอนที่เป็นผลลัพธ์ที่ต้องการ...]}— ต้องแตกเป็นขั้นตอนก่อน
Contract ของ JSON เข้มงวด และมีretry แค่ 1 ครั้งถ้า parse ไม่ผ่าน (JSON เพี้ยน, mode ไม่รู้จัก, plan ไม่ใช่ list, complex แต่ step น้อยกว่า 2 — ทุกกรณีนี้ถือเป็น schema violation ที่ต้อง retry ไม่ใช่ปัดตกเป็น simple เงียบๆ) — ถ้ายัง parse ไม่ผ่านอีกรอบสอง ค่อย fallback ไปเป็น simple mode แทนที่จะค้าง ถ้าตอบมาเกิน 6 ขั้นตอน ระบบตัดเหลือ 5 ขั้นแรก + ขั้นสุดท้ายเสมอ (ไม่ใช่ตัดจาก step 7 ทิ้งตรงๆ) เพราะขั้นสุดท้ายมักเป็น synthesis ที่ห้ามหาย
ในซอร์สยังมีฟังก์ชัน
is_trivial()(จับคำทักทาย/เรียกความจำด้วย regex) หลงเหลืออยู่ แต่ไม่ได้ถูกเรียกใช้จริงในเส้นทางการทำงาน — เหลือไว้เป็น diagnostic ให้ unit test เท่านั้น การจัดหมวด simple/complex จริงๆ ผ่าน LLM call ด้านบนเสมอ ไม่มี shortcut ข้ามโมเดลด้วย regex
กลไกที่น่าสนใจที่สุด: บังคับสร้างแผนแบบ deterministic
จุดที่ทำให้ LOCAL AGENT TH ต่างจาก agent ทั่วไปคือฟังก์ชัน _force_plan_or_directive() ใน graph.py — แทนที่จะหวังว่าโมเดลจะเรียก tool สร้างแผนเองตามที่ planner บอก ระบบเขียนโค้ดบังคับให้เกิดขึ้นจริง:
- ถ้าคำถามมีคำกริยาชี้ชัดว่าเป็นงานค้นคว้า/วิจัย (search-verb) และ planner บอกว่า “complex” — โค้ดจะฉีดคู่ข้อความปลอมเข้าไปในประวัติสนทนาโดยตรง:
AIMessageที่มีtool_calls=[create_plan]ตามด้วยToolMessageที่บรรจุแผนงานจาก planner ไว้แล้ว การเรียก tool สร้างแผนจึงถูกการันตีว่าจะเกิดขึ้นโดยไม่ต้องพึ่งว่าโมเดลจะ “นึกออก” ที่จะเรียกเองไหม - ถ้า planner บอกว่า “simple” — ฉีดข้อความสั่งแบบ soft directive แทน (เป็นคำแนะนำ ไม่ใช่ tool call ปลอม) ให้โมเดลตอบตรงได้เลยโดยไม่ต้องผ่านขั้นตอนวางแผน
หลักคิดเดียวกับ AGENT LITE — โค้ด ไม่ใช่ prompt — แต่ประยุกต์กับปัญหาคนละแบบ: AGENT LITE ใช้โค้ดชดเชยความผิดพลาดของโมเดลเล็ก ส่วน LOCAL AGENT TH ใช้โค้ดการันตีขั้นตอนสำคัญไม่ให้พลาดแม้โมเดลจะฉลาดกว่ามากก็ตาม
การจัดการ context ยาว: บีบอัดแบบมีเกณฑ์ชัดเจน
Session สนทนายาวๆ ทำให้ context ใกล้เพดานของโมเดลได้ ระบบตั้งค่าคงที่ 4 ตัวควบคุมการบีบอัด:
| ค่าคงที่ | ความหมาย |
|---|---|
_COMPACT_TRIGGER = 1.00 |
context เต็ม 100% ของเพดานที่ตั้งไว้ → เริ่มบีบอัด |
_COMPACT_RESET = 0.70 |
บีบอัดจนเหลือ 70% แล้วหยุด ไม่บีบจนสั้นเกินจำเป็น |
_COMPACT_MIN_MSGS = 4 |
ต้องมีอย่างน้อย 4 ข้อความถึงจะเริ่มพิจารณาบีบอัด (กันบีบ session สั้นๆ โดยไม่จำเป็น) |
_COMPACT_STRIP_MAX_CHARS = 5000 |
ตัดผลลัพธ์ tool ที่ยาวเกิน 5,000 ตัวอักษรต่อรายการก่อน ไม่ใช่ตัดข้อความสนทนา |
การบีบอัดเลือกตัดผลลัพธ์ดิบจาก tool ที่ยาวเกินไปก่อนเป็นอันดับแรก เพราะเป็นส่วนที่กินพื้นที่มากสุดแต่มีค่าน้อยสุดเมื่อผ่านไปหลายรอบแล้ว
ระบบความปลอดภัย: สองชั้นที่ทำงานอิสระจากกัน
จุดที่ต่างจาก AGENT LITE ชัดเจนที่สุดคือ LOCAL AGENT TH มีเครื่องมือให้เข้าถึงไฟล์และรันโค้ดได้จริง (ไม่ใช่แค่ค้นเว็บ) จึงต้องมีระบบป้องกันที่แน่นหนากว่า — ออกแบบเป็น 2 ชั้นที่ทำงานอิสระจากกัน ไม่พึ่งพากันเอง:
คำสั่ง bash/python_exec เข้ามา
│
▼
┌───────────────────────────────┐
│ ชั้น 1: Python path guard │ ← ถ้าพลาด/มีบั๊ก...
│ เขียนได้แค่ workspace/ │
│ อ่านบล็อก .ssh/.aws/.gnupg │
└───────────────────────────────┘
│
▼
┌───────────────────────────────┐
│ ชั้น 2: macOS Seatbelt (OS) │ ← ...ชั้นนี้ยังกันไว้ได้
│ sandbox-exec บังคับที่ระดับ OS │ เพราะเป็นคนละกลไก ไม่พึ่งกัน
│ ไม่ผ่าน Python เลย │
└───────────────────────────────┘
│
▼
คำสั่งรันจริง (ถ้าผ่านทั้ง 2 ชั้น)
ชั้นที่ 1: Python-level path guard
- เขียนไฟล์: จำกัดเฉพาะในโฟลเดอร์
workspace/เท่านั้น เขียนออกนอกนี้ไม่ได้เลย - อ่านไฟล์: เปิดกว้างกว่า (อ่าน path นอก workspace ได้บ้างเพื่อความยืดหยุ่น) แต่บล็อกกลุ่ม path ที่เข้าข่ายเก็บ credential เช่น
.ssh,.aws,.gnupgและ path ระบบสำคัญ - กัน path traversal: ทุก path ผ่าน
os.path.realpath()ก่อนตรวจสอบ กัน symlink หลอกและ../ไต่ออกนอกขอบเขตที่อนุญาต
ชั้นที่ 2: macOS sandbox-exec (Seatbelt) — ชั้น OS
Layer แรกเป็นแค่โค้ด Python ซึ่งถ้ามี bug หรือถูกเลี่ยงได้ก็ยังเสี่ยง — ทีมพัฒนาจึงเพิ่มชั้นที่ 2 ที่ทำงานที่ระดับระบบปฏิบัติการไม่ใช่ระดับแอป: ทุกครั้งที่รันคำสั่ง bash, bash_bg หรือ python_exec process ย่อยนั้นถูกห่อด้วย macOS Seatbelt sandbox profile ที่กำหนด rule แบบ (deny file-write*) / (deny file-read*) / (allow file-write* <path>) ชัดเจน และมี timeout ต่อ process แยกกัน
บทเรียนจริงที่เจอระหว่างพัฒนา: สมมติฐานเดิมของทีมคือ sandbox-exec ใช้กติกา “last-match-wins” (rule หลังสุดที่ match ชนะ) — แต่การทดสอบจริงพบว่าไม่ใช่: เมื่อ path ใดซ้อนทับกัน (เช่น deny ทั้งโฟลเดอร์ แล้ว allow path ย่อยข้างใน) deny ชนะ allow เสมอไม่ว่าจะเขียนก่อนหรือหลังในไฟล์ profile เรื่องนี้ถูกค้นพบตอนแก้บั๊กจริง: bash('cat /etc/passwd') เคยอ่านไฟล์ผ่านได้ทั้งที่ตั้งใจ block เพราะ deny เดิมครอบแค่ ~/.ssh/~/.aws/~/.gnupg แต่ไม่ครอบ /etc เมื่อลองแก้ด้วยการ deny ทั้ง /etc แล้ว allow เฉพาะ /etc/ssl กลับมา ก็พังคนละแบบทันที (curl/git ผ่าน HTTPS ใช้ไม่ได้ เพราะ deny ทั้งโฟลเดอร์ชนะ allow ย่อยเสมอ ไม่มีทาง exception กลับมาได้) จนต้อง fix ด้วยการ deny เป็นไฟล์เจาะจง (literal) แทนการ deny ทั้งโฟลเดอร์ (subpath) — เช่น deny เฉพาะ /etc/passwd, /etc/shadow, /etc/sudoers ตรงๆ โดยไม่แตะ /etc/ssl, /etc/hosts ที่ curl/git ต้องใช้
ความสำคัญของชั้นนี้คือ แม้ path guard ชั้น Python จะพลาดหรือถูกข้าม (เช่นบั๊กในโค้ด หรือช่องโหว่ที่ยังไม่รู้) sandbox-exec ยังบล็อกการเข้าถึงไฟล์นอกขอบเขตที่ระดับ OS อยู่ดี — นี่คือหลัก defense-in-depth: สองชั้นป้องกันคนละแบบ ไม่ใช่ป้องกันซ้ำแบบเดียวกัน และแม้แต่ชั้นนี้เองก็ยังพลาดสมมติฐานผิดได้ ต้องทดสอบจริงถึงจะมั่นใจ ไม่ใช่แค่เชื่อ documentation ของเครื่องมือ
เครื่องมือ 26 ตัว จัดเป็น 9 หมวด
| หมวด | จำนวน | ตัวอย่าง |
|---|---|---|
| Web & Research | 7 | ค้นเว็บ, ดึงเนื้อหาหน้าเว็บ, ค้นข่าว |
| File & Code | 5 | อ่าน/เขียนไฟล์, แก้โค้ด, list โฟลเดอร์ |
| Code Execution | 4 | รัน bash (sync), bash แบบ background (bash_bg), รัน Python, วาดกราฟ (ทั้งหมดผ่าน sandbox-exec) |
| Vision | 1 | อ่านข้อความจากรูปด้วย OCR |
| Memory | 1 | จำข้อเท็จจริงเกี่ยวกับผู้ใช้ข้าม session แบบถาวร |
| Audio | 1 | อ่านข้อความออกเสียงผ่านลำโพงเครื่องที่รัน agent |
| MCP | 4 | ต่อ/เรียก MCP server ภายนอกที่ผู้ใช้ระบุ URL ให้ |
| Automation | 1 | ตั้ง standing trigger (awake) ให้ agent ทำงานเองตามเวลา/เมื่อไฟล์เปลี่ยน — ดูหัวข้อถัดไป |
| Planning & Loops | 2 | สร้างแผน (create_plan), วนทำตามแผน |
| Research Skill | 1 | โหมดค้นคว้าเชิงลึกเฉพาะทาง (เปิดด้วย /research) |
Standing triggers: agent ทำงานเองได้โดยไม่ต้องมีคนพิมพ์
จุดที่ต่างจากโมเดล chat ทั่วไปชัดเจน — เครื่องมือ awake ให้โมเดลตั้งเงื่อนไขล่วงหน้าว่าเมื่อไรควรกลับมาทำงานเองอีกครั้งโดยไม่ต้องรอผู้ใช้พิมพ์ถาม มี daemon thread (awake_engine.py) คอยเช็คทุก 30 วินาทีว่าเงื่อนไขไหน “ตื่น” แล้วบ้าง แล้วส่ง query เข้า agent loop เดิมทันที:
| ประเภท trigger | ทำงานเมื่อ |
|---|---|
file |
ไฟล์ที่ระบุถูกแก้ไข |
every |
ครบทุก N นาทีตามที่ตั้ง |
times / run_at |
ถึงเวลาที่กำหนดไว้ล่วงหน้า |
once |
ครั้งเดียวหลังจากดีเลย์ที่ตั้ง |
screen |
จอภาพเปลี่ยนแปลง (อ่านผ่าน OCR) |
screen เห็นการเปลี่ยนแปลงบนจอและแจ้งได้ แต่ agent เวอร์ชันนี้คลิก/พิมพ์บนจอเองไม่ได้ (ไม่มี computer-use tool ในชุดนี้) — เหมาะกับ “เฝ้าดูแล้วบอก” มากกว่า “เฝ้าดูแล้วกดให้”
trigger ที่ fired แล้วจะแย่ง lock เดียวกับคำถามจริงของผู้ใช้ (_busy) เสมอ — ป้องกันไม่ให้งานเบื้องหลังกับคำถามสดของผู้ใช้รันพร้อมกันจนชนกัน ใครถือ lock อยู่ก่อนก็ทำงานให้เสร็จก่อน ส่วนกรณีรัน engine พร้อมกันหลายจุด (เช่นเครื่องหลักกับ Telegram bot) มีกลไกแยกต่างหากตัดสินว่า process ไหน “เป็นเจ้าของ” การยิง trigger จริง กันไม่ให้ trigger เดียวกันทำงานซ้ำสองรอบ
Skill modes: สลับ prompt และ toolset ทั้งชุด
สั่ง /research หรือ /pdf_to_text ในแชทจะเปลี่ยนทั้ง system prompt และชุดเครื่องมือที่โมเดลมองเห็น ไม่ใช่แค่เพิ่ม instruction เข้าไปในบทสนทนาเดิม — วิธีนี้ทำให้โมเดลโฟกัสกับ tool ที่เกี่ยวข้องกับงานนั้นจริงๆ ไม่สับสนกับเครื่องมือทั่วไปที่ไม่จำเป็นสำหรับงานเฉพาะทาง
ทำไมเรื่องนี้ถึงสำคัญ
LOCAL AGENT TH พิสูจน์ว่าการมีโมเดลใหญ่กว่าไม่ได้แปลว่าเลิกใช้ guardrail แบบ deterministic ได้ทั้งหมด — แค่ เปลี่ยนจุดที่ใช้: AGENT LITE ใช้โค้ดชดเชยจุดอ่อนของโมเดลเล็กแทบทุกขั้นตอน ส่วน LOCAL AGENT TH ปล่อยให้โมเดลตัดสินใจเนื้อหาส่วนใหญ่เอง แต่ยังคงบังคับขั้นตอนวางแผนด้วยโค้ดและป้องกันความปลอดภัยด้วย 2 ชั้นอิสระ เพราะงานที่เข้าถึงไฟล์และรันโค้ดได้จริงมีความเสี่ยงสูงกว่าการค้นเว็บอย่างเดียวมาก — ยิ่งงานเสี่ยงสูง ยิ่งไม่ควรฝากไว้กับดุลพินิจของโมเดลล้วนๆ
ความคิดเห็น
กำลังโหลดความคิดเห็น...