Building effective agents

TMT

https://www.anthropic.com/engineering/building-effective-agents
- anthropic

지난 1년 동안, 우리는 다양한 산업 분야에서 대형 언어 모델(LLM) 에이전트를 구축하는 수십 개 팀과 협업해왔습니다. 일관되게, 가장 성공적인 구현은 복잡한 프레임워크나 특수 라이브러리를 사용하지 않았습니다. 대신, 단순하고 조합 가능한 패턴으로 구축하고 있었습니다.

이 글에서는 고객과 협업하고 직접 에이전트를 구축하면서 얻은 경험을 공유하고, 효과적인 에이전트 구축에 대한 실용적인 조언을 개발자에게 제공합니다.

에이전트란 무엇인가?

“에이전트”는 여러 방식으로 정의될 수 있습니다. 일부 고객은 에이전트를 다양한 도구를 사용해 복잡한 작업을 수행하며 장기간 독립적으로 작동하는 완전 자율 시스템으로 정의합니다. 다른 이들은 미리 정의된 워크플로우를 따르는 보다 규정적인 구현을 에이전트라고 부릅니다. Anthropic에서는 이러한 모든 변형을 에이전틱 시스템으로 분류하지만, 워크플로우와 에이전트 사이에 중요한 아키텍처적 구분을 둡니다:

  • 워크플로우는 LLM과 도구가 미리 정의된 코드 경로를 통해 오케스트레이션되는 시스템입니다.
  • 에이전트는 LLM이 자신의 프로세스와 도구 사용을 동적으로 지시하며, 작업을 수행하는 방식을 스스로 제어하는 시스템입니다.

아래에서는 이 두 가지 유형의 에이전틱 시스템을 자세히 살펴봅니다. 부록 1(“실전에서의 에이전트”)에서는 고객이 이러한 시스템을 사용해 특별한 가치를 발견한 두 가지 도메인을 설명합니다.

에이전트를 언제(그리고 언제 사용하지 말아야 하는가)

LLM을 활용한 애플리케이션을 구축할 때, 우리는 가능한 한 단순한 해결책을 찾고, 필요할 때만 복잡성을 높일 것을 권장합니다. 이는 에이전틱 시스템을 아예 구축하지 않는 것을 의미할 수도 있습니다. 에이전틱 시스템은 종종 더 나은 작업 성능을 위해 지연 시간과 비용을 희생하므로, 이러한 트레이드오프가 언제 타당한지 고려해야 합니다.

더 높은 복잡성이 필요할 때, 워크플로우는 잘 정의된 작업에 대해 예측 가능성과 일관성을 제공합니다. 반면, 에이전트는 대규모로 유연성과 모델 주도 의사결정이 필요할 때 더 나은 선택입니다. 그러나 많은 애플리케이션에서는 검색 및 인컨텍스트 예시로 단일 LLM 호출을 최적화하는 것만으로도 충분합니다.

프레임워크를 언제, 어떻게 사용할 것인가

에이전틱 시스템 구현을 쉽게 해주는 다양한 프레임워크가 있습니다. 예를 들어:

  • LangChain의 LangGraph;
  • Amazon Bedrock의 AI Agent framework;
  • 드래그 앤 드롭 GUI LLM 워크플로우 빌더인 Rivet;
  • 복잡한 워크플로우를 구축하고 테스트할 수 있는 또 다른 GUI 도구인 Vellum.

이러한 프레임워크는 LLM 호출, 도구 정의 및 파싱, 호출 체이닝 등 표준 저수준 작업을 단순화하여 쉽게 시작할 수 있게 해줍니다. 하지만 종종 추가 추상화 계층을 만들어 기본 프롬프트와 응답을 가려 디버깅을 어렵게 만들 수 있습니다. 또한 더 단순한 설정으로도 충분할 때 불필요하게 복잡성을 추가하게 만들 수 있습니다.

우리는 개발자가 LLM API를 직접 사용하는 것부터 시작할 것을 제안합니다. 많은 패턴은 몇 줄의 코드로 구현할 수 있습니다. 프레임워크를 사용할 경우, 반드시 기본 코드를 이해해야 합니다. 내부 동작에 대한 잘못된 가정은 고객 오류의 흔한 원인입니다.

샘플 구현은 우리의 쿡북을 참고하세요.

빌딩 블록, 워크플로우, 에이전트

이 섹션에서는 실제로 사용되는 에이전틱 시스템의 일반적인 패턴을 살펴봅니다. 기본 빌딩 블록인 증강 LLM에서 시작해, 단순한 조합 워크플로우에서 자율 에이전트로 점차 복잡성을 높여가겠습니다.

빌딩 블록: 증강 LLM

에이전틱 시스템의 기본 빌딩 블록은 검색, 도구, 메모리 등으로 강화된 LLM입니다. 현재 모델은 이러한 기능을 적극적으로 사용할 수 있습니다. 예를 들어, 자체적으로 검색 쿼리를 생성하고, 적절한 도구를 선택하며, 어떤 정보를 보존할지 결정할 수 있습니다.

Image

구현 시 두 가지 핵심 요소에 집중할 것을 권장합니다: 이러한 기능을 특정 사용 사례에 맞게 조정하는 것, 그리고 LLM에 쉽고 잘 문서화된 인터페이스를 제공하는 것입니다. 이러한 증강 기능을 구현하는 방법은 다양하지만, 최근 출시한 Model Context Protocol을 통해 간단한 클라이언트 구현으로 다양한 서드파티 도구와 통합할 수 있습니다.

이후의 내용에서는 각 LLM 호출이 이러한 증강 기능에 접근할 수 있다고 가정합니다.

워크플로우: 프롬프트 체이닝

프롬프트 체이닝은 작업을 일련의 단계로 분해하여, 각 LLM 호출이 이전 단계의 출력을 처리하는 방식입니다. 중간 단계마다 프로그래밍적 검증(아래 다이어그램의 “게이트” 참고)을 추가해 프로세스가 제대로 진행되고 있는지 확인할 수 있습니다.

Image

이 워크플로우를 사용할 때: 작업이 고정된 하위 작업으로 쉽게, 명확하게 분해될 수 있을 때 이상적입니다. 주요 목표는 각 LLM 호출을 더 쉬운 작업으로 만들어 지연 시간을 희생하고 정확도를 높이는 것입니다.

프롬프트 체이닝이 유용한 예시:

  • 마케팅 카피를 생성한 후, 이를 다른 언어로 번역하기
  • 문서 개요를 작성하고, 그 개요가 특정 기준을 충족하는지 확인한 뒤, 개요를 바탕으로 문서를 작성하기

워크플로우: 라우팅

라우팅은 입력을 분류하여 특화된 후속 작업으로 전달하는 워크플로우입니다. 이 방식은 관심사의 분리를 가능하게 하고, 더 특화된 프롬프트를 만들 수 있게 해줍니다. 이 워크플로우가 없다면, 한 종류의 입력에 최적화하면 다른 입력의 성능이 저하될 수 있습니다.

Image

워크플로우를 사용할 때: 복잡한 작업에서 명확히 구분되는 카테고리가 있고, 분류를 LLM이나 전통적인 분류 모델/알고리즘으로 정확히 처리할 수 있을 때 적합합니다.

라우팅이 유용한 예시:

  • 다양한 유형의 고객 서비스 문의(일반 질문, 환불 요청, 기술 지원 등)를 각기 다른 다운스트림 프로세스, 프롬프트, 도구로 전달하기
  • 쉬운/일반적인 질문은 Claude 3.5 Haiku와 같은 소형 모델로, 어려운/특이한 질문은 Claude 3.5 Sonnet과 같은 더 강력한 모델로 라우팅하여 비용과 속도를 최적화하기

워크플로우: 병렬화

LLM이 때로는 작업을 동시에 처리하고, 그 출력을 프로그래밍적으로 집계할 수 있습니다. 이 워크플로우인 병렬화는 두 가지 주요 변형이 있습니다:

  • 섹셔닝(Sectioning): 작업을 독립적인 하위 작업으로 나누어 병렬로 실행
  • 투표(Voting): 동일한 작업을 여러 번 실행해 다양한 출력을 얻음
Image

이 워크플로우를 사용할 때: 분할된 하위 작업을 병렬로 처리해 속도를 높이거나, 더 높은 신뢰도의 결과를 위해 여러 관점이나 시도가 필요할 때 효과적입니다. 여러 고려사항이 있는 복잡한 작업의 경우, 각 고려사항을 별도의 LLM 호출로 처리하면 더 나은 성능을 보입니다.

병렬화가 유용한 예시:

  • 섹셔닝:
    • 한 모델 인스턴스가 사용자 쿼리를 처리하고, 다른 인스턴스가 부적절한 콘텐츠나 요청을 필터링하는 가드레일 구현. 동일 LLM 호출이 두 역할을 모두 하는 것보다 성능이 좋음.
    • LLM 성능 평가 자동화에서 각 LLM 호출이 주어진 프롬프트에 대한 모델 성능의 다른 측면을 평가함.
  • 투표:
    • 여러 프롬프트가 코드를 검토해 취약점이 발견되면 플래그를 다는 방식으로 코드 취약점 검토
    • 여러 프롬프트가 다양한 측면에서 콘텐츠의 부적절성을 평가하고, 다양한 투표 기준을 적용해 오탐과 누락을 균형 있게 조정

워크플로우: 오케스트레이터-워커

오케스트레이터-워커 워크플로우에서는 중앙 LLM이 작업을 동적으로 분해하고, 워커 LLM에 위임한 뒤 결과를 종합합니다.

Image

이 워크플로우를 사용할 때: 필요한 하위 작업을 예측할 수 없는 복잡한 작업에 적합합니다(예: 코딩에서는 변경해야 할 파일 수와 각 파일의 변경 내용이 작업에 따라 달라짐). 병렬화와 유사하지만, 핵심 차이는 유연성에 있습니다. 하위 작업이 미리 정의되어 있지 않고, 오케스트레이터가 입력에 따라 결정합니다.

오케스트레이터-워커가 유용한 예시:

  • 여러 파일에 복잡한 변경을 가하는 코딩 제품
  • 여러 소스에서 정보를 수집·분석해 관련 정보를 찾는 검색 작업

워크플로우: 평가자-최적화자

평가자-최적화자 워크플로우에서는 한 번의 LLM 호출이 응답을 생성하고, 다른 호출이 평가와 피드백을 반복적으로 제공합니다.

Image

이 워크플로우를 사용할 때: 명확한 평가 기준이 있고, 반복적 개선이 측정 가능한 가치를 제공할 때 특히 효과적입니다. 두 가지 적합 신호는, 첫째, 사람이 피드백을 제공할 때 LLM 응답이 명확히 개선될 수 있다는 점, 둘째, LLM이 그러한 피드백을 제공할 수 있다는 점입니다. 이는 사람이 완성도 높은 문서를 작성할 때 거치는 반복적 글쓰기 과정과 유사합니다.

평가자-최적화자가 유용한 예시:

  • 번역 LLM이 처음에는 뉘앙스를 놓칠 수 있지만, 평가 LLM이 유용한 비평을 제공해 문학 번역을 개선
  • 포괄적 정보 수집이 필요한 복잡한 검색 작업에서, 평가자가 추가 검색이 필요한지 판단하며 여러 차례 검색과 분석을 반복

에이전트

에이전트는 LLM이 복잡한 입력을 이해하고, 추론과 계획을 수행하며, 도구를 신뢰성 있게 사용하고, 오류에서 회복하는 주요 역량이 성숙해지면서 실제 환경에 등장하고 있습니다. 에이전트는 인간 사용자의 명령이나 상호작용으로 작업을 시작합니다. 작업이 명확해지면, 에이전트는 독립적으로 계획을 세우고 실행하며, 필요에 따라 추가 정보나 판단을 위해 인간에게 다시 문의할 수 있습니다. 실행 중에는 각 단계에서(도구 호출 결과나 코드 실행 등) 환경에서 “실제 정보”를 얻어 진행 상황을 평가하는 것이 중요합니다. 에이전트는 체크포인트나 장애물에 부딪혔을 때 인간 피드백을 위해 일시정지할 수 있습니다. 작업은 완료 시 종료되지만, 제어를 위해 최대 반복 횟수와 같은 중단 조건을 포함하는 것도 일반적입니다.

에이전트는 복잡한 작업을 처리할 수 있지만, 구현은 종종 간단합니다. 일반적으로 LLM이 환경 피드백을 바탕으로 도구를 반복적으로 사용하는 구조입니다. 따라서 도구 세트와 그 문서를 명확하고 신중하게 설계하는 것이 매우 중요합니다. 도구 개발에 대한 모범 사례는 부록 2(“도구 프롬프트 엔지니어링”)에서 자세히 다룹니다.

Image

에이전트를 사용할 때: 필요한 단계 수를 예측하거나 고정 경로로 하드코딩할 수 없는 개방형 문제에 사용할 수 있습니다. LLM이 여러 차례 동작할 수 있으며, 그 의사결정에 일정 수준의 신뢰가 필요합니다. 에이전트의 자율성은 신뢰할 수 있는 환경에서 작업을 확장하는 데 이상적입니다.

에이전트의 자율성은 더 높은 비용과 누적 오류 가능성을 의미합니다. 우리는 샌드박스 환경에서 광범위한 테스트와 적절한 가드레일을 권장합니다.

에이전트가 유용한 예시:

다음 예시는 우리의 자체 구현에서 가져왔습니다:

  • 여러 파일을 편집해야 하는 SWE-bench 작업을 해결하는 코딩 에이전트
  • Claude가 컴퓨터를 사용해 작업을 수행하는 “computer use” 참조 구현
Image

이러한 패턴의 결합 및 맞춤화

이 빌딩 블록들은 규범이 아닙니다. 개발자가 다양한 사용 사례에 맞게 형태를 바꾸고 결합할 수 있는 일반적인 패턴입니다. 다른 LLM 기능과 마찬가지로, 성공의 열쇠는 성능을 측정하고 구현을 반복하는 것입니다. 다시 강조하자면: 복잡성을 추가하는 것은 실제로 결과가 개선될 때만 고려해야 합니다.

요약

LLM 분야에서의 성공은 가장 정교한 시스템을 구축하는 것이 아니라, 필요에 맞는 시스템을 구축하는 데 있습니다. 단순한 프롬프트로 시작해, 포괄적 평가로 최적화하고, 더 단순한 해결책이 부족할 때만 다단계 에이전틱 시스템을 추가하세요.

에이전트 구현 시, 우리는 세 가지 핵심 원칙을 따르려고 합니다:

  1. 에이전트 설계의 단순성을 유지하세요.
  2. 에이전트의 계획 단계를 명확히 드러내 투명성을 우선하세요.
  3. 철저한 도구 문서화 및 테스트를 통해 에이전트-컴퓨터 인터페이스(ACI)를 신중하게 설계하세요.

프레임워크는 빠른 시작에 도움이 되지만, 프로덕션 단계로 넘어갈 때는 추상화 계층을 줄이고 기본 구성 요소로 구축하는 것을 주저하지 마세요. 이러한 원칙을 따르면, 강력할 뿐 아니라 신뢰할 수 있고 유지보수 가능하며 사용자에게 신뢰받는 에이전트를 만들 수 있습니다.

부록 1: 실전에서의 에이전트

고객과의 협업을 통해, 위에서 논의한 패턴의 실질적 가치를 보여주는 AI 에이전트의 두 가지 특히 유망한 적용 사례를 발견했습니다. 두 사례 모두, 에이전트가 대화와 행동이 모두 필요한 작업, 명확한 성공 기준, 피드백 루프, 의미 있는 인간 감독이 통합된 작업에서 가장 큰 가치를 더한다는 점을 보여줍니다.

A. 고객 지원

고객 지원은 익숙한 챗봇 인터페이스에 도구 통합을 통한 향상된 기능을 결합합니다. 이는 보다 개방형 에이전트에 자연스럽게 적합합니다. 그 이유는:

  • 지원 상호작용은 자연스럽게 대화 흐름을 따르면서 외부 정보 및 행동에 접근해야 하며,
  • 도구를 통합해 고객 데이터, 주문 내역, 지식 베이스 문서를 불러올 수 있고,
  • 환불 처리나 티켓 업데이트 등 행동을 프로그래밍적으로 처리할 수 있으며,
  • 성공을 사용자 정의 해결책을 통해 명확히 측정할 수 있습니다.

여러 회사가 성공적인 해결에 대해서만 요금을 부과하는 사용량 기반 가격 모델을 통해 이 접근법의 실현 가능성을 입증했습니다. 이는 에이전트의 효과에 대한 자신감을 보여줍니다.

B. 코딩 에이전트

소프트웨어 개발 분야는 코드 완성에서 자율적 문제 해결로 LLM 기능이 진화하며 놀라운 잠재력을 보여주고 있습니다. 에이전트가 특히 효과적인 이유는:

  • 코드 솔루션을 자동화된 테스트로 검증할 수 있고,
  • 에이전트가 테스트 결과를 피드백 삼아 솔루션을 반복 개선할 수 있으며,
  • 문제 공간이 잘 정의되고 구조화되어 있고,
  • 산출물의 품질을 객관적으로 측정할 수 있기 때문입니다.

우리의 자체 구현에서는, 에이전트가 풀 리퀘스트 설명만으로 SWE-bench Verified 벤치마크의 실제 GitHub 이슈를 해결할 수 있습니다. 하지만 자동화된 테스트가 기능을 검증하는 데 도움이 되더라도, 더 넓은 시스템 요구사항과의 정합성을 보장하기 위해서는 인간의 검토가 여전히 중요합니다.

부록 2: 도구 프롬프트 엔지니어링

어떤 에이전틱 시스템을 구축하든, 도구는 에이전트의 중요한 부분이 될 가능성이 높습니다. 도구는 Claude가 외부 서비스 및 API와 상호작용할 수 있게 해주며, API에서 그 구조와 정의를 명확히 지정합니다. Claude가 응답할 때, 도구를 호출할 계획이 있으면 API 응답에 도구 사용 블록을 포함합니다. 도구 정의와 명세는 전체 프롬프트만큼 프롬프트 엔지니어링에 신경 써야 합니다. 이 짧은 부록에서는 도구 프롬프트 엔지니어링 방법을 설명합니다.

동일한 동작을 지정하는 방법은 여러 가지가 있을 수 있습니다. 예를 들어, 파일 수정을 diff로 지정하거나 전체 파일을 다시 작성할 수 있습니다. 구조화된 출력의 경우, 코드를 마크다운이나 JSON 내부에 반환할 수 있습니다. 소프트웨어 엔지니어링에서는 이러한 차이가 형식적이며, 한 형식에서 다른 형식으로 무손실 변환이 가능합니다. 하지만 일부 형식은 LLM이 작성하기 훨씬 더 어렵습니다. diff를 작성하려면, 새로운 코드를 쓰기 전에 변경되는 줄 수를 chunk 헤더에 미리 알아야 합니다. JSON 내부에 코드를 작성할 때는(마크다운에 비해) 줄바꿈과 따옴표를 추가로 이스케이프해야 합니다.

도구 형식을 결정할 때 우리의 제안은 다음과 같습니다:

  • 모델이 스스로를 곤란하게 만들기 전에 “생각”할 수 있을 만큼 충분한 토큰을 제공하세요.
  • 형식을 인터넷에서 자연스럽게 발생하는 텍스트와 가깝게 유지하세요.
  • 수천 줄의 코드를 정확히 세거나, 작성하는 모든 코드를 이스케이프해야 하는 등 형식적 “오버헤드”가 없도록 하세요.

한 가지 기준은 인간-컴퓨터 인터페이스(HCI)에 얼마나 많은 노력이 들어가는지 생각해보고, 에이전트-컴퓨터 인터페이스(ACI)에도 그만큼의 노력을 투자하는 것입니다. 다음은 그 방법에 대한 몇 가지 생각입니다:

  • 모델의 입장에서 생각해보세요. 설명과 파라미터만 보고 이 도구를 어떻게 써야 할지 명확한가요, 아니면 신중히 생각해야 하나요? 후자라면 모델에게도 마찬가지일 것입니다. 좋은 도구 정의에는 사용 예시, 엣지 케이스, 입력 형식 요구사항, 다른 도구와의 명확한 경계가 포함됩니다.
  • 파라미터 이름이나 설명을 어떻게 바꾸면 더 명확해질까요? 이는 팀의 주니어 개발자를 위한 훌륭한 docstring을 작성하는 것과 같습니다. 특히 유사한 도구가 많을 때 중요합니다.
  • 모델이 도구를 어떻게 사용하는지 테스트하세요: 워크벤치에서 다양한 예시 입력을 실행해 모델이 어떤 실수를 하는지 확인하고, 반복적으로 개선하세요.
  • 도구를 포카요케(poka-yoke)하세요. 실수하기 어렵게 인자를 변경하세요.

우리가 SWE-bench용 에이전트를 구축할 때, 전체 프롬프트보다 도구 최적화에 더 많은 시간을 쏟았습니다. 예를 들어, 에이전트가 루트 디렉토리에서 벗어난 후 상대 경로 도구에서 실수를 하는 경우가 있었습니다. 이를 해결하기 위해 도구가 항상 절대 경로만 요구하도록 변경했고, 모델은 이 방식을 완벽하게 사용했습니다.

Edit this page