Series 2 - Bài 6: Tool Design Principles cho LLM
Viết tool cho LLM khác viết function cho human developer. Bài này đưa ra 6 nguyên tắc thiết kế tool dựa trên cách LLM reason và ra quyết định.
Nghe bài viết này dưới dạng podcastTool tốt cho developer và tool tốt cho LLM không giống nhau. Developer đọc docs, hỏi Stack Overflow, hỏi đồng nghiệp. LLM chỉ có tool definition trong context window.
Nếu tool design không optimize cho LLM, agent sẽ:
- Gọi sai tool trong tình huống đúng
- Gọi đúng tool nhưng với input sai
- Gọi thêm tool không cần thiết
- Không biết recover khi tool fail
Principle 1: Single Responsibility
Mỗi tool làm một việc rõ ràng. Không phải một việc và một việc khác tùy option.
Anti-pattern: Tool đa năng
file_ops(operation: "read" | "write" | "delete", path, content?)
Problem:
• LLM phải biết các operation có thể có
• Parameters conditional theo operation (content chỉ cần khi write)
• Khó viết description rõ cho tất cả cases
• Permission check phức tạp (read vs write vs delete ≠ cùng policy)
Better: Tách riêng
read_file(path)
write_file(path, content)
delete_file(path)
Benefits:
• Mỗi tool có clear description
• Parameters không conditional
• Permission riêng lệ
• LLM chọn đúng tool theo tên
Principle 2: Idempotency là Feature
Tool idempotent có thể gọi nhiều lần mà không thay đổi kết quả. Trong agent context, đây rất quan trọng:
Idempotent tools:
read_file → đọc lần n, kết quả như lần 1
list_directory → list lần n, kết quả như lần 1
search_files → search lần n, kết quả như lần 1
Non-idempotent tools:
write_file → mỗi lần ghi là một change
run_command → mỗi lần chạy có thể khác nhau
append_to_file → mỗi lần thêm content mới
Việc thiết kế: minimize non-idempotent tools. Khi không thể tránh, description phải explicit:
description: "Write content to a file, REPLACING existing content.
Each call overwrites the entire file.
Use with caution — ensure you have read the
file first to understand what will be lost."
Principle 3: Granularity phải phù hợp với task
Too fine-grained:
read_line(path, line_number)
read_lines(path, start, end)
read_function(path, function_name)
read_class(path, class_name)
…
Problem: LLM cần nhiều tool calls để đọc một file.
Mỗi call = một LLM round trip. 5x latency.
Too coarse-grained:
read_all_project_files()
Problem: Return quá nhiều → context pollution.
Token cost khổng lồ.
Right granularity:
read_file(path) ← toàn bộ 1 file
list_directory(path) ← entries trong 1 folder
search_files(pattern) ← tìm file theo pattern
LLM tự quyết định đọc file nào sau khi list.
Không cần grained hơn, không cần coarser hơn.
Principle 4: Result phải Actionable
LLM cần biết làm gì với result. Nếu result không actionable, LLM có thể gọi thêm tools không cần thiết.
Non-actionable:
list_directory("/src") → "3 items"
• LLM không biết 3 items là gì
• Phải gọi thêm tool để biết chi tiết
Actionable:
list_directory("/src") →
"src/
user.service.ts (245 lines, modified 2h ago)
user.controller.ts (89 lines)
user.entity.ts (34 lines)"
• LLM biết immediately đọc file nào tiếp theo
Meta-info hữu ích trong result:
File size / line count → LLM biết file lớn hay nhỏ
Last modified → LLM biết file có recent changes không
Exit code + duration → LLM biết command thành công hay không
Principle 5: Fail Loudly và Specifically
Bad failure:
run_command("npm test") → "Command failed"
LLM không biết:
- Exit code bao nhiêu?
- Stderr có gì?
- Phải làm gì tiếp?
Good failure:
run_command("npm test") →
"Command exited with code 1 (test failure).
Failed tests:
✕ UserService > processLoan > should throw for amount=0
Error: Expected InvalidAmountError but got undefined
at processLoan (src/user.service.ts:45)
Suggestion: Check processLoan at line 45 in user.service.ts"
LLM biết:
- Test nào fail
- Tại sao fail
- File nào cần đọc tiếp
Principle 6: Tool có consistent output format
LLM parse tool results theo pattern. Nếu format thay đổi giữa các tools, LLM có thỉ misinterpret:
Consistency trong error format:
Mọi tool khi fail:
"Error: [reason]"
"Suggestion: [what to try next]"
Consistency trong success format:
read_file: "[content]" (raw content)
list_directory: "[path]/\n [item]\n [item]" (indented)
run_command: "Exit code: [n]\nOutput:\n[stdout]" (labeled)
Không:
read_file: {content: "..."} (JSON)
list_directory: "Item 1: ..." (prose)
run_command: "OK" (ambiguous)
Consistency giảm cognitive load của LLM — nó không phải learn format mới cho mỗi tool.
Trade-off: Rất hữu ích vs An toàn
Tool rất hữu ích:
execute_arbitrary_code(code: string)
• Agent có thể làm bất cứ điều
• Cực kì mạnh
• Attack surface khổng lồ
• Một prompt injection = game over
Tool an toàn:
run_command(command: string, allowedCommands: string[])
• Chỉ chạy commands trong whitelist
• ít flexible hơn
• Attack surface rất nhỏ
Quy tắc: Design tools với minimum capability cần thiết. Không expose power không cần. Đây là principle of least privilege cho tools.
Kết luận
- Single responsibility: mỗi tool làm một việc, tầch riêng khi nào có thể.
- Idempotency là feature: tool idempotent an toàn hơn và dễ test hơn.
- Granularity phải vừa đủ: không quá fine (nhiều round trips) không quá coarse (context pollution).
- Result actionable: include meta-info để LLM ra quyết định tiếp theo mà không cần thêm tool call.
- Fail specifically: error message với reason và suggestion giảm vòng loop.
- Consistent format: LLM không phải learn format mới cho mỗi tool.
- Principle of least privilege: expose minimum capability cần thiết.
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ị.