Series 2 - Bài 3: Tool Registry Pattern
Tool Registry là pattern giữ cho agent core không bị ô nhiễm bởi implementation của từng tool. Bài này thiết kế một registry tập trung, pluggable và type-safe.
Nghe bài viết này dưới dạng podcastKhông có registry, tool system thường kết thúc thành một giant switch statement trong Agent Core. Thêm tool mới = sửa Agent Core. Xóa tool = sửa Agent Core. Test một tool = phải bootstrap toàn bộ agent.
Registry pattern giải quyết đó bằng cách tách biệt hoàn toàn tool implementation khỏi orchestration logic.
Vấn đề không có Registry
Anti-pattern: Giant switch trong Agent Core
class AgentCore {
async executeTool(toolUse) {
switch (toolUse.name) {
case "read_file":
return await fs.readFile(toolUse.input.path);
case "write_file":
return await fs.writeFile(toolUse.input.path, toolUse.input.content);
case "run_command":
return await exec(toolUse.input.command);
case "search_files":
return await glob(toolUse.input.pattern);
// ... thêm mới = sửa file này
}
}
}
Problems:
✕ Agent Core biết quá nhiều về implementation
✕ Mỗi tool mới = sửa Agent Core
✕ Không thể test tool độc lập
✕ Tool definitions và implementation nằm rải rác
Tool Interface: Contract chung
Tất cả tools implement cùng một interface:
Tool Interface:
Metadata:
name: string ← unique identifier
description: string ← LLM đọc để biết khi nào dùng
input_schema: Schema ← LLM đọc để biết truyền gì
requires_confirm: bool ← có cần user confirm không
Execution:
execute(input) → ToolResult
│
├─ Success: { ok: true, output: string }
└─ Failure: { ok: false, error: string, recoverable: bool }
recoverable là property quan trọng: nó cho Dispatcher biết nên trả error về LLM để retry, hay nên stop loop và escalate cho user.
Registry: Tập trung, không centralize logic
Tool Registry:
┌────────────────────────────────────────────────────────────┐
│ Map<string, Tool> │
│ │
│ register(tool: Tool): void │
│ └─ Kiểm tra name duplicate │
│ └─ Validate schema format │
│ └─ Add vào map │
│ │
│ get(name: string): Tool | undefined │
│ └─ Lookup từ map │
│ └─ Return undefined nếu không tồn tại │
│ │
│ getDefinitions(): ToolDefinition[] │
│ └─ Return array của tất cả { name, description, │
│ input_schema } │
│ └─ Đây là thứ gửi cho LLM trong mỗi request │
└────────────────────────────────────────────────────────────┘
Registry không chứa logic. Nó chỉ là map từ name đến tool instance.
Tool Groups: Tổ chức tools theo domain
Với nhiều tools, tổ chức thành groups:
Registry
│
├── FileTools group
│ read_file
│ write_file
│ list_directory
│ delete_file
│
├── ShellTools group
│ run_command
│ get_env
│
├── SearchTools group
│ search_files
│ grep_content
│ find_symbol
│
└── Custom tools (project-specific)
get_linear_issues
call_cloudflare_api
Groups cho phép register/unregister theo batch, và enable/disable theo config.
Dynamic Registration: Tool có thể thêm runtime
Một điểm mạnh của Registry là tools có thể được register dynamically:
Use cases:
Project-specific tools:
Agent start trong project A → load tools từ /project-a/tools/
Agent start trong project B → load tools từ /project-b/tools/
Core tools giống nhau, project tools khác nhau
Permission-based tools:
User không có write permission → chỉ register read tools
User là admin → register cả write và delete tools
Feature flags:
Experimental tool X → chỉ register khi flag enable
Core tools luôn có mặt
Plugin Pattern: Tools từ external sources
Mở rộng hơn: tools có thể load từ:
Built-in tools:
Compiled vào binary
Always available
Project tools (AGENT.md định nghĩa):
Load từ /project-root/.agent/tools/
Mỗi file = một tool
Auto-register khi agent start
Remote tools (advanced):
Load từ MCP server
Register vào cùng registry
Agent không phân biệt built-in vs remote
Agent Core không biết tool đến từ đâu — nó chỉ biết tên và interface. Đây là sức mạnh của Registry.
getDefinitions(): Bridge đến LLM
getDefinitions() là method quan trọng nhất của Registry. Nó tạo ra array mà bạn gửi cho LLM trong mỗi request:
Registry.getDefinitions() → [
{
name: "read_file",
description: "...",
input_schema: { ... }
},
{
name: "write_file",
description: "...",
input_schema: { ... }
},
// ... all registered tools
]
Sử dụng trong LLMClient:
POST /v1/messages
{
messages: [...],
tools: registry.getDefinitions() ← đây
}
LLM thấy tôi đây. Nó đọc tất cả tool definitions mỗi request và quyết định dùng cái nào.
Implication: Nếu bạn muốn giấu tool khỏi LLM (ví dụ tool chỉ dùng cho internal), không register nó. LLM chỉ biết những gì Registry expose.
Trade-off: Dynamic Registry và Type Safety
Dynamic registry advantages:
+ Flexible: thêm tool không cần sửa core
+ Pluggable: load tools từ nhiều sources
+ Testable: mock individual tools dễ dàng
Dynamic registry disadvantages:
- Runtime errors thay vì compile-time
- Tool không tồn tại được phát hiện lúc runtime
- Type system không giúp nhiều
Mitigation:
- Validate registry ở startup (fail fast)
- Test với fixture tool definitions
- Log unknown tool call attempts rõ ràng
Kết luận
- Registry pattern tách tool implementation khỏi Agent Core bằng một map name → tool instance.
- Tất cả tools implement cùng một interface: metadata + execute method.
getDefinitions()là bridge giữa Registry và LLM — LLM chỉ thấy tools được expose.- Dynamic registration cho phép project-specific tools, permission-based tools, và feature flags.
- Trade-off: flexibility và pluggability đổi lấy runtime errors thay vì compile-time — nên validate ở startup.
Chưa có bình luận
Để lại bình luận
Bình luận sẽ được phê duyệt trước khi hiển thị.