Agent의 성공을 결정하는 Tool 설계 베스트 프랙티스와 재사용 가능한 Skills 개발 방법을 학습합니다.
Agent-Computer Interface (ACI)를 Human-Computer Interface (HCI)와 동일한 수준으로 대우하세요.
Tool 설계는 에이전트 성공의 핵심입니다. 잘 설계된 도구는 Agent의 능력을 극대화하고, 잘못 설계된 도구는 최고의 모델도 무용지물로 만듭니다.
목표: 모델의 인지 부하 최소화
// ❌ 나쁜 예 - 불필요한 구조적 오버헤드
{
"line_count": 42,
"content": "escaped\"content\"here",
"metadata": {
"tokens": 150,
"encoding": "utf-8"
}
}
// ✅ 좋은 예 - 자연스러운 포맷
Here's the file content:
[actual content without escaping]
// 메타데이터가 필요하다면 간단히
Lines: 42
class FileTool:
def read_file(self, path: str) -> str:
"""
❌ 나쁜 구현
"""
content = read(path)
return json.dumps({
"path": path,
"lines": len(content.split('\n')),
"content": content.replace('"', '\\"'),
"size": len(content)
})
def read_file_better(self, path: str) -> str:
"""
✅ 좋은 구현
"""
content = read(path)
# 자연스러운 포맷
return f"File: {path}\n\n{content}"
목표: Agent가 도구를 올바르게 사용할 수 있도록 명확한 지침 제공
def search_code(
query: str,
file_pattern: str = "**/*",
case_sensitive: bool = False,
context_lines: int = 2
) -> List[SearchResult]:
"""
Search for code patterns across the codebase.
This tool searches file CONTENTS using regex. For searching
by FILE NAME, use `find_files` instead.
Args:
query: The search pattern (supports regex).
Examples:
- "def \\w+\\(" to find function definitions
- "class \\w+" to find class declarations
- "import.*pandas" to find pandas imports
file_pattern: Glob pattern to filter files (default: all files).
Examples:
- "**/*.py" for Python files
- "src/**/*.ts" for TypeScript in src/
- "tests/**/*" for test files
case_sensitive: Whether to match case exactly.
Default: False (case-insensitive)
context_lines: Number of lines to show before/after match.
Default: 2 lines of context
Returns:
List of SearchResult objects with:
- file_path: Absolute path to the file
- line_number: Line where match was found
- matched_text: The actual matched text
- context: Surrounding lines for context
Examples:
# Find all async functions in Python files
search_code(r"async def \\w+", "**/*.py")
# Case-sensitive search for API keys
search_code("API_KEY", case_sensitive=True)
# Find TODO comments with more context
search_code("TODO:", context_lines=5)
Edge cases:
- Empty results return [] (not None)
- Invalid regex raises ValueError with explanation
- Non-existent file_pattern returns []
- Binary files are automatically skipped
When to use this vs other tools:
- Use THIS for: finding code patterns, text in files
- Use `find_files`: finding files by name/path
- Use `read_file`: reading entire file contents
- Use `get_definition`: jumping to function/class definition
"""
pass
"""
Tool boundaries - when to use what:
search_code():
✅ "Find all uses of deprecated_function"
✅ "Where is error handling done?"
❌ "Find the auth.py file" → use find_files()
find_files():
✅ "Find all TypeScript test files"
✅ "Locate the configuration files"
❌ "Find where we import React" → use search_code()
read_file():
✅ "Read the entire config.json"
✅ "Show me the contents of README.md"
❌ "Find the main() function" → use search_code()
"""
목표: 실수를 하기 어렵게 인자를 재구성
일본어 ポカヨケ (poka-yoke)는 "실수 방지"를 의미합니다. 사용자가 잘못 사용할 수 없도록 설계하는 것입니다.
from pathlib import Path
from typing import NewType
# ❌ 나쁜 예 - 상대 경로 허용 (에러 발생 가능)
def read_file(path: str) -> str:
return open(path).read()
# ✅ 좋은 예 - 절대 경로 강제
def read_file(path: Path) -> str:
if not path.is_absolute():
raise ValueError(f"Path must be absolute, got: {path}")
return path.read_text()
# ✅✅ 더 좋은 예 - 타입 시스템으로 강제
AbsolutePath = NewType('AbsolutePath', Path)
def read_file(path: AbsolutePath) -> str:
"""Path는 이미 절대 경로임이 보장됨"""
return Path(path).read_text()
def make_absolute(path: str) -> AbsolutePath:
"""상대 경로를 절대 경로로 변환"""
abs_path = Path(path).absolute()
return AbsolutePath(abs_path)
from enum import Enum
# ❌ 나쁜 예 - 문자열로 받아 오타 가능
def set_log_level(level: str):
if level not in ["debug", "info", "warning", "error"]:
raise ValueError(f"Invalid level: {level}")
# ...
# ✅ 좋은 예 - Enum으로 강제
class LogLevel(Enum):
DEBUG = "debug"
INFO = "info"
WARNING = "warning"
ERROR = "error"
def set_log_level(level: LogLevel):
"""오타 불가능 - 타입 체커가 잡아줌"""
# ...
# 사용
set_log_level(LogLevel.INFO) # ✅
set_log_level("info") # ❌ 타입 에러
# ❌ 나쁜 예 - 매개변수가 너무 많고 순서 헷갈림
def create_database_connection(
host: str,
port: int,
username: str,
password: str,
database: str,
ssl: bool = False,
timeout: int = 30,
pool_size: int = 10,
retry_attempts: int = 3
):
pass
# ✅ 좋은 예 - Builder 패턴
class DatabaseConfig:
def __init__(self):
self.host = "localhost"
self.port = 5432
self.ssl = False
self.timeout = 30
self.pool_size = 10
self.retry_attempts = 3
def with_host(self, host: str) -> 'DatabaseConfig':
self.host = host
return self
def with_credentials(self, username: str, password: str) -> 'DatabaseConfig':
self.username = username
self.password = password
return self
def with_ssl(self) -> 'DatabaseConfig':
self.ssl = True
return self
def build(self) -> 'DatabaseConnection':
if not hasattr(self, 'username'):
raise ValueError("Credentials required")
return DatabaseConnection(self)
# 사용 - 명확하고 실수하기 어려움
config = (DatabaseConfig()
.with_host("db.example.com")
.with_credentials("user", "pass")
.with_ssl()
.build())
목표: 중복 도구를 명확히 구분하여 Agent가 올바른 도구를 선택하도록 함
def search_by_content(pattern: str, file_pattern: str = "**/*") -> List[Result]:
"""
Search WITHIN file contents using regex.
Use this when:
- Looking for code patterns, function names, or text
- Need to find WHERE something is used
- Need line numbers and context
Examples:
- "Find all calls to deprecated_api()"
- "Where do we handle authentication?"
"""
pass
def search_by_filename(pattern: str, directory: str = ".") -> List[Path]:
"""
Search for files by NAME pattern (glob).
Use this when:
- Looking for files by name or extension
- Need to locate specific files
- Don't care about file contents
Examples:
- "Find all test files"
- "Locate the config.yaml file"
"""
pass
def get_file_info(path: Path) -> FileInfo:
"""
Get metadata about a file WITHOUT reading contents.
Use this when:
- Need file size, modification time, permissions
- Checking if file exists
- Getting file type
Examples:
- "When was this file last modified?"
- "How big is the log file?"
"""
pass
Skills는 파일시스템 기반의 재사용 가능한 리소스로, Claude에게 도메인별 전문성을 제공합니다.
my-skill/
├── skill.json # Skill 메타데이터 및 설정
├── instructions.md # 주요 지침 문서
├── examples/ # 사용 예시
│ ├── good-example.md
│ └── bad-example.md
├── resources/ # 추가 리소스
│ ├── templates/
│ └── checklists/
└── tests/ # Skill 테스트 (선택)
└── test-cases.md
{
"name": "code-review",
"version": "1.0.0",
"description": "Comprehensive code review focusing on security, performance, and best practices",
"author": "Your Team",
"tags": ["code-review", "security", "best-practices"],
"triggers": ["review", "code review", "check code"],
"model_preference": "claude-opus-4-6",
"tools_required": ["read_file", "search_code", "run_tests"]
}
최고의 접근 방식: Claude A가 Claude B를 위한 Skill을 만들도록 합니다.
Claude A와 함께 설계
Claude B로 테스트
반복적 개선
# Code Security Review Skill
## Purpose
Perform security-focused code reviews to identify vulnerabilities.
## Scope
✅ IN SCOPE:
- OWASP Top 10 vulnerabilities
- Authentication/authorization issues
- Data exposure risks
- Input validation problems
❌ OUT OF SCOPE:
- Code style (use code-style-review skill)
- Performance optimization (use performance-review skill)
- Architecture decisions (escalate to human)
## When to Use
- Before merging security-critical code
- After dependency updates
- When handling sensitive data
- Regular security audits
## Examples
### Good Review Example
**Code:**
```python
def login(username, password):
user = db.query(f"SELECT * FROM users WHERE username='{username}'")
if user and user.password == password:
return create_session(user)
Review:
🔴 CRITICAL - SQL Injection (OWASP A03)
- Line 2: User input directly in SQL query
- Impact: Complete database compromise possible
- Fix: Use parameterized queries
```python
user = db.query("SELECT * FROM users WHERE username=?", [username])
🔴 HIGH - Password Stored in Plain Text
### Bad Review Example
**Review:**
```markdown
❌ Too vague
"This code has security issues"
❌ No severity
"SQL injection possible"
❌ No actionable fix
"Fix the security problem"
#### 3. 체크리스트 제공
```markdown
## Review Checklist
### Authentication & Authorization
- [ ] Passwords hashed with strong algorithm (bcrypt/argon2)?
- [ ] Session tokens cryptographically random?
- [ ] Authorization checked on every sensitive operation?
- [ ] No hardcoded credentials?
### Input Validation
- [ ] All user input validated?
- [ ] SQL injection prevented (parameterized queries)?
- [ ] XSS prevented (output encoding)?
- [ ] Path traversal prevented?
### Data Protection
- [ ] Sensitive data encrypted at rest?
- [ ] TLS used for sensitive data in transit?
- [ ] No sensitive data in logs?
- [ ] PII handling complies with regulations?
### Error Handling
- [ ] No sensitive info in error messages?
- [ ] Errors logged securely?
- [ ] Graceful degradation on errors?
# Claude에게 Skill 사용 지시
"""
Please review this code using the code-security-review skill.
File: auth.py
Focus: Authentication flow
"""
"""
1. Use code-security-review skill for security analysis
2. Use performance-review skill for optimization opportunities
3. Use code-style-review skill for style consistency
4. Synthesize findings into prioritized action items
"""
class CodingAgentTools:
"""
Coding Agent에 최적화된 Tool 세트
"""
def search_symbol_definition(self, symbol: str, language: str) -> Location:
"""
Find where a function/class is defined.
Poka-yoke: Language enum으로 오타 방지
Documentation: 명확한 사용 시점
Format: 자연스러운 응답
"""
pass
def find_usages(self, symbol: str, scope: str = "project") -> List[Usage]:
"""
Find all places where a symbol is used.
Boundary: Definition은 search_symbol_definition 사용
"""
pass
def run_tests(
self,
test_pattern: str = "**/*test*.py",
fail_fast: bool = False
) -> TestResult:
"""
Run tests and return structured results.
Format: 실패한 테스트와 에러 메시지만 포함 (성공은 요약)
"""
pass
class ResearchAgentTools:
def search_academic_papers(
self,
query: str,
start_year: int = 2020,
max_results: int = 10
) -> List[Paper]:
"""
Search academic papers.
Documentation: 명확한 매개변수 설명
Poka-yoke: 연도 제한으로 무효한 검색 방지
"""
pass
def extract_citations(self, paper_id: str) -> List[Citation]:
"""
Extract citations from a paper.
Boundary: Paper 내용은 read_paper 사용
"""
pass
def summarize_paper(self, paper_id: str, focus: str = "methodology") -> Summary:
"""
Summarize a paper with specific focus.
Format: 구조화된 요약 (Background, Method, Results, Limitations)
"""
pass
실제 프로덕션에서 사용 중인 MCP 서버 구현 사례로 requarks-wiki-mcp가 있다. 이 프로젝트는 Wiki.js GraphQL API를 29개의 MCP 도구로 브릿지하며, 위에서 다룬 Tool 설계 원칙들을 실전에 적용한 좋은 예시다:
Tool 설계를 익혔다면 Part 4: Multi-Agent 시스템으로 진행하여 여러 Agent를 조율하는 방법을 학습하세요.