k
korAI
중급 전체
중급2026-05-207분

Tool Use 실전: AI가 도구 호출에 실패했을 때 무너지지 않는 에러 핸들링

Claude의 tool use 기능을 쓰다 보면 잘못된 인자, 타임아웃, 빈 결과 등 다양한 실패 상황을 만난다. 각 실패 유형별로 모델이 스스로 복구하도록 유도하는 패턴을 정리한다.

tool-useerror-handlingagentic-loop

Tool Use가 깨지는 3가지 순간

tool use를 처음 도입한 개발자들이 흔히 겪는 문제는 도구 호출 자체가 실패했을 때 어떻게 할지 설계하지 않은 것이다. 모델이 잘못된 파라미터를 생성하거나, 외부 API가 오류를 반환하거나, 결과가 비어 있을 때 파이프라인 전체가 멈춰버린다.

실패 유형과 대응 전략

| 실패 유형 | 원인 | 권장 대응 | |---|---|---| | 잘못된 인자 | 모델이 스키마 불일치 값 생성 | 에러 메시지를 tool_result로 되돌려 재시도 유도 | | 외부 API 오류 | 네트워크·인증 실패 | 에러 내용을 자연어로 설명해 모델이 대안 행동 선택 | | 빈 결과 | 검색 결과 없음 등 | "결과 없음" 명시 → 모델이 다른 쿼리로 재시도 | | 무한 루프 | 모델이 같은 도구를 반복 호출 | 최대 반복 횟수(maxIterations) 하드 제한 |

에러를 모델에게 돌려주는 agentic 루프 구현

import anthropic
import json
from typing import Any

client = anthropic.Anthropic()

# 예시 도구: 날씨 조회 (실패 시뮬레이션 포함)
def get_weather(city: str) -> dict:
    if city == "없는도시":
        raise ValueError(f"'{city}'에 대한 날씨 데이터를 찾을 수 없습니다.")
    return {"city": city, "temp": 22, "condition": "맑음"}

tools = [
    {
        "name": "get_weather",
        "description": "도시 이름으로 현재 날씨를 조회합니다.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "조회할 도시 이름"}
            },
            "required": ["city"],
        },
    }
]

def run_agent(user_message: str, max_iterations: int = 5) -> str:
    messages = [{"role": "user", "content": user_message}]

    for iteration in range(max_iterations):
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=tools,
            messages=messages,
        )

        # 도구 호출 없이 최종 답변
        if response.stop_reason == "end_turn":
            text_blocks = [b.text for b in response.content if b.type == "text"]
            return "\n".join(text_blocks)

        # 도구 호출 처리
        if response.stop_reason == "tool_use":
            messages.append({"role": "assistant", "content": response.content})

            tool_results = []
            for block in response.content:
                if block.type != "tool_use":
                    continue

                # ★ 핵심: 에러를 잡아서 모델에게 되돌려 줌
                try:
                    if block.name == "get_weather":
                        result = get_weather(**block.input)
                        tool_results.append({
                            "type": "tool_result",
                            "tool_use_id": block.id,
                            "content": json.dumps(result, ensure_ascii=False),
                        })
                except Exception as e:
                    # 에러를 is_error=True 로 반환 → 모델이 상황 인지 후 재시도
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": f"오류 발생: {str(e)}",
                        "is_error": True,
                    })

            messages.append({"role": "user", "content": tool_results})

    return "최대 반복 횟수를 초과했습니다. 작업을 완료할 수 없습니다."

# 테스트
print(run_agent("없는도시와 서울의 날씨를 비교해줘"))

에러 핸들링 설계 체크리스트

  • [ ] 도구 실행 코드를 try/except로 감싸고 is_error: true와 함께 에러 메시지를 반환하는가
  • [ ] max_iterations(또는 max_turns)를 반드시 설정해 무한 루프를 차단했는가
  • [ ] 빈 결과([], null)도 명시적인 문자열로 변환해 모델에 전달하는가
  • [ ] stop_reasontool_useend_turn도 아닌 경우(예: max_tokens)를 처리했는가
  • [ ] 동일 도구가 연속 3회 이상 호출될 때 조기 탈출 로직이 있는가