สวัสดีครับ ตอนนี้ผมกำลังพัฒนา Spanlens ซึ่งเป็นแพลตฟอร์ม observability แบบโอเพนซอร์สสำหรับดูการบันทึกการเรียก LLM การติดตามค่าใช้จ่าย และ agent trace ได้ในที่เดียว

เหตุผลที่สร้าง

พอได้ใช้ LLM ในโปรเจกต์ส่วนตัว ก็มีอยู่สองอย่างที่คอยติดใจอยู่ตลอด

อย่างแรกคือการติดตามค่าใช้จ่าย
ตอนทำ browser extension ที่ใช้ GPT จุดเริ่มต้นมาจากตอนที่ได้รับบิล OpenAI ตอนสิ้นเดือนแล้วตกใจเป็นครั้งแรก แม้จะไม่ใช่เงินจำนวนมากเพราะยังเป็นนักศึกษา แต่สิ่งที่เห็นมีแค่ยอดรวม โดยไม่รู้ว่าฟีเจอร์ไหนใช้เงินไปเท่าไร หรือแต่ละโมเดลใช้โทเค็นเฉลี่ยเท่าไร ต้องคอยใส่ console.log ลงในโค้ดทุกครั้ง export เป็น CSV แล้วไปสรุปต่อใน Excel พร้อมคูณราคาต่อโมเดลเพื่อคำนวณตัวเลขประมาณการ วนแบบนี้ซ้ำ ๆ

อีกอย่างคือการดีบักเอเจนต์
นี่เป็นสิ่งที่เจอด้วยตัวเองตอนเพิ่มการเชื่อมต่อ LangGraph ให้ Spanlens พอมี trace หนึ่งที่ใช้เวลา 30 วินาทีและมีการเรียก LLM หลายตัวปะปนกัน ก็ต้องมานั่งไล่จากล็อกด้วยมือว่า

  • เวลาไปหายอยู่ที่โหนดไหน
  • ทำไมถึงเรียกเครื่องมือเดียวกันซ้ำสองครั้ง
  • state ของ LangGraph เปลี่ยนเมื่อไร

ผมเลยสร้าง Spanlens ขึ้นมาเพื่อช่วยลดปัญหาสองอย่างนี้

ฟีเจอร์หลัก

  1. รวมเข้ากับระบบได้ด้วย baseURL แค่บรรทัดเดียว

ถ้าเปลี่ยน baseURL ของ OpenAI/Anthropic/Gemini SDK ให้เป็นรูปแบบอย่าง https://api.spanlens.io/proxy/openai/v1 ระบบจะบันทึก request, response, token และค่าใช้จ่ายให้อัตโนมัติ
response จะ passthrough ตรง ๆ ดังนั้น streaming, tool calling และ JSON mode จะทำงานเหมือนต้นฉบับทุกอย่าง

ถ้าเป็นกรณีแบบเอเจนต์ที่ต้องใช้การ wrap ก็สามารถ inject trace_id, span_id ผ่าน SDK เพื่อบันทึกความสัมพันธ์แบบ parent-child ไปพร้อมกันได้

  1. agent trace + มุมมอง topology ของ LangGraph

trace ไม่ได้ดูได้แค่เป็นไทม์ไลน์ตามเวลา แต่ยังดูซ้อนอยู่บนโหนดของกราฟจริงได้ด้วย ถ้าเป็นเอเจนต์ที่เขียนด้วย LangGraph ก็จะเห็นได้ในหน้าจอเดียวว่าเวลาไปค้างอยู่ที่โหนดไหน และ edge ไหนถูกวิ่งผ่านบ่อยที่สุด

  1. การวิเคราะห์ Critical Path อัตโนมัติ

ระบบจะแสดง call chain ที่กิน latency มากที่สุดใน trace ให้อัตโนมัติ ผมอยากลดจำนวนคลิกที่ต้องใช้กว่าจะหาคำตอบได้ว่า “trace นี้ช้าทำไม”

  1. เปรียบเทียบสถิติ Prompts A/B

เปรียบเทียบ prompt สองเวอร์ชันของ prompt เดียวกัน โดยดูจาก latency, ค่าใช้จ่าย และปริมาณการใช้โทเค็น ไม่ได้ดูแค่ค่าเฉลี่ยต่างกันเท่าไร แต่ใช้ Welch t-test เพื่อแสดงความแตกต่างโดยคำนึงถึงความแปรปรวนของตัวอย่างด้วย ตั้งใจใส่มาเพื่อให้พูดได้ว่าไม่ใช่แค่ “ค่าเฉลี่ยต่ำกว่านิดหน่อย” แต่เป็น “แตกต่างอย่างมีนัยสำคัญ”

  1. self-hosting

สามารถนำไปติดตั้งบนเซิร์ฟเวอร์ของตัวเองได้ด้วย Docker image และโค้ดชุดเดียวกับเวอร์ชัน SaaS ก็เปิดไว้ใน public repository แบบตรงกันทั้งหมด โค้ดทั้งโปรเจกต์ใช้สัญญาอนุญาต MIT

ทำอย่างไรถึงสร้างได้

ปัจจุบัน pipeline โดยคร่าว ๆ เป็นแบบนี้

  • คำขอที่ผ่านพร็อกซีจะรับด้วย Hono จากนั้นแยก Authorization header และเมทาดาทา X-Spanlens-* ออกจากกัน แล้วถอดรหัส provider key ด้วย AES-256-GCM เพื่อใช้เฉพาะในหน่วยความจำก่อนเรียกใช้งานจริงเท่านั้น
  • สำหรับ response แบบสตรีม จะใช้ body.tee() เพื่อส่งสตรีมต้นฉบับกลับไปยังไคลเอนต์ทันที และให้ตัวที่คัดลอกไว้ถูก parser ประมวลผลต่อเบื้องหลังเพื่อคำนวณโทเค็นและค่าใช้จ่าย
  • ล็อกจะถูกเขียนลง ClickHouse แบบ asynchronous ถ้า INSERT ล้มเหลว จะเก็บไว้ใน fallback queue ของ Supabase แล้วให้ cron มาลองใหม่ โครงสร้างนี้เป็นแบบ fire-and-forget แต่พยายามหลีกเลี่ยงการสูญหายของข้อมูล
  • ราคาของโมเดลเก็บไว้ในตารางฐานข้อมูล และแคชด้วย 5-minute TTL แบบ stale-while-revalidate โดยมีราคา fallback เป็นตาข่ายนิรภัยตอน cold start

ตอนแรกมันเป็นแค่พร็อกซีธรรมดา แต่พอใช้งานจริง สิ่งที่สำคัญกว่าล็อกดิบคือ trace ที่ถูกทำให้เป็นมาตรฐาน หากจะหาคำตอบว่า “trace นี้ช้าทำไม” ก็ไม่ได้ต้องการแค่ลำดับการเรียก แต่ต้องการความสัมพันธ์ parent-child, การเรียกแบบขนาน และ Critical Path ด้วย ฟีเจอร์อย่างมุมมอง topology ของ LangGraph หรือ Critical Path อัตโนมัติจึงเกิดจากตรงนี้

สแตกที่ใช้คือ Next.js 14, Hono, Supabase Postgres, ClickHouse และทั้งหมดอยู่ใน TypeScript pnpm monorepo

จุดที่ยังคิดอยู่

  • ผมอยากทำให้ self-hosting ใช้แค่ docker run บรรทัดเดียว แต่ถ้าจะทำแบบนั้นก็ต้องเอา Supabase Postgres ลงมาด้วย ตอนนี้ docker-compose ตั้งอยู่บนสมมติฐานว่าใช้ managed Supabase และมี 3 คอนเทนเนอร์คือ web, server, ClickHouse เลยกำลังคิดอยู่ว่าควรทำตัวเลือก self-host สำหรับ Supabase ด้วย หรือคงสมมติฐานแบบ managed ไว้ต่อไปดี

  • ในฟีเจอร์เปรียบเทียบ Prompts A/B ตอนนี้มีการคำนวณ Welch t-test แล้ว แต่ยังตัดสินใจไม่ได้ว่าการแสดง p-value ตรง ๆ จะมีประโยชน์ไหม หรือควรแสดงแค่ป้ายสรุปผลลัพธ์ว่าแตกต่างอย่างมีนัยสำคัญ/ไม่แตกต่างเท่านั้น รวมถึงกำลังคิดด้วยว่าถ้าขนาดตัวอย่างเล็ก (n<30) ควรเตือนอย่างไร

  • ในมุมมอง topology ของ LangGraph ตอนนี้แสดงโหนด, edge และ Critical Path แล้ว แต่ตั้งใจตัดการเปลี่ยนแปลงของ state channel ออกไปก่อนเพราะมี noise มากเกินไป เลยอยากฟังความเห็นว่าจริง ๆ แล้วในการดีบักจำเป็นต้องติดตามการเปลี่ยนแปลงของ state มากกว่านี้ไหม หรือระดับตอนนี้เหมาะสมแล้ว

นี่เป็นโปรเจกต์ที่เริ่มจากปัญหาที่ผมเจอเองและค่อย ๆ ปรับปรุงมาอย่างต่อเนื่อง

[ Spanlens ]
เว็บ: https://www.spanlens.io
GitHub: https://github.com/spanlens/Spanlens
คู่มือ self-hosting: https://www.spanlens.io/docs/self-host

ไม่ว่าจะเป็น UX ของแดชบอร์ด, การมองเห็น trace, วิธีรวมพร็อกซีเข้ากับระบบ, ประสบการณ์ self-hosting หรือมุมมองแบบไหนก็ตาม ถ้าให้ฟีดแบ็กได้จะขอบคุณมากครับ โดยเฉพาะถ้ามี provider อื่นนอกเหนือจาก OpenAI/Anthropic/Gemini ที่อยากให้รองรับ รบกวนบอกกันในคอมเมนต์ได้เลย

บนเว็บไซต์มีเดโมเวอร์ชันอยู่ด้วย ฝากติดตามกันด้วยครับ
ตอนนี้ UI เป็นภาษาอังกฤษ และกำลังจะเพิ่มการรองรับภาษาเกาหลีในเร็ว ๆ นี้.

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น