로드 중

에이전트 루프 설명

강의 47분
로드 중

에이전트 루프 설명

여러분은 API 호출을 해봤지만, 한 번의 호출은 하나의 응답만 반환합니다. 워크플로우를 자동화하려면 Claude가 행동하고, 결과를 확인하고, 다음에 무엇을 할지 결정하고, 계속 진행해야 합니다. 이 패턴이 바로 사람들이 에이전틱 워크플로우를 이야기할 때 의미하는 것입니다.

에이전트란 실제로 무엇인가

에이전트는 중간에 사람이 없이 메시징 루프의 양쪽을 모두 실행하는 Claude의 자율적인 버전입니다. 에이전트는 작업을 받고, 도구를 선택하고, Claude가 작업이 완료되었다고 판단할 때까지 루프 안에서 코드를 실행합니다.

에이전트 루프를 구현하는 가장 간단한 방법은 다음과 같습니다:

  1. 사용 가능한 도구와 함께 Claude에 메시지를 보냅니다.
  2. Claude는 최종 답변 또는 여러분이 정의한 도구를 사용해 달라는 요청으로 응답합니다.
  3. 여러분의 코드가 해당 도구를 실행합니다.
  4. 결과를 다시 Claude에 보냅니다.
  5. 정지 이유(stop reason)가 tool_use인 동안 반복합니다. end_turn은 Claude가 완료되었다는 의미이며, 그 밖의 정지 이유도 루프를 종료시킵니다.

이것을 턴이 번갈아 가는 대화라고 생각해보세요: 사용자가 시작하고, 에이전트가 도구를 호출하고, 도구가 결과를 반환하고, 에이전트는 답을 얻을 때까지 계속 진행합니다.

최소 동작 예제

데이터베이스나 UI를 끌어오지 않고도 이 루프가 처음부터 끝까지 실행되는 모습을 보기 위해, get_weather라는 가짜 도구를 연결하고 오늘 오스틴에서 무엇을 입어야 할지 Claude에게 물어보겠습니다. Claude는 스스로 날씨를 알 방법이 없으므로, 도구를 호출하고 결과를 읽은 다음 여러분에게 답을 줘야 합니다.

전체 스크립트는 다음과 같습니다:

python
import anthropic

client = anthropic.Anthropic()

# tools 배열은 Claude에게 사용 가능한 것이 무엇인지 알려줍니다:
# 이름, 설명, 그리고 입력에 대한 JSON 스키마입니다.
tools = [
    {
        "name": "get_weather",
        "description": "Get the current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "The city to get weather for",
                }
            },
            "required": ["city"],
        },
    }
]

# run_tool은 그냥 하드코딩된 조회일 뿐입니다.
# 실제 앱이라면 데이터베이스, API 등 무엇이든 호출하게 될 것입니다.
def run_tool(name, tool_input):
    if name == "get_weather":
        return f"Weather in {tool_input['city']}: 95F, sunny"
    raise ValueError(f"Unknown tool: {name}")

messages = [
    {"role": "user", "content": "What should I wear in Austin today?"}
]

# 에이전트 루프입니다. 매 반복마다 Claude에 메시지를 보내고,
# 응답의 stop reason에 따라 분기합니다.
while True:
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=1024,
        tools=tools,
        messages=messages,
    )

    if response.stop_reason == "end_turn":
        # Claude가 완료되었습니다. 최종 텍스트를 출력하고 break합니다.
        for block in response.content:
            if block.type == "text":
                print(block.text)
        break

    elif response.stop_reason == "tool_use":
        # 응답에서 도구 사용 블록을 찾아 각각 실행합니다.
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = run_tool(block.name, block.input)
                tool_results.append(
                    {
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result,
                    }
                )

        # 어시스턴트의 응답과 우리의 도구 결과를 messages에
        # 다시 넣은 후, Claude가 답할 수 있도록 다시 루프를 돕니다.
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})

    else:
        # max_tokens나 거부 같은 그 외의 stop reason인 경우입니다.
        # messages에 변경된 것이 없으므로 여기서 멈춥니다. 다시
        # 루프를 돌면 같은 요청에 대해 반복해서 비용을 지불하게 됩니다.
        print(f"Stopped early: {response.stop_reason}")
        break

주목할 세 가지 부분이 있습니다:

  • tools 배열은 Claude에게 사용 가능한 것을 알려줍니다: 이름, 설명, 그리고 입력에 대한 JSON 스키마입니다.
  • run_tool은 그저 하드코딩된 조회일 뿐입니다. 실제 앱에서는 데이터베이스나 API 등 무엇이든 호출하게 됩니다.
  • 루프가 바로 에이전트 루프입니다. 각 반복마다 메시지를 Claude에 보내고 응답의 정지 이유에 따라 분기합니다. end_turn이면 Claude가 완료된 것이므로 최종 텍스트를 출력하고 종료합니다. tool_use이면 도구 사용 블록을 찾아 각각 실행하고, 어시스턴트의 응답과 여러분의 도구 결과를 다시 messages에 추가한 다음 다시 루프를 돌려 Claude가 답할 수 있게 합니다. 그 밖의 경우에는 정지 이유를 출력하고 종료합니다.

마지막 분기를 빼먹지 마세요. 이게 없으면 max_tokens에서 잘린 응답이 messages에 아무것도 추가하지 않으므로, 루프는 동일한 요청을 다시 보내고 여러분은 매번 그 비용을 지불하게 됩니다. 문서에는 모든 정지 이유와 그에 대한 대처법이 정리되어 있습니다.

실행하기

스크립트를 실행하면 두 번의 턴을 보게 됩니다:

  1. 첫 번째 턴: 정지 이유는 tool_use입니다. Claude는 오스틴에 대해 get_weather를 요청하고, 여러분의 코드는 기온과 기상 상태를 반환합니다.
  2. 두 번째 턴: 정지 이유는 end_turn이며, Claude는 가볍고 통기성이 좋은 옷을 입으라고 알려줍니다.

에이전트 루프의 터미널 출력: 턴 1은 tool_use로 정지하며 오스틴에 대해 get_weather를 호출하고, 턴 2는 end_turn으로 정지하며 Claude가 최종 의상 추천을 출력합니다

두 번의 API 호출, 한 번의 도구 실행, 하나의 최종 답변. 이것이 전체 루프입니다. 여러분이 Claude API로 구축하는 모든 것은 이와 비슷한 형태가 될 것입니다.

프로덕션에서의 동일한 루프

실제 환경에서는 이와 동일한 루프가 자동 검토 엔드포인트 같은 것을 구동합니다: 구조 보고서를 읽고, 도구를 통해 관련 건축 법규를 조회하고, 작업하면서 위험 발견 사항을 하나씩 데이터베이스에 다시 기록하는 규정 준수 에이전트입니다.

업로드된 구조 보고서를 나열하는 규정 준수 검토 대시보드. 각 보고서에는 에이전트를 시작시키는 자동 검토 실행 버튼이 있습니다

루프의 형태는 여러분이 방금 실행한 것과 동일합니다. 차이점은 다음과 같습니다:

  • 가짜 날씨 조회 대신 실제 도구를 사용합니다.
  • 결과가 서버 전송 이벤트(server-sent events)로 UI에 스트리밍됩니다.
  • 발견 사항이 위험 발견 테이블에 저장됩니다.

실행 중인 규정 준수 에이전트의 검토 추적: 루프가 반복되면서 건축 법규 라이브러리를 검색하고 특정 법규 조항을 조회하는 수십 번의 도구 호출

요약

  • 에이전트는 루프 안의 Claude입니다: 관찰하고, 결정하고, 행동하고, 반복합니다.
  • 루프는 간단합니다: 도구와 함께 메시지를 보내고, Claude가 요청하는 도구를 실행하고, 결과를 다시 전달하고, 정지 이유가 tool_use가 아닌 경우 중단합니다.
  • 여러분이 루프와 도구를 소유합니다. Claude는 추론을 소유합니다.
  • 동일한 루프 형태가 가짜 날씨 데모에서 프로덕션 규정 준수 에이전트로 확장됩니다 — 바뀌는 것은 도구와 배선뿐입니다.
  • 루프를 직접 소유하고 싶지 않을 때는, 관리형 에이전트가 Anthropic의 인프라에서 여러분을 대신해 이 정확한 루프를 실행해 줍니다.