첫 번째 도구를 연결해 본다
이론은 여기까지 하고, 실제로 돌아가는 코드를 만들어 보겠습니다. 목표는 소박합니다 — 사용자가 "서울 날씨 알려줘"라고 물으면, 모델이 도구를 부르고, 우리 코드가 그 요청을 실제로 실행하고, 결과를 다시 모델에게 돌려줘서 자연어 답변을 받는 것까지. 아직 루프는 아닙니다. 딱 한 번의 왕복만 처리합니다 — 루프는 5장에서 다룹니다.
도구 정의와 실제 구현을 분리한다
도구에는 두 부분이 있습니다. 모델에게 보여줄 스키마와, 실제로 값을 계산하는 함수입니다. 이 둘을 한곳에 섞어 두면 나중에 도구가 늘어날 때 관리가 지저분해집니다.
import anthropic
client = anthropic.Anthropic()
# 모델에게 보여줄 스키마
WEATHER_TOOL = {
"name": "get_weather",
"description": "주어진 도시의 현재 기온과 날씨 상태를 조회한다. 예: city='서울' -> '맑음, 24도'",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "도시 이름 (한글, 예: '서울', '부산')"}
},
"required": ["city"]
}
}
# 실제 구현 — 여기서는 하드코딩된 더미 데이터로 대체한다
FAKE_WEATHER_DB = {"서울": "맑음, 24도", "부산": "흐림, 21도"}
def get_weather(city: str) -> str:
return FAKE_WEATHER_DB.get(city, f"{city}의 날씨 정보를 찾을 수 없습니다.")실제 서비스라면 get_weather가 외부 API를 호출하겠지만, 이 장에서는 왕복 구조 자체에 집중하기 위해 더미로 남겨 둡니다.
첫 번째 호출 — 모델이 도구를 요청한다
messages = [{"role": "user", "content": "서울 날씨 알려줘"}]
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[WEATHER_TOOL],
messages=messages,
)
print(response.stop_reason) # "tool_use"stop_reason이 tool_use로 나온다면 모델이 아직 답을 하지 않고 도구 결과를 기다리는 중입니다. 이 상태에서 response.content를 그대로 사용자에게 보여주면 빈 화면이나 이상한 부분 텍스트만 보이게 되니, 반드시 stop_reason을 먼저 확인해야 합니다.
도구 실행 결과를 다시 돌려준다
이제 실제로 함수를 실행하고, 그 결과를 메시지 배열에 추가한 뒤 모델을 다시 호출합니다.
tool_use_block = next(b for b in response.content if b.type == "tool_use")
result = get_weather(tool_use_block.input["city"])
messages.append({"role": "assistant", "content": response.content})
messages.append({
"role": "user",
"content": [{
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": result,
}],
})
final_response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
tools=[WEATHER_TOOL],
messages=messages,
)
print(final_response.content[0].text)
# "서울은 현재 맑고 기온은 24도입니다."tool_use_id를 정확히 맞춰서 돌려주는 게 까다로운 부분입니다. 모델이 도구를 여러 개 동시에 부르면 각 결과가 어떤 요청에 대한 응답인지 이 ID로 짝을 맞춥니다. ID를 빼먹거나 잘못 넣으면 API가 에러를 던지는 게 아니라, 다음 호출에서 모델이 문맥을 헷갈려하는 형태로 조용히 실패하는 경우가 있어서 디버깅이 더 성가십니다.
여기서 멈추면 안 되는 이유
지금 짠 코드는 도구 호출이 딱 한 번 일어난다고 가정합니다. 하지만 실제 질문은 "서울이랑 부산 중에 어디가 더 따뜻해?"처럼 도구를 두 번 부르거나, 첫 결과를 보고 추가로 판단해야 하는 경우가 훨씬 많습니다. if response.stop_reason == "tool_use" 를 한 번만 체크하는 코드로는 이걸 처리할 수 없습니다. 다음 장에서 이 왕복을 while로 감싸서 몇 번이고 반복되는 진짜 에이전트 루프를 만듭니다.