AI가 실제로 사용할 수 있는 도구 설계하기
요약
AI 에이전트가 도구를 정확하게 선택하고 호출할 수 있도록 효과적인 도구 설명(Description)을 설계하는 방법을 다룹니다. 단순한 독스트링을 넘어 모델의 행동을 가이드하는 프롬프트로서의 설명 작성법과 Literal 타입을 활용한 파라미터 제약 방법을 제안합니다.
핵심 포인트
- 도구 설명은 모델을 위한 프롬프트로 취급하여 '언제 호출하는지'와 '무엇을 반환하는지'를 명시해야 함
- 구현 세부 사항보다는 모델이 얻을 수 있는 결과와 동작 중심의 설명을 작성해야 함
- 다양한 예시 질문(Example questions)을 통해 도구의 용도를 명확히 고정해야 함
- Literal 타입을 사용하여 모델의 추측을 방지하고 유효한 값의 범위를 제한해야 함
제가 항상 접하게 되는 도구 정의는 다음과 같습니다:
@tool
def get_data(id: str):
"""데이터를 가져옵니다."""
...
모델은 이 도구를 언제 호출해야 할지 알 방법이 없습니다. "데이터를 가져옵니다"라는 문구는 거의 모든 요청에 부합할 수 있습니다. 그리고 에이전트 (Agent)가 30개의 도구 중에서 선택해야 할 때, 설명 (Description)이 라우팅 (Routing)의 대부분을 담당합니다. 모델에게는 그 두 단어로 된 독스트링 (Docstring)이 인터페이스이며, 그 아래의 코드는 결정 과정에 거의 영향을 미치지 않습니다.
제가 추적하는 대부분의 에이전트 버그는 도구 선택 (Tool-selection) 버그로 밝혀집니다. 모델이 잘못된 도구를 선택하거나, 행동해야 할 때 아무것도 선택하지 않는 경우입니다. 때로는 올바른 도구를 잘못된 인자 (Arguments)와 함께 호출하기도 합니다. 해결책은 코드에 있는 것이 아니라 거의 항상 설명에 있습니다.
설명은 독스트링이 아니라 프롬프트입니다
설명은 모델을 위해 두 가지 질문에 답해야 합니다: '언제 이것을 호출해야 하는가'와 '무엇을 돌려받는가'입니다. 다음은 인간 독자를 위해 작성된 동일한 도구입니다:
@tool
def search_entities(query: str):
"""엔티티를 검색합니다."""
그리고 이것을 호출하도록 라우팅해야 하는 모델을 위해 작성된 버전은 다음과 같습니다:
@tool
def search_entities(
query: Annotated[str, "검색어, 이름 및 도메인과 일치함. 정확한 용어를 사용하고, 와일드카드(Wildcards) 사용 금지"],
...
두 번째 방식은 언제 이 도구를 사용해야 하는지, 무엇이 반환되는지, 그리고 명백한 접근 방식이 실패했을 때 어떻게 해야 하는지를 알려줍니다. Example questions 블록은 보기보다 더 큰 역할을 합니다. 이는 이 도구로 라우팅되어야 하는 사용자 요청의 유형을 고정(Anchor)해주며, 이것이 바로 모델이 내리는 결정 그 자체이기 때문입니다.
무엇이 빠졌는지 주목해 보세요. 제 첫 번째 초안에는 "퍼지 매칭 (Fuzzy matching)을 사용하여 이름으로 엔티티를 찾습니다"라고 적혀 있었습니다. 모델은 매칭이 퍼지(Fuzzy)한지 여부에는 관심이 없습니다. 그것은 구현 세부 사항 (Implementation detail)일 뿐입니다. 모델은 대략적인 내용을 입력했을 때 결과를 얻을 수 있는지에 관심이 있습니다. 따라서 저는 모델이 행동할 수 있는 동작을 설명하며, 내부적으로 매칭이 어떻게 작동하는지는 텍스트에 포함하지 않습니다.
이러한 예시 질문들에 대해 한 가지 주의할 점은, 질문들을 다양하게 유지해야 한다는 것입니다. 만약 질문들이 모두 비슷해 보인다면, 모델은 해당 도구가 실제보다 더 좁은 용도로만 쓰인다고 판단하여 질문의 형태가 일치하지 않을 때 도구 사용을 건너뛰어 버립니다. 저는 주로 형식이 명확하지 않은 파라미터(parameter)를 위해 예시를 활용하며, 사용하는 예시들은 의도적으로 서로 다르게 구성합니다.
Literal은 추가할 수 있는 가장 가치 있는 어노테이션(annotation)입니다
country라고 불리는 파라미터는 국가 이름, 두 글자 코드, 세 글자 코드, 또는 UN 숫자 코드일 수 있습니다. 모델은 이를 추측할 것이며, 그 추측이 틀리는 빈도가 무시할 수 없을 만큼 잦을 것입니다.
from typing import Annotated, Literal
from pydantic import Field
...
모델이 Literal["active", "inactive", "undetermined"]를 보면, 유효한 값의 정확한 집합을 알게 됩니다. 추측할 필요도 없고, 대문자 A가 포함된 "Active"를 보낼 일도 없습니다. 열거할 수 없는 개방형 파라미터(open-ended parameters)의 경우, examples 필드는 형식을 고정하지 않으면서도 모델에게 형식을 인지시켜 주며, page에 적용된 ge=1 제약 조건은 무료로 얻을 수 있는 가드레일(guardrail) 역할을 합니다.
국가 코드는 가이드(guidance)가 실제로 어디에 위치해야 하는지를 가르쳐 주었습니다. 파라미터에는 "두 글자 코드"라고 명시되어 있었지만, 사용자가 "Germany"라고 말했을 때 모델은 여전히 "Germany"를 그대로 전달했습니다. 파라미터 설명을 강화하는 것은 도움이 되지 않았습니다. 도움이 된 것은 도구(tool) 설명에 추가한 한 줄이었습니다: "사용자가 국가 이름을 제공하면, 호출하기 전에 ISO alpha-2 코드로 변환하십시오." 파라미터 설명은 형식을 설정하지만, 모델은 국가를 보내기로 이미 결정한 후에야 이를 읽게 되며, 그 시점에는 이미 사용자의 "Germany"가 슬롯에 들어가 있는 상태입니다. 변환하라는 지침은 모델이 더 일찍 확인하는 곳인 도구 설명(tool description)에 위치해야 했습니다.
비어 있다는 것이 0을 의미하지는 않습니다
모델이 무언가를 쿼리(query)하고 빈 리스트(empty list)를 받으면, 도구가 실패했다고 판단합니다. 그래서 다른 파라미터로 재시도하거나, 다른 도구로 전환하거나, 실제 정답이 "일치하는 결과가 0개"임에도 불구하고 사용자에게 아무런 문제가 없다고 말해버립니다. 도구가 내내 정확히 해야 할 일을 수행하고 있었기 때문에, 저는 이 문제를 파악하는 데 꽤 오랜 시간이 걸렸습니다.
강화(Reinforcement)는 한 곳 이상의 장소에 적용되어야 합니다:
- 도구 설명 (Tool description): "일치하는 항목이 없으면 빈 리스트를 반환합니다. 빈 결과는 오류가 아닌 유효한 답변입니다."
- 시스템 프롬프트 (System prompt) 또는 서버 지침 (Server instructions): "이 도구들의 빈 결과는 쿼리가 실행되었으나 일치하는 데이터를 찾지 못했음을 의미합니다."
- 응답 자체:
[]대신{"results": [], "count": 0, "message": "No records matched"}를 반환합니다. 이 구조는 모델이 호출이 실패했다고 가정하는 대신 결과를 보고할 수 있을 만큼 충분한 문맥(Context)을 제공합니다.
그 이후로는 "다른 방식을 시도해 보겠습니다"와 같은 잘못된 재시도(False retries)가 대부분 중단되었습니다.
가이드라인이 존재하는 위치
모델은 네 가지 수준에서 도구의 동작을 습득하며, 각 수준은 서로 다른 역할을 수행합니다.
| 수준 (Level) | 범위 (Scope) | 용도 (What it's for) |
|---|---|---|
| 도구 설명 (Tool description) | 단일 도구 (one tool) | 언제 호출해야 하는지 및 무엇이 반환되는지; 라우팅 (Routing)이 일어나는 곳 |
| ... |
상위 두 수준은 단일 도구 또는 필드에 특화되어 있습니다. 하위 두 수준은 하나의 소스로부터 모든 도구에 걸쳐 적용되는 동작을 전달하며, 마지막에 채워지거나 아예 채워지지 않을 수도 있습니다.
시스템 프롬프트는 제가 도구 간 공통 규칙(Cross-tool rules)을 넣는 곳입니다. 다음과 같이 구조화된 문맥(Structured context)으로 주입할 수 있습니다:
<plugins>
<plugin name="analytics">
이 플러그인은 분석 데이터 웨어하우스(Analytics warehouse)를 쿼리하기 위한 도구들을 제공합니다.
...
해당 블록을 작성할 때는 참조용(Referential)이 아닌 동작 중심(Behavioral)으로 유지하세요: 날짜 형식, 빈 결과의 의미론(Semantics), 속도 제한(Rate-limit) 인지 등이 이에 해당합니다. 이는 모든 개별 도구 설명에 반복할 경우 내용을 비대하게 만들 수 있는 종류의 가이드라인을 담기에 적합한 장소입니다.
MCP 서버 지침(MCP server instructions)도 동일한 종류의 가이드라인을 전달하지만, 프롬프트에 포함되는 대신 도구 목록과 함께 프로토콜 메타데이터(Protocol metadata)로 전달됩니다. 클라이언트마다 이를 일관성 없게 노출하거나 일부는 완전히 무시하기도 하므로, 저는 instructions 필드(FastMCP는 이를 서버에 배치함)를 시스템 프롬프트의 대체재가 아닌 보완재로 취급합니다.
모든 내용을 도구 설명에 쑤셔 넣는 설정을 본 적이 있습니다. 하지만 한 서버에 15개의 도구가 있고 각 설명이 200단어씩 된다면 그 방식은 한계에 부딪힙니다.
도구 선택을 망가뜨리는 가장 빠른 방법
다른 도구의 이름을 언급하십시오. 설명에 "전체 이력을 확인하려면 get_entity_details를 사용하세요"라고 적는 순간, 해당 이름에 대한 의존성이 생성됩니다. 도구를 필터링하거나 이름을 변경하더라도 모델은 여전히 해당 도구를 호출하려고 시도합니다. 저는 모델이 다른 도구의 설명에서 참조된 것을 본 도구를 환각 (hallucinate)하여 호출하는 것을 목격했습니다. "전체 이력을 반환하는 도구를 호출하세요"와 같은 완곡한 버전조차 취약합니다. 그래서 이제 각 설명은 오직 자신의 도구에 대해서만 이야기합니다. 하나가 다른 것을 참조하기 시작하는 순간, 저는 그것을 버그로 간주합니다.
두 가지 작은 문제도 있습니다. q나 data와 같이 모호한 파라미터 (parameter) 이름은 모델이 추론할 수 있는 근거를 전혀 제공하지 않습니다. 이름 자체가 하나의 신호이므로, search_query나 date_range_start와 같은 이름은 그 길이에 합당한 가치를 가집니다. 또한 구현 세부 사항("페이지네이션을 사용하여 /api/v2/entities를 호출함")은 소스 코드를 읽는 사람을 위한 것이지, 도구 호출 여부를 결정하는 모델을 위한 것이 아닙니다.
때로는 해결책이 도구 하나를 줄이는 것일 수도 있습니다
수백만 행의 지리 데이터가 포함된 거대한 참조 데이터셋을 노출하는 도구가 하나 있었습니다. 작동은 잘 되었습니다. 하지만 그 설명이 의도하지 않은 온갖 종류의 질문과도 매칭될 만큼 광범위했기 때문에, 모델은 계속해서 그 도구에 손을 뻗었습니다. 해당 도구를 제거하고, 그 질문들을 더 좁은 범위의 도구로 라우팅 (routing)하자 관련 쿼리 전체 영역에서 정확도가 향상되었습니다.
이는 Anthropic의 도구 사용 문서 (tool use docs)에서 권장하는 사항과 일치합니다. 즉, 상세한 설명과 함께, 기능이 중복되는 많은 도구보다는 역량 있는 적은 수의 도구를 사용하는 것입니다. 그들의 도구 검색 (tool search) 기능은 한 단계 더 나아가, 모델이 실제로 도구를 선택할 때까지 도구의 전체 스키마 (schema)를 지연시킵니다. 이것이 Claude Code가 많은 수의 도구 사이에서도 선택 정확도를 유지하는 방식입니다. 저는 항상 그렇게 지연시킬 수는 없기에, 제가 사용할 수 있는 수단은 투박합니다. 만약 어떤 도구가 가져다주는 이득보다 혼란을 더 많이 야기한다면, 그 도구는 제거해야 합니다.
효과적인 설명은 독스트링 (docstrings)처럼 읽히지 않습니다. 그것은 마치 당신의 교대 근무를 대신하는 사람에게 남기는 인수인계 노트처럼 읽힙니다. 즉, 실제로 언제 이 도구를 사용해야 하는지, 그리고 사람들이 항상 실수하게 만드는 입력값 (input)이 무엇인지에 대한 내용입니다. 모델이 내용을 대충 훑어보기 시작하기 전까지 이 노트가 얼마나 길어질 수 있는지는 저도 모릅니다. 수백 단어를 넘어서는 어느 지점이겠지만, 정확히 어디인지는 실제로 측정해 본 적이 없습니다.
원문은 blog.wentland.io에 게시되었습니다. 관련 글: Why I only build read-only MCP servers 및 Your MCP server is not an API adapter.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기