효과적인 AI 에이전트 구축하기
2024년 12월 19일 게재
지난 한 해 동안 저희는 여러 산업에 걸쳐 대규모 언어 모델(LLM) 에이전트를 구축하는 수십 개의 팀과 협업했습니다. 그리고 일관되게, 가장 성공적인 구현은 복잡한 프레임워크나 특수한 라이브러리를 쓰지 않았습니다. 오히려 단순하고 조합 가능한 패턴으로 구축하고 있었습니다.
이 글에서는 고객사와 협업하고 저희가 직접 에이전트를 만들면서 배운 점을 공유하고, 효과적인 에이전트를 구축하려는 개발자에게 실질적인 조언을 드리고자 합니다.
에이전트란 무엇인가
"에이전트"는 여러 방식으로 정의할 수 있습니다. 어떤 고객은 에이전트를 다양한 도구를 활용해 복잡한 작업을 수행하며 오랜 기간 독립적으로 동작하는 완전 자율 시스템으로 정의합니다. 또 어떤 고객은 정해진 워크플로를 따르는, 보다 규범적인 구현을 가리키는 용어로 사용합니다. Anthropic에서는 이 모든 변형을 통틀어 에이전트형 시스템(agentic systems)으로 분류하되, 워크플로와 에이전트 사이에 중요한 아키텍처적 구분을 둡니다.
- 워크플로는 LLM과 도구가 미리 정의된 코드 경로를 통해 오케스트레이션되는 시스템입니다.
- 반면 에이전트는 LLM이 자신의 프로세스와 도구 사용을 동적으로 주도하며, 작업을 어떻게 수행할지에 대한 통제권을 스스로 유지하는 시스템입니다.
아래에서 두 유형의 에이전트형 시스템을 자세히 살펴보겠습니다. 부록 1("실전 속 에이전트")에서는 고객이 이러한 시스템을 활용해 특히 큰 가치를 얻은 두 가지 영역을 설명합니다.
에이전트를 언제 써야 하는가 (그리고 쓰지 말아야 하는가)
LLM으로 애플리케이션을 구축할 때는, 가능한 한 가장 단순한 해법을 찾고 필요할 때만 복잡도를 높이기를 권장합니다. 이는 곧 에이전트형 시스템을 아예 만들지 않는 선택일 수도 있습니다. 에이전트형 시스템은 더 나은 작업 성능을 얻기 위해 지연 시간(latency)과 비용을 희생하는 경우가 많으므로, 이러한 절충이 언제 합리적인지 따져 봐야 합니다.
복잡도를 높이는 것이 정당한 상황이라면, 워크플로는 잘 정의된 작업에 대해 예측 가능성과 일관성을 제공하고, 에이전트는 대규모에서 유연성과 모델 주도의 의사결정이 필요할 때 더 나은 선택입니다. 다만 많은 애플리케이션에서는 검색(retrieval)과 맥락 내 예시(in-context examples)로 단일 LLM 호출을 최적화하는 것만으로도 대개 충분합니다.
프레임워크를 언제, 어떻게 쓸 것인가
에이전트형 시스템 구현을 더 쉽게 해 주는 프레임워크는 다음을 비롯해 여럿 있습니다.
- Claude Agent SDK
- AWS의 Strands Agents SDK
- 드래그 앤 드롭 GUI 방식의 LLM 워크플로 빌더 Rivet
- 복잡한 워크플로를 구축·테스트하는 또 다른 GUI 도구 Vellum
이러한 프레임워크는 LLM 호출, 도구 정의 및 파싱, 호출 연결(chaining) 같은 표준적인 저수준 작업을 단순화해 시작을 쉽게 해 줍니다. 하지만 흔히 추가적인 추상화 계층을 만들어, 기저의 프롬프트와 응답을 가려 디버깅을 더 어렵게 만들 수 있습니다. 또한 더 단순한 구성으로 충분한 상황에서도 복잡도를 더하고 싶게 만들 수 있습니다.
저희는 개발자가 LLM API를 직접 사용하는 것부터 시작하기를 권합니다. 많은 패턴은 단 몇 줄의 코드로 구현할 수 있습니다. 프레임워크를 사용하더라도, 그 기저의 코드를 반드시 이해하시기 바랍니다. 내부 동작에 대한 잘못된 가정은 고객 오류의 흔한 원인입니다.
샘플 구현은 저희의 cookbook을 참고하시기 바랍니다.
빌딩 블록, 워크플로, 그리고 에이전트
이 절에서는 프로덕션에서 확인한 에이전트형 시스템의 공통 패턴을 살펴봅니다. 가장 기초적인 빌딩 블록인 증강된 LLM(augmented LLM)에서 출발해, 단순한 조합형 워크플로부터 자율 에이전트에 이르기까지 점진적으로 복잡도를 높여 가겠습니다.
빌딩 블록: 증강된 LLM
에이전트형 시스템의 기본 빌딩 블록은 검색, 도구, 메모리 같은 증강(augmentation)이 더해진 LLM입니다. 현재의 모델은 이러한 기능을 능동적으로 활용할 수 있습니다. 즉, 스스로 검색 쿼리를 생성하고, 적절한 도구를 선택하며, 어떤 정보를 유지할지 판단합니다.
구현에서는 두 가지 핵심에 집중하기를 권합니다. 첫째, 이러한 기능을 여러분의 구체적인 사용 사례에 맞게 다듬는 것. 둘째, LLM에게 쉽고 잘 문서화된 인터페이스를 제공하는 것입니다. 이러한 증강을 구현하는 방법은 여러 가지이며, 그중 하나는 최근 출시한 Model Context Protocol을 이용하는 것입니다. 이를 통해 개발자는 간단한 클라이언트 구현만으로 점점 커지는 서드파티 도구 생태계와 통합할 수 있습니다.
이 글의 나머지 부분에서는 각 LLM 호출이 이렇게 증강된 기능에 접근할 수 있다고 가정하겠습니다.
워크플로: 프롬프트 체이닝(Prompt chaining)
프롬프트 체이닝은 작업을 일련의 단계로 분해하고, 각 LLM 호출이 이전 호출의 결과물을 처리하도록 하는 방식입니다. 중간 단계 어디에든 프로그램적 검사(아래 다이어그램의 "gate" 참고)를 추가해, 과정이 여전히 정상 궤도에 있는지 확인할 수 있습니다.
이 워크플로를 쓸 때: 작업을 고정된 하위 작업으로 쉽고 깔끔하게 분해할 수 있는 상황에 이상적입니다. 주된 목적은 각 LLM 호출을 더 쉬운 작업으로 만들어, 지연 시간을 내주는 대신 더 높은 정확도를 얻는 것입니다.
프롬프트 체이닝이 유용한 예:
- 마케팅 카피를 생성한 뒤, 이를 다른 언어로 번역하기.
- 문서의 개요를 작성하고, 그 개요가 특정 기준을 충족하는지 확인한 다음, 개요를 바탕으로 문서를 작성하기.
워크플로: 라우팅(Routing)
라우팅은 입력을 분류하고 그에 맞는 전문화된 후속 작업으로 보내는 방식입니다. 이 워크플로는 관심사의 분리를 가능하게 하고, 더 전문화된 프롬프트를 만들 수 있게 합니다. 이 방식이 없으면 한 종류의 입력에 최적화하는 것이 다른 입력의 성능을 해칠 수 있습니다.
이 워크플로를 쓸 때: 라우팅은 별도로 처리하는 편이 나은 뚜렷한 범주들이 존재하는 복잡한 작업에 잘 맞으며, 분류를 LLM이나 보다 전통적인 분류 모델/알고리즘으로 정확히 처리할 수 있을 때 적합합니다.
라우팅이 유용한 예:
- 서로 다른 유형의 고객 서비스 문의(일반 질문, 환불 요청, 기술 지원)를 각기 다른 다운스트림 프로세스, 프롬프트, 도구로 보내기.
- 쉽고 흔한 질문은 Claude Haiku 4.5처럼 더 작고 비용 효율적인 모델로, 어렵고 드문 질문은 Claude Sonnet 4.5처럼 더 유능한 모델로 보내 최적의 성능을 추구하기.
워크플로: 병렬화(Parallelization)
LLM은 때때로 하나의 작업을 동시에 수행하고, 그 결과물을 프로그램적으로 취합할 수 있습니다. 병렬화라고 부르는 이 워크플로는 두 가지 주요 변형으로 나타납니다.
- 분할(Sectioning): 작업을 서로 독립적인 하위 작업으로 나누어 병렬로 실행하기.
- 투표(Voting): 같은 작업을 여러 번 실행해 다양한 결과물을 얻기.
이 워크플로를 쓸 때: 병렬화는 나뉜 하위 작업을 병렬로 처리해 속도를 높일 수 있을 때, 또는 더 높은 신뢰도의 결과를 위해 여러 관점이나 시도가 필요할 때 효과적입니다. 고려할 점이 여럿인 복잡한 작업에서는, 각 고려사항을 별도의 LLM 호출이 맡아 각 측면에 집중하도록 할 때 LLM이 대체로 더 좋은 성능을 냅니다.
병렬화가 유용한 예:
분할(Sectioning):
- 가드레일을 구현할 때, 한 모델 인스턴스가 사용자 쿼리를 처리하는 동안 다른 인스턴스가 부적절한 콘텐츠나 요청이 있는지 선별하기. 같은 LLM 호출이 가드레일과 핵심 응답을 모두 처리하게 하는 것보다 대체로 성능이 좋습니다.
- LLM 성능 평가를 위한 eval 자동화. 각 LLM 호출이 주어진 프롬프트에 대한 모델 성능의 서로 다른 측면을 평가합니다.
투표(Voting):
- 코드의 취약점을 검토할 때, 서로 다른 여러 프롬프트가 코드를 검토하고 문제를 발견하면 표시하기.
- 특정 콘텐츠가 부적절한지 평가할 때, 여러 프롬프트가 서로 다른 측면을 평가하거나 서로 다른 득표 임계값을 요구해 거짓 양성과 거짓 음성의 균형을 맞추기.
워크플로: 오케스트레이터-워커(Orchestrator-workers)
오케스트레이터-워커 워크플로에서는 중앙의 LLM이 작업을 동적으로 분해하고, 이를 워커 LLM들에게 위임한 뒤, 그 결과를 종합합니다.
이 워크플로를 쓸 때: 필요한 하위 작업을 미리 예측할 수 없는 복잡한 작업에 잘 맞습니다(예를 들어 코딩에서는 변경해야 할 파일의 개수와 각 파일에서 일어날 변경의 성격이 작업에 따라 달라질 가능성이 큽니다). 구조상 병렬화와 닮았지만 핵심 차이는 유연성에 있습니다. 즉, 하위 작업이 미리 정의되어 있지 않고, 구체적인 입력에 따라 오케스트레이터가 결정합니다.
오케스트레이터-워커가 유용한 예:
- 매번 여러 파일에 걸쳐 복잡한 변경을 가하는 코딩 제품.
- 여러 출처에서 잠재적으로 관련된 정보를 모으고 분석하는 검색 작업.
워크플로: 평가자-최적화자(Evaluator-optimizer)
평가자-최적화자 워크플로에서는 한 LLM 호출이 응답을 생성하고, 다른 LLM 호출이 루프 안에서 평가와 피드백을 제공합니다.
이 워크플로를 쓸 때: 명확한 평가 기준이 있고, 반복적인 개선이 측정 가능한 가치를 줄 때 특히 효과적입니다. 잘 맞는다는 두 가지 신호는 다음과 같습니다. 첫째, 사람이 피드백을 명확히 표현했을 때 LLM의 응답이 눈에 띄게 개선될 수 있어야 합니다. 둘째, LLM이 그러한 피드백을 제공할 수 있어야 합니다. 이는 사람 작가가 완성도 높은 문서를 만들 때 거치는 반복적 글쓰기 과정과 유사합니다.
평가자-최적화자가 유용한 예:
- 번역 LLM이 처음에는 잡아내지 못할 수 있는 미묘한 뉘앙스가 있는 문학 번역. 이때 평가자 LLM이 유용한 비평을 제공할 수 있습니다.
- 포괄적인 정보를 모으기 위해 여러 차례의 검색과 분석이 필요한 복잡한 검색 작업. 이때 평가자가 추가 검색이 필요한지 판단합니다.
에이전트
LLM이 핵심 역량을 갖추면서 에이전트가 프로덕션에 등장하고 있습니다. 그 핵심 역량이란 복잡한 입력을 이해하고, 추론과 계획을 수행하며, 도구를 안정적으로 사용하고, 오류로부터 회복하는 능력입니다. 에이전트는 사람 사용자의 명령이나 대화형 논의로 작업을 시작합니다. 작업이 명확해지면 에이전트는 독립적으로 계획하고 동작하며, 필요하면 추가 정보나 판단을 얻기 위해 사람에게 되돌아오기도 합니다. 실행 중에는 매 단계에서 환경으로부터 "그라운드 트루스(ground truth)"를 확보해(예: 도구 호출 결과나 코드 실행 결과) 진행 상황을 평가하는 것이 매우 중요합니다. 에이전트는 체크포인트나 막힌 지점에서 사람의 피드백을 위해 멈출 수 있습니다. 작업은 흔히 완료 시 종료되지만, 통제를 유지하기 위해 중단 조건(예: 최대 반복 횟수)을 두는 것도 일반적입니다.
에이전트는 정교한 작업을 처리할 수 있지만, 그 구현은 대체로 단순합니다. 보통은 환경 피드백을 바탕으로 도구를 사용하는 LLM을 루프 안에서 돌리는 것에 지나지 않습니다. 따라서 도구 집합과 그 문서를 명확하고 사려 깊게 설계하는 것이 매우 중요합니다. 도구 개발의 모범 사례는 부록 2("도구에 대한 프롬프트 엔지니어링")에서 자세히 다룹니다.
에이전트를 쓸 때: 에이전트는 필요한 단계 수를 예측하기 어렵거나 불가능하고, 고정된 경로를 하드코딩할 수 없는 개방형 문제에 사용할 수 있습니다. LLM이 잠재적으로 여러 턴에 걸쳐 동작하므로, 그 의사결정을 어느 정도 신뢰할 수 있어야 합니다. 에이전트의 자율성은 신뢰할 수 있는 환경에서 작업을 확장하는 데 이상적입니다.
에이전트는 자율적인 만큼 비용이 더 높고 오류가 누적될 가능성이 있습니다. 그러므로 샌드박스 환경에서의 광범위한 테스트와 함께 적절한 가드레일을 갖추기를 권장합니다.
에이전트가 유용한 예:
다음은 저희가 직접 구현한 사례입니다.
- 작업 설명을 바탕으로 여러 파일을 편집하는 SWE-bench 과제를 해결하는 코딩 에이전트.
- Claude가 컴퓨터를 사용해 작업을 수행하는 저희의 "컴퓨터 사용(computer use)" 레퍼런스 구현.
패턴을 조합하고 맞춤화하기
이 빌딩 블록들은 규범이 아닙니다. 개발자가 서로 다른 사용 사례에 맞게 다듬고 조합할 수 있는 공통 패턴일 뿐입니다. 다른 모든 LLM 기능과 마찬가지로, 성공의 열쇠는 성능을 측정하고 구현을 반복 개선하는 데 있습니다. 다시 한번 강조합니다. 복잡도는 그것이 결과를 분명히 개선할 때에만 더해야 합니다.
요약
LLM 분야에서의 성공은 가장 정교한 시스템을 만드는 데 있지 않습니다. 여러분의 필요에 맞는 적절한 시스템을 만드는 데 있습니다. 단순한 프롬프트로 시작하고, 종합적인 평가로 이를 최적화하며, 더 단순한 해법으로 충분하지 않을 때에만 다단계 에이전트형 시스템을 추가하시기 바랍니다.
에이전트를 구현할 때, 저희는 세 가지 핵심 원칙을 따르려 노력합니다.
- 에이전트 설계에서 단순함을 유지하기.
- 에이전트의 계획 단계를 명시적으로 보여 주어 투명성을 우선하기.
- 철저한 도구 문서화와 테스트를 통해 에이전트-컴퓨터 인터페이스(ACI)를 정성껏 설계하기.
프레임워크는 빠른 시작을 도울 수 있지만, 프로덕션으로 넘어갈 때는 주저하지 말고 추상화 계층을 줄이고 기본 요소로 구축하시기 바랍니다. 이 원칙들을 따르면 강력할 뿐 아니라 신뢰할 수 있고, 유지보수하기 좋으며, 사용자에게 신뢰받는 에이전트를 만들 수 있습니다.
감사의 글
작성: Erik S., Barry Zhang. 이 글은 Anthropic에서 에이전트를 구축한 경험과 고객이 공유해 준 소중한 통찰에 기반하며, 이에 깊이 감사드립니다.
부록 1: 실전 속 에이전트
고객과의 협업에서, 앞서 논의한 패턴의 실질적 가치를 잘 보여 주는 특히 유망한 두 가지 AI 에이전트 활용 사례를 확인했습니다. 두 사례 모두 대화와 실행이 함께 필요하고, 명확한 성공 기준이 있으며, 피드백 루프를 가능하게 하고, 의미 있는 사람의 감독이 통합되는 작업에서 에이전트가 가장 큰 가치를 더한다는 점을 보여 줍니다.
A. 고객 지원
고객 지원은 익숙한 챗봇 인터페이스에 도구 통합으로 향상된 기능을 결합합니다. 이는 보다 개방형인 에이전트에 자연스럽게 잘 맞는데, 그 이유는 다음과 같습니다.
- 지원 상호작용은 자연스럽게 대화 흐름을 따르면서도, 외부 정보와 동작에 대한 접근이 필요합니다.
- 도구를 통합해 고객 데이터, 주문 내역, 지식 베이스 문서를 가져올 수 있습니다.
- 환불 처리나 티켓 업데이트 같은 동작을 프로그램적으로 처리할 수 있습니다.
- 사용자가 정의한 해결(resolution)을 통해 성공을 명확히 측정할 수 있습니다.
여러 기업이 성공적으로 해결된 건에 대해서만 과금하는 사용량 기반 가격 모델로 이 접근법의 실효성을 입증했는데, 이는 자사 에이전트의 효과를 그만큼 확신한다는 뜻입니다.
B. 코딩 에이전트
소프트웨어 개발 영역은 LLM 기능에 있어 놀라운 잠재력을 보여 왔으며, 그 역량은 코드 자동완성에서 자율적 문제 해결로 발전해 왔습니다. 에이전트가 특히 효과적인 이유는 다음과 같습니다.
- 코드 솔루션은 자동화된 테스트로 검증할 수 있습니다.
- 에이전트는 테스트 결과를 피드백 삼아 솔루션을 반복 개선할 수 있습니다.
- 문제 공간이 잘 정의되어 있고 구조적입니다.
- 결과물의 품질을 객관적으로 측정할 수 있습니다.
저희의 자체 구현에서, 에이전트는 이제 SWE-bench Verified 벤치마크의 실제 GitHub 이슈를 풀 리퀘스트(pull request) 설명만으로 해결할 수 있습니다. 다만 자동화된 테스트가 기능을 검증하는 데 도움을 주더라도, 솔루션이 더 넓은 시스템 요구사항에 부합하는지 보장하려면 여전히 사람의 리뷰가 매우 중요합니다.
부록 2: 도구에 대한 프롬프트 엔지니어링
어떤 에이전트형 시스템을 구축하든, 도구는 에이전트의 중요한 부분이 될 가능성이 큽니다. 도구는 API에서 정확한 구조와 정의를 명시함으로써 Claude가 외부 서비스 및 API와 상호작용할 수 있게 해 줍니다. Claude가 응답할 때 도구를 호출할 계획이라면, API 응답에 도구 사용(tool use) 블록을 포함합니다. 도구의 정의와 명세에는 전체 프롬프트만큼이나 많은 프롬프트 엔지니어링 주의를 기울여야 합니다. 이 짧은 부록에서는 도구에 대한 프롬프트 엔지니어링 방법을 설명합니다.
같은 동작을 명시하는 방법은 흔히 여러 가지가 있습니다. 예를 들어 파일 편집은 diff를 작성해 지정할 수도 있고, 파일 전체를 다시 작성해 지정할 수도 있습니다. 구조화된 출력의 경우 코드를 마크다운 안에 넣어 반환할 수도 있고 JSON 안에 넣어 반환할 수도 있습니다. 소프트웨어 공학에서 이런 차이는 겉모습에 불과하며 무손실로 서로 변환할 수 있습니다. 하지만 어떤 형식은 LLM이 작성하기에 다른 형식보다 훨씬 어렵습니다. diff를 작성하려면 새 코드를 쓰기 전에 청크 헤더에서 몇 줄이 바뀌는지 알아야 합니다. (마크다운에 비해) JSON 안에 코드를 작성하려면 줄바꿈과 따옴표를 추가로 이스케이프해야 합니다.
도구 형식을 정할 때 저희가 제안하는 바는 다음과 같습니다.
- 모델이 막다른 길에 스스로를 몰아넣기 전에, "생각"할 수 있는 충분한 토큰을 주십시오.
- 형식을 모델이 인터넷상의 텍스트에서 자연스럽게 접했을 법한 것에 가깝게 유지하십시오.
- 수천 줄의 코드 개수를 정확히 세거나, 작성하는 코드를 문자열 이스케이프해야 하는 등의 형식 "오버헤드"가 없도록 하십시오.
한 가지 경험칙은, 사람-컴퓨터 인터페이스(HCI)에 얼마나 많은 노력이 들어가는지 생각해 보고, 좋은 에이전트-컴퓨터 인터페이스(ACI)를 만드는 데에도 그만큼의 노력을 들일 계획을 세우는 것입니다. 이를 위한 몇 가지 생각은 다음과 같습니다.
- 모델의 입장이 되어 보십시오. 설명과 매개변수만 보고도 이 도구를 어떻게 쓰는지 명백한가요, 아니면 신중히 따져 봐야 하나요? 후자라면, 모델에게도 마찬가지일 가능성이 큽니다. 좋은 도구 정의에는 흔히 사용 예시, 엣지 케이스, 입력 형식 요구사항, 그리고 다른 도구와의 명확한 경계가 포함됩니다.
- 매개변수 이름이나 설명을 어떻게 바꾸면 더 명확해질까요? 이를 팀의 주니어 개발자를 위한 훌륭한 docstring을 쓰는 일이라고 생각하십시오. 유사한 도구를 여럿 사용할 때 특히 중요합니다.
- 모델이 도구를 어떻게 쓰는지 테스트하십시오. 저희 workbench에서 다양한 예시 입력을 돌려 보며 모델이 어떤 실수를 하는지 확인하고, 반복 개선하십시오.
- 도구를 포카요케(poka-yoke)하십시오. 실수하기 더 어렵도록 인자를 바꾸십시오.
SWE-bench용 에이전트를 만들 때, 저희는 사실 전체 프롬프트보다 도구를 최적화하는 데 더 많은 시간을 들였습니다. 예를 들어, 에이전트가 루트 디렉터리 밖으로 이동한 뒤에는 모델이 상대 경로(relative filepath)를 쓰는 도구에서 실수를 한다는 점을 발견했습니다. 이를 해결하기 위해 도구가 항상 절대 경로(absolute filepath)를 요구하도록 바꾸었고, 그 결과 모델이 이 방식을 완벽하게 사용한다는 것을 확인했습니다.
