본문 바로가기
C.W.K.
Stream
Lesson 04 of 06 · published

Python 도구 루프에는 등록부와 반복 예산이 필요해

~18 min · tool-use, tool-loop, function-calling

Level 0Observer
0 XP0/64 lessons0/13 achievements
0/150 XP to next level150 XP to go0% complete

도구 호출은 적어도 두 번 왕복해

먼저 도구 목록과 사용자 메시지를 보내면 모델이 tool_use 블록과 stop_reason='tool_use'를 돌려줘. 애플리케이션이 로컬 도구를 실행하고 tool_result를 이력에 덧붙여 다시 보내면 모델이 다음 답을 만들지. 실제 에이전트는 stop_reason='end_turn'이 나올 때까지 이 과정을 반복해.

이름에서 작은 처리기로 배분해

{tool_name: callable} 형태의 등록부를 두고, 각 tool_use 블록의 이름으로 처리기를 찾아 인자를 넘겨. 모든 분기를 거대한 함수 하나에 넣으면 도구가 늘어날수록 검증과 오류 처리, 권한 규칙이 뒤엉켜. 도구 하나의 계약은 처리기 하나에 가깝게 유지해.

끝나지 않는 루프를 막아

정당하게 열 번 넘게 도구를 쓰는 작업도 있지만, 같은 시도를 반복하는 건 막힌 신호일 수 있어. 최대 반복 횟수를 두고 10 정도에서 시작해 실제 자료로 조정해. 예산 때문에 멈췄다면 사용자에게 그 이유와 마지막 상태를 보여 줘.

원칙: 도구 루프는 등록부와 반복 예산을 가진 통제된 반복문이야. 둘 다 코드에 드러나게 둬.

Code

Tool 레지스트리와 완전한 루프·python
from anthropic import Anthropic
import json

client = Anthropic()

# 1. 모델한테 보내는 도구 정의.
TOOLS = [
    {
        "name": "get_weather",
        "description": "Get current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    }
]

# 2. 로컬 핸들러 — tool name당 하나.
def get_weather(city: str) -> dict:
    return {"city": city, "temp_c": 22, "condition": "clear"}

HANDLERS = {"get_weather": get_weather}

# 3. 루프.
def run_loop(user_text: str, max_iters: int = 10) -> str:
    messages = [{"role": "user", "content": user_text}]
    for _ in range(max_iters):
        resp = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages,
        )
        # tool_results 전에 assistant 턴(전체 content 리스트) append.
        messages.append({"role": "assistant", "content": resp.content})

        if resp.stop_reason != "tool_use":
            return next(b.text for b in resp.content if b.type == "text")

        # 모든 tool_use 블록 resolve, tool_results 모두를 한 user 턴에 보냄.
        tool_results = []
        for block in resp.content:
            if block.type != "tool_use":
                continue
            handler = HANDLERS[block.name]
            output = handler(**block.input)
            tool_results.append({
                "type": "tool_result",
                "tool_use_id": block.id,
                "content": json.dumps(output),
            })
        messages.append({"role": "user", "content": tool_results})
    raise RuntimeError("tool loop exceeded max_iters")

print(run_loop("What is the weather in Seoul?"))
한 라운드 안에서 병렬 tool 실행·python
import asyncio

async def execute_block(block, async_handlers):
    output = await async_handlers[block.name](**block.input)
    return {
        "type": "tool_result",
        "tool_use_id": block.id,
        "content": json.dumps(output),
    }

# 모델이 한 턴에 도구 여러 개 호출하면 동시 실행.
async def resolve_round(blocks, async_handlers):
    return await asyncio.gather(*(execute_block(b, async_handlers) for b in blocks))

External links

Exercise

로컬 함수 하나와 HTTP 조회 하나, 최대 반복 예산을 가진 도구 루프를 만들어. 단순 날씨 질문에서 5회를 넘으면 실패하는 단위 시험도 추가해.
Hint
tool_use 뒤에는 문자열이 아니라 도우미 콘텐츠 목록 전체를 이력에 넣었는지 확인해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.