← All guides

Agent SDK로 자동화 에이전트 만들기: 실전 가이드 (2026)

Claude Agent SDK로 실제 동작하는 자동화 에이전트를 만드는 법 — 툴 정의, 멀티스텝 워크플로우, 에러 핸들링, 프로덕션 배포까지 한국어 실전 가이드.

Agent SDK로 자동화 에이전트 만들기: 실전 가이드 (2026)

Claude Agent SDK는 Claude가 외부 도구(툴)를 직접 호출하며 멀티스텝 작업을 자율 실행하게 해준다. 단순 API 호출(질문→답변)과 달리, 에이전트는 검색 → 분석 → 결과 저장처럼 여러 단계를 스스로 결정하며 완료한다. 이 가이드는 첫 에이전트 작성부터 실제 자동화 워크플로우, 프로덕션 배포까지 전부 다룬다.


에이전트 vs 단순 API 호출

단순 API 호출 에이전트
작동 방식 질문 → 응답 목표 → 계획 → 툴 실행 → 완료
스텝 수 1 여러 스텝 (자율 결정)
툴 사용 없음 직접 함수/API 호출
적합한 작업 단순 Q&A, 텍스트 생성 데이터 수집, 분석, 자동화

에이전트가 필요한 경우: "웹을 검색해서 최신 정보 가져오기", "파일 읽고 분석 후 보고서 저장", "외부 API 여러 개를 조합해 작업 완료"


설치 및 기본 구조

pip install anthropic

Agent SDK는 별도 패키지가 아니라 anthropic SDK의 일부다. 핵심은 툴 정의툴 루프다.

가장 단순한 에이전트

import anthropic
import json

client = anthropic.Anthropic()

# 1. 툴 정의
tools = [
    {
        "name": "get_current_time",
        "description": "현재 날짜와 시간을 반환합니다",
        "input_schema": {
            "type": "object",
            "properties": {},
            "required": []
        }
    }
]

# 2. 툴 실행 함수
def get_current_time() -> str:
    from datetime import datetime
    return datetime.now().strftime("%Y년 %m월 %d일 %H:%M:%S")

# 3. 에이전트 루프
def run_agent(user_message: str) -> str:
    messages = [{"role": "user", "content": user_message}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )
        
        # Claude가 작업 완료 → 최종 답변 반환
        if response.stop_reason == "end_turn":
            return response.content[-1].text
        
        # Claude가 툴 사용 요청 → 실행 후 결과 전달
        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":
                    if block.name == "get_current_time":
                        result = get_current_time()
                    else:
                        result = f"Unknown tool: {block.name}"
                    
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })
            
            messages.append({"role": "user", "content": tool_results})

print(run_agent("지금 몇 시야?"))

실전 에이전트 1: 뉴스 수집 + 요약 에이전트

웹에서 뉴스를 가져와 한국어로 요약하는 에이전트:

import anthropic
import requests
from bs4 import BeautifulSoup

client = anthropic.Anthropic()

tools = [
    {
        "name": "search_news",
        "description": "주제에 관한 최신 뉴스를 검색합니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "검색할 키워드"
                },
                "num_results": {
                    "type": "integer",
                    "description": "가져올 결과 수 (최대 5)",
                    "default": 3
                }
            },
            "required": ["query"]
        }
    },
    {
        "name": "fetch_article",
        "description": "URL에서 기사 본문을 가져옵니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "url": {"type": "string", "description": "기사 URL"}
            },
            "required": ["url"]
        }
    },
    {
        "name": "save_summary",
        "description": "요약 내용을 파일로 저장합니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "filename": {"type": "string"},
                "content": {"type": "string"}
            },
            "required": ["filename", "content"]
        }
    }
]

def search_news(query: str, num_results: int = 3) -> str:
    # 실제 구현에서는 News API, Naver News API 등을 사용
    # 예: Naver Search API (클라이언트 ID/시크릿 필요)
    return json.dumps([
        {"title": f"{query} 관련 뉴스 1", "url": "https://example.com/1"},
        {"title": f"{query} 관련 뉴스 2", "url": "https://example.com/2"},
    ], ensure_ascii=False)

def fetch_article(url: str) -> str:
    try:
        response = requests.get(url, timeout=10)
        soup = BeautifulSoup(response.text, "html.parser")
        paragraphs = soup.find_all("p")
        return " ".join(p.get_text() for p in paragraphs[:10])
    except Exception as e:
        return f"기사 가져오기 실패: {e}"

def save_summary(filename: str, content: str) -> str:
    with open(filename, "w", encoding="utf-8") as f:
        f.write(content)
    return f"저장 완료: {filename}"

def execute_tool(name: str, input_data: dict) -> str:
    dispatch = {
        "search_news": lambda d: search_news(**d),
        "fetch_article": lambda d: fetch_article(**d),
        "save_summary": lambda d: save_summary(**d),
    }
    handler = dispatch.get(name)
    return handler(input_data) if handler else f"알 수 없는 툴: {name}"

def news_agent(topic: str) -> str:
    messages = [{
        "role": "user",
        "content": f"""'{topic}'에 관한 오늘의 주요 뉴스를 수집하고 한국어로 요약해줘.
1. 관련 뉴스 3개 검색
2. 각 기사 본문 확인
3. 전체 요약을 'news_summary.md' 파일로 저장
4. 핵심 내용 3가지 불릿으로 정리해서 알려줘"""
    }]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )
        
        if response.stop_reason == "end_turn":
            for block in response.content:
                if hasattr(block, "text"):
                    return block.text
        
        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":
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })
            messages.append({"role": "user", "content": tool_results})

result = news_agent("인공지능 규제")
print(result)

실전 에이전트 2: 코드 분석 에이전트

프로젝트 파일을 읽고 보안 이슈를 찾는 에이전트:

tools = [
    {
        "name": "read_file",
        "description": "파일 내용을 읽습니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "path": {"type": "string"}
            },
            "required": ["path"]
        }
    },
    {
        "name": "list_files",
        "description": "디렉토리의 파일 목록을 반환합니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "directory": {"type": "string"},
                "extension": {"type": "string", "default": ""}
            },
            "required": ["directory"]
        }
    },
    {
        "name": "write_report",
        "description": "분석 보고서를 저장합니다",
        "input_schema": {
            "type": "object",
            "properties": {
                "content": {"type": "string"},
                "filename": {"type": "string", "default": "security_report.md"}
            },
            "required": ["content"]
        }
    }
]

def security_audit_agent(project_path: str) -> str:
    messages = [{
        "role": "user",
        "content": f"""'{project_path}' 프로젝트 보안 감사:
1. Python/JS 파일 목록 확인
2. SQL injection, 하드코딩된 시크릿, 위험한 직렬화 패턴 검사
3. 이슈를 Critical/High/Medium/Low로 분류
4. 보고서를 security_report.md로 저장
5. 요약 알려줘"""
    }]
    # 에이전트 루프 실행 (위와 동일한 패턴)
    ...

멀티에이전트 패턴

복잡한 작업은 전문화된 서브에이전트로 분업:

def orchestrator_agent(task: str) -> str:
    """오케스트레이터: 작업을 분석하고 서브에이전트에게 위임"""
    
    tools = [
        {
            "name": "delegate_to_researcher",
            "description": "정보 수집이 필요한 작업을 리서치 에이전트에게 위임",
            "input_schema": {
                "type": "object",
                "properties": {
                    "research_task": {"type": "string"}
                },
                "required": ["research_task"]
            }
        },
        {
            "name": "delegate_to_writer",
            "description": "콘텐츠 작성 작업을 라이터 에이전트에게 위임",
            "input_schema": {
                "type": "object",
                "properties": {
                    "writing_task": {"type": "string"},
                    "research_context": {"type": "string"}
                },
                "required": ["writing_task"]
            }
        }
    ]
    
    def delegate_to_researcher(research_task: str) -> str:
        response = client.messages.create(
            model="claude-haiku-4-5",  # 리서치엔 Haiku로 비용 절감
            max_tokens=2048,
            messages=[{"role": "user", "content": f"다음을 조사해줘: {research_task}"}]
        )
        return response.content[0].text
    
    def delegate_to_writer(writing_task: str, research_context: str) -> str:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            messages=[{
                "role": "user",
                "content": f"참고 자료:\n{research_context}\n\n작성 요청: {writing_task}"
            }]
        )
        return response.content[0].text
    
    dispatch = {
        "delegate_to_researcher": lambda d: delegate_to_researcher(**d),
        "delegate_to_writer": lambda d: delegate_to_writer(**d),
    }
    
    messages = [{"role": "user", "content": task}]
    
    while True:
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )
        
        if response.stop_reason == "end_turn":
            return response.content[-1].text
        
        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":
                    handler = dispatch.get(block.name)
                    result = handler(block.input) if handler else f"Unknown: {block.name}"
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })
            messages.append({"role": "user", "content": tool_results})

에러 핸들링 및 안정성

재시도 로직

import time
from anthropic import APIStatusError

def robust_agent(task: str, max_retries: int = 3) -> str:
    for attempt in range(max_retries):
        try:
            return run_agent(task)
        except APIStatusError as e:
            if e.status_code == 429:  # 레이트 리밋
                wait_time = 2 ** attempt  # 지수 백오프: 1s, 2s, 4s
                print(f"레이트 리밋. {wait_time}초 후 재시도...")
                time.sleep(wait_time)
            elif e.status_code == 529:  # API 과부하
                time.sleep(5)
            else:
                raise
    raise RuntimeError(f"{max_retries}번 재시도 후 실패")

무한 루프 방지

def safe_agent(task: str, max_steps: int = 20) -> str:
    messages = [{"role": "user", "content": task}]
    step = 0
    
    while step < max_steps:
        step += 1
        response = client.messages.create(
            model="claude-sonnet-4-5",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )
        
        if response.stop_reason == "end_turn":
            return response.content[-1].text
        
        # 툴 처리 로직...
    
    return "최대 스텝 수 초과 — 작업 중단"

비용 최적화

서브에이전트 모델 선택

def select_model(task_complexity: str) -> str:
    model_map = {
        "simple": "claude-haiku-4-5",    # 분류, 간단한 추출
        "medium": "claude-sonnet-4-5",   # 분석, 코드 생성
        "complex": "claude-opus-4-7",    # 복잡한 추론, 전략적 판단
    }
    return model_map.get(task_complexity, "claude-sonnet-4-5")

에이전트 비용 예시

작업 평균 스텝 수 비용 (Sonnet)
뉴스 3개 요약 5-8 스텝 ~$0.05-0.10
코드 파일 5개 분석 10-15 스텝 ~$0.10-0.20
리포트 작성 (10페이지) 15-25 스텝 ~$0.20-0.50

Vercel Serverless 배포

에이전트를 API 엔드포인트로 배포:

// app/api/agent/route.ts
import Anthropic from "@anthropic-ai/sdk";
import { NextRequest } from "next/server";

export const maxDuration = 300; // 최대 5분 (Vercel Pro 필요)

export async function POST(req: NextRequest) {
  const { task } = await req.json();
  const client = new Anthropic();
  
  const encoder = new TextEncoder();
  const stream = new ReadableStream({
    async start(controller) {
      const sendUpdate = (message: string) => {
        controller.enqueue(
          encoder.encode(`data: ${JSON.stringify({ status: message })}\n\n`)
        );
      };
      
      try {
        const result = await runAgent(task, client, sendUpdate);
        controller.enqueue(
          encoder.encode(`data: ${JSON.stringify({ done: true, result })}\n\n`)
        );
      } finally {
        controller.close();
      }
    }
  });
  
  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
    }
  });
}

자주 묻는 질문

Q: Agent SDK와 일반 API 호출의 비용 차이는? 에이전트는 여러 번 API를 호출하므로 비용이 더 든다. 단순 작업에 에이전트를 쓰면 비용 낭비다. 멀티스텝 자동화가 필요한 경우에만 사용하는 것이 맞다.

Q: 에이전트가 잘못된 툴을 실행하면 어떻게 하나요? 툴 실행 전 사용자 확인을 추가하거나, 위험한 툴(파일 삭제, 외부 API 호출 등)은 샌드박스 환경에서 실행하는 것을 권장한다.

Q: LangChain과 어떻게 다른가요? LangChain은 여러 LLM을 지원하는 추상화 레이어다. Claude Agent SDK는 Claude 특화로 더 단순하고 직접적이다. 프로젝트가 Claude 전용이라면 SDK 직접 사용이 낫다.

Q: 에이전트가 루프에 빠지는 경우는? max_steps 제한과 명확한 완료 조건 정의로 방지한다. 에이전트 로그를 저장하면 디버깅에 도움이 된다.


관련 가이드


더 깊게 배우기

Claude Agent SDK Cookbook — $49 — 프로덕션에서 검증된 40개 에이전트 패턴. 뉴스 수집, 코드 분석, 고객 지원, 데이터 파이프라인까지. Python + TypeScript 완전한 코드.

→ Agent SDK Cookbook 구매 — $49

30일 환불 보장. 즉시 다운로드.

AI Disclosure: Claude Code로 작성; 모든 코드는 Anthropic Agent SDK 공식 문서 기반 2026년 4월 검증.

도구와 자료