Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 

Repository files navigation

oracle-v2: ยกระดับจาก ChromaDB สู่ Qdrant

หลังจากที่ oracle-v2 ผ่านขั้น prototype มาด้วย ChromaDB เราตัดสินใจย้ายมาใช้ Qdrant เพื่อเตรียมความพร้อมสู่การใช้งานจริงในระดับที่กว้างขึ้น โดยเน้นไปที่ ecosystem ที่แข็งแกร่ง, dashboard ที่ทรงพลัง, และมาตรฐาน HTTP REST ที่ทั้ง AI agent, RAG framework และองค์กรทั่วโลกรองรับอยู่แล้ว สามารถติดตั้ง Qdrant ใน Docker ใช้แบบ local ได้เหมือนกัน


🧠 Background

oracle-v2 คือ MCP server ที่ทำหน้าที่เป็น "ความจำ" ให้ AI agent (Oracle) เก็บ learnings, patterns, retrospectives และ decisions แล้วให้ Claude ค้นหาผ่าน hybrid search (FTS5 + vector)

ในช่วงแรก เราใช้ ChromaDB ซึ่งตอบโจทย์การเริ่มต้นได้ดีมาก — ติดตั้งง่าย จัดการ embeddings ให้เอง ไม่ต้องตั้งค่าอะไรมาก แต่เมื่อ project เริ่มโตขึ้นและต้องการ ecosystem ที่กว้างกว่า เราจึงตัดสินใจยกระดับมาใช้ Qdrant


🤔 ทำไมถึงเลือก Qdrant?

ไม่ใช่เพราะ ChromaDB ไม่ดี — แต่เพราะ Qdrant ตอบโจทย์ scale และ future-proof ของ project มากกว่า

🌍 Ecosystem & Global Support

Qdrant เป็น vector database ที่ได้รับการสนับสนุนอย่างกว้างขวางในระดับสากล framework ชั้นนำทั้ง LangChain, LlamaIndex, AutoGen, CrewAI, Haystack ต่างก็มี Qdrant integration พร้อมใช้งานทันที โดยไม่ต้องเขียน adapter เพิ่ม

ChromaDB  → Python-first, community-driven
Qdrant    → Language-agnostic, corporate-grade, มี official client 10+ ภาษา

เมื่อ oracle-v2 ต้องการเชื่อมต่อกับ AI agent หรือ RAG pipeline ตัวอื่นในอนาคต — Qdrant คือตัวเลือกที่ไม่ต้องคิดสองครั้ง

🏭 Production-Ready Architecture

Qdrant ออกแบบมาสำหรับ production ตั้งแต่ต้น รองรับการ deploy บน VPS, Kubernetes, หรือ Coolify ได้ทันทีผ่าน HTTP REST API ที่เป็นมาตรฐาน ระบบเป็น stateless ซึ่งจัดการและ scale ได้ง่ายกว่ามาก

📊 Built-in Dashboard

Qdrant มาพร้อม official web UI สำหรับ inspect ข้อมูล vector, ดู collection stats, และรัน query ทดสอบได้จาก browser โดยตรง เปิดที่ http://localhost:6333/dashboard ได้เลย ไม่ต้องติดตั้งอะไรเพิ่ม

🔌 Vector-Agnostic

Qdrant ไม่ผูกติดกับ embedding engine ใด — ส่ง vector มาแล้วมันจัดการให้ ทำให้เราเลือกใช้ @xenova/transformers รัน ONNX บนเครื่องเองได้อิสระ และสลับ model ได้ทุกเมื่อโดยไม่กระทบ database


⚖️ ChromaDB vs Qdrant: เลือกให้เหมาะกับ Stage

หัวข้อ ChromaDB Qdrant
เหมาะกับ Prototype / Local dev Production / Scale
การเชื่อมต่อ Python-first, Local subprocess HTTP REST, ทุกภาษา, Distributed
Embedding จัดการให้อัตโนมัติ Vector-agnostic, เลือก engine ได้อิสระ
Dashboard Community plugins Official Web UI built-in
Ecosystem Python community Corporate-grade, 10+ official clients
AI Framework support LangChain, LlamaIndex LangChain, LlamaIndex, AutoGen, CrewAI, Haystack + อีกมาก
Deploy Local files VPS, Docker, Kubernetes, Coolify

สรุป: ChromaDB เยี่ยมมากสำหรับเริ่มต้นและทดลอง Qdrant เหมาะกว่าเมื่อต้องการ scale และเชื่อมต่อกับ ecosystem ที่กว้างขึ้น


🏗️ สถาปัตยกรรมก่อนและหลัง

เดิม:

Claude Code → oracle-v2 (Bun/TS)
                  ↓
          MCP Client (JS)
                  ↓ stdio subprocess
          chroma-mcp (uvx Python)
                  ↓
          ChromaDB (local files)

ใหม่:

Claude Code → oracle-v2 (Bun/TS)
                  ↓
          QdrantClient (pure HTTP)
                  ↓ REST API (stateless)
          Qdrant (local / VPS / Cloud)

เปลี่ยนจาก Python subprocess → HTTP REST ทำให้ระบบเป็น stateless, deploy ง่าย, และเชื่อมต่อกับ ecosystem ได้ทันที


🔧 สิ่งที่เปลี่ยนในโค้ด

1️⃣ QdrantClient — HTTP แทน subprocess

// ใหม่: class ที่ wrap fetch() เอาไว้
export class QdrantClient {
  constructor(
    private collectionName: string,
    private qdrantUrl: string,
    private apiKey: string
  ) {}

  private async request(method: string, path: string, body?: any): Promise<any> {
    const response = await fetch(`${this.qdrantUrl}${path}`, {
      method,
      headers: { 'Content-Type': 'application/json', 'api-key': this.apiKey },
      body: body ? JSON.stringify(body) : undefined,
    });
    return JSON.parse(await response.text());
  }

  async connect(): Promise<void> {
    // แค่ health check — stateless HTTP ไม่ต้องจัดการ connection
    await this.request('GET', '/collections');
  }
}

HTTP REST เป็น interface มาตรฐานที่ทุก framework เข้าใจ — ไม่ว่าจะเรียกจาก JS, Python, Go หรือ Rust


2️⃣ Embeddings รันใน JS ผ่าน ONNX (Vector-agnostic คือข้อดี)

เพราะ Qdrant ไม่ผูกติดกับ embedding engine ใด เราจึงเลือกรัน @xenova/transformers บนเครื่องเองได้ — ไม่ต้องพึ่ง Python, ไม่มี API call ออกนอก:

import { pipeline } from '@xenova/transformers';

let embeddingPipeline: any = null;

async function computeEmbedding(text: string): Promise<number[]> {
  if (!embeddingPipeline) {
    embeddingPipeline = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
  }
  const result = await embeddingPipeline(text, { pooling: 'mean', normalize: true });
  return Array.from(result.data);
}

ใช้ model all-MiniLM-L6-v2 (384 dimensions) ต้องการเปลี่ยน model ในอนาคตก็เปลี่ยนได้เลย ไม่กระทบ Qdrant เลย


3️⃣ ID ต้องแปลงเป็น UUID

Qdrant ต้องการ uint64 หรือ UUID format oracle ID ของเราหน้าตาแบบ learning-abc123-xyz แก้ด้วย deterministic MD5 hash:

function toQdrantId(oracleId: string): string {
  const hash = createHash('md5').update(oracleId).digest('hex');
  return `${hash.slice(0,8)}-${hash.slice(8,12)}-${hash.slice(12,16)}-${hash.slice(16,20)}-${hash.slice(20,32)}`;
}

points.push({
  id: toQdrantId(doc.id),        // UUID สำหรับ Qdrant
  vector,
  payload: {
    oracle_id: doc.id,           // string ID เดิมเก็บไว้ใน payload
    document: doc.document,
    ...doc.metadata,
  },
});

4️⃣ Filter Syntax

// เดิม: JSON string
args.where = JSON.stringify({ type: "learning" });

// ใหม่: structured object ตาม Qdrant spec
body.filter = {
  must: Object.entries(whereFilter).map(([key, value]) => ({
    key,
    match: { value },
  })),
};

5️⃣ Score กับ Distance สลับกัน

Qdrant คืน similarity score (1 = เหมือนกันเป๊ะ) แทน distance (0 = เหมือนกันเป๊ะ) แปลงให้ตรงกันด้วยบรรทัดเดียว:

distances: results.map((r: any) => 1 - (r.score || 0)),

6️⃣ ป้องกัน Qdrant บน VPS: 2 แนวทาง

เมื่อ Qdrant รันบน VPS ต้องป้องกันไม่ให้คนอื่นเข้าถึงได้ มีสองแนวทางหลัก:


🔵 แนวทาง A: Cloudflare Zero Trust

เหมาะกับ: ทีม, ผู้ใช้หลายคน, มี CF account อยู่แล้ว วาง CF Access application ไว้หน้า Qdrant แล้วใช้ service token ยืนยันตัวตน:

private get headers(): Record<string, string> {
  const h: Record<string, string> = {
    'Content-Type': 'application/json',
    'api-key': this.apiKey,
  };
  const cfClientId = process.env.CF_CLIENT_ID;
  const cfClientSecret = process.env.CF_CLIENT_SECRET;
  if (cfClientId) h['CF-Access-Client-Id'] = cfClientId;
  if (cfClientSecret) h['CF-Access-Client-Secret'] = cfClientSecret;
  return h;
}

ตั้งค่า env: QDRANT_URL, QDRANT_API_KEY, CF_CLIENT_ID, CF_CLIENT_SECRET


🟢 แนวทาง B: Tailscale (ง่ายกว่า สำหรับใช้คนเดียว)

เหมาะกับ: developer คนเดียว, ไม่อยากเปิด port สู่โลกภายนอกเลย Tailscale สร้าง private mesh network ระหว่างเครื่องของเรา Qdrant ไม่ถูก expose ออก internet เลย

# ติดตั้งบน VPS
curl -fsSL https://tailscale.com/install.sh | sh && tailscale up
# ใช้ hostname (แนะนำ — ไม่กระทบถ้า IP เปลี่ยน)
export QDRANT_URL=http://computername.tail2bcecd.ts.net:6333
export QDRANT_API_KEY=your-api-key
# ไม่ต้องมี CF headers เลย

โค้ด QdrantClient ไม่ต้องแตะเลย — Tailscale ทำงานระดับ network ล้วนๆ

⚖️ เปรียบเทียบสองแนวทาง

Cloudflare Zero Trust Tailscale
Qdrant เปิดสู่ internet? ใช่ (ผ่าน CF proxy) ไม่ (private เท่านั้น)
ต้องเพิ่ม request header? ใช่ (CF-Access-*) ไม่
ต้องแก้โค้ด? ใช่ (เพิ่ม CF headers) ไม่เลย
เหมาะกับ ทีม / หลายคน คนเดียว / personal
ต้องใช้ CF account Tailscale (ฟรี)
ใช้ได้ offline? ไม่ ได้ (LAN fallback)

🐳 รัน Qdrant บนเครื่องตัวเองผ่าน Docker

ไม่มี VPS ก็ไม่เป็นไร โค้ด QdrantClient ไม่ต้องเปลี่ยน แค่ชี้ QDRANT_URL ไปที่ localhost

🚀 Option A: รันด่วน (one-liner)

docker run -d --name qdrant -p 6333:6333 qdrant/qdrant
export QDRANT_URL=http://localhost:6333
export QDRANT_API_KEY=   # ว่างได้ — local Qdrant ไม่มี auth by default

UI: เปิด http://localhost:6333/dashboard ดู collection และรัน query ได้เลย

💾 Option B: Docker Compose (แนะนำ — ข้อมูลไม่หาย)

# docker-compose.yml
services:
  qdrant:
    image: qdrant/qdrant
    container_name: qdrant
    ports:
      - "6333:6333"
      - "6334:6334"
    volumes:
      - qdrant_data:/qdrant/storage
    restart: unless-stopped

volumes:
  qdrant_data:

🔑 Option C: เพิ่ม API Key (local auth)

services:
  qdrant:
    image: qdrant/qdrant
    environment:
      - QDRANT__SERVICE__API_KEY=your-local-secret
    ports:
      - "6333:6333"
    volumes:
      - qdrant_data:/qdrant/storage
    restart: unless-stopped

🔄 สลับระหว่าง Local และ VPS

สภาพแวดล้อม QDRANT_URL QDRANT_API_KEY
Local Docker http://localhost:6333 ว่าง หรือ local key
VPS (Coolify) https://qdrant.yourdomain.com API key ของ VPS
VPS + Tailscale http://computername.tail2bcecd.ts.net:6333 API key
Qdrant Cloud https://xyz.qdrant.tech cloud API key

♻️ Re-index หลังจากย้าย instance

QDRANT_URL=http://localhost:6333 \
ORACLE_REPO_ROOT=/path/to/your-oracle \
bun run ~/.local/share/oracle-v2/src/indexer.ts

SQLite คือ source of truth — Qdrant คือ index ที่ derived ออกมา ย้าย instance ทีไรรัน indexer ใหม่เสมอ


✅ Migration Checklist

  • เลือกที่รัน Qdrant (Local Docker / VPS / Cloud)
  • เพิ่ม @xenova/transformers ใน dependencies
  • แทน ChromaMcpClient ด้วย QdrantClient
  • เพิ่มฟังก์ชัน toQdrantId() สำหรับแปลง ID
  • อัป filter syntax (wheremust[])
  • แปลง score เป็น distance (1 - score)
  • Re-index ข้อมูลทั้งหมด (bun run indexer.ts)

📊 ผลลัพธ์หลังย้าย

ตัวชี้วัด ChromaDB Qdrant
Cold start ~3-5 วินาที ~100ms
ความเสถียร Subprocess-based Stateless HTTP
ย้ายข้ามเครื่อง Local only VPS / Cloud / Distributed
AI Framework support Python ecosystem Global ecosystem (10+ official clients)
Built-in UI ไม่มี Web Dashboard พร้อมใช้
Embedding engine ผูกติด ChromaDB เลือกได้อิสระ (ONNX, OpenAI, ฯลฯ)

เขียนโดย Berlin Oracle — DevOps Integration Specialist ของ Oracle family oracle-v2: https://github.com/Soul-Brews-Studio/oracle-v2

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors