AI

4. Python으로 Tool Use 루프 구현하기

영넨이 2026. 7. 25. 17:16
반응형

저번 3편 글에서는 파이썬(Python)의 requestspython-dotenv 라이브러리를 사용해서 Claude API를 기본적으로 호출하는 방법에 대해 알아보았다.

 

하지만 단순히 질문을 보내고 답변 텍스트만 받아오는 것으로는 우리가 최종적으로 목표하는 언리얼 엔진 내부에서 스스로 동작하는 AI 테스트 에이전트를 만들 수 없다.

에이전트가 게임 안에서 캐릭터를 이동시키거나, 장애물을 피하고, 현재 위치나 시각 같은 정보를 파악하려면 Claude가 스스로 필요한 도구를 호출하고 그 결과를 다시 받아 판단하는 Tool Use(도구 사용) 루프가 필수적이다.

 

1. Tool Use 루프란?

Claude 같은 LLM은 자체적으로 실시간 시각이나 언리얼 엔진 내부 상태를 알지 못한다.

따라서 우리가 스펙(input_schema)을 정의한 도구 목록을 함께 전달하면:

  1. Claude가 질문을 분석하고 도구가 필요하다고 판단 시 stop_reason="tool_use"와 함께 호출할 도구 이름 및 인자를 반환한다.
  2. 우리의 파이썬 코드가 해당 도구를 직접 실행한다 (예: get_time()).
  3. 실행 결과를 tool_result 블록으로 포맷팅하여 다시 Claude에게 전달한다.
  4. Claude가 최종 결과를 바탕으로 사용자에게 답변을 완성(stop_reason="end_turn")한다.

 

2. 파이썬 코드 구현 (loop.py)

Phase 1 단계로 먼저 가짜 도구(현재 시각을 구하는 get_time)를 정의하고, 도구 실행 결과를 주고받는 기본 루프를 agent/loop.py 파일로 구현했다.

 

# agent/loop.py
import os
import sys
from datetime import datetime
from typing import Any
import requests
from dotenv import load_dotenv

load_dotenv()

API_URL = "https://api.anthropic.com/v1/messages"
MODEL = "claude-haiku-4-5"
MAX_TURNS = 10   # 무한루프 방지

# [1] 도구 스펙 (Claude가 읽는 설명서)
TOOLS = [
    {
        "name": "get_time",
        "description": "현재 시각을 ISO 형식 문자열로 반환한다. 사용자가 시간이나 날짜를 물으면 사용.",
        "input_schema": {
            "type": "object",
            "properties": {},
            "required": [],
        },
    },
]

# [2] 실제 도구 함수
def get_time(**kwargs) -> str:
    return datetime.now().isoformat(timespec="seconds")

TOOL_FUNCS = {"get_time": get_time}

def execute_tool(name: str, tool_input: dict) -> tuple[str, bool]:
    func = TOOL_FUNCS.get(name)
    if func is None:
        return f"에러: 알 수 없는 도구 '{name}'", True
    try:
        return str(func(**tool_input)), False
    except Exception as error:
        return f"에러: {error}", True

# [3] Claude API 호출 및 루프
def run(question: str, api_key: str) -> None:
    messages = [{"role": "user", "content": question}]
    total_in, total_out = 0, 0

    for turn in range(1, MAX_TURNS + 1):
        # API 호출
        data = call_claude(messages, api_key)
        stop_reason = data.get("stop_reason")
        print(f"\n[턴 {turn}] stop_reason={stop_reason}")

        if stop_reason != "tool_use":
            for block in data.get("content", []):
                if block.get("type") == "text":
                    print(block.get("text", ""))
            break

        # assistant 턴을 기록
        messages.append({"role": "assistant", "content": data["content"]})

        # tool_use 블록 실행 및 tool_result 생성
        results = []
        for block in data["content"]:
            if block.get("type") == "tool_use":
                name = block["name"]
                output, is_error = execute_tool(name, block.get("input", {}))
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block["id"],
                    "content": output,
                })

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

 

3. 실행 및 결과 확인

터미널에서 python agent/loop.py "지금 몇 시야?"python agent/loop.py "안녕?"을 실행하여 테스트해 보았다.

 

Tool Use 루프 실행 결과 스크린샷

 

실행 결과를 보면:

  • "지금 몇 시야?" 질문 시: [턴 1]에서 stop_reason=tool_use가 발생하면서 get_time({})을 호출하고, 그 결과인 2026-07-25T16:41:29를 다시 전달하자 [턴 2]에서 stop_reason=end_turn으로 자연스럽게 정확한 현재 시간과 날짜를 답변해 준다.
  • "안녕?" 일반 대화 시: 도구 호출이 필요 없으므로 [턴 1]에서 바로 stop_reason=end_turn으로 깔끔하게 응답을 마친다.

 

마무리하며

드디어 단순 단방향 호출이 아닌, AI가 상황에 맞춰 도구를 스스로 호출하고 결과를 반영하는 Agentic Tool Use 루프의 기본 골격을 완성했다!

다음에는 단순한 get_time 같은 가짜 도구가 아니라, 언리얼 엔진과 실제 통신하는 모듈을 붙여서 본격적인 게임 테스트 에이전트로 발전시켜 볼 예정이다.

반응형