저번 3편 글에서는 파이썬(Python)의 requests와 python-dotenv 라이브러리를 사용해서 Claude API를 기본적으로 호출하는 방법에 대해 알아보았다.
하지만 단순히 질문을 보내고 답변 텍스트만 받아오는 것으로는 우리가 최종적으로 목표하는 언리얼 엔진 내부에서 스스로 동작하는 AI 테스트 에이전트를 만들 수 없다.
에이전트가 게임 안에서 캐릭터를 이동시키거나, 장애물을 피하고, 현재 위치나 시각 같은 정보를 파악하려면 Claude가 스스로 필요한 도구를 호출하고 그 결과를 다시 받아 판단하는 Tool Use(도구 사용) 루프가 필수적이다.
1. Tool Use 루프란?
Claude 같은 LLM은 자체적으로 실시간 시각이나 언리얼 엔진 내부 상태를 알지 못한다.
따라서 우리가 스펙(input_schema)을 정의한 도구 목록을 함께 전달하면:
- Claude가 질문을 분석하고 도구가 필요하다고 판단 시
stop_reason="tool_use"와 함께 호출할 도구 이름 및 인자를 반환한다. - 우리의 파이썬 코드가 해당 도구를 직접 실행한다 (예:
get_time()). - 실행 결과를
tool_result블록으로 포맷팅하여 다시 Claude에게 전달한다. - 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 "안녕?"을 실행하여 테스트해 보았다.
실행 결과를 보면:
- "지금 몇 시야?" 질문 시: [턴 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 같은 가짜 도구가 아니라, 언리얼 엔진과 실제 통신하는 모듈을 붙여서 본격적인 게임 테스트 에이전트로 발전시켜 볼 예정이다.
'AI' 카테고리의 다른 글
| 6. 에이전트 코드 리팩터링: 계층 분리와 이벤트 기반 루프 (1) | 2026.07.25 |
|---|---|
| 5. Python으로 병렬 도구 호출(Parallel Tool Use) 구현하기 (0) | 2026.07.25 |
| 3. Python으로 Claude API 호출하기 (0) | 2026.07.24 |
| 2. HTTP로 Claude API 호출하기 (0) | 2026.07.24 |
| 1. 언리얼에서 동작하는 테스트 AI 에이전트 만들기 (0) | 2026.07.24 |