การพัฒนา AI Agent ที่มีประสิทธิภาพในระดับ Production ไม่ใช่เรื่องง่าย โดยเฉพาะเมื่อต้องจัดการระบบ Tool Registry ที่ซับซ้อน บทความนี้จะพาคุณไปรู้จักกับสถาปัตยกรรมที่ผมใช้งานจริงในโปรเจกต์หลายตัว พร้อม Benchmark จากการใช้งานจริงบน HolySheep AI ที่ให้บริการ API ราคาประหยัดกว่า 85% เมื่อเทียบกับผู้ให้บริการอื่น

ภาพรวมของระบบ Tool Architecture

ระบบ Tool Use ใน Agent แบ่งออกเป็น 3 ส่วนหลักที่ต้องออกแบบให้ดี:

┌─────────────────────────────────────────────────────────────┐
│                    Tool Orchestrator                        │
├─────────────────────────────────────────────────────────────┤
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐    │
│  │   Registry   │──▶│  Discovery   │──▶│    Chain     │    │
│  │   Service    │   │   Engine     │   │   Executor   │    │
│  └──────────────┘   └──────────────┘   └──────────────┘    │
│         │                  │                   │            │
│         ▼                  ▼                   ▼            │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐    │
│  │   Redis      │   │  Vector DB   │   │ Async Pool   │    │
│  │   Cache      │   │  (Embedding)│   │  + Timeout   │    │
│  └──────────────┘   └──────────────┘   └──────────────┘    │
└─────────────────────────────────────────────────────────────┘

การออกแบบ Tool Registry ระดับ Production

จากประสบการณ์การสร้าง Agent หลายตัว ผมพบว่า Registry ที่ดีต้องรองรับ:

import asyncio
import redis.asyncio as redis
from pydantic import BaseModel, Field
from typing import Optional, Callable, Any
from dataclasses import dataclass, asdict
import json
import hashlib
from datetime import datetime, timedelta

@dataclass
class ToolMetadata:
    name: str
    description: str
    parameters: dict
    return_schema: dict
    category: str
    cost_estimate: float  # บาทต่อครั้ง
    rate_limit: int  # ครั้งต่อนาที
    cache_ttl: int = 300  # วินาที

class ToolRegistry:
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis = redis.from_url(redis_url, decode_responses=True)
        self._local_cache: dict[str, ToolMetadata] = {}
    
    async def register(self, tool: ToolMetadata) -> bool:
        key = f"tool:{tool.name}"
        tool_data = asdict(tool)
        tool_data["registered_at"] = datetime.utcnow().isoformat()
        
        # เก็บข้อมูลเครื่องมือ
        await self.redis.set(key, json.dumps(tool_data))
        
        # เพิ่มใน Set ตาม category เพื่อใช้ค้นหาเร็ว
        await self.redis.sadd(f"tools:category:{tool.category}", tool.name)
        
        # สร้าง index สำหรับ embedding search
        await self.redis.zadd(
            "tools:by_cost",
            {tool.name: tool.cost_estimate}
        )
        
        # Update local cache
        self._local_cache[tool.name] = tool
        
        return True
    
    async def get_tool(self, name: str) -> Optional[ToolMetadata]:
        # ลองดึงจาก local cache ก่อน
        if name in self._local_cache:
            return self._local_cache[name]
        
        # ถ้าไม่มี ดึงจาก Redis
        key = f"tool:{name}"
        data = await self.redis.get(key)
        
        if data:
            tool_dict = json.loads(data)
            tool = ToolMetadata(**{k: v for k, v in tool_dict.items() 
                                   if k != "registered_at"})
            self._local_cache[name] = tool
            return tool
        
        return None
    
    async def get_tools_by_category(self, category: str) -> list[ToolMetadata]:
        tool_names = await self.redis.smembers(f"tools:category:{category}")
        tools = []
        
        for name in tool_names:
            tool = await self.get_tool(name)
            if tool:
                tools.append(tool)
        
        return tools
    
    async def get_cheapest_tools(self, limit: int = 5) -> list[ToolMetadata]:
        """ดึงเครื่องมือราคาถูกที่สุด"""
        tool_names = await self.redis.zrange("tools:by_cost", 0, limit - 1)
        tools = []
        
        for name in tool_names:
            tool = await self.get_tool(name)
            if tool:
                tools.append(tool)
        
        return tools

ตัวอย่างการใช้งาน

async def main(): registry = ToolRegistry() # ลงทะเบียนเครื่องมือวิเคราะห์ข้อมูล await registry.register(ToolMetadata( name="data_analyzer", description="วิเคราะห์ข้อมูล CSV และสร้างรายงานสถิติ", parameters={ "type": "object", "properties": { "file_path": {"type": "string"}, "analysis_type": {"type": "string", "enum": ["summary", "correlation", "regression"]} }, "required": ["file_path"] }, return_schema={"type": "object"}, category="data", cost_estimate=0.15, # บาท rate_limit=100, cache_ttl=600 )) tools = await registry.get_cheapest_tools(3) print(f"พบ {len(tools)} เครื่องมือราคาถูก") if __name__ == "__main__": asyncio.run(main())

ระบบ Tool Discovery ด้วย Semantic Matching

การค้นหาเครื่องมือที่เหมาะสมไม่ใช่แค่การ Match คีย์เวิร์ด แต่ต้องเข้าใจความหมายของคำขอ ผมใช้ embedding model จาก HolySheep AI ในการสร้าง vector representation

import httpx
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from typing import TypedDict

class DiscoveredTool(TypedDict):
    name: str
    score: float
    estimated_cost: float

class ToolDiscoveryEngine:
    def __init__(self, api_key: str, registry: ToolRegistry):
        self.api_key = api_key
        self.registry = registry
        self.base_url = "https://api.holysheep.ai/v1"
        self._tool_embeddings: dict[str, list[float]] = {}
        self._client = httpx.AsyncClient(timeout=30.0)
    
    async def index_tools(self):
        """สร้าง embedding index สำหรับทุกเครื่องมือที่ลงทะเบียนไว้"""
        all_tools = []
        
        for category in ["data", "web", "file", "api", "ml"]:
            tools = await self.registry.get_tools_by_category(category)
            all_tools.extend(tools)
        
        for tool in all_tools:
            text = f"{tool.name}: {tool.description}"
            embedding = await self._get_embedding(text)
            self._tool_embeddings[tool.name] = embedding
    
    async def _get_embedding(self, text: str) -> list[float]:
        """เรียก HolySheep API สำหรับสร้าง embedding"""
        response = await self._client.post(
            f"{self.base_url}/embeddings",
            headers={
                "Authorization": f"Bearer {self.api_key}",
                "Content-Type": "application/json"
            },
            json={
                "model": "embedding-3-large",
                "input": text
            }
        )
        response.raise_for_status()
        data = response.json()
        return data["data"][0]["embedding"]
    
    async def discover(
        self, 
        query: str, 
        top_k: int = 5,
        budget: Optional[float] = None,
        require_categories: Optional[list[str]] = None
    ) -> list[DiscoveredTool]:
        """ค้นหาเครื่องมือที่เหมาะสมที่สุด"""
        
        # สร้าง embedding สำหรับ query
        query_embedding = await self._get_embedding(query)
        
        # คำนวณความ相似度 กับทุกเครื่องมือ
        candidates = []
        
        for name, tool_emb in self._tool_embeddings.items():
            tool = await self.registry.get_tool(name)
            
            if not tool:
                continue
            
            # กรองตาม category
            if require_categories and tool.category not in require_categories:
                continue
            
            # กรองตาม budget
            if budget and tool.cost_estimate > budget:
                continue
            
            # คำนวณ cosine similarity
            score = cosine_similarity(
                [query_embedding], 
                [tool_emb]
            )[0][0]
            
            candidates.append({
                "name": name,
                "score": float(score),
                "estimated_cost": tool.cost_estimate,
                "tool": tool
            })
        
        # เรียงลำดับตาม score และเลือก top_k
        candidates.sort(key=lambda x: x["score"], reverse=True)
        
        return [
            DiscoveredTool(
                name=c["name"],
                score=c["score"],
                estimated_cost=c["estimated_cost"]
            )
            for c in candidates[:top_k]
        ]

Benchmark ความเร็วของ Discovery Engine

async def benchmark_discovery(): import time api_key = "YOUR_HOLYSHEEP_API_KEY" # ใช้ HolySheep API registry = ToolRegistry() engine = ToolDiscoveryEngine(api_key, registry) # สร้าง index ก่อน start = time.perf_counter() await engine.index_tools() index_time = time.perf_counter() - start print(f"Indexing time: {index_time*1000:.2f}ms") # ทดสอบค้นหา queries = [ "วิเคราะห์ข้อมูลลูกค้าจากไฟล์ CSV", "ดึงข้อมูลราคาหุ้นวันนี้", "สร้างรายงานประจำเดือน" ] for query in queries: start = time.perf_counter() results = await engine.discover(query, top_k=3) search_time = time.perf_counter() - start print(f"\nQuery: {query}") print(f"Search time: {search_time*1000:.2f}ms") for r in results: print(f" - {r['name']} (score: {r['score']:.3f}, cost: ฿{r['estimated_cost']})")

ผลลัพธ์ Benchmark บน HolySheep AI:

Indexing time: 1,247.83ms (500 tools)

Average search time: 48.32ms

P99 latency: <100ms

Tool Chain Executor พร้อม Concurrent Control

ส่วนสำคัญที่สุดคือการเรียกใช้เครื่องมืออย่างมีประสิทธิภาพ ผมออกแบบระบบที่รองรับ:

import asyncio
from typing import Any, Optional
from dataclasses import dataclass
from enum import Enum
import logging
from datetime import datetime

logger = logging.getLogger(__name__)

class ExecutionMode(Enum):
    SEQUENTIAL = "sequential"
    PARALLEL = "parallel"
    PIPELINE = "pipeline"

@dataclass
class ToolResult:
    tool_name: str
    success: bool
    result: Any
    error: Optional[str] = None
    execution_time_ms: float = 0
    cost: float = 0

class CircuitState(Enum):
    CLOSED = "closed"      # ปกติ
    OPEN = "open"          # ปิดชั่วคราว
    HALF_OPEN = "half_open" # ทดสอบว่าพร้อมหรือยัง

@dataclass
class CircuitBreakerConfig:
    failure_threshold: int = 5
    recovery_timeout: float = 30.0
    half_open_max_calls: int = 3

class CircuitBreaker:
    def __init__(self, name: str, config: Optional[CircuitBreakerConfig] = None):
        self.name = name
        self.config = config or CircuitBreakerConfig()
        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.last_failure_time: Optional[datetime] = None
        self.half_open_calls = 0
        self._lock = asyncio.Lock()
    
    async def can_execute(self) -> bool:
        async with self._lock:
            if self.state == CircuitState.CLOSED:
                return True
            
            if self.state == CircuitState.OPEN:
                if self.last_failure_time:
                    elapsed = (datetime.utcnow() - self.last_failure_time).total_seconds()
                    if elapsed >= self.config.recovery_timeout:
                        self.state = CircuitState.HALF_OPEN
                        self.half_open_calls = 0
                        return True
                return False
            
            if self.state == CircuitState.HALF_OPEN:
                return self.half_open_calls < self.config.half_open_max_calls
    
    async def record_success(self):
        async with self._lock:
            self.failure_count = 0
            self.state = CircuitState.CLOSED
            logger.info(f"Circuit {self.name}: Reset to CLOSED")
    
    async def record_failure(self):
        async with self._lock:
            self.failure_count += 1
            self.last_failure_time = datetime.utcnow()
            
            if self.failure_count >= self.config.failure_threshold:
                self.state = CircuitState.OPEN
                logger.warning(f"Circuit {self.name}: Opened after {self.failure_count} failures")
            elif self.state == CircuitState.HALF_OPEN:
                self.state = CircuitState