
AI 에이전트가 도구를 잘 사용하지 못하는 이유는 모델 때문이 아니라 '도구함'이 허술하기 때문일지도 모릅니다 — Function
요약
AI 에이전트의 성능 저하 원인이 모델이나 프롬프트가 아닌, Function Calling(도구 설계)의 부실함에 있음을 지적합니다. 안정적인 에이전트 구축을 위해 도구의 설계와 인자 전달 방식을 최적화하는 방법론을 다룹니다.
핵심 포인트
- 에이전트 실패의 주원인은 모델 성능보다 허술한 도구함 설계에 있음
- Function Calling은 AI가 외부 세계와 상호작용하는 핵심 메커니즘임
- 잘못된 도구 선택, 인자 오류, 응답 해석 오류가 주요 실패 패턴임
- 프롬프트 수정보다 도구(Tool) 자체의 정교한 설계가 안정성에 더 중요함
최근 AI 에이전트를 다루면서 이런 경험을 해본 적 없으신가요?
모델을 더 똑똑한 것으로 바꿨다. 프롬프트(Prompt)도 몇 번이나 다시 작성했다. 그런데도 에이전트는 여전히 불안정하다. 이상한 툴을 호출하거나, 인자(Argument)를 틀리거나, 툴의 응답을 잘못 읽어서 이상한 방향으로 나아가 버린다.
솔직히 말하면, 저도 처음에는 "모델이 별로인가", "프롬프트가 미숙한가"라고 생각했습니다. 하지만 다양한 실패 사례를 관찰하면서 점점 보는 눈이 달라졌습니다.
범인은 모델의 똑똑함도 프롬프트도 아니라, AI에게 전달하고 있는 "도구함"의 설계가 허술했기 때문인 경우가 매우 많습니다.
오늘은 그 "도구함"——즉 Function Calling (함수 호출/툴) 설계에 대해, AI 개발이 아직 익숙하지 않은 분들도 이해할 수 있도록, 그러면서도 실무에서 내일부터 바로 사용할 수 있는 수준까지 깊이 있게 써 내려가 보겠습니다. 코드 예시와 프롬프트 예시도 풍부하게 담았습니다.
먼저 여기서부터 시작하죠. 전문 용어처럼 들리니 한마디로 쉽게 풀어보겠습니다.
Function Calling (함수 호출)이란, 거칠게 말하면 "AI가 외부 세계로 손을 뻗기 위한 유일한 입"을 의미합니다.
LLM (ChatGPT나 Claude 같은 대규모 언어 모델 (Large Language Model))은 그대로 두면 문장을 반환하는 것밖에 할 수 없습니다. 데이터베이스를 보거나, 파일을 삭제하거나, 메일을 보내는 것도 할 수 없습니다. 뇌는 있지만 손이 없는 상태인 것이죠.
그래서 인간 측에서 "이것을 사용해도 좋아"라고 미리 도구(=함수)를 몇 개 전달해 둡니다. 예를 들어 get_order (주문 조회)라든가 send_email (메일 전송) 같은 것들 말이죠. AI는 "지금 이 도구를, 이 인자로 사용하고 싶다"라고 요청(Request)을 보내고, 실제 처리는 이쪽의 프로그램이 실행하여 그 결과를 AI에게 돌려줍니다. 이 일련의 메커니즘이 Function Calling이며, 전달하는 도구를 **툴 (Tool)**이라고 부릅니다.
여기서 중요한 것은 다음과 같은 감각입니다.
AI에게 툴은 요리사에게 있어 식칼이나 도마와 같습니다.
아무리 실력이 뛰어난 요리사라도, 잘 들지 않는 칼과 흔들거리는 도마를 받게 된다면 좋은 결과물을 낼 수 없습니다. 반대로 도구가 제대로 갖춰져 있다면, 평범한 요리사라도 안정적인 아웃풋을 낼 수 있습니다. AI 에이전트도 완전히 마찬가지입니다.
2026년에 들어서며 툴 이용 (Tool Use)의 실패 패턴은 연구를 통해 상당히 분류가 진행되었습니다. 어려운 이야기는 빼고, 현장에서 자주 발생하는 실패를 3가지로 압축하면 다음과 같습니다.
잘못된 도구를 선택함— 주문을 "확인만" 하면 되는데, 갑자기 "환불하기" 툴을 호출해 버림 -
인자를 틀림— 필요한 항목을 채우지 않거나, 말도 안 되는 값(마이너스 금액 등)을 전달함 -
도구의 응답을 잘못 읽음— 에러가 반환되었음에도 성공했다고 착각하여 다음 처리로 넘어가 버림
게다가 까다로운 점은, 툴 호출은 인자가 하나만 어긋나도 하류(Downstream)의 결과가 크게 뒤틀린다는 성질이 있다는 것입니다. 문장이라면 다소 차이가 있어도 읽을 수 있지만, amount: 1000이 amount: 100000이 된다면 그것은 사고입니다.
여기서 무심코 "그럼 프롬프트로 더 세세하게 지시하면 되겠지"라고 생각하기 쉽지만…… 잠시만 기다려 주세요. 그 부분이 오늘 가장 전달하고 싶은 포인트입니다.
많은 사람은 AI가 실패하면 프롬프트를 고치려고 합니다. "더 정중하게 지시하자", "주의하라고 쓰자"라고 말이죠.
하지만 한번 생각해 보시기 바랍니다. 칼이 잘 들지 않아 요리를 망쳤을 때, 요리사에게 "다음에는 좀 더 주의해서 썰어줘"라고 계속 말하는 것과, 칼 자체를 가는 것 중 어느 쪽이 효과적일까요?
AI 에이전트의 안정화는 사실 "프롬프트 엔지니어링 (Prompt Engineering)"보다 "도구 측의 설계"로 결정되는 부분이 엄청나게 큽니다. 이것은 최근 자주 언급되는 **Context Engineering (문맥 설계)**의 가장 구현에 가까운 부분이라고 저는 파악하고 있습니다. AI에게 전달하는 도구의 형태, 설명, 제약 그 자체가 AI의 판단 재료(=문맥)가 되기 때문입니다.
이후부터는 그 "도구의 설계"를 구체적으로 5가지 원칙으로 나누어 코드와 함께 살펴보겠습니다. 전부 오늘부터 하나씩 시도해 볼 수 있는 것들입니다.
가장 흔히 하는 실수는, 편리하다는 이유로 "무엇이든 할 수 있는 만능 툴"을 만들어 버리는 것입니다.
{
"name": "manage_order",
"description": "주문을 관리함 (조회, 갱신, 취소, 환불)",
...
겉보기에는 스마트해 보이지만, 이는 AI에게 지옥과 같습니다. action에 무엇을 넣어야 할지, data의 형식은 무엇인지 AI는 매번 추측해야 합니다. 게다가 '조회(읽기 전용)'와 '환불(돈이 움직임)'이 같은 도구에 공존하고 있기 때문에, 단순히 읽으려던 의도가 실수로 환불을 호출하는 사고가 발생하기 쉽습니다.
2026년의 베스트 프랙티스(Best Practice)는 명확합니다. 하나의 만능 도구에 read·write·delete를 모두 집어넣지 말고, 책임(Responsibility)에 따라 분할하는 것입니다. 이렇게 하면 AI의 판단 면(선택지의 폭)이 좁아져 혼란을 줄일 수 있고, 로그 추적도 쉬워지며, "이 위험한 작업만 승인제로 운영한다"와 같은 제어도 걸기 쉬워집니다.
도구함을 정리 정돈하는 개념은 다음과 같습니다.
get_order… 주문을 가져옴 (읽기 전용 · 안전)update_shipping_address… 배송지만 변경 (쓰기 작업이지만 저위험)refund_payment… 환불 (돈이 움직임 · 고위험)
특히 "읽기 도구"와 "쓰기 도구"를 분리하는 것은 효과가 매우 큽니다. 읽기 전용 도구는 가볍게 몇 번이고 호출해도 되지만, 쓰기 도구는 신중해야 합니다. 이 경계선이 도구 레벨에서부터 작용하게 됩니다.
스키마(Schema)란 해당 도구가 받는 인자(Argument)의 "타입(Type)"이나 "규칙"을 정의한 것입니다. 이곳을 느슨하게 만들면 AI는 너무 자유로워져서 실수를 합니다. 반대로 엄격하게 제한하면, 애초에 잘못된 값을 생성할 수 없게 됩니다.
앞서 본 manage_order를 refund_payment 단독 도구로서 스키마를 제대로 다시 작성하면 다음과 같습니다.
{
"name": "refund_payment",
"description": "확정된 주문에 대해 환불을 진행합니다. 명확한 환불 지시가 있을 때만 사용하십시오. 내용 확인만 필요한 경우에는 get_order를 사용하고, 이 도구는 사용하지 마십시오.",
...
}
포인트를 자세히 짚어보겠습니다.
pattern… ID 형식을 정규 표현식(Regular Expression)으로 고정. 그럴듯한 가짜 ID를 만들지 못하게 함minimum… 금액은 1 이상의 정수. 음수, 0, 소수를 차단enum… 이유는 3가지 선택지뿐. 자유 기술을 허용하지 않고 선택하게 함required… 필수 항목을 명시. 누락 방지additionalProperties: false… 불필요한 항목을 마음대로 추가하지 못하게 함
참고로, 이 input_schema의 형태는 예를 들어 Anthropic의 Messages API에서 tools로 전달하는 스키마와 구조가 동일합니다. **스키마는 "AI가 준수해야 할 계약서"**라고 생각하십시오. 계약이 모호하면 AI도 모호하게 행동합니다. 계약이 확실하면 AI도 확실하게 움직입니다.
이 부분은 의외로 간과되기 쉽지만, 굉장히 중요합니다.
도구의 description(설명문)을 인간용 문서처럼 "주문을 환불함"이라고만 적고 끝내고 계시지는 않나요? AI는 이 설명문을 읽고 "언제 이 도구를 사용할지"를 판단합니다. 즉, description은 AI에게 있어 취급 설명서 그 자체입니다.
좋은 취급 설명서에는 최소한 다음 4가지가 포함되어야 합니다.
- 해당 도구가 무엇을 하는가 (부작용, 즉 "무엇이 변하는가"도 작성)
- 사용해야 하는 상황
- 사용해서는 안 되는 상황과 그 대신 사용할 도구
- 각 인자의 의미와 제약 사항 (가능하면 예시 포함)
특히 3번, "사용하지 않을 때"를 적는 것이 효과적입니다. 방금 전 스키마에서도 일부러 이렇게 적었습니다.
내용 확인만 필요한 경우에는 get_order를 사용하고, 이 도구는 사용하지 마십시오.
이 한 줄이 있는 것만으로도, "주문을 살짝 보고 싶을 뿐"인 상황에서 환불 도구가 잘못 발화되는 사고를 크게 줄일 수 있습니다. 인간 신입 사원에게 도구를 건네줄 때와 같습니다. "이것은 ○○일 때 사용하는 거야. △△일 때는 절대 쓰지 말고, 옆에 있는 이걸 사용해"라고 덧붙여 주는 것만으로 사고가 줄어듭니다. AI에게도 똑같은 친절을 베푸는 것뿐입니다.
도구가 에러(Error)를 반환하는 것 자체는 나쁜 일이 아닙니다. 문제는 그 반환 방식입니다.
흔히 하는 잘못된 방식은 프로그램의 내부 에러(Stack Trace)를 그대로 AI에게 던지는 것입니다. 인간조차 읽고 싶지 않은 것을 전달받으면, AI는 "그래서 다음에 뭘 해야 하지?"를 알 수 없어 같은 실수를 반복하거나 무한 루프에 빠지게 됩니다.
목표로 해야 하는 것은 **"AI가 그 에러를 읽고 스스로 다음의 올바른 수를 둘 수 있는 에러"**입니다. 구체적으로는, 기계 판독이 가능한 error
코드와 함께, 다음에 무엇을 해야 할지에 대한 hint를 세트로 반환합니다. 그리고 무엇보다도, 부작용(side effect)을 일으키기 전에 스스로 입력을 검증해야 합니다. AI의 인자(argument)를 그대로 믿지 마세요.
Python으로 작성하면 다음과 같은 형태가 됩니다.
from typing import Any
def refund_payment(args: dict[str, Any]) -> dict[str, Any]:
# 1) 우선 스스로 입력을 검증한다 (AI의 인자를 신뢰하지 않는다)
...
이 코드의 핵심은 두 가지입니다.
하나는, 실행(환불)을 함수의 가장 마지막에 배치했다는 점입니다. 그 전 단계는 모두 체크 과정입니다. 돈을 움직이기 전에 이상한 입력을 모두 걸러냅니다. 또 다른 하나는, 에러의 hint가 「다음에 어떤 도구를 사용해야 하는지」까지 알려준다는 점입니다. 예를 들어 「먼저 get_order로 확인해 주세요」라고 적혀 있다면, AI는 제대로 그 지침에 따라 다시 움직여 줍니다. 에러가 단순한 지적이 아니라 길 안내 역할을 하게 되는 것입니다.
마지막은 가장 중요한 보안 이야기입니다.
2026년에 가장 많이 발생할 설정 오류는 무엇일까요? 바로 에이전트가 실행한 사람과 동일한 강력한 권한으로 동작하고 있는 것이라고 합니다. 파일 쓰기, 네트워크 전송, 명령 실행, 데이터베이스 관리자 권한까지, 실제로는 그 업무에 필요하지 않은데도 전부 가지고 있는 상황입니다. 이는 사고가 발생했을 때 피해가 너무나 큽니다.
따라서 도구를 설계할 때는 다음 네 가지를 의식해야 합니다.
- 최소 권한 (least privilege): 해당 도구에 필요한 권한만 부여합니다. 삭제 도구에 송금 권한은 필요 없습니다.
- 확인 게이트 (confirmation gate): 삭제, 송금, 공개와 같이 되돌릴 수 없는 작업은 실행 전에 인간의 승인을 거치도록 합니다.
- 드라이 런 (dry-run): 「실제로 실행하지 않고, 어떤 일이 일어날지만 보여주는」 모드를 준비합니다.
- 멱등성 (idempotency): 동일한 작업을 두 번 호출해도 결과가 중복되지 않도록 만듭니다 (예: 동일한 주문을 이중으로 환불하지 않도록 함).
'멱등성'은 생소한 용어일 수 있습니다. 「몇 번을 눌러도 1회분의 결과만 나타나는」 성질을 의미합니다. 엘리베이터 버튼을 연타해도 한 번만 호출되는 것과 같습니다. AI는 가끔 동일한 툴을 연속해서 호출하기 때문에, 이것이 없으면 중복 실행 사고로 이어집니다.
확인 게이트를 넣은 예시를 TypeScript(zod라는 라이브러리로 입력 검증)로 작성해 보겠습니다.
import { z } from "zod";
// 입력 스키마 = AI가 준수해야 할 계약
const DeleteFileInput = z.object({
...
path를 /workspace/ 하위로만 정규 표현식으로 제한한 것이 포인트입니다. 이렇게 하면 AI가 실수로 /etc/와 같은 중요한 위치를 삭제하려 해도, 도구 수준에서 물리적으로 차단됩니다. 프롬프트로 「위험한 파일은 삭제하지 마세요」라고 부탁하는 것보다, 애초에 삭제할 수 없도록 도구를 만드는 것이 수십 배는 더 확실합니다.
지금까지의 원칙을 자신의 프로젝트에 적용하기 위한 프롬프트 3개를 남겨둡니다. AI에게 자신의 도구함을 리뷰하게 만드는 용도입니다.
프롬프트 1: 도구 점검 (Inventory)
당신은 AI 에이전트의 툴 설계 리뷰어입니다.
다음의 툴 목록을 아래의 관점에서 점검해 주세요.
- 하나의 툴이 여러 책무(read와 write, 여러 가지 작업)를 떠안고 있지는 않은가
...
프롬프트 2: 설명문 (description) 재작성
다음 툴의 description을 AI가 「언제 사용하고 언제 사용하지 않을지」를 망설임 없이 판단할 수 있는 사용 설명서로 재작성해 주세요. 반드시 포함할 내용:
1. 이 툴이 무엇을 하는가 (부작용 = 무엇이 변하는지도 명시)
...
프롬프트 3: 에러 설계 리뷰
다음 툴 구현의 에러 핸들링(error handling)을 리뷰해 주세요.
판단 기준은 「AI가 이 에러를 받았을 때, 다음의 올바른 수를 스스로 둘 수 있는가」입니다.
- 스택 트레이스(stack trace)나 내부 예외를 그대로 반환하고 있지는 않은가
...
이 세 가지를 순서대로 돌리는 것만으로도 자신의 도구함이 상당히 정돈됩니다. AI에게 자신의 도구를 점검하게 하는 것은 꽤 기분 좋은 활용법입니다.
이 글의 핵심은 「AI가 똑똑해지면 모든 것이 해결된다」는 데 있지 않습니다. 도구를 설계하는 것은 마지막까지 인간의 일입니다. 역할을 정리해 두겠습니다.
| 공정 | AI에게 맡기는 것 | 인간이 설계·판단하는 것 |
|---|---|---|
| 도구 실행 | 인자(Argument)를 구성하여 호출 | 어떤 도구를 선반에 배치할 것인가 |
| ... |
이렇게 정리하면 명확해집니다. AI에게 맡기는 것은 "도구를 사용하는 것". 인간이 가져야 할 역할은 "도구를 설계하고, 위험한 곳에 자물쇠를 채우며, 선반을 계속 정돈하는 것"입니다. AI 시대에 강한 엔지니어란, 똑똑한 프롬프트(Prompt)를 쓰는 사람이 아니라, AI가 안전하게 사용할 수 있는 도구 선반을 설계할 수 있는 사람일지도 모릅니다.
거창하게 시작하지 않아도 괜찮습니다. 작게 하나씩 해나가면 됩니다.
- 현재 있는 도구(또는 앞으로 만들 예정인 도구)를 전부 나열하고, "읽기 계열·쓰기 계열·파괴 계열"로 색깔을 나누어 분류하기
- 가장 위험한 도구를 딱 하나만 골라서, 스키마(Schema)를 아주 엄격하게(필수 항목, enum, 범위 지정 등) 설정하기. description에 "사용하지 않을 때"에 대한 설명을 한 줄 추가하기
- 해당 도구의 에러를
error와hint형태로 다시 쓰기. 실행 전에 입력을 검증하는 몇 줄의 코드를 추가하기 - 되돌릴 수 없는 작업에 확인 게이트(Confirmation Gate)를 딱 하나만 달아보기
이것만으로도 에이전트(Agent)의 안정감은 상당히 달라집니다. 모델을 교체하지 않아도, 오늘 나의 손으로 직접 고칠 수 있습니다. 그 점이 바로 장점입니다.
마지막으로, 제가 느끼는 점을 조금만 말씀드리겠습니다.
도구 선반을 제대로 설계하는 것은 솔직히 말해 지루한 작업입니다. 스키마를 제한하고, 설명문을 수정하고, 에러를 다시 쓰고, 확인 게이트를 추가하고……. 화려함은 없습니다. 하지만 이것을 해두면 미래가 훨씬 편해집니다.
저는 무언가를 설계할 때 항상 "내일의 내가 오늘의 나에게 고맙다고 말해줄까?"라고 스스로에게 묻곤 합니다. 허술한 도구 선반을 방치하면, 내일의 나(와 AI)는 사고 처리에 쫓기게 됩니다. 반대로 오늘 조금 수고를 들여 도구를 정돈해 두면, 내일의 나는 안심하고 AI에게 일을 맡길 수 있습니다.
도구 설계란, 미래의 자신과 AI를 위한 선물을 오늘 미리 준비해 두는 작업이라고 생각합니다. 게다가 제대로 만든 도구는 일회용 프롬프트와 달리 계속 재사용할 수 있는 자산이 됩니다. 쓴 만큼 쌓여가는 것이죠.
AI가 점점 똑똑해지는 앞으로의 시대에, 우리의 업무는 "AI 대신 손을 움직이는 것"에서 "AI가 안전하게 손을 움직일 수 있는 장소를 설계하는 것"으로 조용히 옮겨가고 있는 것이 아닐까 느낍니다.
그 첫걸음으로, 우선 자신의 도구 선반을 하나씩 정돈해 보세요. 오늘의 그 작은 수고에 대해, 내일의 당신이 분명 "고마워"라고 말해줄 것입니다.
여기까지 읽어주셔서 감사합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기