에이전트·RAG

AGENT / 38번째 글

모델이 읽는 것은 구현이 아니라 스키마다

도구 스키마를 프롬프트로 보고 설계하는 법 — 이름·설명문·인자 타입이 선택률과 인자 정확도를 어떻게 바꾸는지, 도구를 몇 개까지 두어도 되는지, 스키마가 차지하는 토큰을 어떻게 관리하는지 정리합니다.

PALDYN Team10 MIN READ

지난 글까지 호출을 어떻게 실행하고 묶을지를 봤다. 그런데 실행 전에 결정되는 것이 하나 있다. 모델이 그 도구를 고를지, 인자를 어떻게 채울지는 전적으로 스키마에 적힌 텍스트만 보고 정해진다. 함수 본문이 얼마나 정확하든 모델은 그것을 볼 수 없다. 이 사실을 받아들이면 도구 스키마는 API 문서가 아니라 프롬프트의 일부라는 게 분명해진다.

좋은 스키마와 나쁜 스키마의 차이

이름이 가장 강한 신호다

모델은 도구 목록을 훑을 때 이름을 먼저 본다. 설명문은 후보를 좁힌 뒤에 읽는다. 그래서 이름 하나만 고쳐도 선택 정확도가 눈에 띄게 바뀐다.

좋은 이름은 동사 + 대상의 꼴이고, 대상이 구체적이다.

나쁜 이름 왜 나은 이름
search 무엇을 검색하는지 없다 search_orders
get_data 대상도 동작도 모호하다 get_customer_profile
handler 동사가 아니다 cancel_subscription
query_v2 버전은 모델에게 의미가 없다 query_inventory
do_refund_and_notify 두 가지를 한다 issue_refund + send_notice

마지막 줄은 이름 문제로 보이지만 실은 설계 문제다. 한 도구가 두 가지를 하면 모델은 둘 중 하나만 필요한 상황에서도 그 도구를 부르거나, 부르기를 망설인다. 부작용이 다르면 도구를 나눈다.

설명문에는 '언제 쓰지 않는가'를 쓴다

설명문에서 자주 빠지는 것은 기능이 아니라 경계다. 무엇을 하는지는 대개 이름에서 짐작되지만, 무엇을 못 하는지는 적어 두지 않으면 알 방법이 없다.

{
  "name": "search_orders",
  "description": "주문 이력을 키워드로 검색한다. 최근 2년치만 들어 있고, 배송 추적 상태는 포함되지 않는다(그쪽은 track_shipment를 쓸 것). 주문번호를 이미 알고 있다면 이 도구 대신 lookup_order를 쓴다.",
  "input_schema": {
    "type": "object",
    "properties": {
      "keyword": {
        "type": "string",
        "description": "상품명 또는 주문자명. 주문번호를 여기에 넣지 않는다."
      },
      "since": {
        "type": "string",
        "format": "date",
        "description": "이 날짜 이후 주문만. YYYY-MM-DD. 사용자가 기간을 말하지 않았으면 생략한다."
      },
      "limit": {
        "type": "integer",
        "minimum": 1,
        "maximum": 50,
        "default": 20
      }
    },
    "required": ["keyword"]
  }
}

설명문에 들어간 정보가 넷이다. 범위(최근 2년), 제외 항목(배송 상태), 대안 도구 둘. 이 중 대안 도구를 가리키는 문장이 특히 잘 듣는다. 비슷한 도구가 여럿일 때 모델이 헤매는 이유는 각 도구가 자기 얘기만 하기 때문이다. 서로를 가리켜 주면 경계가 생긴다.

인자 쪽도 마찬가지다. since의 "사용자가 기간을 말하지 않았으면 생략한다"는 한 줄이 없으면 모델은 오늘 날짜나 임의의 과거 날짜를 채워 넣는다. 선택 인자는 비워도 된다는 사실을 명시해야 실제로 비운다.

타입으로 말할 수 있는 것은 설명문에 쓰지 않는다

설명문은 토큰을 쓰고, 모델이 반드시 지킨다는 보장도 없다. 반면 스키마의 제약은 구조화 출력 경로에서 강제되는 경우가 많다. 같은 내용이라면 제약 쪽에 넣는 편이 싸고 확실하다.

"status": {
  "type": "string",
  "enum": ["pending", "shipped", "delivered", "cancelled"]
}

이렇게 쓰면 "description": "상태는 pending, shipped, delivered, cancelled 중 하나입니다"가 통째로 필요 없어진다. 같은 요령으로 옮길 수 있는 것들이 있다.

설명문에 쓰던 말 옮길 곳
"~ 중 하나" enum
"1에서 50 사이" minimum / maximum
"YYYY-MM-DD 형식" format: "date"
"ORD-로 시작하는 8자리" pattern
"비워도 됨" required에서 빼기
"기본값은 20" default

설명문에는 제약으로 표현할 수 없는 것만 남긴다 — 값의 출처, 쓰지 말아야 할 상황, 다른 도구와의 관계. 이렇게 정리하면 스키마가 짧아지면서 오히려 정확해진다.

도구가 몇 개까지 괜찮은가

도구 목록은 매 요청마다 통째로 컨텍스트에 들어간다. 개수가 늘면 두 가지가 동시에 나빠진다. 토큰이 늘고, 비슷한 도구 사이의 혼동이 는다. 후자가 먼저 온다.

경험적으로 갈리는 지점은 이렇다.

  • 10개 이하 — 대체로 문제없다. 이름과 설명만 정리하면 된다.
  • 10~30개 — 혼동이 보이기 시작한다. 이름의 접두사로 묶고(order_*, ship_*), 겹치는 도구를 합치거나 경계 문장을 넣는다.
  • 30개 이상 — 목록을 그대로 다 주면 안 된다. 상황에 따라 부분집합만 노출한다.

마지막 경우의 흔한 처리가 단계별 노출이다. 대화 초반에는 도구를 몇 개만 주고, 사용자의 의도가 정해지면 그 영역의 도구를 추가로 붙인다. 다른 방법은 도구를 계층으로 두는 것이다 — 상위 도구 하나가 영역을 고르고, 그 결과로 하위 도구 목록이 들어온다. 어느 쪽이든 공통점은 하나다. 한 번에 모델이 판단해야 할 선택지 수를 줄인다.

여기서 주의할 것은 캐시다. 도구 목록은 프롬프트 앞쪽의 고정 부분이라 캐시가 가장 잘 듣는 자리인데, 대화 중간에 목록을 바꾸면 그 지점부터 캐시가 깨진다. 도구를 동적으로 붙일 거라면 바뀌는 지점을 최대한 뒤로 두거나, 대화 시작 시점에 한 번만 정하는 편이 낫다.

반환값도 스키마 설계의 일부다

입력 스키마만 다듬고 반환값을 방치하는 경우가 많은데, 모델이 다음 행동을 정하는 재료는 반환값이다. 두 가지만 지켜도 크게 나아진다.

필요한 필드만 돌려준다. 내부 API 응답을 그대로 넘기면 모델이 안 쓰는 필드 수십 개가 매 호출마다 컨텍스트에 쌓인다. 도구 결과는 대화 이력에 남아 다음 턴에도 계속 실려 다니므로, 여기서 절약한 토큰은 반복해서 절약된다.

빈 결과에 말을 붙인다. []만 돌려주면 모델은 이것이 "없음"인지 "조회 실패"인지 구별하지 못한다. {"results": [], "note": "조건에 맞는 주문이 없다. 기간을 넓히거나 키워드를 바꿔 볼 것"} 정도면 충분하다.

스키마를 고쳤는지 확인하는 법

스키마 수정은 프롬프트 수정과 똑같이 취급해야 한다. 즉, 고치기 전후를 같은 입력으로 비교해야 한다. 최소한의 형태는 이렇다 — 도구를 써야 하는 질문 20개와 쓰면 안 되는 질문 20개를 모아 두고, 스키마를 고칠 때마다 선택률(써야 할 때 골랐는가)과 오선택률(안 써도 될 때 골랐는가)을 함께 본다.

둘을 같이 봐야 하는 이유가 있다. 설명문에 "적극적으로 사용하세요"를 넣으면 선택률은 오르지만 오선택률이 같이 오른다. 한쪽만 보면 개선으로 착각하기 쉽다.

도구 스키마는 코드베이스에서 가장 자주 방치되면서 가장 자주 읽히는 텍스트다. 호출이 이상할 때 프롬프트를 열기 전에 그 도구의 설명문부터 다시 읽어 볼 값어치가 있다.


읽어주셔서 감사합니다. 😊

LATEST

에이전트·RAG의 최신 글

에이전트·RAG2026.08.23

에이전트는 어디서 어긋나기 시작하는가

에이전트가 이상한 답을 낼 때 증상은 마지막에 보이지만 어긋난 자리는 그보다 앞입니다. 한 걸음이 무너지는 여섯 자리를 나누고, 증상에서 원인을 거슬러 찾는 방법과 자리별 처방을 정리합니다.

11 MIN
에이전트·RAG2026.08.23

에이전트가 무엇을 했는지 나중에 알 수 있게 만들기

에이전트는 같은 입력에도 다른 경로로 갑니다. 그래서 로그 몇 줄로는 왜 그렇게 됐는지 복원이 안 됩니다. 트레이스를 어떻게 나누고 구간마다 무엇을 붙이며 어떤 지표를 볼지 정리합니다.

11 MIN
에이전트·RAG2026.08.23

에이전트 비용은 걸음 수보다 빨리 늘어난다

걸음이 두 배면 비용은 두 배가 아니라 서너 배입니다. 왜 그렇게 되는지, 어디서 새는지, 상한을 몇 겹으로 어떻게 거는지와 실제로 효과가 큰 순서대로의 대응을 정리합니다.

13 MIN