Tool Use & Function Calling 완전 정복 — AI가 직접 도구를 쓰게 만드는 법
AI가 직접 API를 호출하고 함수를 실행하게 만드는 법. Tool Use의 원리부터 실전 설계까지 완전 정복.
들어가며: 16편이 남긴 질문
16편에서 프롬프트 인젝션 방어를 다루며 이런 예고를 남겼습니다.
"AI가 API를 직접 호출하고, 함수를 실행하게 하려면 어떻게 해야 할까?"
지금까지 AI는 텍스트를 받아 텍스트를 출력했습니다. 아무리 좋은 답변을 생성해도, 그것은 결국 텍스트였습니다.
그런데 현실 문제는 텍스트 답변만으로 해결되지 않습니다.
"지금 서울 날씨를 알려줘"라는 질문에 AI가 훈련 데이터에서 떠올린 서울의 평균 기온을 말해주는 것과, 실제 기상청 API를 호출해서 지금 이 순간의 기온을 알려주는 것은 완전히 다릅니다.
"이 고객의 최근 주문 내역을 확인해줘"라는 요청에 AI가 "데이터베이스를 확인해보세요"라고 말하는 것과, 실제 데이터베이스를 직접 조회해서 결과를 가져오는 것도 다릅니다.
이것이 Tool Use(도구 사용), 또는 Function Calling입니다. AI가 텍스트만 생성하는 것이 아니라, 외부 함수와 API를 직접 호출해서 실제 데이터를 가져오고 실제 작업을 수행하는 기술입니다.
이번 편에서는 Tool Use의 작동 원리부터, 도구를 어떻게 정의하는지, 실전에서 어떻게 활용하는지까지 처음부터 끝까지 다룹니다.
1. Tool Use란 무엇인가
1.1. 가장 쉬운 비유
사람을 고용했다고 생각해보세요. 이 사람은 매우 똑똑하고 지식이 풍부합니다. 그런데 두 가지 버전이 있습니다.
버전 A: 책과 기억만 있습니다. 질문하면 자신이 아는 것을 말해줍니다. 하지만 전화를 걸거나, 컴퓨터로 검색하거나, 실제로 무언가를 조작하지는 못합니다.
버전 B: 책과 기억 외에도, 전화기, 컴퓨터, 계산기, 데이터베이스 접근 권한이 있습니다. 필요하면 직접 전화를 걸어 확인하고, 데이터베이스를 조회하고, 계산기로 정확한 숫자를 냅니다.
Tool Use 이전의 AI는 버전 A입니다. Tool Use를 적용한 AI는 버전 B입니다.
# Tool Use 없는 AI
사용자: "지금 달러/원 환율이 얼마야?"
AI: "제 훈련 데이터 기준으로 약 1,300원대입니다."
(← 실제 환율이 아님)
# Tool Use 있는 AI
사용자: "지금 달러/원 환율이 얼마야?"
AI: exchange_rate_api("USD", "KRW") 호출
→ 결과: 1,378.50원
AI: "현재 달러/원 환율은 1,378.50원입니다." (← 실제 환율)
1.2. 작동 원리
Tool Use는 다음 순서로 동작합니다.
1. 개발자가 사용 가능한 도구 목록을 AI에게 알려준다
↓
2. 사용자가 질문을 보낸다
↓
3. AI가 "이 질문에 답하려면 어떤 도구가 필요한가"를 판단한다
↓
4. AI가 도구 호출 요청을 생성한다 (실제 호출은 AI가 하지 않음)
↓
5. 개발자 코드가 실제 API/함수를 호출한다
↓
6. 결과를 AI에게 돌려준다
↓
7. AI가 결과를 바탕으로 최종 답변을 생성한다
중요한 점: AI가 직접 API를 호출하는 것이 아닙니다. AI는 "이 도구를 이 파라미터로 호출해야 한다"는 요청을 생성하고, 실제 호출은 우리 코드가 합니다. 보안과 제어권을 개발자가 유지할 수 있는 이유가 여기 있습니다.
2. 도구 정의하는 법
2.1. 도구 정의의 4요소
AI에게 도구를 알려줄 때는 네 가지를 명확하게 써야 합니다.
# Claude API 기준 도구 정의
tool = {
"name": "get_weather", # 1. 도구 이름
"description": "특정 도시의 현재 날씨를 반환한다. "
"날씨 관련 질문이 오면 반드시 이 도구를 사용한다.", # 2. 설명
"input_schema": { # 3. 파라미터 스키마
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "날씨를 조회할 도시명. 한국어 또는 영어 모두 가능."
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "온도 단위. 기본값은 celsius."
}
},
"required": ["city"] # 4. 필수 파라미터
}
}
이름: 함수명처럼 간결하고 의미가 명확해야 합니다. get_weather가 weather 보다 낫고, search_product_by_name이 search 보다 낫습니다.
설명: 가장 중요합니다. AI는 이 설명을 보고 언제 이 도구를 쓸지 판단합니다. "언제 써야 하는지"를 명확하게 쓰세요.
파라미터: 타입, 허용 값(enum), 설명을 꼼꼼하게 씁니다. 파라미터 설명이 부실하면 AI가 잘못된 값을 넣습니다.
required: 필수 파라미터를 명시합니다. 명시하지 않으면 AI가 빠뜨릴 수 있습니다.
2.2. 좋은 도구 설명 vs 나쁜 도구 설명
# ❌ 나쁜 설명 — 언제 쓸지 모른다
{
"name": "search",
"description": "검색한다.",
"input_schema": {
"type": "object",
"properties": {
"q": {"type": "string"}
}
}
}
# ✅ 좋은 설명 — 언제, 어떻게 써야 하는지 명확
{
"name": "web_search",
"description": "최신 뉴스, 실시간 정보, 훈련 데이터 이후의 사건을 조회할 때 사용한다. "
"모델이 알 수 없는 최신 정보가 필요한 모든 상황에 이 도구를 사용한다. "
"검색어는 핵심 키워드 위주로 간결하게 작성할수록 결과 품질이 높다.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "검색할 키워드 또는 질문. 5단어 이내로 간결하게."
},
"language": {
"type": "string",
"enum": ["ko", "en"],
"description": "검색 언어. 기본값 ko."
}
},
"required": ["query"]
}
}
3. 실전 구현 — Claude API 기준
3.1. 기본 구조
import anthropic
import json
client = anthropic.Anthropic()
# 도구 정의
tools = [
{
"name": "get_weather",
"description": "도시의 현재 날씨를 조회한다. 날씨 관련 질문에 반드시 사용한다.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "날씨를 조회할 도시명"
}
},
"required": ["city"]
}
}
]
# 실제 함수 (우리가 구현)
def get_weather(city: str) -> dict:
# 실제로는 기상 API 호출
return {"city": city, "temperature": 18, "condition": "맑음", "humidity": 55}
# 1단계: AI에게 질문 + 도구 목록 전달
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "지금 서울 날씨 어때?"}]
)
# 2단계: AI가 도구 호출을 요청했는지 확인
if response.stop_reason == "tool_use":
tool_use_block = next(b for b in response.content if b.type == "tool_use")
tool_name = tool_use_block.name # "get_weather"
tool_input = tool_use_block.input # {"city": "서울"}
tool_use_id = tool_use_block.id
# 3단계: 실제 함수 호출
result = get_weather(**tool_input)
# 4단계: 결과를 AI에게 돌려주고 최종 답변 요청
final_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "지금 서울 날씨 어때?"},
{"role": "assistant", "content": response.content},
{
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use_id,
"content": json.dumps(result, ensure_ascii=False)
}]
}
]
)
print(final_response.content[0].text)
# 출력: "현재 서울의 날씨는 맑음이고, 기온은 18도, 습도는 55%입니다."
3.2. 여러 도구를 함께 정의하기
실제 서비스에서는 도구가 여러 개입니다. 여러 도구를 함께 제공하면 AI가 상황에 맞게 선택합니다.
tools = [
{
"name": "get_weather",
"description": "도시의 현재 날씨를 조회한다.",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "도시명"}
},
"required": ["city"]
}
},
{
"name": "search_restaurant",
"description": "특정 지역의 맛집을 검색한다. 식당, 음식점, 카페 관련 질문에 사용한다.",
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "검색할 지역"},
"cuisine": {"type": "string", "description": "음식 종류 (한식, 일식, 양식 등)"},
"max_results": {"type": "integer", "description": "최대 결과 수. 기본값 5."}
},
"required": ["location"]
}
},
{
"name": "calculate",
"description": "수식을 계산한다. 숫자 계산이 필요한 모든 상황에 사용한다.",
"input_schema": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "계산할 수식. 예: '(100 * 1.1) + 50'"}
},
"required": ["expression"]
}
}
]
# AI가 스스로 어떤 도구를 쓸지 결정
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "강남에서 일식 맛집 알려줘. 3곳만."}]
)
# AI는 search_restaurant 도구를 선택하고
# location="강남", cuisine="일식", max_results=3 파라미터를 넣음
4. 병렬 도구 호출
AI가 여러 도구를 동시에 호출해야 할 때 병렬로 처리할 수 있습니다. 순서대로 하나씩 기다리는 것보다 훨씬 빠릅니다.
# 질문: "서울이랑 부산 날씨를 비교해줘"
# AI가 두 도시를 동시에 조회 요청
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "서울이랑 부산 날씨 비교해줘"}]
)
# response.content에 두 개의 tool_use 블록이 들어있음
tool_uses = [b for b in response.content if b.type == "tool_use"]
# 두 도구를 병렬로 실행
import concurrent.futures
def execute_tool(tool_use_block):
if tool_use_block.name == "get_weather":
result = get_weather(**tool_use_block.input)
return tool_use_block.id, result
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = [executor.submit(execute_tool, t) for t in tool_uses]
results = [f.result() for f in concurrent.futures.as_completed(futures)]
# 모든 결과를 AI에게 한 번에 돌려줌
tool_results = [
{
"type": "tool_result",
"tool_use_id": tool_id,
"content": json.dumps(result, ensure_ascii=False)
}
for tool_id, result in results
]
final_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "서울이랑 부산 날씨 비교해줘"},
{"role": "assistant", "content": response.content},
{"role": "user", "content": tool_results}
]
)
5. 도구 사용 강제하기
기본적으로 AI는 도구를 쓸지 말지 스스로 판단합니다. 하지만 특정 상황에서는 반드시 도구를 쓰도록 강제하거나, 반대로 절대 쓰지 않도록 설정할 수 있습니다.
# 항상 도구 사용 (특정 도구 강제)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "tool", "name": "get_weather"}, # 반드시 이 도구 사용
messages=[{"role": "user", "content": "서울 날씨 어때?"}]
)
# 도구 사용 여부를 AI가 결정 (기본값)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "auto"}, # 기본값
messages=[...]
)
# 도구 사용 금지 (텍스트 답변만)
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
tool_choice={"type": "none"}, # 도구 사용 금지
messages=[...]
)
6. 실전 활용 사례
6.1. 고객 서비스 챗봇
tools = [
{
"name": "get_order_status",
"description": "주문 번호로 주문 상태를 조회한다.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "주문 번호 (예: ORD-20240424-001)"}
},
"required": ["order_id"]
}
},
{
"name": "get_product_info",
"description": "제품 ID나 이름으로 제품 정보를 조회한다.",
"input_schema": {
"type": "object",
"properties": {
"product_id": {"type": "string", "description": "제품 ID"},
"product_name": {"type": "string", "description": "제품 이름"}
}
}
},
{
"name": "create_refund_request",
"description": "환불 요청을 생성한다. 사용자가 환불을 요청하고 조건을 확인한 경우에만 사용한다.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"reason": {"type": "string", "description": "환불 사유"}
},
"required": ["order_id", "reason"]
}
}
]
# 사용자: "ORD-20240424-001 주문 지금 어디 있어?"
# AI: get_order_status(order_id="ORD-20240424-001") 호출
# 결과: {"status": "배송 중", "location": "수원 물류센터", "expected": "내일 오후"}
# AI: "고객님의 주문은 현재 수원 물류센터에서 배송 중입니다. 내일 오후 도착 예정입니다."
6.2. 데이터 분석 AI
tools = [
{
"name": "run_sql",
"description": "SQL 쿼리를 실행하고 결과를 반환한다. 데이터 조회가 필요한 경우 사용한다.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "실행할 SELECT SQL 쿼리. INSERT/UPDATE/DELETE는 허용되지 않는다."
}
},
"required": ["query"]
}
}
]
# 사용자: "지난달 매출 상위 5개 제품이 뭐야?"
# AI: run_sql("SELECT product_name, SUM(amount) as total FROM orders WHERE ...") 호출
# 결과: [{"product_name": "A", "total": 1500000}, ...]
# AI: "지난달 매출 상위 5개 제품은 A (150만원), B (130만원)..." 형태로 답변
7. 실패 패턴
7.1. 설명이 부실해서 엉뚱한 도구를 쓴다
# ❌ 설명이 부실한 두 도구
{
"name": "search_news",
"description": "검색한다." # 너무 짧음
},
{
"name": "search_products",
"description": "검색한다." # 동일한 설명
}
# AI가 어떤 도구를 써야 할지 구분하지 못함
# "아이폰 16 출시 뉴스 찾아줘" → 둘 중 아무거나 선택
# ✅ 명확한 설명
{
"name": "search_news",
"description": "최신 뉴스 기사를 검색한다. 시사, 사건, 이슈 관련 정보가 필요할 때 사용한다."
},
{
"name": "search_products",
"description": "쇼핑몰 상품을 검색한다. 구매, 가격 비교, 제품 정보가 필요할 때 사용한다."
}
7.2. 도구 결과를 검증하지 않는다
# ❌ 검증 없이 바로 사용
result = execute_tool(tool_name, tool_input)
# result가 에러이거나 None이어도 그냥 AI에게 전달
# ✅ 결과 검증 후 전달
result = execute_tool(tool_name, tool_input)
if result is None or "error" in result:
tool_result_content = "도구 호출에 실패했습니다. 잠시 후 다시 시도해주세요."
else:
tool_result_content = json.dumps(result, ensure_ascii=False)
7.3. 도구가 너무 많다
도구가 많을수록 AI가 잘못 선택할 확률이 높아집니다. 하나의 에이전트에 7개 이상의 도구는 피하는 것이 좋습니다.
# ❌ 도구가 너무 많음 (15개)
tools = [search, weather, maps, news, stocks, calendar,
email, translate, calculate, database, files,
images, videos, music, social_media]
# ✅ 역할별로 에이전트를 분리
# 검색 에이전트: search, news, web
# 비즈니스 에이전트: database, calendar, email
# 유틸리티 에이전트: calculate, translate, weather
7.4. 무한 루프
AI가 도구 결과를 받고도 다시 같은 도구를 호출하는 경우입니다.
# 최대 호출 횟수 제한 필수
MAX_TOOL_CALLS = 5
tool_call_count = 0
while response.stop_reason == "tool_use":
tool_call_count += 1
if tool_call_count > MAX_TOOL_CALLS:
break # 강제 종료
# 도구 실행 및 다음 응답 요청
...
결론: AI가 세상과 연결되는 순간
1편부터 지금까지 프롬프트의 모든 것을 다뤘습니다. 좋은 지시를 쓰고, 구조를 잡고, 컨텍스트를 설계하고, 에이전트를 만들고, 보안을 지키는 것까지.
Tool Use는 그 모든 것의 완성입니다. AI가 더 이상 텍스트 상자 안에만 있지 않습니다. 실제 시스템과 연결되고, 실제 데이터를 가져오고, 실제 작업을 수행합니다.
핵심은 세 가지입니다. 도구 설명을 명확하게 써야 AI가 올바른 도구를 선택합니다. 파라미터를 꼼꼼하게 정의해야 AI가 올바른 값을 넣습니다. 결과를 반드시 검증해야 에러가 그대로 사용자에게 전달되지 않습니다.
핵심 원칙 요약
| 원칙 | 핵심 |
|---|---|
| 설명이 전부 | 도구 description에 "언제 써야 하는지"를 명확히 써야 AI가 올바르게 선택 |
| 파라미터 꼼꼼히 | 타입, enum, description, required를 모두 채워야 AI가 올바른 값을 넣음 |
| 검증 필수 | 도구 결과가 에러이거나 비어있을 경우 처리 코드 반드시 작성 |
| 도구는 최소화 | 하나의 에이전트에 7개 이하. 역할별로 에이전트 분리 |
| 루프 제한 | 최대 호출 횟수를 설정해 무한 루프 방지 |
18편을 향하여
Tool Use까지 익혔다면, 이제 이 모든 것을 하나로 엮어서 AI 하네싱을 직업으로 만드는 이야기가 남아있습니다.
"프롬프트를 잘 쓰는 것이 어떻게 경쟁력이 될 수 있을까?"
[18편: AI 하네싱을 직업으로 — 프롬프트 엔지니어링의 미래] 에서는 이 시리즈 전체를 관통하는 핵심 질문의 답을 다룹니다.
