Claude API Tool Use 고급 패턴: 멀티 툴 에이전트 설계

AI 기술

Claude APITool UseAI 에이전트함수 호출멀티 툴

이 글은 누구를 위한 것인가

  • 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초다. 에이전트 루프에는 항상 최대 반복 횟수와 타임아웃을 설정하라. 코드 실행 툴은 반드시 샌드박스에서 실행하고 파일 시스템·네트워크 접근을 제한해야 한다.