AI 에이전트가 실제로 올바르게 사용할 도구를 설계하는 방법
요약
AI 에이전트가 올바르게 작동하려면, 연결된 '도구(Tool)'의 설계 품질이 매우 중요합니다. 특히 `@Tool` 설명은 단순한 문서가 아니라 모델에게 전달되는 런타임 지침입니다. 따라서 도구는 사용 시점, 반환 값, 그리고 제약 조건을 명확하게 정의해야 합니다.
핵심 포인트
- 도구 설명(description)은 모델의 런타임 지침이다.
- 모호한 설명은 모델이 추측하게 만들어 오류를 유발한다.
- 설명에는 '무엇을 하는지', '언제 사용해야 하는지'가 포함되어야 한다.
- 출력 형태, 통화, 제약 조건 등 구체적인 반환 정보를 명시하라.
이전 글에서는 Spring AI 에이전트에 도구를 연결하는 방법을 보여주었습니다. 메커니즘은 간단합니다. 메서드에 어노테이션을 추가하고, 이를 ChatClient로 전달하면 끝입니다. 덜 명확한 부분은 여러분의 도구 품질이 에이전트가 올바르게 작동할지 여부를 결정한다는 점입니다.
설명이 부실한 도구는 잘못된 시점에, 잘못된 인자(argument)와 함께 호출되거나 아예 호출되지 않습니다. 잘 설계된 도구는 필요한 정확한 순간에, 정확히 맞는 입력과 함께 호출됩니다.
설명은 모델에게 주는 지침이다
@Tool(description = "...")를 작성할 때, 이 문자열은 팀을 위한 문서가 아닙니다. 이것은 모델이 모든 요청 전에 읽는 런타임 지침(runtime instruction)입니다.
모델의 의사 결정 과정은 다음과 같습니다:
- 사용자가 무엇을 요청했는가?
- 내가 사용할 수 있는 도구는 무엇인가? (각 설명을 읽는다)
- 이 답변에 도구가 필요한가? 어떤 도구를 사용해야 하는가?
- 어떤 인자를 전달해야 하는가? (각
@ToolParam설명을 읽는다)
만약 여러분의 설명이 모호하다면, 모델은 추측을 합니다. 때로는 그 추측이 맞습니다. 하지만 종종 그렇지 않습니다. 그리고 이것은 언어 모델(language model)이기 때문에, 자신이 추측하고 있다는 사실을 알려주지 않습니다. 단지 가장 합리적으로 보이는 것에 따라 진행할 뿐입니다.
모호한 설명:
@Tool(description = "날씨 가져오기")
public String getWeather(String city) { ... }
모델은 알 수 없습니다: 이것이 현재 날씨인지 아니면 예보인지? 무엇을 반환하는가—온도? 상태? 둘 다? 도시 이름은 어떤 형식으로 기대되는가?
명확한 설명:
@Tool(description = "인도의 특정 도시의 현재 날씨를 가져옵니다. 온도는 섭씨로, 하늘 상태는 (맑음, 흐림, 비)로 반환합니다. 사용자가 현재 날씨나 우산을 가져갈지 여부에 대해 질문할 때 사용하세요.")
...
이제 모델은 언제 호출해야 하는지, 무엇을 반환하는지, 그리고 도시 이름을 어떻게 전달해야 하는지를 정확히 알고 있습니다. 같은 메서드이지만, 완전히 다른 지침입니다.
도구 설명에 포함해야 할 것들
유용한 도구 설명은 네 가지 질문에 답합니다:
무엇을 하는가? 동작을 명확하게 진술하세요. "불러온다(Fetches)", "반환한다(returns)", "계산한다(calculates)", "질의한다(queries)" 등 작업에 대해 구체적으로 설명해야 합니다.
모델이 언제 사용해야 하는가? 이 부분이 가장 흔히 놓치는 부분입니다. "다음과 같은 경우에 사용하세요(use this when...)"라는 조항을 추가하세요. 이것이 없으면, 모델은 도구가 적용되어야 하는 경우에도 자신이 확신하지 못하기 때문에 해당 도구를 건너뛸 수 있습니다.
무엇을 반환하는가? 출력의 형태를 설명하세요. 가격을 반환한다면 어떤 통화인지 명시하세요. 날짜를 반환한다면 어떤 형식인지 말해주세요. 목록을 반환한다면 대략 몇 개의 항목이 포함되는지 언급하세요.
제약 조건은 무엇인가? 도구가 특정 입력에만 작동하는 경우(예: 인도 도시명, 미래 날짜, 데이터베이스에 존재하는 ID) 이를 명시하세요. 모델은 지원되지 않는 경우에는 호출을 피하게 되므로 불필요한 도구 실패를 방지할 수 있습니다.
네 가지 요소를 모두 적용한 예시:
@Tool(description = "인도 내 두 도시 간 이용 가능한 가장 저렴한 버스 또는 기차표 가격을 가져옵니다. " +
"가격은 INR로, 교통 유형(버스 또는 기차)을 반환합니다. " +
"사용자가 여행 비용, 티켓 가격 또는 도시 간 이동 방법을 문의할 때 사용하세요.")
...
좋은 파라미터 설명을 작성하는 방법
파라미터 설명은 도구 설명만큼 중요합니다. 모델이 이 설명을 바탕으로 인자(arguments)를 구성하기 때문입니다.
형식에 대해 구체적으로 명시:
// 나쁨 — 모델이 어떤 형식을 사용해야 하는지 모릅니다
@ToolParam(description = "날짜") String date
...
단위를 지정:
// 나쁨
@ToolParam(description = "예산") int amount
...
열거형(enums) 또는 제한된 옵션에 대한 예상 값 명확화:
@ToolParam(description = "좌석 등급: SLEEPER, AC_3_TIER, 또는 AC_2_TIER") String seatClass
모델이 정확한 예상 값을 알면 추측하는 대신 유효한 값을 전달합니다.
모델이 추론할 수 있는 결과를 반환하기
도구의 반환 값은 모델이 다음에 읽는 내용입니다. 모델은 이 텍스트를 사용하여 무엇을 할지 결정합니다—다른 도구를 호출하거나, 명확히 하는 질문을 하거나, 최종 답변을 생성하는 것입니다.
순수 JSON을 반환하지 마세요:
// 나쁜 예시 — 모델이 내부적으로 파싱해야 함
return "{"temp":28,"condition":"PC","humidity":85}";
모델은 JSON을 처리할 수 있지만, 먼저 정신적으로 이를 파싱해야 합니다. 이것이 오류를 유발하는 인지적 오버헤드입니다. 반환 값은 자연어로 작성하세요.
읽기 쉬운 문장으로 반환:
// 좋은 예시 — 모델이 사람이 읽듯이 인식함
return "Mumbai의 현재 날씨: 28°C, 부분적으로 흐림, 습도 85%.";
결과에 컨텍스트 포함하기:
만약 도구가 가격을 가져온다면, 그 가격이 무엇에 대한 것인지 포함해야 합니다. 모델은 여러 도구를 호출하고 결과를 결합할 수 있으므로, 이를 올바르게 수행하려면 각 결과에 컨텍스트가 필요합니다.
// 컨텍스트 없음 — 다른 결과와 조합될 때 혼란스러움
return "₹3,240";
...
일반적인 실패 모드
모델이 도구를 호출하지 않고 답변함
이는 설명에 언제 도구를 사용해야 하는지 명시되어 있지 않고, 모델이 훈련 데이터에서 관련 정보를 우연히 알고 있을 때 발생합니다. 모델은 도구 사용을 완전히 건너뛰고 직접 답변합니다.
해결책: 설명에 "다음 상황일 때 사용하세요..."를 추가합니다. 시스템 프롬프트에 다음 내용을 추가합니다: "현재 정보를 얻으려면 항상 사용 가능한 도구를 사용하십시오. 기억에 의존하여 답변하지 마십시오."
모델이 잘못된 인자 형식으로 도구를 호출함
날짜 도구가 "2026-10-15" 대신 "15th October 2026"을 받는 경우. 도시 도구가 "Bengaluru" 대신 "Blr"를 받는 경우.
해결책: @ToolParam에서 정확한 형식을 지정합니다. 예시 추가: `
도구가 예외를 발생시키거나, 빈 문자열을 반환하거나, 모델이 예상하지 못한 오류 메시지를 반환할 수 있습니다. 그러면 모델은 이를 도움이 되지 않는 결과로 간주하고 그 결과가 아마 어떠했을지 꾸며냅니다.
해결책: 예외 대신 의미 있는 오류 문자열을 반환하세요. '이 도시의 날씨를 가져올 수 없습니다 — 해당 도시는 존재하지 않습니다.'와 같은 문구는 모델이 사용자에게 솔직하게 전달할 수 있습니다. 빈 결과로 나타나는 처리되지 않은 예외는 모델이 덮어씌우려고 시도하는 것입니다.
너무 많은 도구는 얼마나 될까요?
정해진 규칙은 없지만, 실질적인 한계가 있습니다. 20개의 도구를 받은 모델은 어떤 것을 사용할지 결정하는 데 더 많은 시간을 소비합니다. 잘못된 도구를 선택할 확률이 높아집니다. 컨텍스트 창이 도구 정의로 가득 차게 됩니다.
도구에 집중하세요. 하나의 도구는 한 가지 일만 해야 합니다. 날씨, 명소, 티켓 가격을 모두 가져오는 도구는 하나인 척하는 세 개의 도구입니다 — 분리하세요.
도구 이름을 명확하게 지정하세요. 도구 이름은 모델이 읽는 부분의 일부입니다. getWeather, getAttractions, getTicketPrice가 명확합니다. getData, fetch, process는 그렇지 않습니다.
에이전트가 필요로 하지 않는 도구를 제거하세요. 도구가 존재하지만 현재 사용 사례에서 결코 필요하지 않을 것이라면, 등록하지 마세요. 도구가 적을수록 더 빠르고 정확한 의사결정이 가능합니다.
도구 테스트하기
도구의 품질은 쉽게 검증할 수 있습니다. 각 도구를 트리거해야 하는 질문과 그렇지 않은 질문을 던져보고, 올바른 인수로 올바른 도구가 호출되었는지 확인하세요.
모든 도구는 세 가지 테스트 케이스를 통과해야 합니다:
긍정적 사례 (Positive case) — 이 도구가 명확하게 필요한 무언가를 요청합니다. 호출되었고 합리적인 결과가 반환되었는지 확인합니다.
부정적 사례 (Negative case) — 관련 없는 것을 요청합니다. 해당 도구가 호출되지 않았는지 확인합니다.
경계 사례 (Edge case) — 모호하거나 특이한 입력으로 무언가를 요청합니다. 모델이 구성한 인수가 유효한지 확인합니다.
개발 중 각 도구 메서드에 로깅을 추가하세요:
@Tool(description = "..." )
public String getWeather(@ToolParam(description = "도시 이름") String city) {
log.info("getWeather 호출됨, 도시={}", city);
...
로그는 모델이 정확히 무엇을 전달했는지 보여줍니다. 인자 형식이 잘못되었나요? @ToolParam 설명을 수정하세요. 도구가 전혀 호출되지 않았나요? 도구 설명이나 시스템 프롬프트를 수정하세요.
다음 내용
도구(Tools)는 메서드입니다. 에이전트 루프는 이를 순차적 또는 병렬적으로 실행합니다. 하지만 단일 도구 호출로는 할 수 있는 것에 한계가 있습니다. 일부 문제는 모델이 여러 단계에 걸쳐 계획하고, 중간 결과를 기억하며, 무언가 잘못되었을 때 방향을 수정하는 것을 요구합니다. 바로 여기에 더 구조화된 에이전트 패턴들이 필요한 것입니다.
다음 글에서는 에이전트의 메모리(memory)를 다룹니다. 이전 턴의 컨텍스트를 에이전트에 제공하여, 여러 턴에 걸친 대화를 처리하고, 이전 도구 호출에서 배운 것을 기억하며, 자신의 이전 답변을 기반으로 구축할 수 있도록 하는 방법입니다.
Sham Prakash K — 백엔드 엔지니어, Java, Spring Boot 및 분산 시스템 분야 경력 4년 이상. AI 백엔드 인프라를 구축하고 있습니다. 실제로 배운 것들, 실수까지 포함하여 글을 쓰고 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기