Tool use with Claude
해당 챕터를 처음 공부할 때, 큰 그림이 잡히지 않아서 좀 곤란했다. 두 번째 보면서 어떻게 이 장을 보면 좋을지 생각해봤고, 그 흐름도를 첨부한다.

Introducing tool use
Tool을 사용하면 얻게 되는 이점
- Real-Time information
- 외부 시스템과 통합
- 동적 대답
- 구조적인(structured) 상호작용 : 클로드가 어떤 정보가 필요하고 어떻게 요청할지 알게 됨
Project overview
왜 클로드는 Tool을 필요로 하는가?
예시로 '몇일 몇시에 치과 예약 스케줄을 등록해줘' 라고 하면, 클로드는 다음의 세 가지 문제에 봉착한다.
- 지금이 정확히 몇일 몇시인지 알지 못한다.
- 시간 계산을 정확하게 해낼 수 없다.
- reminder 기능이 없다!
=> AI와 일을 할 때 한계에 마주한다면, 프롬프트로 개선하려고 애를 쓰는 것 보다, 외부 tool을 사용해서 문제를 해결하자!
Tool functions
tool function 작성할 때 다음과 같은 가이드라인을 따를 것.
- 함수 이름이랑 파라미터 이름 잘 짓기
- input 검증하고 틀리면 error 던지도록 설계하기
- output도 마찬가지로 검증하고 틀리면 error 던지도록 설계하기
- error 던질 때, 의미있는 정보 제공하기(error의 원인)
클로드는 error에서 배우기 때문에 중요하다!
그래서 다음의 함수를 작성하도록 설명한다.
def get_current_datetime(date_format="%Y-%m-%d %H:%M:%S"):
if not date_format:
raise ValueError("date_format cannot be empty")
return datetime.now().strftime(date_format)
Tool schemas
클로드에게 Tool 함수에 어떤 입력이 들어가야 하는지, 그리고 어떻게 함수를 사용하는지 알려줄 필요가 있다. 이를 tool schema라고 부르고, json 형태로 클로드에게 알려준다.
mandetory attribute는 다음과 같다.
- name : tool 함수 이름
- description : tool의 역할, 언제 사용하는지, 반환값이 무슨 의미를 가지고 있는지 설명한다.
- input_schema : tool 함수의 입력 인자에 대해 설명
Best practice에는 이렇게 description을 써보라고 한다.
- tool의 역할을 3~4문장으로 설명할 것
- 언제 클로드가 tool을 써야 할 지 쓸 것
- 어떤 종류의 데이터를 리턴하는지 설명할 것
- 각 입력 인자에 대해 상세하게 설명할 것
그렇게 해서 작성한 tool schema는 다음과 같다.
get_current_datetime_schema = {
"name": "get_current_datetime",
"description": "Returns the current date and time formatted according to the specified format",
"input_schema": {
"type": "object",
"properties": {
"date_format": {
"type": "string",
"description": "A string specifying the format of the returned datetime. Uses Python's strftime format codes.",
"default": "%Y-%m-%d %H:%M:%S"
}
},
"required": []
}
}
입력 인자를 좀 더 안전하게 받고 싶다면, ToolParam을 이용해서 좀 더 견고하게 하라고 나와있는데, 설명이 부실해서 클로드한테 자세히 물어봤다.
ToolParam을 쓰게 되면 이전에는 일반 dictionary로 썼을 때 발견되지 않는 오타나 구조 오류를 작성시에 잡아낼 수 있다고 나와있다.
그러니까 틀린 형태의 dict를 컴파일(타입 체크) 시에 잡아낼 수 있다는게 이점이라고 한다.
(+)베타입니다 좀 더 찾아보니까 @beta_tool 이라는 데코레이터도 있다고 한다.
스키마를 작성하지 않고도 함수의 타입 힌트와 docstring을 읽어서 JSON 스키마를 자동으로 만들어준다고 한다.
import json
from anthropic import Anthropic, beta_tool
client = Anthropic()
@beta_tool
def get_weather(location: str, unit: str = "celsius") -> str:
"""Get the current weather in a given location.
Args:
location: The city and state, e.g. San Francisco, CA
unit: Temperature unit, either 'celsius' or 'fahrenheit'
"""
# ↓↓↓ 여기부터는 진짜 구현 코드 (docstring이 아님)
# 실제로는 외부 날씨 API를 호출하거나 DB를 조회하는 로직이 들어감
if unit == "fahrenheit":
temp = "68°F"
else:
temp = "20°C"
return json.dumps({"location": location, "temperature": temp, "condition": "Sunny"})
runner = client.beta.messages.tool_runner(
model="claude-sonnet-4-5-20250929",
max_tokens=1024,
tools=[get_weather],
messages=[{"role": "user", "content": "What's the weather like in Paris?"}],
)
for message in runner:
print(message)
Handling message blocks
클로드와 왔다 갔다 주고 받는 메세지에는 text block만 있는게 아니다.
assistant가 보내는 message block에는 ToolUseBlock이라는 것이 있어서, tool에 대한 정보를 다른 block을 써서 보낼 수 있다.
ToolUseBlock에는 다음과 같은 내용이 들어간다.
- ID : call 한 tool의 ID
- name : 호출한 tool의 function 이름
- input : dict 형태로, 입력할 인자를 넣는다. {'date_format' : '%H:%M:%S'}
- type : 'tool_use' 라고 쓰면 된다.
tool을 쓰기 위해서 다음의 단계를 거친다.
- 클로드에게 사용자가 tool schema를 보낸다.
- 클로드에게 text block과 tool use block이 담긴 메세지를 받는다.
- 받은 tool use block에서 tool 정보를 추출하고, tool을 실행시킨다.
- 클로드에게 사용자가 tool을 실행한 결과를 보낸다. 물론 앞에서 배운 것 대로 1-3에 대한 히스토리도 함께 포함되어 있다.
- 클로드에게 최종 응답을 받는다.

Sending tool results
앞서 설명한 tool을 쓰기 위한 5단계에서 3. tool 실행 ~ 4. tool 실행 결과 에 해당하는 부분을 살펴보겠다.
클로드에게서 받은 응답에서 tool parameter에 접근하는 방법은 쉽다.
response.content[1].input
get_current_datetime(**response.content[1].input)
이렇게 하면 클로드가 tool function에 전달하려고 한 인자를 넣을 수 있다.
클로드에게 tool 실행 결과를 전달하는 건 Tool Result Block을 만들어서 전달한다.
여기에는 다음과 같은 내용이 들어간다.
- tool_use_id : ToolUseBlock에 입력했던 ID와 동일한 ID를 넣는다.
- type : 'tool_result' 라고 쓰면 된다.
- content : tool 실행 결과를 넣는다.
- is_error : error가 발생했다면 true를 넣어야 한다.
이렇게 하면 최종적으로 사용자가 클로드에게 전달하는 메세지 블럭은 다음과 같은 블럭을 담게 된다.
- User Message : Text Block
- Assistant Message : Text Block, ToolUseBlock
- User Message : ToolResultBlock
다음에 나오는 절인 'Multi-Turn conversations' 부터 'Using multiple tools'까지는
여러 tool 요청을 한번에 할 수 있고, 이를 어떻게 처리하는지 코드를 조금씩 계속 리팩토링 하면서 알아가는 시간을 가진다.
Multi-Turn conversations
이 부분은 쭉 실습해 왔던 코드에서 '여러 툴을 연달아 요청할 경우 어떻게 처리하는가'에 초점을 맞춰 리펙토링하는 부분으로,
리펙토링한 결과 중 핵심 부분만 첨부하겠다.
아래 chat 함수를 리펙토링한 결과를 첨부한다. tools에는 list로 여러 tool을 클로드에게 알릴 수 있다.
def chat(messages, system=None, temperature=1.0, stop_sequences=[], tools=None):
params = {
"model": model,
"max_tokens": 1000,
"messages": messages,
"temperature": temperature,
"stop_sequences": stop_sequences,
}
if tools:
params["tools"] = tools
if system:
params["system"] = system
message = client.messages.create(**params)
return message
그리고 run_conversation에서는 클로드에게 tool을 사용하게끔 하는 요청을 보낸 경우, 클로드가 tool을 요청하지 않을 때 까지 클로드가 보내온 tool 요청을 처리하게 리팩토링했다.
def run_conversation(messages):
while True:
response = chat(messages)
add_assistant_message(messages, response)
# Pseudo code
if response isn't asking for a tool:
break
tool_result_blocks = run_tools(response)
add_user_message(messages, tool_result_blocks)
return messages
이렇게 리펙토링한 함수들은 그 다음 절인 'Implementing multiple turns'에서 사용한다.
Implementing multiple turns
여기서는 아까 리펙토링한 함수를 실제 구동 가능하게 살을 더 붙이는 실습을 하는데,
핵심은 '한 번의 요청(message)에 여러 Tool 사용 요청이 들어가 있을 수 있고, 이를 처리할 수 있게끔 리팩토링한다'이다.
message의 content내에서 ToolUseBlock를 걸러내서 한꺼번에 Tool 사용을 처리한 후, 이를 user쪽 응답 메세지에 ToolResultBlock를 더해주면 된다.
요런 식으로 먼저 클로드 쪽에서 온 메세지에서 ToolUseBlock을 걸러준다.
def run_tools(message):
tool_requests = [
block for block in message.content if block.type == "tool_use"
]
tool_result_blocks = []
for tool_request in tool_requests:
# Process each tool request...
그리고 위 코드에서 '#Process each tool request...' 부분에 아래 코드가 들어가게 된다.
각 tool을 실행한 결과를 ToolResultBlock으로 감싸서 클로드에게 전달하는 코드이다.
try:
tool_output = run_tool(tool_request.name, tool_request.input)
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": json.dumps(tool_output),
"is_error": False
}
except Exception as e:
tool_result_block = {
"type": "tool_result",
"tool_use_id": tool_request.id,
"content": f"Error: {e}",
"is_error": True
}
Using multiple tools
여러 툴을 쓸 수 있다는 것을 클로드에게 알려줘야 한다.
앞서 두 절에서 리팩토링한 결과를 다음과 같이 사용할 수 있다.
response = chat(messages, tools=[
get_current_datetime_schema,
add_duration_to_datetime_schema,
set_reminder_schema
])
tools라는 파라미터로 tool schema의 목록을 보낸다.
이 글은 복습용이기 때문에, 이해에 필요한 최소한의 코드만을 첨부했다.
이 말인 즉슨, 리팩토링 대상인 코드를 모두 첨부하지 않았다는 얘기다. 필요하면 완성본을 Using multiple tools에서 다운로드 받으면 된다.
이렇게 처음 코드에서 우리가 계속 살을 덧붙여가면서 어떻게 tool을 클로드가 쓸 수 있도록 할 수 있을까?에 대해 배웠다.
정리하면 다음과 같다.
- tool function을 구현한다.
- tool schema를 정의한다.
- run_conversation에 tool schema를 넘겨준다.
- run_tool에 해당 tool을 처리할 수 있는 분기를 추가한다. (run_tool은 tool function을 호출하는 함수로 구현이 되어있다.)
Find grained tool calling
개인적으로 이 절은 뭔가 갑자기 튀어나온 쌩뚱맞은 절이라는 생각이 든다.
이전까지는 한꺼번에 여러 tool 요청하고 처리하는 코드를 만들다가, 갑자기 'tool을 사용할 때 말야, streaming으로 받는다면 json같이 format이 있는 애들은 어떻게 들어오고 뒷단에서 어떻게 처리하게?'를 알려준다. 뭔가 tool 호출 옵션에 대해서 알려주는건 좋긴 한데 처음에 공부할 때는 흐름이 끊겼다. 제목이라도 차라리 'tool calling option for faster streaming' 같은 걸로 바꿔줬으면 한다.
streaming 할 때, json은 전체 내용이 있어야 이게 생성이 잘 됬는지 안 됬는지 format check를 할 수 있는데, streaming은 조각 조각으로 들어오니까 streaming mode에서 tool을 호출할 경우 이걸 옵션으로 어떻게 제어하는지를 알려준다.
streaming을 할 경우, InputJsonEvent를 통해 json을 보내게 된다. InputJsonEvent는 'partial json'과 snapshot'으로 구성되어 있다. partial json은 이번에 보내오는 json, snapshot은 여태 보낸 json을 한꺼번에 모은 것이다.
원래는 streaming이라도 buffer에 이 partial json을 모으고 난 후 한꺼번에 유효성 검증을 하게 되는데, buffer에 넣는데까지 기다리는 시간이 있기 때문에 작동이 조금 느릴 수 있다. 그래서 fine grained tool calling이 등장한다. 이 옵션을 켜게 되면 key-value 쌍이 전부 다 모일 때 까지 기다리지 않고, 그대로 내보낸다. 대신, JSON 유효성 검사가 꺼진다.
사용은 다음과 같이 하면 된다.
run_conversation(
messages,
tools=[save_article_schema],
fine_grained=True
)
validation이 꺼져 있으니 유효하지 않은 결과를 받을 수도 있는데, 빠르게 chunck들을 받은 후 사용자 단에서 유효성을 검증하면 된다.
python은 아래와 같이 쉽게 유효성 검증이 가능하다.
try:
parsed_args = json.loads(chunk.snapshot)
except json.JSONDecodeError:
# Handle invalid JSON appropriately
print("Received invalid JSON, continuing...")
그러면 언제 이 find-grained tool calling을 쓰는가?
- real-time으로 뭔가를 처리해야 할 때
- tool에서 생성되는 부분 결과라도 빨리 받아서 처리하고 싶을 때
- buffering이 UI에서 부정적인 요소로 작용할 때
- json 에러 처리하는데 별 어려움을 느끼지 않을 경우에
기본적으로는 validation을 켜놓고 써야겠지만, 에러를 처리할 줄 알고 빠르게 결과를 처리하고 싶다면 꺼놓고 써도 된다.
The text edit tool
Text Editor Tool이 할 수 있는 일
- 파일이나 디렉토리의 내용을 보는 것
- 파일 내의 특정 범위의 라인에 접근
- 파일 내의 특정 텍스트 대치
- 새 파일 생성
- 파일 내의 특정 라인에 접근해서 텍스트 넣기
- 파일에서 최근 변경점 되돌리기
왜 Text Editor Tool이 필요한가?
- 프로그래밍으로 파일 수정하기
- 간단하게 파일 수정하고 싶을 때(without acccess to full-featured code)
- 클로드를 쓰는 어플리케이션에 파일 수정 기능을 더하고 싶을 때
The web search tool
시작하기 전에 : 계정이 기관에 종속되어있다면 인터넷 접근이 허용되어야 함
따로 구현할 필요 없이 built-in web search tool을 사용하면 된다.
다른 tool과 다르게 클로드가 자동적으로 처리한다.
web_search_schema = {
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5
}
max_uses: 클로드가 최대 몇 번 검색할 지 정함
그렇다면 응답은 어떤 형태로 전달되어올까?
- Text block : 클로드가 지금 뭘 하고 있는지 담음
- ServerToolUseBlock : 클로드가 검색 시 사용한 쿼리를 담음
- WebSearchToolResultBlock : 전체 웹 검색 결과를 담음
- WebSearchResultBlock : titles와 urls가 첨부되어있는 개별 검색 결과들
- Citation blocks : 클로드가 검색 결과를 서술하는 데 도움되는 블럭들
web_search_schema = {
"type": "web_search_20250305",
"name": "web_search",
"max_uses": 5,
"allowed_domains": ["nih.gov"]
}
allowed_domains: 검색에 허용하는 도메인
web search tool은 다음을 위해 쓰일 때 좋다.
- 현재 일어난 일들과 최근의 개발 사항들
- 클로드의 학습 데이터에 포함 되어 있지 않은 특정 데이터들
- 사실 확인과 권위있는 출처에서 정보를 찾을 때
- up-to-date 정보를 요구하는 작업들
'AI > CLAUDE' 카테고리의 다른 글
| [CCA-F] Building With The Claude API (4/6) (0) | 2026.08.27 |
|---|---|
| [CCA-F] Building With The Claude API (2/6) (0) | 2026.06.17 |
| [CCA-F] Building With The Claude API (1/6) (0) | 2026.06.10 |
| [CCA-F] Claude Code in Action(2/2) (0) | 2026.06.10 |
| [CCA-F] 중간 상황 공유 (1) | 2026.06.09 |