도구(Tool) 설계 패턴
Function calling 스키마, read/write/compute 분류, 에러 반환 규약, toolbox 패턴
tool은 모델의 능력을 확장하는 기능이 아니라 모델에게 노출된 시스템 경계입니다. 따라서 API를 감싸는 수준이 아니라, 잘못 호출되어도 시스템이 버틸 수 있게 설계해야 합니다.
도구를 분류하는 기본 축
| 유형 | 목적 | 예시 | 설계 포인트 |
|---|---|---|---|
| Read | 상태 조회, 문서 검색, 검색 결과 수집 | search_docs, get_order | 멱등성, 출처 반환 |
| Write | 외부 시스템 변경 | create_ticket, send_email | 승인, dry-run, idempotency key |
| Compute | 계산, 변환, 검증 | score_risk, diff_summary | 입력 검증, deterministic output |
대부분의 문제는 tool이 너무 많아서가 아니라, read와 write가 섞여 있고 실패 계약이 없어서 발생합니다.
tool 호출 흐름과 안전 경계
좋은 tool 인터페이스의 조건
| 조건 | 이유 |
|---|---|
| 스키마가 좁다 | 모델이 불필요한 자유도를 가지지 않음 |
| 필수 필드가 명확하다 | 추론이 아니라 검증으로 오류를 줄임 |
| 단위와 포맷이 고정된다 | 날짜, 통화, locale 혼선을 줄임 |
| 실패 응답이 구조화된다 | 재시도와 fallback이 자동화 가능 |
| side effect가 설명된다 | human approval 경계 설정 가능 |
스키마 예시
import { z } from 'zod'
export const createTicketInput = z.object({
title: z.string().min(5).max(120),
severity: z.enum(['low', 'medium', 'high', 'critical']),
summary: z.string().min(20),
dryRun: z.boolean().default(true),
idempotencyKey: z.string().min(8),
})
export type CreateTicketInput = z.infer<typeof createTicketInput>여기서 핵심은 모델이 자유롭게 쓰게 하는 것이 아니라, 시스템이 받아들일 수 있는 형태만 허용하는 것입니다.
에러 반환 규약
tool이 throw만 하면 orchestrator는 무엇을 해야 할지 알 수 없습니다. 가능하면 다음과 같이 복구 가능한 오류 형태를 노출합니다.
type ToolResult<T> =
| { ok: true; data: T }
| {
ok: false
errorCode: 'RATE_LIMIT' | 'NOT_FOUND' | 'VALIDATION_ERROR' | 'PERMISSION_DENIED'
retryable: boolean
message: string
}이 패턴이 있으면 orchestrator는 retry, fallback, human handoff를 분기할 수 있습니다.
toolbox 패턴
모든 tool을 한 agent에 몰아주지 말고 capability 기준으로 묶습니다.
| Toolbox | 포함 예시 | 연결할 agent |
|---|---|---|
| Retrieval Toolbox | 검색, 문서 조회, DB read | research agent |
| Execution Toolbox | 티켓 생성, 발송, 배포 | execution agent |
| Validation Toolbox | 규칙 검사, 정책 검증, 포맷 체크 | evaluator agent |
| Admin Toolbox | 고권한 설정 변경 | human-approved admin agent |
write tool의 추가 규칙
write tool은 일반 조회 tool보다 훨씬 엄격해야 합니다.
dryRun또는 preview 모드를 우선 지원합니다.idempotencyKey를 받아 중복 실행을 막습니다.- side effect를 응답에 명시합니다.
- 사람이 검토할 수 있는
summary를 남깁니다. - 가능하면 rollback 또는 compensate step를 준비합니다.
컨텍스트 누수 방지
tool 설명문이 길수록 좋은 것이 아닙니다. description에는 다음 세 가지 정도만 남기는 편이 안정적입니다.
- 이 tool이 해결하는 문제
- 언제 사용하면 안 되는지
- 입력에서 특히 중요한 필드
정책 문서 전체를 tool description에 복붙하면 모델은 규칙보다 잡음을 더 많이 읽게 됩니다.
관측성 필드
각 tool 호출에는 최소한 다음 필드를 남기는 것이 좋습니다.
tool_nameactor_agentrequest_idinput_hashduration_msretry_countside_effectresult_status
이 정보가 있어야 "어떤 agent가 어떤 도구에서 실패했는가"를 추적할 수 있습니다.
안티패턴
| 안티패턴 | 문제 | 개선 |
|---|---|---|
| 자연어 인자 하나로 모든 걸 받음 | 검증이 불가능 | 구조화 스키마로 분리 |
| read와 write를 한 tool에 혼합 | 승인 경계가 사라짐 | 별도 tool로 분리 |
| 예외 메시지를 그대로 모델에 노출 | 내부 정보가 새거나 재시도 정책이 흔들림 | 표준 오류 envelope 사용 |
| 너무 많은 tool을 한번에 노출 | 선택 품질 저하 | toolbox 단위 노출 |
ADR 스타일 결론
Decision
tool은 모델을 위한 함수가 아니라 시스템 안전 경계입니다. read/write/compute를 명확히 분리하고, write tool에는 dry-run, idempotency, structured error를 기본 규칙으로 둡니다. agent에는 필요한 toolbox만 최소 권한으로 제공합니다.
실무 체크리스트
- 각 tool의 side effect를 한 줄로 설명할 수 있는가
- 날짜, 통화, locale, enum 값이 스키마에 고정되어 있는가
- 오류가 retryable 여부와 함께 구조화되어 있는가
- write tool에 dry-run과 idempotency key가 있는가
- agent별로 toolbox가 분리되어 있는가