취미용 AI 에이전트와 프로덕션용 에이전트를 구분 짓는 5가지 도구 호출 (Tool-calling) 패턴
요약
단순 데모를 넘어 실제 프로덕션 환경에서 안정적인 AI 에이전트를 구축하기 위한 5가지 도구 호출(Tool-calling) 패턴을 소개합니다. 타임아웃, 중복 호출, 에러 처리 등 예외 상황을 관리하여 비용을 절감하고 환각 현상을 방지하는 실무적인 가이드를 제공합니다.
핵심 포인트
- 도구 호출 예산을 설정하여 무분별한 API 호출로 인한 비용 폭증 방지
- 도구 호출 중복 제거를 통해 동일 작업 반복 및 쓰기 작업 오류 방지
- 도구 에러를 모델에 명시적으로 전달하여 에이전트의 환각(Confabulation) 방지
- 멱등성을 고려한 읽기/쓰기 작업의 차별화된 캐싱 전략 필요
거의 모든 "AI 에이전트 구축" 튜토리얼은 똑같은 방식으로 끝납니다. 모델이 도구 (tool)를 호출하고, 도구가 데이터를 반환하면, 모델이 그 데이터를 사용하여 응답하는 방식입니다. 데모에서는 잘 작동합니다.
튜토리얼이 보여주지 않는 것: 도구 호출이 타임아웃 (timeout)될 때 어떤 일이 발생하는가? 혹은 모델이 동일한 도구를 연속으로 세 번 호출할 때? 또는 사용자의 의도 없이 모델이 파괴적인 도구를 호출할 때? 혹은 도구가 에러를 반환했는데도 모델이 어떻게든 답변을 지어낼 (confabulates) 때 어떤 일이 벌어지는가?
이것들은 예외적인 상황 (edge cases)이 아닙니다. 프로덕션 (production) 에이전트의 정상적인 운영 조건입니다. 제가 출시하는 모든 에이전트에서 이러한 상황을 처리하기 위해 사용하는 다섯 가지 패턴을 소개합니다.
패턴 1: 명시적인 도구 호출 예산 (Explicit tool call budgets)
기본적으로 대부분의 에이전트 프레임워크 (agent frameworks)는 모델이 중단하고 응답하기로 결정할 때까지 무제한으로 도구를 호출하도록 허용합니다. 데모에서는 괜찮습니다. 하지만 프로덕션에서는 단 하나의 오작동하는 에이전트가 수십 번의 API 호출을 반복하며 누군가 알아차리기 전에 비용을 쌓아 올릴 수 있음을 의미합니다.
해결책은 턴 (turn)당 엄격한 도구 호출 예산을 설정하는 것입니다.
import Anthropic from "@anthic-ai/sdk";
const client = new Anthropic();
...
maxToolCalls = 5라는 기본값은 보수적인 설정입니다. 에이전트가 실제로 수행하는 작업에 따라 조정하십시오. 단순 조회 에이전트라면 3번이면 충분합니다. 다단계 합성 (multi-step synthesis)을 수행하는 연구 에이전트라면 10~15번이 적절할 수 있습니다. 핵심은 어떤 식으로든 제한을 두는 것입니다.
패턴 2: 도구 호출 중복 제거 (Tool call deduplication)
흔한 에이전트 실패 모드: 모델이 한 번의 턴 내에서(또는 여러 턴에 걸쳐) 동일한 인자 (arguments)로 동일한 도구를 여러 번 호출하는 경우입니다. 이는 최선의 경우에도 낭비이며, 최악의 경우 위험합니다. 동일한 내용으로 send_email을 두 번 호출하는 상황을 상상해 보십시오.
class ToolCallDeduplicator {
private seen = new Map<string, unknown>();
private readonly ttlMs: number;
...
멱등성(Idempotent)을 가진 읽기 작업(검색, 조회)의 경우, 결과를 캐싱(Caching)하는 것은 안전하며 비용을 절감할 수 있습니다. 쓰기 작업(이메일 전송, 레코드 생성, 웹훅 호출)의 경우, 캐싱된 결과를 조용히 반환하는 대신 에러와 함께 중복 요청을 거부하고 싶을 수 있습니다. 이러한 차이점을 도구 정의(Tool definitions)에 명시적으로 나타내십시오.
패턴 3: 도구 에러 전파 (환각 방지가 아님)
도구가 실패했을 때 가장 좋지 않은 방법은 모델로부터 에러를 숨기는 것입니다. 다음은 흔히 발생하는 안티 패턴(Anti-pattern)입니다:
// 나쁜 예: 에러를 삼킴 (swallowing errors)
async function executeToolBad(name: string, input: unknown): Promise<string> {
try {
...
모델은 빈 문자열을 받게 되며, 도구가 실패했다는 사실을 알지 못합니다. 모델은 도구가 반환할 것으로 예상했던 내용을 바탕으로 그럴듯하게 들리는 응답을 만들어내는 환각(Confabulation)을 일으키는 경우가 많습니다. 이것이 에이전트에서 발생하는 환각 데이터의 근원입니다. 모델의 학습 때문이 아니라, 에이전트 프레임워크가 실패를 숨기기 때문입니다.
// 좋은 예: 구조화된 에러 전파 (structured error propagation)
async function executeToolGood(name: string, input: unknown): Promise<string> {
try {
...
구조화된 에러 응답을 사용하면, 모델은 거짓 답변을 만들어내는 대신 무엇이 잘못되었는지 추론하고 사용자에게 복구 경로를 제안할 수 있습니다.
패턴 4: 읽기 vs 쓰기 도구 분류
읽기 도구(검색, 조회, 파일 읽기)와 쓰기 도구(이메일 전송, 레코드 생성, 삭제, API 호출)를 모두 가진 에이전트는 각 카테고리에 대해 서로 다른 안전 프로필(Safety profiles)이 필요합니다. 모델은 읽기 도구는 자유롭게 호출할 수 있어야 하지만, 쓰기 도구를 호출하기 전에는 더 주의를 기울여야 하며, 선택적으로 사용자에게 확인을 요청해야 합니다.
const READ_TOOLS = new Set(["search", "lookup_user", "get_document", "read_calendar"]);
const WRITE_TOOLS = new Set(["send_email", "create_record", "delete_file", "call_webhook"]);
const DESTRUCTIVE_TOOLS = new Set(["delete_file", "cancel_subscription"]);
...
핵심 결정 지점: 분류 결과가 requiresConfirmation: true를 반환할 때, 도구 (tool)를 호출하는 대신 모델이 제안한 작업을 사용자 인터페이스 (UI)에 반환하고, 계속 진행하기 전에 명시적인 승인을 요청합니다. 에이전트는 쓰기 경계 (write boundaries)에서 일시 중지합니다.
패턴 5: 경계에서의 도구 입력 강제 변환 (Tool input coercion)
도구 스키마 (Tool schemas)는 당신이 기대하는 바를 정의합니다. 하지만 모델이 항상 정확히 그에 맞춰 결과물을 내놓지는 않습니다. 엄격한 JSON 스키마 (JSON schemas)를 사용하더라도 다음과 같은 상황을 마주하게 됩니다: 열거형 (enums)을 지정한 곳에 문자열이 들어오거나, 숫자가 문자열로 들어오거나, 요소가 직접 들어오는 대신 단일 요소가 포함된 배열이 들어오거나, 선택적 필드 (optional fields)가 누락되거나, 모델이 임의로 만들어낸 추가 필드가 발생하는 경우 등입니다.
도구 경계에 위치한 강제 변환 레이어 (coercion layer)는 이러한 예측 가능한 불일치들을 오류 없이 처리합니다:
import { z } from "zod";
const SearchInputSchema = z.object({
...
z.coerce와 z.preprocess는 흔히 발생하는 불일치(문자열-숫자, 문자열-불리언, 문자열-배열 변환)를 처리하는 역할을 수행합니다. 스키마가 계약 (contract)을 정의한다면, 강제 변환 레이어는 현실적인 모델 출력값을 처리합니다.
종합하기
이 다섯 가지 패턴은 독립적이지 않으며, 서로 결합됩니다:
- 예산 (Budget): 통제 불능의 루프 (runaway loops)를 방지합니다.
- 중복 제거 (Deduplication): 불필요하고 중복된 쓰기 작업을 방지합니다.
- 오류 전파 (Error propagation): 모델이 추론할 수 있도록 정확한 피드백을 제공합니다.
- 읽기/쓰기 분류 (Read/write classification): 파괴적인 작업 (destructive actions)을 승인 절차 뒤로 격리합니다.
- 입력 강제 변환 (Input coercion): 도구 경계에서 발생하는 현실적인 모델 출력값을 처리합니다.
이들이 결합되어 예측 가능하고, 비용이 통제되며, 감독 없이 실행해도 안전한 도구 실행기 (tool executor)를 형성합니다. 이것들이 없다면 당신은 데모 (demo)를 가진 것이지만, 이것들이 있다면 실제로 배포할 수 있는 에이전트 (agent)를 가진 것입니다.
이를 Python 또는 TypeScript로 구현한 프로덕션 버전은 약 200줄 정도입니다. 데모 버전은 30줄입니다. 대부분의 AI 에이전트 프로젝트는 바로 그 간극 사이에 머물러 있습니다.
무료 Reliable Agent Field Guide에서 이러한 패턴들의 전체 구현체와 테스트 전략을 확인할 수 있습니다: penloomstudio.com/field-guide.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기