Series 2 - Bài 2: JSON Schema là ngôn ngữ LLM dùng hiểu tool
LLM đọc JSON Schema để hiểu tool — giống như developer đọc API docs. Chất lượng schema quyết định LLM có dùng đúng tool không. Bài này phân tích anatomy của schema tốt và các anti-patterns.
JSON Schema trong tool definition không phải chỉ là validation spec. Đây là documentation mà LLM sẽ đọc để quyết định: có nên dùng tool này không, khi nào dùng, và truyền gì vào.
Chất lượng schema ảnh hưởng trực tiế đến behavior của agent.
Anatomy của một Tool Definition
┌────────────────────────────────────────────────────────────┐
│ Tool Definition │
│ │
│ name: "read_file" │
│ └─ LLM dùng tên này khi gọi tool │
│ └─ Phải unique trong registry │
│ └─ Snake_case, mô tả action (động từ + noun) │
│ │
│ description: "Read the content of a file..." │
│ └─ LLM đọc để quyết định có nên dùng không │
│ └─ Phải rõ khi nào nên dùng, khi nào không │
│ └─ Quan trọng hơn name │
│ │
│ input_schema: │
│ type: "object" │
│ properties: │
│ path: │
│ type: "string" │
│ description: "Absolute or relative path..." │
│ encoding: │
│ type: "string" │
│ enum: ["utf8", "base64"] │
│ default: "utf8" │
│ required: ["path"] │
└────────────────────────────────────────────────────────────┘
Description là phần quan trọng nhất
Nhiều developer tập trung vào schema structure và bỏ qua description. Đầu tư vào description cho ROI cao hơn nhiều.
LLM đọc description để quyết định:
- Tool này làm gì?
- Khi nào nên dùng cái này thay vì tool khác?
- Có side effects không?
- Giới hạn là gì?
Weak description:
name: "run_command"
description: "Run a shell command"
Problem: LLM không biết:
- Command nào được phép?
- Có cần confirm không?
- Side effects như thế nào?
Strong description:
name: "run_command"
description: "Execute a shell command in the project
working directory. Use for: running tests (npm test,
jest), type checking (tsc --noEmit), linting. Do NOT
use for commands that modify system state outside the
project (rm, sudo, git push). Commands with
significant side effects require user confirmation."
LLM biết chính xác:
- Dùng khi nào
- Tránh khi nào
- Expectation về confirmation
Parameter Descriptions
Mỗi parameter cũng cần description riêng:
Weak:
properties:
path:
type: string
Strong:
properties:
path:
type: string
description: "File path relative to project root
or absolute. Examples: 'src/user.service.ts',
'./config/database.ts', '/project/package.json'.
Do not use ~ for home directory."
LLM sẽ dùng description để format input đúng cách. Nếu description chưa nói rõ format, LLM gứ — đôi khi đúng, đôi khi sai.
Enum và Constraints giúp LLM đưa ra better decision
Thay vì:
encoding:
type: string
description: "Encoding to use"
Dùng:
encoding:
type: string
enum: ["utf8", "base64", "hex"]
default: "utf8"
description: "File encoding. Use utf8 for text files,
base64 for binary files (images, PDFs)."
enum giới hạn LLM đến valid values. Không có enum, LLM có thể gửi "UTF-8", "utf-8", "UTF8" — tất cả sai format mà code bạn expect.
default cho LLM biết không cần specify parameter nếu dùng case phổ biến, giảm noise trong tool calls.
Required vs Optional: Thiết kế cẩn thận
Required parameters:
Làm required khi không có reasonable default
LLM sẽ luôn phải cung cấp
→ Validation error nếu thiếu
Optional parameters:
Có default hoặc không luôn cần
LLM chỉ specify khi cần override
→ Giảm cognitive load cho LLM
Anti-pattern: quá nhiều required parameters
read_file(path, encoding, maxBytes, skipBOM, normalize)
→ LLM phải specify 5 thứ cho một file read
→ Thấy phức tạp → có thể chọn tool khác
Tool Naming: Convention quan trọng
Pattern: {verb}_{noun} hoặc {verb}_{noun}_{qualifier}
Good:
read_file
write_file
list_directory
run_command
search_files
get_git_status
Bad:
file ← không rõ action
fileOps ← quá generic
handleFile ← "handle" không có nghĩa
doSomething ← useless
LLM dùng tên tool để reason về các lựa chọn. Tên rõ ràng = nhất quán hơn trong việc chọn tool đúng.
Disambiguating Similar Tools
Khi có nhiều tools tương tự, descriptions phải explicit về sự khác biệt:
search_files:
description: "Search for files by name pattern or
file type. Use when you know what FILE to look
for but not where it is. Example: find all
*.service.ts files."
grep_content:
description: "Search for text patterns within
file contents. Use when you know WHAT TEXT to
look for but not which file contains it.
Example: find all usages of processPayment."
Không có sự rõ ràng này, LLM sẽ dùng sai tool hoặc gọi cả hai khi chỉ cần một.
Schema như Documentation: Quy trình viết
Quy trình tốt nhất để viết tool definition:
1. Viết description như viết README cho team member
2. Hỏi: "Nếu team member chỉ đọc description này,
họ có biết khi nào dùng và khi nào không không?"
3. Nếu không → rõ hơn
4. Kiểm tra: Gửi tool definition cho LLM với một
vài task examples. LLM có chọn đúng tool không?
Có truyền đúng parameters không?
Trade-off: Verbose vs Concise Schema
Verbose schema:
+ LLM nhất quán hơn, ít lỗi hơn
- Tốn nhiều token (schema được gửi trong mỗi request)
- Maintain phức tạp hơn
Concise schema:
+ Token efficient
- LLM có thể misinterpret
- Nhiều unexpected behavior hơn
Midpoint tốt nhất:
Description: 2-4 câu rõ ràng về purpose và when-to-use
Parameters: description ngắn cho mỗi param, enum khi có finite options
Không verbose hơn cần thiết
Kết luận
- JSON Schema là documentation cho LLM, không chỉ là validation spec.
- Description quan trọng hơn tên tool — LLM đọc để quyết định khi nào dùng.
- Enum giới hạn LLM vào valid values, giảm sai format.
- Similar tools cần explicit disambiguation trong description.
- Viết schema như viết README — nếu team member hiểu, LLM cũng sẽ hiểu.
- Balance verbosity với token cost — 2-4 câu description là sweet spot.