이 글은 누구를 위한 것인가
- Claude tool_use 기초를 넘어 복잡한 에이전트를 만들려는 개발자
- 여러 API를 조합하는 멀티 툴 워크플로우가 필요한 팀
- 에이전트 루프에서 무한 반복·오류를 방지하고 싶은 엔지니어
들어가며
Claude의 tool_use는 단순 API 호출을 넘어 복잡한 멀티 스텝 에이전트를 구현할 수 있다. 병렬 툴 호출, 툴 체이닝, 동적 스키마 생성 패턴을 알면 에이전트의 능력이 비약적으로 향상된다.
이 글은 bluefoxdev.kr의 Claude 에이전트 구축 가이드 를 참고하여 작성했습니다.
1. Tool Use 고급 패턴
[에이전트 루프 설계]
기본 루프:
1. 사용자 메시지 전송
2. Claude 응답 수신
3. tool_use 블록 감지
4. 툴 실행
5. 결과를 tool_result로 추가
6. 2번으로 반복
7. stop_reason == "end_turn" 시 종료
병렬 툴 호출:
Claude가 여러 tool_use 블록을 한 번에 반환
모든 툴을 동시에 실행 (asyncio.gather)
모든 결과를 한 번에 반환
→ 순차 실행 대비 N배 빠름
툴 체이닝:
툴A 결과 → 툴B 입력
Claude가 자동으로 연결 결정
복잡한 파이프라인 자동 구성
안전장치:
최대 반복 횟수 제한 (보통 10-20회)
타임아웃 설정
비용 상한선
위험 툴 승인 요청
2. 고급 에이전트 구현
import anthropic
import asyncio
import json
from typing import Any
client = anthropic.Anthropic()
# 툴 정의
TOOLS = [
{
"name": "search_web",
"description": "웹에서 정보를 검색합니다",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "검색 쿼리"},
"num_results": {"type": "integer", "default": 5},
},
"required": ["query"],
},
},
{
"name": "execute_python",
"description": "Python 코드를 안전한 샌드박스에서 실행합니다",
"input_schema": {
"type": "object",
"properties": {
"code": {"type": "string", "description": "실행할 Python 코드"},
},
"required": ["code"],
},
},
{
"name": "read_file",
"description": "파일 내용을 읽습니다",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string"},
},
"required": ["path"],
},
},
]
async def execute_tool(tool_name: str, tool_input: dict) -> Any:
"""툴 실행 디스패처"""
if tool_name == "search_web":
# 실제 구현: Tavily, Serper 등
return {"results": [{"title": "검색 결과", "url": "https://example.com"}]}
elif tool_name == "execute_python":
# 샌드박스 실행 (RestrictedPython, E2B 등)
import subprocess
result = subprocess.run(
["python3", "-c", tool_input["code"]],
capture_output=True, text=True, timeout=10
)
return {
"stdout": result.stdout,
"stderr": result.stderr,
"returncode": result.returncode,
}
elif tool_name == "read_file":
try:
with open(tool_input["path"]) as f:
return {"content": f.read()}
except FileNotFoundError:
return {"error": f"파일을 찾을 수 없음: {tool_input['path']}"}
return {"error": f"알 수 없는 툴: {tool_name}"}
async def run_agent(
user_message: str,
max_iterations: int = 15,
system_prompt: str = "당신은 유능한 AI 어시스턴트입니다.",
) -> str:
"""멀티 툴 에이전트 루프"""
messages = [{"role": "user", "content": user_message}]
iteration = 0
while iteration < max_iterations:
iteration += 1
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=4096,
system=system_prompt,
tools=TOOLS,
messages=messages,
)
# 모든 tool_use 블록 수집
tool_uses = [
block for block in response.content
if block.type == "tool_use"
]
# 텍스트 블록 수집
text_blocks = [
block for block in response.content
if block.type == "text"
]
# 종료 조건
if response.stop_reason == "end_turn" or not tool_uses:
return " ".join(b.text for b in text_blocks)
# 어시스턴트 응답 추가
messages.append({"role": "assistant", "content": response.content})
# 병렬 툴 실행
tool_results = await asyncio.gather(*[
execute_tool(tu.name, tu.input) for tu in tool_uses
])
# 툴 결과 메시지 추가
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": tu.id,
"content": json.dumps(result, ensure_ascii=False),
}
for tu, result in zip(tool_uses, tool_results)
],
})
return "최대 반복 횟수 초과"
def create_dynamic_tool(
name: str,
description: str,
parameters: dict,
) -> dict:
"""동적 툴 스키마 생성"""
return {
"name": name,
"description": description,
"input_schema": {
"type": "object",
"properties": parameters,
"required": list(parameters.keys()),
},
}
마무리
병렬 툴 호출은 에이전트 성능의 핵심이다. 5개 웹 검색을 순차로 하면 10초, 병렬로 하면 2초다. 에이전트 루프에는 항상 최대 반복 횟수와 타임아웃을 설정하라. 코드 실행 툴은 반드시 샌드박스에서 실행하고 파일 시스템·네트워크 접근을 제한해야 한다.