에이전트 설계 패턴

지능형 시스템 구축을 위한 실습 가이드1
                                                    , Antonio Gulli
목차 - 총 424 페이지 = 1+2+1+1+4+9+103+61+34+114+74+5+4 11
헌사, 1페이지
감사의 글, 2페이지 [완료, 마지막 읽기 완료]
서문, 1페이지 [완료, 마지막 읽기 완료]
사상 리더의 관점: 힘과 책임 [완료, 마지막 읽기 완료]
서론, 4페이지 [완료, 마지막 읽기 완료]
AI 시스템을 "에이전트"로 만드는 것은 무엇인가?, 9페이지 [완료, 마지막 읽기 완료]
파트 원, (총합: 103 페이지)
    1. 1장: 프롬프트 체이닝 (코드), 12페이지 [완료, 마지막 읽기 완료, 코드 확인]
    2. 2장: 라우팅 (코드), 13페이지 [완료, 마지막 읽기 완료, 코드 확인]
    3. 3장: 병렬화 (코드), 15페이지 [완료, 마지막 읽기 완료, 코드 확인]
    4. 4장: 반성 (코드), 13페이지 [완료, 마지막 읽기 완료, 코드 확인]
    5. 5장: 도구 사용 (코드), 20페이지 [완료, 마지막 읽기 완료, 코드 확인]
    6. 6장: 계획 (코드), 13페이지 [완료, 마지막 읽기 완료, 코드 확인]
    7. 7장: 다중 에이전트 (코드), 17페이지 [완료, 마지막 읽기 완료, 코드 확인], 121
파트 투 (총합: 61 페이지)
    8. 8장: 메모리 관리 (코드), 21페이지 [완료, 마지막 읽기 완료, 코드 확인]
    9. 9장: 학습 및 적응 (코드), 12페이지 [완료, 마지막 읽기 완료, 코드 확인]
    10. 10장: 모델 컨텍스트 프로토콜 (MCP) (코드), 16페이지 [완료, 마지막 읽기 완료, 코드 확인]
    11. 11장: 목표 설정 및 모니터링 (코드), 12페이지 [완료, 마지막 읽기 완료, 코드 확인], 182
파트 쓰리 (총합: 34 페이지)
    12. 12장: 예외 처리 및 복구 (코드), 8페이지 [완료, 마지막 읽기 완료, 코드 확인]
    13. 13장: 인간 참여 (코드), 9페이지 [완료, 마지막 읽기 완료, 코드 확인]
    14. 14장: 지식 검색 (RAG) (코드), 17페이지 [완료, 마지막 읽기 완료, 코드 확인], 216
파트 포 (총합: 114 페이지)
    15. 15장: 에이전트 간 통신 (A2A) (코드), 15페이지 [완료, 마지막 읽기 완료, 코드 확인]
    16. 16장: 리소스 인식 최적화 (코드), 15페이지 [완료, 마지막 읽기 완료, 코드 확인]
    17. 17장: 추론 기술 (코드), 24페이지 [완료, 마지막 읽기 완료, 코드 확인]
    18. 18장: 가드레일/안전 패턴 (코드), 19페이지 [완료, 마지막 읽기 완료, 코드 확인]
    19. 19장: 평가 및 모니터링 (코드), 18페이지 [완료, 마지막 읽기 완료, 코드 확인]
    20. 20장: 우선순위 지정 (코드), 10페이지 [완료, 마지막 읽기 완료, 코드 확인]
    21. 21장: 탐색 및 발견 (코드), 13페이지 [완료, 마지막 읽기 완료, 코드 확인], 330
부록 (총합: 74 페이지)
    22. 부록 A: 고급 프롬프팅 기술, 28페이지 [완료, 마지막 읽기 완료, 코드 확인]
    23. 부록 B - 에이전트 상호작용...: GUI에서 실제 환경으로, 6페이지 [완료, 마지막 읽기 완료, 코드 확인]
    24. 부록 C - 에이전트 프레임워크 개요, 8페이지 [완료, 마지막 읽기 완료, 코드 확인] ,
    25. 부록 D - AgentSpace를 사용한 에이전트 구축 (온라인 전용), 6페이지 [완료, 마지막 읽기 완료, 코드 확인]
    26. 부록 E - CLI에서의 AI 에이전트 (온라인), 5페이지 [완료, 마지막 읽기 완료, 코드 확인]
    27. 부록 F - 내부 들여다보기: 에이전트 추론 엔진의 내부 모습, 14페이지 [완료, 마지막 읽기 완료, 코드 확인],
    28. 부록 G - 코딩 에이전트, 7페이지 406
결론, 5페이지 [완료, 마지막 읽기 완료]
용어 해설, 4페이지 [완료, 마지막 읽기 완료]
용어 색인, 11페이지 (Gemini에 의해 생성됨. 에이전트 설계 예시로 추론 단계 포함) [완료, 마지막 읽기 완료]
온라인 기고 - 자주 묻는 질문: 에이전트 설계 패턴
프리 프린트: https://www.amazon.com/Agentic-Design-Patterns-Hands-Intelligent/dp/3032014018/

1 모든 저의 인세는 Save the Children에 기부됩니다

제 아들 브루노에게,

두 살의 나이에 제 삶에 새롭고 눈부신 빛을 가져다준 아이. 제가 내일을 규정할 시스템들을 탐구하는 동안, 제 마음속에는 가장 먼저 떠오르는 것은 바로 당신이 물려받을 세상입니다.

제 아들 레오나르도와 로렌조, 그리고 제 딸 아우로라에게,

여러분이 되어 온 여성과 남성, 그리고 여러분이 만들어가고 있는 멋진 세상에 가슴 벅찬 자부심을 느낍니다.

이 책은 지능형 도구를 만드는 방법에 관한 것이지만, 여러분의 세대가 지혜와 연민을 가지고 이 도구들을 이끌어 주리라는 깊은 희망에 바칩니다. 우리가 이 강력한 기술들을 인류에 봉사하고 진보를 돕기 위해 배우고 사용한다면, 미래는 우리 모두에게, 그리고 여러분에게 놀랍도록 밝을 것입니다.

모든 사랑을 담아.

감사의 글

이 책을 출간할 수 있도록 도와주신 많은 개인과 팀에 진심으로 감사를 표하고 싶습니다.

무엇보다도, 구글이 그 미션을 고수하고, 구글러들에게 권한을 부여하며, 혁신할 기회를 존중해 준 것에 대해 감사드립니다.

“실용적인 마법”이라는 그들의 사명을 고수하고 새로운 신흥 기회에 적응하는 능력에 대해, 새로운 분야를 탐구할 기회를 준 CTO실에 감사합니다.

신뢰를 사람들에게 심어주고 항상 탁월한 리더십을 발휘해 준 VP인 윌 그래니스에게 진심으로 감사를 전합니다. 저의 활동 추구를 격려해 주시고 항상 훌륭한 영국식 재치로 훌륭한 지도를 해준 제 매니저인 존 에이블에게도 감사합니다. LLM 코드 작업에 대한 앙투안 라르만자, 에이전트 논의에 대한 한 한 왕, 시계열 통찰력에 대한 잉차오 황에게 감사를 표합니다. 리더십을 발휘해 준 아쉬윈 람, 영감을 주는 작업에 대한 마시 마스카로, 기술 전문성에 대한 제니퍼 베넷, 엔지니어링에 대한 브렛 슬랫킨, 자극적인 토론에 대한 에릭 셴에게 감사를 전합니다. 특히 스콧 펜버시를 비롯한 OCTO 팀에게도 인정을 보냅니다. 마지막으로, 에이전트의 사회적 영향력에 대한 영감을 준 비전의 패트리샤 플로리시에게 깊은 감사를 드립니다.

인간 노동력을 증강하는 에이전트의 도전적이고 동기를 부여하는 비전을 제시해 준 마르코 아르젠티에게 감사를 표합니다. 또한 검색의 세계와 에이전트의 세계 사이의 관계에서 기준을 높여 준 짐 란조니와 조르디 리바스에게도 감사를 전합니다.

클라우드 AI 팀, 특히 리더인 사우라브 티와리에게 원칙적인 진보를 향해 AI 조직을 이끌어 준 것에 대해 빚을 지고 있습니다. 영감을 주는 동료가 되어 준 에어리어 기술 리더인 살렘 살렘 하이칼에게 감사를 표합니다. 구글 AgentSpace의 공동 창립자인 블라디미르 부스코빅, Kaggle 게임 아레나에서 에이전트 협업에 대한 케이트(카타지나) 올셰프스카, 그리고 AI에 많은 것을 기여한 커뮤니티인 Kaggle을 열정적으로 이끌어 준 네이트 키팅에게 감사를 전합니다. 에이전트와 엔터프라이즈 노트북LM(Enterprise NotebookLM)에 중점을 두고 응용 AI 및 ML 팀을 이끌어 준 카멜리아 아리파, 그리고 항상 조언을 해줄 준비가 된 진정한 리더이자 개인적인 친구인 존 월랜드에게도 감사를 표합니다.

뛰어난 AI 엔지니어이자 눈부신 경력을 앞둔 잉차오 황에게 특별한 감사를, 1994년 에이전트에 대한 초기 관심으로 돌아가도록 저에게 도전해 준 한 왕에게 감사를, 그리고 프롬프트 엔지니어링에 대한 놀라운 작업에 대해 리 분스트라에게 감사를 표합니다.

GenAI 5일 팀, 특히 팀에 신뢰를 보여준 VP 앨리슨 웨이그펠드, 항상 결과물을 내놓는 아난트 나와르갈리아, 그리고 할 수 있다는 태도와 리더십을 보여준 페이지 베일리에게도 감사를 전합니다.

저는 2025년 Google I/O에서 세 가지 에이전트를 출시하는 데 도움을 준 마이크 스티어, 투란 불무스, 그리고 칸차나 파톨라에게 진심으로 감사합니다. 여러분의 엄청난 노력에 감사드립니다.

클라우드 및 AI 이니셔티브를 추진하는 데 보여준 토마스 쿠리안의 확고한 리더십, 열정, 그리고 신뢰에 진심으로 감사드립니다. 또한, 구글에서 제가 만난 동료 중 가장 뛰어난 동료였던 이매뉴얼 타로파의 영감을 주는 “할 수 있다”는 태도에 깊이 감사드립니다. 마지막으로, 구글에 대한 저의 열정적인 토론에 참여해 준 피오나 치코니에게도 감사를 표합니다.

제미나이(Gemini), 알파폴드(AlphaFold), 알파고(AlphaGo), 알파유전체(AlphaGenome) 등의 프로젝트 개발에 열정을 쏟은 데미스 허사비스, 푸쉬미트 코흘리, 그리고 GDM 팀 전체, 그리고 사회의 이익을 위한 과학 발전에 기여해 준 것에 대해 감사를 표합니다. 구글 리서치를 이끌고 항상 귀중한 조언을 아끼지 않은 요시 마티아스에게 특별한 감사를 전합니다. 당신에게서 많은 것을 배웠습니다.

90년대에 소프트웨어 에이전트(Software Agents) 개념을 개척하고 메모리, 학습, 의사 결정, 건강, 웰빙과 같은 문제에 사람들을 보강하고 돕는 방법론에 계속 집중하고 있는 패티 메이스에게 특별한 감사를 전합니다. 91년의 당신의 비전이 오늘 현실이 되었습니다.

이 책을 가능하게 해준 스프링거(Springer)의 폴 드루가스 및 모든 출판팀에게도 감사를 전하고 싶습니다.

이 책을 현실로 만드는 데 도움을 준 수많은 재능 있는 사람들에게 깊은 빚을 지고 있습니다. 코딩과 다이어그램부터 전체 텍스트 검토에 이르기까지 엄청난 기여를 해준 마르코 파고에게 진심으로 감사합니다. 코딩 작업을 해준 마흐타브 시예드와 여러 장에 걸친 매우 상세한 피드백을 준 안키타 구하에게도 감사합니다. 이 책은 프리야 삭세나의 통찰력 있는 수정, 재 리의 세심한 검토, 그리고 마리오 다 로자가 NotebookLM 버전을 만드는 데 헌신적인 노력으로 크게 개선되었습니다. 초기 장들에 대한 전문가 검토팀이 있었던 것은 행운이었으며, 암리타 카푸어 박사, 파트마 타를라치 박사, 알레산드로 콘나키아 박사, 그리고 아디티야 만들레카르가 전문 지식을 제공해 준 것에 대해 감사드립니다. 또한 애슐리 밀러, A 아미르 존, 팔락 캄다르(바사니)의 독특한 기여에 대해서도 진심으로 감사드립니다. 변함없는 지원과 격려에 대해 라잣 자인, 알도 파호르, 가우라브 버마, 파비트라 사이나스, 마리우즈 코츠와라, 아비지트 쿠마르, 암스트롱 푼젬, 하이밍 란, 우디타 파텔, 그리고 카우나카르 코타에게 마지막으로 따뜻한 감사를 전합니다.

이 프로젝트는 여러분 없이는 정말 불가능했을 것입니다. 모든 공로는 여러분에게 있으며, 모든 실수는 저에게 있습니다.

모든 저의 인세는 Save the Children에 기부됩니다.

서문

인공지능 분야는 흥미로운 변곡점에 있습니다. 우리는 단순히 정보를 처리하는 모델을 넘어, 모호한 작업으로 복잡한 목표를 달성하기 위해 추론하고, 계획하고, 행동할 수 있는 지능형 시스템을 구축하는 방향으로 나아가고 있습니다. 이 책이 매우 적절하게 설명하듯이, 이러한 “에이전트형” 시스템은 AI의 다음 개척지를 나타내며, 그 개발은 구글에서 우리를 흥분시키고 영감을 주는 도전입니다.

“에이전트 설계 패턴: 지능형 시스템 구축을 위한 실습 가이드”는 이 여정을 안내할 완벽한 시점에 도착했습니다. 이 책은 에이전트의 인지 엔진인 대규모 언어 모델(LLM)의 힘이 구조와 사려 깊은 설계를 통해 활용되어야 한다는 점을 정확히 지적합니다. 설계 패턴이 일반적인 문제에 대한 공통 언어와 재사용 가능한 솔루션을 제공함으로써 소프트웨어 엔지니어링에 혁명을 일으켰듯이, 이 책의 에이전트 패턴들은 강력하고, 확장 가능하며, 신뢰할 수 있는 지능형 시스템을 구축하는 기반이 될 것입니다.

에이전트형 시스템을 구축하기 위한 “캔버스”라는 비유는 구글의 Vertex AI 플랫폼에 대한 우리의 작업과 깊이 공명합니다. 우리는 개발자들에게 차세대 AI 애플리케이션을 구축할 수 있는 가장 강력하고 유연한 캔버스를 제공하기 위해 노력하고 있습니다. 이 책은 개발자들이 그 캔버스를 잠재력껏 활용할 수 있도록 실용적이고 실습적인 지침을 제공합니다. 프롬프트 체이닝과 도구 사용부터 에이전트 간 협업, 자체 수정, 안전 및 가드레일까지 패턴을 탐색함으로써, 이 책은 정교한 AI 에이전트를 구축하려는 모든 개발자에게 포괄적인 도구 키트를 제공합니다.

AI의 미래는 이러한 지능형 시스템을 구축하는 개발자의 창의성과 독창성에 의해 정의될 것입니다. “에이전트 설계 패턴”은 이러한 창의성을 발휘하는 데 없어서는 안 될 자료입니다. 이는 에이전트형 시스템의 “무엇”과 “왜”를 이해하는 데 필요한 필수 지식과 실용적인 예제뿐만 아니라 “방법”까지 제공합니다.

이 책이 개발자 커뮤니티의 손에 들어가는 것을 보게 되어 매우 기쁩니다. 이 페이지에 담긴 패턴과 원칙들은 앞으로 몇 년 동안 우리 세상을 형성할 혁신적이고 영향력 있는 AI 애플리케이션 개발을 의심할 여지 없이 가속화할 것입니다.

Saurabh Tiwary

VP & General Manager, CloudAI @ Google

사상 리더의 관점: 힘과 책임

지난 40년 동안 제가 목격한 모든 기술 주기, 즉 개인용 컴퓨터와 웹의 탄생부터 모바일과 클라우드의 혁명에 이르기까지, 이 주기와 같은 것은 없었습니다. 수년 동안 인공지능에 대한 담론은 흥분과 실망이 뒤섞인 익숙한 리듬, 즉 긴 겨울이 뒤따르는 소위 “AI 여름”이었습니다. 하지만 이번에는 무언가 다릅니다. 대화가 확연히 바뀌었습니다. 지난 18개월이 엔진, 즉 대규모 언어 모델(LLM)의 놀라운, 거의 수직적인 상승에 관한 것이었다면, 다음 시대는 그 주변에 구축하는 자동차에 관한 것이 될 것입니다. 이 원시적인 힘을 활용하여 단순한 그럴듯한 텍스트 생성기가 아닌, 진정한 행동의 대리인으로 변모시키는 프레임워크에 관한 것이 될 것입니다.

솔직히 말해서, 저는 처음에는 회의적이었습니다. 그럴듯함은 종종 그 주제에 대한 자신의 지식과 반비례한다는 것을 알게 되었습니다. 초기 모델들은 유창했지만, 정확성보다는 신뢰성을 최적화하는 일종의 사기 증후군으로 작동하는 것처럼 느껴졌습니다. 하지만 그 후 변곡점이 왔는데, 이는 새로운 등급의 “추론” 모델로 인해 발생한 단계적 변화였습니다. 갑자기 우리는 단순히 다음 단어를 순서대로 예측하는 통계 기계와 대화하는 것이 아니라, 초기 형태의 인지 능력에 엿보는 기회를 얻게 된 것입니다.

새로운 에이전트형 코딩 도구 중 하나를 실험했을 때, 저는 그 친숙한 마법의 불꽃을 느꼈습니다. 저는 그것에게 제가 결코 시간을 내지 못했던 개인적인 프로젝트를 맡겼습니다. 즉, 단순한 웹 빌더에서 제대로 된 현대적인 CI/CD 환경으로 자선 웹사이트를 마이그레이션하는 것이었습니다. 다음 20분 동안 그것은 작업하며, 명확히 하는 질문을 하고, 자격 증명을 요청하고, 상태 업데이트를 제공했습니다. 도구를 사용하는 것보다 마치 주니어 개발자와 협력하는 것처럼 느껴졌습니다. 그것이 저에게 완벽한 문서와 단위 테스트가 포함된 완전히 배포 가능한 패키지를 제시했을 때, 저는 깜짝 놀랐습니다.

물론, 그것은 완벽하지 않았습니다. 실수를 했고, 막히기도 했으며, 경로를 수정하도록 감독하고, 무엇보다도 제 판단이 필요했습니다. 그 경험은 오랜 경력에서 뼈저리게 배운 교훈을 절감하게 했습니다. 바로 맹목적으로 신뢰할 여유가 없다는 것입니다. 그럼에도 불구하고, 그 과정은 흥미로웠습니다. 그것의 “생각의 사슬(chain of thought)“을 엿보는 것은 마치 마음이 작동하는 것을 보는 것과 같았습니다. 지저분하고, 비선형적이며, 시작과 멈춤, 그리고 자체 수정을 포함하고 있었는데, 이는 우리의 인간 추론과 다르지 않았습니다. 그것은 직선이 아니었고, 해결책을 향한 무작위 탐색이었습니다. 여기에 새로운 것의 핵심이 있었습니다. 단순히 콘텐츠를 생성하는 지능이 아니라 계획을 생성할 수 있는 지능이었습니다.

이것이 에이전트형 프레임워크의 약속입니다. 이는 정적인 지하철 노선도와 실시간으로 경로를 재설정하는 동적 GPS의 차이입니다. 고전적인 규칙 기반 자동 기계는 고정된 경로를 따르며, 예상치 못한 장애물에 부딪히면 고장 납니다. 추론 모델로 구동되는 AI 에이전트는 관찰하고, 적응하고, 다른 방법을 찾을 잠재력을 가지고 있습니다. 그것은 현실의 수많은 엣지 케이스를 탐색할 수 있는 일종의 디지털 상식을 가지고 있습니다. 이는 컴퓨터에게 무엇을 해야 할지 말하는 것에서, 무언가가 필요하며 그것을 어떻게 할지는 에이전트에게 맡기고 신뢰하는 것으로의 전환을 나타냅니다.

이 새로운 개척지가 아무리 신나는 일이라도, 특히 글로벌 금융 기관의 CIO로서 볼 때, 막중한 책임감을 수반합니다. 이해관계는 엄청나게 높습니다. 레시피에서 실수를 하는 에이전트는 재미있는 일화가 될 수 있습니다. 거래를 실행하거나, 위험을 관리하거나, 고객 데이터를 처리하는 과정에서 실수를 하는 에이전트는 실제 문제입니다. 저는 면책 조항과 경고를 읽었습니다. 로그인에 실패한 웹 자동화 에이전트가 로그인 장벽에 대해 불평하기 위해 국회의원에게 이메일을 보내기로 결정한 것입니다. 이는 우리가 완전히 이해하지 못하는 기술을 다루고 있다는 것을 익살스럽게 상기시켜 줍니다.

이것이 바로 공예, 문화, 그리고 우리의 원칙에 대한 끊임없는 집중이 우리의 필수적인 지침이 되는 지점입니다. 우리의 엔지니어링 신조(Engineering Tenets)는 단순한 종이 위의 단어가 아니라 우리의 나침반입니다. 우리는 목적을 가지고 구축해야 하며, 설계하는 모든 에이전트가 해결하려는 고객 문제를 명확히 이해하는 것에서 시작하도록 해야 합니다. 우리는 앞을 내다보아야 하며, 실패 모드를 예상하고 탄력적으로 설계해야 합니다. 그리고 무엇보다도, 우리의 방법론에 대해 투명하고 결과에 대해 책임짐으로써 신뢰를 고취해야 합니다.

에이전트형 세계에서 이러한 신조는 새로운 시급성을 띠게 됩니다. 냉정한 사실은, 지저분하고 일관성 없는 시스템에 이러한 강력한 새 도구를 덧씌우고 좋은 결과를 기대할 수 없다는 것입니다. “쓰레기” 데이터로 훈련된 AI는 단순히 쓰레기를 배출하는 것이 아니라, 전체 프로세스를 오염시킬 수 있는 그럴듯하고 확신에 찬 쓰레기를 배출합니다. 따라서, 우리의 첫 번째이자 가장 중요한 과제는 기반을 준비하는 것입니다. 우리는 깨끗한 데이터, 일관된 메타데이터, 잘 정의된 API에 투자해야 합니다. 우리는 이러한 에이전트들이 안전하고 빠르게 작동할 수 있도록 현대적인 “고속도로 시스템”을 구축해야 합니다. 이는 우리의 프로세스가 코드만큼 잘 설계된, 프로그래밍 가능한 “소프트웨어로서의 기업”을 구축하는 어려운 기초 작업입니다.

궁극적으로, 이 여정은 인간의 독창성을 대체하는 것이 아니라 증강하는 것입니다. 그것은 작업 설명을 명확하게 설명하는 능력, 위임할 지혜, 그리고 결과의 품질을 검증할 근면함과 같은 우리 모두에게 새로운 기술을 요구합니다. 그것은 우리가 모른다는 것을 인정하고, 결코 배우는 것을 멈추지 않도록 요구합니다. 이 책에서 이어지는 페이지들은 이러한 새로운 프레임워크를 구축하기 위한 기술적 지도를 제공합니다. 저는 여러분이 이 지침을 단지 가능한 것을 구축하는 데뿐만 아니라, 올바르고, 강력하며, 책임감 있는 것을 구축하는 데 사용하기를 바랍니다.

세상은 모든 엔지니어에게 발돋움하라고 요구하고 있습니다. 저는 우리가 그 도전에 준비되었다고 확신합니다.

여정을 즐기십시오.

Marco Argenti, CIO, Goldman Sachs

서문

“에이전트 설계 패턴: 지능형 시스템 구축을 위한 실습 가이드”에 오신 것을 환영합니다. 현대 인공지능의 풍경을 가로질러 볼 때, 우리는 단순한 반응형 프로그램에서 맥락을 이해하고, 결정을 내리며, 환경 및 다른 시스템과 동적으로 상호 작용할 수 있는 정교한 자율 개체로의 명확한 진화를 봅니다. 이 책에서 매우 적절하게 설명하듯이, 이러한 “에이전트형” 시스템은 지능형 에이전트와 그들이 구성하는 에이전트형 시스템을 나타냅니다.

강력한 대규모 언어 모델(LLM)의 출현은 텍스트 및 미디어와 같은 인간과 유사한 콘텐츠를 이해하고 생성하는 데 전례 없는 기능을 제공했으며, 이는 많은 에이전트의 인지 엔진 역할을 합니다. 그러나 이러한 기능을 복잡한 목표를 안정적으로 달성할 수 있는 시스템으로 조정하려면 강력한 모델 이상의 것이 필요합니다. 이를 위해서는 구조, 설계, 그리고 에이전트가 캔버스에서 어떻게 인지하고, 계획하고, 행동하며, 다양한 시스템과 상호 작용하는지에 대한 사려 깊은 접근 방식이 필요합니다.

지능형 시스템을 구축하는 것을 복잡한 예술 작품이나 엔지니어링 작업을 캔버스 위에 만드는 것이라고 생각해 보세요. 이 캔버스는 에이전트가 존재하고 작동할 수 있는 환경과 도구를 제공하는 근본적인 인프라 및 프레임워크가 아니라 시각적인 공간이 아닙니다. 그것은 선택한 에이전트 아키텍처의 기반 위에서 지능형 애플리케이션을 구축하는 기반입니다.

에이전트형 캔버스 위에서 효과적으로 구축하는 것은 구성 요소를 단순히 쌓는 것 이상을 요구합니다. 이는 재사용 가능한 솔루션, 즉 에이전트 동작을 설계하고 구현하는 데 있어 일반적인 설계 및 구현 문제에 대한 입증된 기술인 **패턴(patterns)**을 이해하는 것을 필요로 합니다. 건축물을 건설할 때 건축 패턴이 지침이 되는 것처럼, 에이전트형 설계 패턴은 선택한 캔버스 위에서 정교한 에이전트를 생명을 불어넣을 때 직면하게 될 일반적인 문제에 대한 재사용 가능한 솔루션을 제공합니다.

이 책은 다양한 기술 캔버스에서 정교한 에이전트를 구성하는 기본 빌딩 블록 및 기술을 나타내는 21가지 핵심 설계 패턴을 추출합니다.

이러한 패턴을 이해하고 적용하는 것은 선택한 개발 캔버스에서 지능형 시스템을 효과적으로 설계하고 구현하는 능력을 크게 향상시킬 것입니다.

에이전트형 시스템이란 무엇인가?

본질적으로 에이전트형 시스템은 환경(디지털 및 잠재적으로 물리적 환경 모두)을 인지하고, 이러한 인식을 바탕으로 정보에 입각한 결정을 내리며, 목표를 달성하기 위해 자율적으로 행동을 실행하도록 설계된 계산 개체입니다. 고정된 단계별 지침을 따르는 기존 소프트웨어와 달리, 에이전트는 어느 정도의 유연성과 주도성을 보입니다.

고객 문의 관리를 위한 시스템이 필요하다고 상상해 보세요. 기존 시스템은 고정된 스크립트를 따를 수 있습니다. 하지만 에이전트형 시스템은 고객 문의의 뉘앙스를 인지하고, 지식 기반에 접근하며, 주문 관리와 같은 다른 내부 시스템과 상호 작용하고, 잠재적으로 명확히 하는 질문을 하고, 선제적으로 문제를 해결하며, 심지어 미래의 필요까지 예측할 수 있습니다. 이 에이전트는 애플리케이션 인프라의 캔버스 위에서 작동하며, 사용 가능한 서비스와 데이터를 활용합니다.

에이전트형 시스템은 종종 자율성(autonomy), 선제성(proactiveness), **반응성(reactiveness)**과 같은 기능으로 특징 지어집니다. 그들은 본질적으로 **목표 지향적(goal-oriented)**입니다. 비판적인 역량은 외부 API, 데이터베이스 또는 서비스와 상호 작용할 수 있는 도구 사용(tool use) 능력으로, 효과적으로 자신의 캔버스 너머에 도달할 수 있습니다. 그들은 **기억(memory)**을 가지고 상호 작용 전반에 걸쳐 정보를 유지하며, **통신(communication)**에 참여하여 사용자와 다른 시스템 또는 동일하거나 연결된 캔버스에서 작동하는 다른 에이전트와 소통합니다.

이러한 특성들을 효과적으로 실현하는 것은 상당한 복잡성을 수반합니다. 에이전트는 여러 단계에 걸쳐 상태를 어떻게 유지할까요? 언제, 어떻게 도구를 사용해야 할지 어떻게 결정할까요? 다른 에이전트 간의 통신은 어떻게 관리될까요? 예상치 못한 결과나 오류에 대처하기 위해 시스템에 복원력을 어떻게 구축할 수 있을까요?

에이전트 개발에서 패턴이 중요한 이유

이러한 복잡성이 바로 에이전트형 설계 패턴이 필수적인 이유입니다. 이것들은 엄격한 규칙이 아니라 에이전트 도메인에서 표준적인 설계 및 구현 문제에 대한 입증된 접근 방식 또는 청사진입니다. 설계 패턴을 인식하고 적용함으로써, 여러분은 캔버스 위에서 지능형 에이전트를 구축할 때 근본적인 솔루션을 재발명하는 수고를 덜 수 있습니다.

설계 패턴을 사용하면 대화 흐름 통합, 외부 기능 통합, 또는 여러 에이전트 작업 조정과 같은 작업에 대한 기본 솔루션을 재발명하는 수고를 덜 수 있습니다. 이는 에이전트의 논리를 더 명확하게 만들고 이해하고 유지 관리하기 쉽게 만드는 공통 언어와 구조를 제공합니다. 오류 처리 또는 상태 관리를 위해 설계된 패턴을 구현하는 것은 더 강력하고 신뢰할 수 있는 시스템을 구축하는 데 직접적으로 기여합니다. 이러한 확립된 접근 방식을 활용하면 에이전트 동작의 기본 메커니즘이 아닌 애플리케이션의 고유한 측면에 집중할 수 있어 개발 프로세스가 가속화됩니다.

이 책은 정교한 에이전트를 구성하기 위한 기본 빌딩 블록과 기술을 나타내는 21가지 핵심 설계 패턴을 추출합니다.

이러한 패턴을 이해하고 적용하는 것은 선택한 개발 캔버스에서 지능형 시스템을 효과적으로 설계하고 구현하는 능력을 크게 향상시킬 것입니다. 이 실습 여정을 시작해 봅시다!

책의 개요 및 활용 방법

이 책, “에이전트 설계 패턴: 지능형 시스템 구축을 위한 실습 가이드”는 실용적이고 접근 가능한 자료로 제작되었습니다. 주된 초점은 각 에이전트 패턴을 명확하게 설명하고, 이를 구현하기 위한 구체적이고 실행 가능한 코드 예제를 제공하는 데 있습니다. 21개의 전용 장에 걸쳐, 우리는 초기 개념인 프롬프트 체이닝(Prompt Chaining) 및 도구 사용(Tool Use)부터 더 고급 주제인 에이전트 간 협업(Multi-Agent Collaboration) 및 자체 수정(Self-Correction)에 이르기까지 다양한 설계 패턴을 탐구할 것입니다.

이 책은 장 별로 구성되어 있으며, 각 장은 단일 에이전트 패턴을 자세히 다룹니다. 각 장 내에서 다음을 찾을 수 있습니다.

  • 상세한 패턴 개요는 패턴과 에이전트 설계에서의 역할에 대한 명확한 설명을 제공합니다.
  • 실용적인 응용 및 사용 사례 섹션은 패턴이 귀중한 실제 시나리오와 그것이 가져다주는 이점을 보여줍니다.
  • 실습 코드 예제는 주요 에이전트 개발 프레임워크를 사용하여 패턴의 구현을 보여주는 실용적이고 실행 가능한 코드를 제공합니다. 여기에서 기술 캔버스(technical canvas)의 맥락에서 패턴을 적용하는 방법을 볼 수 있습니다.
  • **주요 요점(Key Takeaways)**은 빠른 검토를 위해 가장 중요한 요점을 요약합니다.
  • **참고 자료(References)**는 패턴 및 관련 개념에 대한 심층 학습을 위한 리소스를 제공합니다.

챕터는 개념을 점진적으로 구축하도록 순서가 지정되어 있지만, 자체 에이전트 개발 프로젝트에서 직면한 특정 문제에 답하는 참조로 장을 자유롭게 사용할 수 있습니다. 부록은 고급 프롬프팅 기술, 실제 환경에서 AI 에이전트를 적용하기 위한 원칙, 필수 에이전트 프레임워크 개요에 대한 포괄적인 정보를 제공합니다. 이와 보완적으로, 실용적인 온라인 전용 자습서는 AgentSpace 및 명령줄 인터페이스와 같은 특정 플랫폼으로 에이전트를 구축하는 방법에 대한 단계별 지침을 제공합니다. 전반적인 강조점은 실용적인 응용에 있으며, 우리는 코드 예제를 실행하고, 실험하고, 이를 조정하여 선택한 캔버스 위에서 자신만의 지능형 시스템을 구축하는 것을 강력히 권장합니다.

AI가 빠르게 변화하는 세상에서 왜 금방 시대에 뒤떨어질 수 있는 책을 쓰느냐는 질문을 자주 받습니다. 제 동기는 정반대였습니다. 상황이 그만큼 빠르게 움직이고 있기 때문에 우리가 기반을 공고히 하고 있는 근본적인 원칙을 파악해야 할 필요가 있습니다. RAG, 반성, 라우팅, 메모리와 같이 제가 논의하는 다른 패턴들은 기본적인 구성 요소가 되고 있습니다. 이 책은 이러한 핵심 아이디어에 대해 숙고하도록 초대하며, 이는 우리가 구축해야 할 기반을 제공합니다. 인간은 기초 패턴에 대한 이러한 반성의 순간이 필요합니다.

사용된 프레임워크 소개

실제적인 “캔버스”를 위한 코드 예제(부록 참조)를 제공하기 위해, 우리는 주로 세 가지 주요 에이전트 개발 프레임워크를 활용할 것입니다. LangChain과 그 상태 저장 확장인 LangGraph는 언어 모델과 다른 구성 요소를 연결할 수 있는 유연한 방법을 제공하여 복잡한 시퀀스 및 작업 그래프를 구축하기 위한 강력한 캔버스를 제공합니다. Crew AI는 여러 AI 에이전트, 역할 및 작업을 조정하도록 특별히 설계된 구조화된 프레임워크를 제공하며, 특히 협업 에이전트 시스템에 적합한 캔버스 역할을 합니다. **Google 에이전트 개발 키트(Google ADK)**는 에이전트를 구축, 평가 및 배포하기 위한 도구와 구성 요소를 제공하여 에이전트 시스템을 구축할 수 있는 또 다른 귀중한 캔버스를 제공합니다.

이러한 프레임워크는 에이전트 개발 캔버스의 다양한 측면을 나타내며, 각각 강점을 가지고 있습니다. 이러한 도구 전반에 걸쳐 예제를 보여줌으로써, 여러분은 선택한 기술 환경에 관계없이 패턴이 어떻게 적용될 수 있는지에 대한 더 넓은 이해를 얻게 될 것입니다. 예제는 패턴의 핵심 논리와 프레임워크 캔버스에 대한 구현을 명확하게 설명하도록 설계되었으며, 명확성과 실용성에 중점을 둡니다.

이 책을 마치면, 21가지 필수 에이전트 패턴의 기본 개념을 이해할 뿐만 아니라, 이를 효과적으로 적용할 수 있는 실용적인 지식과 코드 예제를 갖추게 될 것이며, 선택한 개발 캔버스에서 더 지능적이고 능숙하며 자율적인 시스템을 구축할 수 있게 될 것입니다. 실습 여정을 시작합시다!

AI를 에이전트로 만드는 것은 무엇인가?

간단히 말해, AI 에이전트는 환경을 인지하고 특정 목표를 달성하기 위해 행동을 취하도록 설계된 시스템입니다. 이는 계획을 세우고, 도구를 사용하며, 주변 환경과 상호 작용하는 능력이 향상된 대규모 언어 모델(LLM)에서 진화한 형태입니다. 에이전트형 AI를 작업 수행을 위해 현장에서 학습하는 스마트한 도우미라고 생각할 수 있습니다. 작업을 완료하기 위해 간단하고 5단계의 루프를 따릅니다(그림 1 참조).

    1. 임무 수신: “내 일정 정리해 줘”와 같이 목표를 제시합니다.
    1. 상황 파악: 이메일을 읽고, 달력을 확인하고, 연락처에 접근하여 목표 달성을 위해 필요한 모든 정보를 수집합니다.
    1. 고려: 목표 달성을 위한 최적의 접근 방식을 고려하여 실행 계획을 세웁니다.
    1. 조치: 초대장을 보내고, 회의를 예약하고, 달력을 업데이트하여 계획을 실행합니다.
    1. 학습 및 개선: 성공적인 결과를 관찰하고 그에 따라 조정합니다. 예를 들어, 회의가 재 조정되면 시스템은 이 이벤트를 통해 미래의 성능을 향상 시키는 방법을 학습합니다.

그림 1: 에이전트형 AI는 지능형 도우미로 기능하며, 경험을 통해 지속적으로 학습합니다. 작업을 완료하기 위해 간단한 5단계 루프를 통해 작동합니다.

에이전트는 놀라운 속도로 인기를 얻고 있습니다. 최근 연구에 따르면 대규모 IT 기업의 대다수가 이러한 에이전트를 적극적으로 사용하고 있으며, 그중 5분의 1은 지난 1년 이내에 시작했습니다. 금융 시장도 주목하고 있습니다. 2024년 말까지 AI 에이전트 스타트업들은 20억 달러 이상을 모금했으며, 시장 가치는 52억 달러로 평가되었습니다. 2034년까지 거의 2,000억 달러 규모로 폭발적으로 증가할 것으로 예상됩니다. 요컨대, 모든 징후는 AI 에이전트가 우리 미래 경제에서 거대한 역할을 할 것임을 가리킵니다.

단 2년 만에 AI 패러다임은 단순 자동화에서 정교한 자율 시스템으로 극적으로 전환되었습니다(그림 2 참조). 초기에 워크플로는 LLM을 사용하여 데이터를 처리하기 위해 기본 프롬프트와 트리거에 의존했습니다. 이는 사실 정보에 모델을 근거로 하여 신뢰성을 향상 시킨 검색 증강 생성(RAG)으로 발전했습니다. 그런 다음 우리는 다양한 도구를 사용할 수 있는 개별 AI 에이전트의 개발을 보았습니다. 오늘날 우리는 AI의 협업 능력이 상당한 도약을 이룬, 복잡한 목표 달성을 위해 공조하여 작동하는 전문 에이전트 팀의 시대에 진입하고 있습니다.

그림 2: LLM에서 RAG, 에이전트형 RAG로, 그리고 최종적으로 에이전트형 AI로의 전환.

이 책의 의도는 전문화된 에이전트가 어떻게 공조하고 협력하여 복잡한 목표를 달성할 수 있는 지에 대한 설계 패턴을 논의하는 것이며, 각 장에서 협업 및 상호 작용의 한 가지 패러다임을 볼 수 있을 것입니다.

그렇게 하기 전에, 에이전트 복잡성의 범위를 포괄하는 예제를 살펴보겠습니다(그림 3 참조).

레벨 0: 핵심 추론 엔진

LLM 자체는 에이전트가 아니지만, 기본 에이전트 시스템의 추론 코어로 사용될 수 있습니다. ‘레벨 0’ 구성에서는 LLM이 도구, 메모리 또는 환경 상호 작용 없이 작동하며, 사전 훈련된 지식에만 의존하여 응답합니다. 강점은 광범위한 훈련 데이터를 활용하여 확립된 개념을 설명하는 데 있습니다. 이 강력한 내부 추론에 대한 대가는 현재 이벤트에 대한 인식 부족입니다. 예를 들어, 2025년 “최우수 작품상” 수상자를 이름으로 알려달라는 요청에 대해서는 사전 훈련된 지식 밖에 있는 정보이므로 대답할 수 없습니다.

레벨 1: 연결된 문제 해결사 : RAG, DB, 검색 , API 호출

이 수준에서 LLM은 외부 도구에 연결하고 활용함으로써 기능적인 에이전트가 됩니다. 문제 해결은 더 이상 사전 훈련된 지식에만 국한되지 않습니다. 대신, 검색(검색을 통해) 또는 검색 증강 생성(RAG)을 통해 데이터베이스와 같은 소스에서 정보를 수집하고 처리하기 위해 일련의 조치를 실행할 수 있습니다. 자세한 정보는 14장을 참조하십시오.

예를 들어, 새로운 TV 프로그램을 찾기 위해 에이전트는 현재 정보가 필요함을 인식하고, 검색 도구를 사용하여 정보를 찾은 다음, 결과를 종합합니다. 중요하게도, 더 높은 정확도를 위해 전문화된 도구를 사용할 수도 있습니다. 예를 들어 금융 API를 호출하여 AAPL의 실시간 주가를 가져오는 것과 같습니다. 이 여러 단계를 가로질러 외부 세계와 상호 작용하는 능력은 레벨 1 에이전트의 핵심 기능입니다.

레벨 2: 전략적 문제 해결사 : **컨텍스트 엔지니어링

이 수준에서 에이전트의 기능은 전략적 계획, 선제적 지원 및 자체 개선(프롬프트 엔지니어링 및 컨텍스트 엔지니어링이 핵심 지원 기술임)을 포함하도록 크게 확장됩니다.

첫째, 에이전트는 단일 도구 사용을 넘어 전략적 문제 해결을 통해 복잡하고 다 단계적인 문제를 해결합니다. 일련의 조치를 실행하면서, 관련된 가장 관련성 높은 정보를 선택, 패키징 및 관리하는 컨텍스트 엔지니어링(context engineering) 을 적극적으로 수행합니다. 예를 들어, 두 위치 사이의 커피숍을 찾으려면 먼저 매핑 도구를 사용합니다. 그런 다음 이 출력을 엔지니어링하여, 예를 들어 거리 이름 목록만,

두 번째 단계가 효율적이고 정확하도록 초점을 맞춘 짧은 컨텍스트를 큐레이션하여 로컬 검색 도구에 공급합니다. AI로부터 최대 정확도를 얻으려면, 짧고, 초점이 맞춰져 있으며, 강력한 컨텍스트가 주어져야 합니다. 컨텍스트 엔지니어링은 사용 가능한 모든 소스에서 가장 중요한 정보를 전략적으로 선택, 패키징 및 관리하여 이를 수행하는 분야입니다. 이는 모델의 제한된 주의력을 큐레이션하여 과부하를 방지하고 모든 작업에서 고품질의 효율적인 성능을 보장합니다. 자세한 정보는 부록 A를 참조하십시오. (큐레이션 : 다양한 정보나 콘텐츠를 전문가나 개인의 취향에 따라 선별, 분류, 구성하여 사용자에게 가치 있게 제공하는 행위) 이 수준은 선제적이고 지속적인 운영으로 이어집니다. 이메일에 연결된 여행 도우미는 상세한 항공편 확인 이메일에서 컨텍스트를 엔지니어링하여, 핵심 세부 정보(항공편 번호, 날짜, 위치)만 선택하여 캘린더 및 날씨 API에 대한 후속 도구 호출을 위해 패키징함으로써 이를 시연합니다.

소프트웨어 엔지니어링과 같은 전문 분야에서 에이전트는 이 훈련을 적용하여 전체 워크플로우를 관리합니다. 버그 보고서가 할당되면 보고서를 읽고 코드베이스에 액세스한 다음, 이 대용량의 정보를 강력하고 초점이 맞춰진 컨텍스트로 전략적으로 엔지니어링하여 정확한 코드 패치를 효율적으로 작성, 테스트 및 제출할 수 있도록 합니다.

마지막으로, 에이전트는 자체 컨텍스트 엔지니어링 프로세스를 개선하여 자체 개선을 달성합니다. 프롬프트가 개선될 수 있는 방법에 대한 피드백을 요청하면, 미래 작업에 대한 정보 패키징을 더 잘 큐레이션하는 방법을 학습하는 것입니다. 이를 통해 정확성과 효율성을 높이는 강력한 자동화된 피드백 루프가 생성됩니다. 자세한 정보는 17장을 참조하십시오.

그림 3: 복잡성 스펙트럼을 보여주는 다양한 인스턴스.

레벨 3: 협업 다중 에이전트 시스템의 부상

레벨 3에서는 AI 개발의 중요한 패러다임 전환이 일어나며, 단일의 모든 것을 갖춘 슈퍼 에이전트를 추구하는 것에서 정교하고 협력적인 다중 에이전트 시스템의 부상으로 이동합니다. 본질적으로 이 접근 방식은 복잡한 문제는 단일 일반 전문가보다는 전문화된 에이전트 팀이 함께 해결할 때 가장 잘 해결된다는 인식을 바탕으로 합니다. 이 모델은 인간 조직의 구조를 직접적으로 반영하며, 서로 다른 부서가 특정 역할을 할당받고 다면적 목표 달성을 위해 협력합니다. 이러한 시스템의 집단적 강점은 노동의 분할과 조정된 노력을 통해 창출되는 시너지 효과에 있습니다. 자세한 정보는 7장을 참조하십시오.

이 개념을 구체화하기 위해 신제품 출시의 복잡한 워크플로우를 고려해 봅시다. 단일 에이전트가 모든 측면을 처리하려고 시도하는 대신, “프로젝트 관리자” 에이전트가 중앙 조정자 역할을 할 수 있습니다. 이 관리자는 소비자 데이터를 수집하기 위한 “시장 조사” 에이전트, 콘셉트를 개발하기 위한 “제품 설계” 에이전트, 홍보 자료를 만들기 위한 “마케팅” 에이전트와 같은 다른 전문화된 에이전트에게 작업을 위임하여 전체 프로세스를 조정합니다. 그들의 성공의 열쇠는 그들 사이의 원활한 통신과 정보 공유이며, 모든 개별 노력이 집단적 목표 달성을 위해 정렬되도록 합니다.

자율적이고 팀 기반의 자동화라는 이러한 비전이 이미 개발되고 있지만, 현재 시스템의 효과가 사용 중인 LLM의 추론 한계로 인해 제약을 받고 있다는 점을 인정하는 것이 중요합니다. 또한, 그들로부터 진정으로 배우고 응집력 있는 단위로 개선할 수 있는 능력은 아직 초기 단계입니다. 이러한 기술적 병목 현상을 극복하는 것이 중요한 다음 단계이며, 그렇게 하는 것은 이 수준의 심오한 약속, 즉 시작부터 끝까지 전체 비즈니스 워크플로우를 자동화할 수 있는 능력을 열어줄 것입니다.

에이전트의 미래: 5가지 주요 가설

AI 에이전트 개발은 소프트웨어 자동화, 과학 연구, 고객 서비스 등 여러 분야에서 전례 없는 속도로 진행되고 있습니다. 현재 시스템이 인상적이지만, 이는 시작에 불과합니다. 다음 혁신 물결은 에이전트를 보다 신뢰성 있고, 협력적이며, 우리 삶에 깊이 통합하는 데 초점을 맞출 가능성이 높습니다. 다음은 AI 에이전트의 미래에 대한 5가지 주요 가설입니다(그림 4 참조).

가설 1: 일반 에이전트의 출현

첫 번째 가설은 AI 에이전트가 좁은 전문가에서 벗어나 복잡하고 모호하며 장기적인 목표를 높은 신뢰성으로 관리할 수 있는 진정한 일반 전문가로 진화할 것이라는 것입니다. 예를 들어, 에이전트에게 “다음 분기에 리스본에서 30명 규모의 회사 오프사이트를 계획하세요”와 같은 간단한 프롬프트를 줄 수 있습니다. 그런 다음 에이전트는 예산 승인 및 항공편 협상부터 장소 선정 및 직원 피드백을 기반으로 상세한 일정표 생성에 이르기까지 몇 주 동안 전체 프로젝트를 관리하며, 모든 과정에서 정기적인 업데이트를 제공할 것입니다. 이러한 수준의 자율성을 달성하려면 AI 추론, 메모리 및 거의 완벽한 신뢰성에 대한 근본적인 돌파구가 필요합니다. 대규모 일반 에이전트 모델의 개발과 소규모 전문 에이전트의 구성이라는 대안적이지만 상호 배타적이지 않은 접근 방식이 있습니다. 이 “레고와 같은” 개념은 단일 거대 모델을 확장하는 대신 작은 전문 에이전트를 구성하여 시스템을 구성하는 것입니다. 이 방식은 디버깅 및 배포가 더 쉽고 저렴한 시스템을 약속합니다. 궁극적으로, 대규모 일반 모델의 개발과 소규모 전문 모델 구성은 모두 그럴듯한 경로이며, 서로 보완할 수도 있습니다.

가설 2: 깊은 개인화 및 선제적 목표 발견

두 번째 가설은 에이전트가 깊이 개인화되고 선제적인 파트너가 될 것이라는 것입니다. 우리는 새로운 종류의 에이전트, 즉 선제적 파트너의 출현을 목격하고 있습니다. 사용자의 고유한 패턴과 목표로부터 학습함으로써 이러한 시스템은 단순히 명령을 따르는 것에서 벗어나 필요를 예측하기 시작하고 있습니다. AI 시스템은 에이전트가 채팅이나 지침에 단순히 응답하는 것을 넘어설 때 에이전트로 작동합니다. 그들은 사용자를 대신하여 작업을 시작하고 실행하며, 그 과정에서 적극적으로 협력합니다. 이는 단순한 작업 실행을 넘어 선제적인 목표 발견(proactive goal discovery) 의 영역으로 나아갑니다.

예를 들어, 지속 가능한 에너지에 대해 탐구하고 있다면, 에이전트는 잠재적인 목표를 식별하고 강한 확신을 가지고 조치를 취할 때 선제적으로 지원할 수 있습니다. 이러한 시스템은 아직 개발 중이지만 그 궤적은 분명합니다. 궁극적으로 에이전트는 아직 완전히 설명하지 않은 야망을 달성하는 데 도움을 주는 필수적인 동맹이 될 것입니다.

그림 4: 에이전트의 미래에 대한 다섯 가지 가설

가설 3: 체화 및 물리적 세계 상호 작용

이 가설은 에이전트가 순수하게 디지털 환경에서 벗어나 실제 세계에서 작동하게 될 것이라고 예측합니다. 에이전트형 AI를 로봇 공학과 통합함으로써 “체화된 에이전트(embodied agents)“의 등장을 보게 될 것입니다. 집 수리공을 예약하는 대신, 집 에이전트에게 새는 수도꼭지를 고치라고 요청할 수 있습니다. 에이전트는 비전 센서를 사용하여 문제를 인지하고, 배관 지식 라이브러리에 액세스하여 계획을 세우고, 정밀하게 로봇 조작기를 제어하여 수리를 수행할 것입니다. 이는 디지털 지능과 물리적 행동 사이의 격차를 해소하는 기념비적인 단계가 될 것이며, 제조 및 물류부터 노인 간병 및 가정 유지 보수에 이르기까지 모든 것을 변화시킬 것입니다.

가설 4: 에이전트 주도 경제

네 번째 가설은 고도로 자율적인 에이전트가 경제의 적극적인 참여자가 되어 새로운 시장과 비즈니스 모델을 창출할 것이라는 것입니다. 이익 극대화를 위해 구체적인 결과를 최대화하도록 임무를 맡은 독립적인 경제 주체로 활동하는 에이전트를 볼 수 있습니다. 기업가는 전체 전자상거래 비즈니스를 운영하기 위해 에이전트를 출시할 수 있습니다. 에이전트는 소셜 미디어를 분석하여 유행하는 제품을 식별하고, 마케팅 문구와 시각 자료를 생성하고, 다른 자동화 시스템과 상호 작용하여 공급망 물류를 관리하고, 실시간 수요에 따라 가격을 동적으로 조정할 것입니다. 이 변화는 인간이 직접 관리하기에는 불가능한 속도와 규모로 운영되는 새롭고 초효율적인 “에이전트 경제”를 창출할 것입니다.

가설 5: 목표 지향적, 변성적 다중 에이전트 시스템

이 가설은 명시적인 프로그래밍이 아닌 선언된 목표에서 작동하는 지능형 시스템의 출현을 가정합니다. 사용자는 원하는 결과를 간단히 선언하고, 시스템은 그것을 달성하는 방법을 자율적으로 파악합니다. 이는 개별 수준과 집단 수준 모두에서 진정한 자체 개선 능력을 갖춘 변성적(metamorphic) 다중 에이전트 시스템으로의 근본적인 변화를 나타냅니다.

이 시스템은 단일 에이전트가 아닌 동적인 개체가 될 것입니다. 이는 자체 성능을 분석하고 현재 작업에 가장 효과적인 팀을 구성하기 위해 다중 에이전트 인력을 동적으로 생성, 복제 또는 제거할 수 있는 능력을 가질 것입니다. 이러한 진화는 여러 수준에서 일어납니다.

  • 아키텍처 수정: 가장 깊은 수준에서 개별 에이전트는 더 높은 효율성을 위해 자체 소스 코드를 다시 작성하고 내부 구조를 재설계할 수 있으며, 이는 원래 가설에서와 같습니다.
  • 지침 수정: 더 높은 수준에서 시스템은 자동 프롬프트 엔지니어링 및 컨텍스트 엔지니어링을 지속적으로 수행합니다. 이는 인간의 개입 없이 최적의 지침을 가지고 작동하도록 각 에이전트에게 주어진 지침과 정보를 개선합니다.

예를 들어, 기업가는 단순히 의도(“성공적인 수제 커피 전자상거래 비즈니스를 시작하세요”)를 선언할 것입니다. 이 시스템은 추가 프로그래밍 없이 즉시 조치에 나설 것입니다. 처음에 “시장 조사” 에이전트와 “브랜딩” 에이전트를 생성할 수 있습니다. 초기 조사 결과를 바탕으로 브랜딩 에이전트를 제거하고 “로고 디자인” 에이전트, “웹 스토어 플랫폼” 에이전트, “공급망” 에이전트라는 세 가지 새로운 전문 에이전트를 생성할 수 있습니다. 또한 성능을 높이기 위해 각 에이전트의 내부 프롬프트를 지속적으로 조정할 것입니다. 웹 스토어 에이전트가 병목 현상이 되면, 시스템은 이를 세 개의 병렬 에이전트로 복제하여 사이트의 다른 부분을 처리하도록 할 수 있으며, 이는 플라이(on the fly)에서 자체 구조를 재설계하여 선언된 목표를 가장 잘 달성하도록 합니다.

결론

본질적으로 AI 에이전트는 자율 시스템으로 기능하며, 특정 목표 달성을 위해 인지하고, 계획하고, 행동하는 것은 전통적인 모델보다 중요한 도약입니다. 이 기술의 진화는 단일 도구 사용 에이전트에서 복잡한 다면적 목표를 해결하는 정교한 협력 다중 에이전트 시스템으로 발전하고 있습니다. 미래 가설은 일반, 개인화, 심지어 체화된 에이전트의 출현을 예측하며, 이들은 경제의 적극적인 참여자가 될 것입니다. 이러한 지속적인 개발은 전체 워크플로우를 자동화하고 기술과의 관계를 근본적으로 재정의할 준비가 된 자체 개선, 목표 지향적 시스템을 향한 주요 패러다임 전환을 나타냅니다.

참고 자료


1장: 프롬프트 체이닝

프롬프트 체이닝 패턴 개요

프롬프트 체이닝은 파이프라인 패턴이라고도 불리며, 대규모 언어 모델(LLM)을 활용하여 복잡한 작업을 처리할 때 강력한 패러다임을 나타냅니다. LLM이 단일하고 거대한 단계로 복잡한 문제를 해결하도록 기대하는 대신, 프롬프트 체이닝은 분할 정복 전략을 권장합니다. 핵심 아이디어는 원래의 어려운 작업을 더 작고 관리하기 쉬운 하위 문제들의 시퀀스로 나누는 것입니다. 각 하위 문제는 특별히 설계된 프롬프트를 통해 개별적으로 처리되며, 한 프롬프트에서 생성된 출력은 체인의 다음 프롬프트에 대한 입력으로 전략적으로 제공됩니다.

이러한 순차적 처리 기술은 LLM과의 상호 작용에 모듈성(modularity)과 명확성(clarity)을 자연스럽게 도입합니다. 복잡한 작업을 분해함으로써, 전체 프로세스를 이해하고 디버깅하기가 더 쉬워지므로, 전반적인 프로세스는 더욱 강력하고 해석 가능해집니다. 체인의 각 단계는 더 큰 문제의 특정 측면에 초점을 맞추도록 세심하게 구성하고 최적화될 수 있으며, 이는 더 정확하고 집중된 출력을 초래합니다.

하나의 단계 출력이 다음 단계의 입력으로 작용하는 것은 중요합니다. 이러한 정보 전달은 의존성 체인을 형성하며, 따라서 이름이 붙여진 것처럼, 이전 작업의 컨텍스트와 결과가 후속 처리를 안내합니다. 이를 통해 LLM은 이전 작업의 성과를 기반으로 이해도를 높이고 원하는 해결책에 점진적으로 가까워질 수 있습니다.

더 나아가, 프롬프트 체이닝은 외부 지식과 도구 통합을 가능하게 합니다. 각 단계에서 LLM은 외부 시스템, API 또는 데이터베이스와 상호 작용하여 내부 훈련 데이터 범위를 넘어 지식과 능력을 풍부하게 하도록 지시받을 수 있습니다. 이 기능은 LLM의 잠재력을 크게 확장하여, 고립된 모델이 아니라 더 광범위하고 지능적인 시스템의 필수 구성 요소로 기능할 수 있도록 합니다.

프롬프트 체이닝의 중요성은 단순한 문제 해결을 넘어섭니다. 이는 정교한 AI 에이전트를 구축하기 위한 기초 기술입니다. 이러한 에이전트는 프롬프트 체인을 활용하여 동적 환경에서 자율적으로 계획하고, 추론하며, 행동할 수 있습니다. 프롬프트 시퀀스를 전략적으로 구성함으로써, 에이전트는 다단계 추론, 계획 및 의사 결정이 필요한 작업을 수행할 수 있습니다. 이러한 에이전트 워크플로우는 인간의 사고 과정을 더 가깝게 모방하여 복잡한 도메인 및 시스템과 더 자연스럽고 효과적인 상호 작용을 가능하게 합니다.

단일 프롬프트의 한계: LLM을 활용한 다면적인 작업의 경우, 단일하고 복잡한 프롬프트를 사용하는 것은 비효율적일 수 있으며, 모델이 제약 조건과 지침을 처리하는 데 어려움을 겪어, 지침 무시(instruction neglect)로 인해 프롬프트의 일부가 간과되거나, 컨텍스트 드리프트(contextual drift)로 인해 모델이 초기 컨텍스트를 추적하지 못하거나, 오류 전파(error propagation)로 인해 초기 오류가 증폭되거나, 모델이 응답하기에 불충분한 정보를 얻는 더 긴 컨텍스트 창이 필요하거나, 인지 부하 증가로 인해 부정확한 정보의 가능성이 높아지는 환각(hallucination)을 초래할 수 있습니다. 예를 들어, 시장 조사 보고서를 분석하고, 주요 사항을 요약하고, 데이터 포인트와 함께 추세를 식별한 다음, 이메일을 초안 작성하도록 요청하는 쿼리는 모델이 요약을 잘해도 데이터 추출이나 이메일 작성을 제대로 처리하지 못할 수 있어 실패할 위험이 있습니다.

순차적 분해를 통한 신뢰성 향상: 프롬프트 체이닝은 복잡한 작업을 집중된 순차적 워크플로우로 분해하여 신뢰성과 제어력을 크게 향상시킴으로써 이러한 문제에 대처합니다. 위 예시의 경우, 파이프라인 또는 체인 접근 방식은 다음과 같이 설명될 수 있습니다.

    1. 초기 프롬프트 (요약): “다음 시장 조사 보고서의 핵심 내용은 다음과 같습니다: [텍스트].” 모델의 유일한 초점은 요약이며, 이 초기 단계의 정확성을 높입니다.
    1. 두 번째 프롬프트 (추세 식별): “이 요약을 사용하여 상위 세 가지 신흥 추세를 식별하고 각 추세를 뒷받침하는 특정 데이터 포인트를 추출하십시오: [1단계의 출력].” 이 프롬프트는 이제 더 제한적이며 검증된 출력을 직접 기반으로 합니다.
    1. 세 번째 프롬프트 (이메일 작성): “다음 추세와 지원 데이터를 설명하는 마케팅 팀에 대한 간결한 이메일을 초안 작성하십시오: [2단계의 출력].”

이러한 분해를 통해 프로세스에 대한 보다 세분화된 제어가 가능해집니다. 각 단계는 덜 모호하고 간단하므로 모델의 인지 부하가 줄어들어 보다 정확하고 신뢰할 수 있는 최종 출력을 얻게 됩니다. 이러한 모듈성은 특정 작업을 수행한 다음 결과를 다음 함수에 전달하는 계산 파이프라인과 유사합니다. 각 특정 작업에 대한 정확한 응답을 보장하기 위해 모델은 각 단계에서 고유한 역할을 할당받을 수 있습니다. 예를 들어, 주어진 시나리오에서 초기 프롬프트는 “시장 분석가”로, 후속 프롬프트는 “거래 분석가”로, 세 번째 프롬프트는 “전문 문서 작성자”로 지정될 수 있습니다.

구조화된 출력의 역할: 프롬프트 체인의 신뢰성은 단계 간에 전달되는 데이터의 무결성에 크게 좌우됩니다. 한 프롬프트의 출력이 모호하거나 형식이 잘못된 경우, 후속 프롬프트는 잘못된 입력으로 인해 실패할 수 있습니다. 이를 완화하기 위해 JSON 또는 XML과 같은 구조화된 출력 형식을 지정하는 것이 중요합니다.

예를 들어, 추세 식별 단계의 출력은 다음과 같이 JSON 개체 형식으로 지정될 수 있습니다.

{
 "trends": [
   {
     "trend_name": "AI 기반 개인화",
     "supporting_data": "소비자의 73%가 개인 정보를 사용하여 쇼핑
경험을 더 관련성 있게 만드는 브랜드와 비즈니스를 선호합니다."
   },
   {
     "trend_name": "지속 가능하고 윤리적인 브랜드",
     "supporting_data": "ESG 관련 주장을 하는 제품의 판매량은 지난
5년간 28% 증가했으며, 관련 주장이 없는 제품은 20% 증가했습니다."
   }
 ]
}

이러한 구조화된 형식은 데이터가 기계가 읽을 수 있고 모호함 없이 다음 프롬프트에 정확하게 구문 분석 및 삽입되도록 보장합니다. 이 관행은 자연어를 해석하는 데서 발생하는 오류를 최소화하며, 강력하고 다단계적인 LLM 기반 시스템을 구축하는 핵심 구성 요소입니다.

실용적인 응용 및 사용 사례

프롬프트 체이닝은 복잡한 문제를 더 작고 관리 가능한 하위 단계로 나누는 데 유용한 다목적 패턴이며, 에이전트형 시스템 구축에 널리 적용됩니다. 다음은 몇 가지 실용적인 응용 사례와 사용 사례입니다.

  • 1. 정보 처리 워크플로우: 많은 작업에는 여러 변환을 통한 원시 정보 처리가 포함됩니다. 예를 들어, 문서를 요약하고, 주요 엔터티를 추출한 다음, 해당 엔터티를 사용하여 데이터베이스를 쿼리하거나 보고서를 생성하는 작업입니다. 프롬프트 체인은 다음과 같이 보일 수 있습니다.
    • 프롬프트 1: 주어진 URL 또는 문서에서 텍스트 콘텐츠를 추출합니다.
    • 프롬프트 2: 정리된 텍스트를 요약합니다.
    • 프롬프트 3: 요약 또는 원본 텍스트에서 특정 엔터티(예: 이름, 날짜, 위치)를 추출합니다.
    • 프롬프트 4: 추출된 엔터티를 사용하여 내부 지식 기반을 검색합니다.
    • 프롬프트 5: 요약, 엔터티 및 검색 결과를 통합하는 최종 보고서를 생성합니다.

이 방법론은 자동화된 콘텐츠 분석, AI 기반 연구 도우미 개발, 복잡한 보고서 생성과 같은 영역에 적용됩니다.

  • 2. 복잡한 쿼리 응답: 여러 단계의 추론 또는 정보 검색이 필요한 복잡한 질문에 답하는 것은 주요 사용 사례입니다. 예를 들어, “1929년 주식 시장 붕괴의 주요 원인은 무엇이었으며 정부 정책은 어떻게 대응했습니까?”
    • 프롬프트 1: 사용자 쿼리에서 핵심 하위 질문(붕괴 원인, 정부 대응)을 식별합니다.
    • 프롬프트 2: 1929년 붕괴 원인에 대한 정보 조사를 수행하거나 검색합니다.
    • 프롬프트 3: 1929년 주식 시장 붕괴에 대한 정부 정책 대응에 대한 정보 조사를 수행하거나 검색합니다.
    • 프롬프트 4: 2단계 및 3단계의 정보를 종합하여 원래 쿼리에 대한 일관된 답변을 생성합니다.

이 순차적 처리 방법론은 다단계 추론 및 정보 종합 능력을 갖춘 AI 시스템 개발에 필수적입니다. 이러한 시스템은 단일 데이터 포인트에서 답변할 수 없고, 대신 일련의 논리적 단계 또는 다양한 소스의 정보 통합이 필요할 때 요구됩니다.

예를 들어, 특정 주제에 대한 포괄적인 보고서를 생성하도록 설계된 자동화된 연구 에이전트는 하이브리드 계산 워크플로우를 실행합니다. 처음에 시스템은 수많은 관련 문서를 검색합니다. 그 후 각 문서에서 핵심 정보를 추출하는 작업은 각 소스에 대해 독립적으로 수행될 수 있습니다. 이 단계는 독립적인 하위 작업이 병렬로 실행되어 효율성을 극대화하는 데 적합합니다.

그러나 개별 추출이 완료되면 프로세스는 본질적으로 순차적이 됩니다. 시스템은 먼저 추출된 데이터를 수집하고, 다음으로 일관된 초안을 생성하기 위해 이를 종합하고, 마지막으로 최종 보고서를 생성하기 위해 이 초안을 검토하고 개선해야 합니다. 이러한 후속 단계 중 각 단계는 선행 단계의 성공적인 완료에 논리적으로 의존합니다. 이것이 프롬프트 체이닝이 적용되는 지점입니다. 종합된 데이터는 합성 프롬프트의 입력으로 사용되며, 결과로 생성된 종합 텍스트는 최종 검토 프롬프트의 입력이 됩니다. 따라서 복잡한 작업은 종종 병렬 처리를 독립적인 데이터 수집에 사용하고, 의존적인 종합 및 개선 단계에는 프롬프트 체이닝을 사용하는 방식으로 결합됩니다.

  • 3. 데이터 추출 및 변환: 비정형 텍스트를 정형 형식으로 변환하는 것은 일반적으로 반복적인 프로세스를 통해 달성되며, 출력의 정확성과 완전성을 개선하기 위해 순차적인 수정이 필요합니다.

    • 프롬프트 1: 송장 문서에서 특정 필드(예: 이름, 주소, 금액)를 추출하려고 시도합니다.
  • 처리: 모든 필수 필드가 추출되었는지, 형식 요구 사항을 충족하는지 확인합니다.

  • 프롬프트 2 (조건부): 필드가 누락되었거나 형식이 잘못된 경우, 모델에 누락/형식이 잘못된 정보를 찾도록 요청하는 새 프롬프트를 작성하고, 실패한 시도에서 컨텍스트를 제공할 수 있습니다.

  • 처리: 결과를 다시 확인합니다. 필요한 경우 반복합니다.

  • 출력: 추출되고 검증된 정형 데이터를 제공합니다.

이러한 순차적 처리 방법론은 양식, 송장 또는 이메일과 같은 비정형 소스에서 데이터 추출 및 분석에 특히 적합합니다. 예를 들어, OCR(광학 문자 인식)과 같은 복잡한 문제를 해결하는 것은 다단계, 분해된 접근 방식을 통해 더 효과적으로 처리됩니다.

처음에 대규모 언어 모델이 문서 이미지에서 기본 텍스트 추출을 수행하는 데 사용됩니다. 그 후 모델은 데이터 정규화를 위해 원시 출력을 처리하며, 이 단계에서 “천오십”과 같은 숫자 텍스트를 숫자 등가물인 1050으로 변환할 수 있습니다. LLM의 주요 과제는 정확한 수학적 계산을 수행하는 것입니다. 따라서 다음 단계에서 시스템은 필요한 모든 산술 연산을 외부 계산기 도구에 위임할 수 있습니다. LLM은 필요한 계산을 식별하고, 정규화된 숫자를 도구에 공급한 다음, 정확한 결과를 통합합니다. 텍스트 추출, 데이터 정규화 및 외부 도구 사용의 이 일련의 순서는 단일 LLM 쿼리에서 신뢰할 수 있게 얻기 어려운 최종적이고 정확한 결과를 달성합니다.

  • 4. 콘텐츠 생성 워크플로우: 복잡한 콘텐츠 작곡은 일반적으로 초기 아이디어 구상, 구조적 개요 작성, 초안 작성 및 후속 개정이라는 별개의 단계로 분해되는 절차적 작업입니다.
    • 프롬프트 1: 사용자의 일반적인 관심사를 기반으로 5가지 주제 아이디어를 생성합니다.
    • 처리: 사용자가 하나의 아이디어를 선택하도록 하거나 자동으로 최상의 아이디어를 선택합니다.
    • 프롬프트 2: 선택한 주제를 기반으로 상세한 개요를 생성합니다.
    • 프롬프트 3: 개요의 첫 번째 항목을 기반으로 초안 섹션을 작성합니다.
    • 프롬프트 4: 이전 섹션을 컨텍스트로 제공하여 두 번째 항목을 기반으로 초안 섹션을 작성합니다. 모든 개요 항목에 대해 이 작업을 계속합니다.
    • 프롬프트 5: 일관성, 어조 및 문법에 대해 전체 초안을 검토하고 개선합니다.

이 방법론은 자동화된 서사 구성, 기술 문서 작성 및 기타 구조화된 텍스트 콘텐츠 생성을 포함하여 다양한 자연어 생성 작업에 사용됩니다.

5. 상태를 가진 대화형 에이전트: 포괄적인 상태 관리 아키텍처는 순차적 연결보다 더 복잡한 방법을 사용하지만, 프롬프트 체이닝은 대화 연속성을 보존하기 위한 기본 메커니즘을 제공합니다. 이 기술은 대화 시퀀스에서 이전 상호 작용에서 추출된 정보나 엔터티를 체계적으로 포함하는 새 프롬프트를 각 대화 턴으로 구성하여 컨텍스트를 유지합니다.

  • 프롬프트 1: 사용자 발언 1을 처리하고 의도 및 주요 엔터티를 식별합니다.
  • 처리: 의도 및 엔터티로 대화 상태를 업데이트합니다.
  • 프롬프트 2: 현재 상태를 기반으로 응답을 생성하고/또는 다음에 필요한 정보 조각을 식별합니다.
  • 후속 턴에 대해 반복하며, 각 새 사용자 발언은 누적되는 대화 기록(상태)을 활용하는 체인을 시작합니다.

이 원칙은 대화형 에이전트 개발의 기본이며, 이 시스템이 이전에 교환된 정보를 기반으로 컨텍스트를 유지하고 적절하게 응답할 수 있도록 합니다.

  • 6. 코드 생성 및 개선: 기능 코드를 생성하는 것은 일반적으로 다단계 프로세스이며, 논리적으로 분리된 작업의 시퀀스로 문제를 분해한 다음 점진적으로 실행해야 합니다.
    • 프롬프트 1: 코드 함수에 대한 사용자 요청을 이해합니다. 의사 코드 또는 개요를 생성합니다.
    • 프롬프트 2: 개요를 기반으로 초기 코드 초안을 작성합니다.
    • 프롬프트 3: 식별된 문제에 따라 코드를 다시 작성하거나 개선합니다.
    • 프롬프트 4: 식별된 문제에 따라 코드를 다시 작성하거나 개선합니다.
    • 프롬프트 5: 문서 또는 테스트 사례를 추가합니다.

AI 지원 소프트웨어 개발과 같은 응용 프로그램에서 프롬프트 체이닝의 유용성은 복잡한 코딩 작업을 관리 가능한 하위 문제로 분해하는 능력에서 비롯됩니다. 이 모듈식 구조는 각 단계에서 대규모 언어 모델의 운영 복잡성을 줄입니다. 결정적으로, 이 접근 방식은 모델 호출 사이에 결정론적 논리를 삽입할 수 있도록 하여 중간 데이터 처리, 출력 유효성 검사 및 워크플로우 내의 조건부 분기를 허용합니다. 이러한 방식으로, 단일 LLM 쿼리에서 신뢰할 수 없거나 불완전한 결과를 초래할 수 있는 단일 다면적 요청은 기본 실행 프레임워크에 의해 관리되는 구조화된 작업 시퀀스로 변환됩니다.

  • 7. 멀티모달 및 다단계 추론: 다양한 모달리티를 가진 데이터 세트를 분석하려면 문제를 더 작은 프롬프트 기반 작업으로 분해해야 합니다. 예를 들어, 이미지와 임베디드 텍스트, 특정 텍스트 세그먼트를 강조하는 레이블, 각 레이블을 설명하는 테이블 데이터가 포함된 이미지를 해석하려면 이러한 접근 방식이 필요합니다.

  • 프롬프트 1: 사용자 이미지 요청에서 텍스트를 추출하고 이해합니다.

  • 프롬프트 2: 추출된 이미지 텍스트를 해당 레이블과 연결합니다.

  • 프롬프트 3: 필요한 출력을 결정하기 위해 테이블을 사용하여 수집된 정보를 해석합니다.

실습 코드 예제

프롬프트 체인의 구현은 스크립트 내에서 직접적인 함수 호출부터 제어 흐름, 상태 및 구성 요소 통합을 관리하도록 설계된 전문 프레임워크 활용에 이르기까지 다양합니다. LangChain, LangGraph, Crew AI 및 Google ADK와 같은 프레임워크는 이러한 다단계 프로세스를 구성하고 실행하기 위한 구조화된 환경을 제공하며, 이는 복잡한 아키텍처에 특히 유리합니다.

이 예제는 기본적인 선형 시퀀스에 중점을 둡니다.

이 절차를 복제하려면 먼저 필요한 라이브러리를 설치해야 합니다. 다음 명령을 사용하여 수행할 수 있습니다.

pip install langchain langchain-community langchain-openai langgraph

OpenAI, Google Gemini 또는 Anthropic과 같은 다른 모델 제공업체를 사용하는 경우 langchain-openai를 해당 패키지로 대체할 수 있습니다. 그런 다음, 선택한 언어 모델 제공업체에 대한 필수 API 자격 증명으로 실행 환경을 구성해야 합니다.

import os
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# 더 나은 보안을 위해 .env 파일에서 환경 변수 로드
from dotenv import load_dotenv

load_dotenv()
# OPENAI_API_KEY가 .env 파일에 설정되어 있는지 확인하세요
# 초기화 (OpenAI ChatOpenAI 사용 권장)
llm = ChatOpenAI(temperature=0)
# --- 프롬프트 1: 정보 추출 ---
prompt_extract = ChatPromptTemplate.from_template(
   "다음 텍스트에서 기술 사양을 추출하십시오:\n\n{text_input}"
)
# --- 프롬프트 2: JSON으로 변환 ---
prompt_transform = ChatPromptTemplate.from_template(
   "다음 사양을 'cpu', 'memory', 'storage' 키를 가진 JSON 개체로
변환하십시오:\n\n{specifications}"
)
# --- LCEL을 사용하여 체인 구축 ---
# StrOutputParser()는 LLM의 메시지 출력을 간단한 문자열로 변환합니다.
extraction_chain = prompt_extract | llm | StrOutputParser()
# 전체 체인은 추출 체인의 출력을 변환 프롬프트의 'specifications'
# 변수에 전달합니다.
full_chain = (
   {"specifications": extraction_chain}
   | prompt_transform
   | llm
   | StrOutputParser()
)
# --- 체인 실행 ---
input_text = "새 노트북 모델은 3.5GHz 옥타코어 프로세서, 16GB RAM,
그리고 1TB NVMe SSD를 특징으로 합니다."
# 입력 텍스트 딕셔너리로 체인 실행.
final_result = full_chain.invoke({"text_input": input_text})
print("\n--- 최종 JSON 출력 ---")
print(final_result)

이 Python 코드는 LangChain 라이브러리를 사용하여 텍스트를 처리하는 방법을 보여줍니다. 이는 비정형 텍스트에서 특정 정보를 추출하는 것과 이 사양을 JSON 개체로 형식화하는 두 가지 별도 프롬프트를 활용합니다. 언어 모델 상호 작용에는 ChatOpenAI 모델이 사용되며, StrOutputParser는 출력이 사용 가능한 문자열 형식임을 보장합니다. LangChain 표현 언어(LCEL)는 이러한 프롬프트와 언어 모델을 우아하게 연결하는 데 사용됩니다. 첫 번째 체인인 extraction_chain은 사양을 추출합니다. 그런 다음 full_chain은 추출의 출력을 받아 변환 프롬프트에 입력으로 사용합니다. 노트북을 설명하는 샘플 입력 텍스트가 제공됩니다. 이 텍스트로 full_chain이 호출되어 두 단계를 모두 처리합니다. 추출 및 형식화된 사양이 포함된 JSON 문자열인 최종 결과가 그런 다음 출력됩니다.

컨텍스트 엔지니어링 및 프롬프트 엔지니어링

컨텍스트 엔지니어링(그림 1 참조)은 토큰 생성이 이루어지기 전에 AI 모델에 완전한 정보 환경을 설계, 구성 및 제공하는 체계적인 분야입니다. 이 방법론은 모델 출력의 품질이 모델 아키텍처 자체보다는 제공된 컨텍스트의 풍부함에 덜 의존한다고 주장합니다.

그림 1: 컨텍스트 엔지니어링은 AI를 위한 풍부하고 포괄적인 정보 환경을 구축하는 분야로, 이 컨텍스트의 품질이 고급 에이전트(Agentic) 성능을 활성화하는 주요 요인입니다.

이는 주로 사용자의 즉각적인 쿼리 구문 최적화에 초점을 맞추는 기존의 프롬프트 엔지니어링에서 중요한 진화를 나타냅니다. 컨텍스트 엔지니어링은 AI의 운영 매개변수를 정의하는 기본 지침 세트인 시스템 프롬프트와 같은 여러 정보 계층을 포함하도록 범위를 확장합니다. 예를 들어, “당신은 기술 작가이며, 당신의 어조는 공식적이고 정확해야 합니다.” 와 같은 지침이 있습니다. 컨텍스트는 외부 데이터로 더욱 풍부해집니다. 여기에는 AI가 지식 기반에서 정보를 적극적으로 가져와 응답에 활용하는 검색된 문서가 포함되며, 프로젝트의 기술 사양을 가져오는 경우가 이에 해당합니다. 또한 AI가 외부 API를 사용하여 실시간 데이터를 얻는 결과인 도구 출력(Tool Outputs)도 포함됩니다. 예를 들어 사용자의 가용성을 확인하기 위해 달력을 쿼리하는 것이 이에 해당합니다. 이 명시적인 데이터는 사용자 신원, 상호 작용 기록 및 환경 상태와 같은 중요한 암시적 데이터와 결합됩니다. 핵심 원칙은 고급 모델이라 할지라도 제한적이거나 부실하게 구성된 운영 환경 보기를 제공받으면 성능이 저하된다는 것입니다.

따라서 이 관행은 단순히 질문에 답하는 작업에서 에이전트를 위한 포괄적인 운영 그림을 구축하는 작업으로 초점을 전환합니다. 예를 들어, 컨텍스트 엔지니어링된 에이전트는 쿼리에 단순히 응답하는 것이 아니라, 먼저 사용자의 달력 가용성(도구 출력), 이메일 수신자와의 전문적인 관계(암시적 데이터), 그리고 이전 회의의 메모(검색된 문서)를 통합할 것입니다. 이를 통해 모델은 매우 관련성이 높고 개인화되었으며 실용적으로 유용한 출력을 생성할 수 있습니다. “엔지니어링” 구성 요소에는 런타임에 이 데이터를 가져오고 변환하기 위한 강력한 파이프라인 생성 및 컨텍스트 품질을 지속적으로 개선하기 위한 피드백 루프 설정이 포함됩니다.

이를 구현하기 위해 특수 튜닝 시스템을 사용하여 대규모 개선 프로세스를 자동화할 수 있습니다. 예를 들어, Google의 Vertex AI 프롬프트 최적화 도구와 같은 도구는 응답을 샘플 입력 및 사전 정의된 평가 지표와 체계적으로 비교하여 모델 성능을 향상시킬 수 있습니다. 이 접근 방식은 광범위한 수동 다시 쓰기가 필요 없이 다양한 모델에 걸쳐 프롬프트 및 시스템 지침을 조정하는 데 효과적입니다. 이러한 최적화 도구에 샘플 프롬프트, 시스템 지침 및 템플릿을 제공하면 컨텍스트 입력을 프로그래밍 방식으로 세부 조정할 수 있어 정교한 컨텍스트 엔지니어링에 필요한 피드백 루프를 구현하기 위한 구조화된 방법을 제공합니다.

이러한 구조화된 접근 방식이 기본 AI 도구와 보다 정교하고 컨텍스트 인식 시스템을 구별하는 요소입니다. 이는 컨텍스트 자체를 주요 구성 요소로 취급하여 에이전트가 무엇을 알고, 언제 알고, 정보를 어떻게 사용하는지에 중요한 중요성을 둡니다. 이 관행은 모델이 사용자의 의도, 기록 및 현재 환경에 대한 균형 잡힌 이해를 갖도록 보장합니다. 궁극적으로 컨텍스트 엔지니어링은 상태 비저장(stateless) 챗봇을 고도로 유능하고 상황 인식적인 시스템으로 발전시키는 중요한 방법론입니다.

한눈에 보기

무엇을: 복잡한 작업은 종종 단일 프롬프트 내에서 처리될 때 LLM에 과부하를 주어 상당한 성능 문제를 야기합니다. 모델의 인지 부하가 증가하면 지침 누락, 컨텍스트 손실 및 잘못된 정보 생성과 같은 오류 가능성이 높아집니다. 단일(monolithic) 프롬프트는 여러 제약 조건과 순차적 추론 단계를 효과적으로 관리하는 데 어려움을 겪습니다. 이로 인해 LLM이 다면적인 요청의 모든 측면을 다루지 못하므로 신뢰할 수 없고 부정확한 출력이 발생합니다.

왜: 프롬프트 체이닝(Prompt chaining)은 복잡한 문제를 작고 상호 연결된 하위 작업 시퀀스로 분해하여 표준화된 솔루션을 제공합니다. 체인의 각 단계는 특정 작업을 수행하기 위해 초점이 맞춰진 프롬프트를 사용하여 신뢰성과 제어력을 크게 향상시킵니다. 한 프롬프트의 출력이 다음 프롬프트의 입력으로 전달되어 최종 솔루션을 점진적으로 구축하는 논리적 워크플로를 만듭니다. 이 모듈식, 분할 정복(divide-and-conquer) 전략은 프로세스를 더 관리하기 쉽고, 디버깅하기 쉬우며, 단계 사이에 외부 도구 또는 구조화된 데이터 형식을 통합할 수 있도록 합니다. 이 패턴은 계획, 추론 및 복잡한 워크플로를 실행할 수 있는 정교한 다단계 에이전트(Agentic) 시스템을 개발하는 데 기본이 됩니다.

경험 법칙: 작업이 단일 프롬프트로 처리하기에는 너무 복잡하거나, 여러 개의 뚜렷한 처리 단계를 포함하거나, 단계 사이에 외부 도구와의 상호 작용이 필요하거나, 다단계 추론을 수행하고 상태를 유지해야 하는 에이전트 시스템을 구축할 때 이 패턴을 사용합니다.

시각적 요약

그림 2: 프롬프트 체이닝 패턴: 에이전트는 사용자로부터 일련의 프롬프트를 수신하며, 각 에이전트의 출력은 체인의 다음 에이전트에 대한 입력으로 사용됩니다.

주요 내용

다음은 몇 가지 주요 내용입니다.

  • 프롬프트 체이닝은 복잡한 작업을 작고 초점이 맞춰진 단계 시퀀스로 분해합니다. 이는 때때로 파이프라인(Pipeline) 패턴이라고도 합니다.
  • 체인의 각 단계에는 이전 단계의 출력을 입력으로 사용하는 LLM 호출 또는 처리 논리가 포함됩니다.
  • 이 패턴은 언어 모델과의 복잡한 상호 작용의 신뢰성과 관리 용이성을 향상시킵니다.
  • LangChain/LangGraph 및 Google ADK와 같은 프레임워크는 이러한 다단계 시퀀스를 정의, 관리 및 실행하기 위한 강력한 도구를 제공합니다.

결론

복잡한 문제를 더 간단하고 관리하기 쉬운 하위 작업 시퀀스로 분해함으로써 프롬프트 체이닝은 대규모 언어 모델을 안내하기 위한 강력한 프레임워크를 제공합니다. 이 “분할 정복” 전략은 모델이 한 번에 하나의 특정 작업에 집중하도록 함으로써 출력의 신뢰성과 제어력을 크게 향상시킵니다. 기본 패턴으로서 이는 다단계 추론, 도구 통합 및 상태 관리가 가능한 정교한 AI 에이전트 개발을 가능하게 합니다. 궁극적으로 프롬프트 체이닝을 마스터하는 것은 단일 프롬프트의 기능을 훨씬 뛰어넘는 복잡한 워크플로를 실행할 수 있는 강력하고 컨텍스트 인식 시스템을 구축하는 데 중요합니다.

참고 자료


2장: 라우팅(Routing)

라우팅 패턴 개요

프롬프트 체이닝을 통한 순차적 처리는 언어 모델을 사용하여 결정론적이고 선형적인 워크플로를 실행하기 위한 기본 기술이지만, 적응형 응답이 필요한 시나리오에서는 그 적용이 제한됩니다. 실제 에이전트 시스템은 환경 상태, 사용자 입력 또는 이전 작업의 결과와 같은 우발적 요인에 따라 여러 잠재적 작업 중에서 중재해야 하는 경우가 많습니다. 제어 흐름을 서로 다른 전문화된 함수, 도구 또는 하위 프로세스로 제어하는 동적 의사 결정 능력은 라우팅(routing)이라고 하는 메커니즘을 통해 달성됩니다.

라우팅은 에이전트의 운영 프레임워크에 조건부 논리를 도입하여 고정된 실행 경로에서 벗어나 에이전트가 특정 기준을 동적으로 평가하여 가능한 후속 작업 세트 중에서 선택하는 모델로 전환할 수 있도록 합니다. 이는 보다 유연하고 컨텍스트 인식적인 시스템 동작을 허용합니다.

예를 들어, 고객 문의를 위해 설계된 에이전트는 라우팅 기능을 갖추고 있을 때 들어오는 쿼리를 먼저 분류하여 사용자의 의도를 판단할 수 있습니다. 이 분류를 기반으로 쿼리를 직접 질문 답변을 위한 전문화된 에이전트, 계정 정보를 위한 데이터베이스 검색 도구, 또는 복잡한 문제에 대한 에스컬레이션 절차로 보낼 수 있으며, 단일의 미리 정해진 응답 경로를 기본값으로 설정하는 대신 사용할 수 있습니다. 따라서 라우팅을 사용하는 보다 정교한 에이전트는 다음을 수행할 수 있습니다.

    1. 사용자 쿼리를 분석합니다.
    1. 쿼리의 의도에 따라 쿼리를 라우팅합니다.
    • 의도가 “주문 상태 확인”인 경우, 주문 데이터베이스와 상호 작용하는 하위 에이전트 또는 도구 체인으로 라우팅합니다.
    • 의도가 “제품 정보”인 경우, 제품 카탈로그를 검색하는 하위 에이전트 또는 체인으로 라우팅합니다.
    • 의도가 “기술 지원”인 경우, 문제 해결 가이드에 액세스하거나 인간에게 에스컬레이션하는 다른 체인으로 라우팅합니다.
    • 의도가 불분명한 경우, 명확화 하위 에이전트 또는 프롬프트 체인으로 라우팅합니다.

라우팅 패턴의 핵심 구성 요소는 평가를 수행하고 흐름을 지시하는 메커니즘입니다. 이 메커니즘은 여러 방식으로 구현될 수 있습니다.

LLM 기반 라우팅: 언어 모델 자체에 입력을 분석하고 다음 단계 또는 대상을 나타내는 특정 식별자나 지침을 출력하도록 프롬프트를 제공할 수 있습니다. 예를 들어, 프롬프트는 LLM에게 “다음 사용자 쿼리를 분석하고 ‘주문 상태’, ‘제품 정보’, ‘기술 지원’ 또는 ‘기타’ 중 하나의 범주만 출력하십시오.”라고 요청할 수 있습니다. 그러면 에이전트 시스템이 이 출력을 읽고 그에 따라 워크플로를 지시합니다.

  • 임베딩 기반 라우팅: 입력 쿼리를 벡터 임베딩(RAG, 14장 참조)으로 변환할 수 있습니다. 이 임베딩은 다양한 라우트 또는 기능에 해당하는 임베딩과 비교됩니다. 쿼리는 임베딩이 가장 유사한 라우트로 라우팅됩니다. 이는 키워드뿐만 아니라 입력의 의미를 기반으로 결정을 내리는 시맨틱 라우팅에 유용합니다.
  • 규칙 기반 라우팅: 여기에는 키워드, 패턴 또는 입력에서 추출된 구조화된 데이터를 기반으로 사전 정의된 규칙 또는 논리(예: if-else 문, switch 케이스)를 사용하는 것이 포함됩니다. 이는 LLM 기반 라우팅보다 빠르고 결정론적일 수 있지만, 미묘하거나 새로운 입력을 처리하는 데는 유연성이 떨어집니다.
  • 머신러닝 모델 기반 라우팅: 이는 분류기와 같은 판별 모델을 사용하여 레이블이 지정된 소규모 데이터 코퍼스에 대해 라우팅 작업을 수행하도록 특별히 훈련된 모델을 사용합니다. 이는 임베딩 기반 방법과 개념적 유사성을 공유하지만, 주요 특징은 지도 미세 조정(supervised fine-tuning) 프로세스로, 라우팅 기능을 전문화하기 위해 모델의 매개변수를 조정합니다. 이 기술은 의사 결정 구성 요소가 추론 시 프롬프트를 실행하는 생성 모델이 아니라는 점에서 LLM 기반 라우팅과 구별됩니다. 대신 라우팅 논리는 미세 조정된 모델의 학습된 가중치 내에 인코딩됩니다. LLM은 훈련 세트를 보강하기 위해 합성 데이터를 생성하는 전처리 단계에서 사용될 수 있지만, 실시간 라우팅 결정 자체에는 관여하지 않습니다.

라우팅 메커니즘은 에이전트 운영 주기의 여러 지점에서 구현될 수 있습니다. 초기에는 기본 작업을 분류하기 위해, 처리 체인 내의 중간 지점에서는 후속 작업을 결정하기 위해, 또는 하위 루틴 중에는 주어진 세트에서 가장 적절한 도구를 선택하기 위해 적용될 수 있습니다.

LangChain, LangGraph 및 Google의 Agent Developer Kit(ADK)와 같은 계산 프레임워크는 이러한 조건부 논리를 정의하고 관리하기 위한 명시적인 구문을 제공합니다. 상태 기반 그래프 아키텍처를 갖춘 LangGraph는 결정이 전체 시스템의 누적 상태에 따라 달라지는 복잡한 라우팅 시나리오에 특히 적합합니다. 마찬가지로 Google의 ADK는 에이전트의 기능 및 상호 작용 모델을 구조화하기 위한 기본 구성 요소를 제공하며, 이는 라우팅 논리 구현의 기반이 됩니다. 이러한 프레임워크에서 제공하는 실행 환경 내에서 개발자는 가능한 운영 경로와 계산 그래프의 노드 간 전환을 결정하는 함수 또는 모델 기반 평가를 정의합니다.

라우팅의 구현은 시스템이 결정론적 순차 처리 이상으로 발전할 수 있도록 합니다. 이는 더 광범위한 입력 및 상태 변화에 동적으로 적절하게 응답할 수 있는 적응형 실행 흐름 개발을 용이하게 합니다.

실제 적용 및 사용 사례

라우팅 패턴은 적응형 에이전트 시스템 설계에서 중요한 제어 메커니즘으로, 가변 입력 및 내부 상태에 응답하여 실행 경로를 동적으로 변경할 수 있도록 합니다. 조건부 논리의 필수 계층을 제공함으로써 그 유용성은 여러 도메인에 걸쳐 확장됩니다.

가상 도우미 또는 AI 기반 튜터와 같은 인간-컴퓨터 상호 작용에서 라우팅은 사용자 의도를 해석하는 데 사용됩니다. 자연어 쿼리의 초기 분석을 통해 특정 정보 검색 도구 호출, 인간 운영자에게 에스컬레이션, 또는 사용자 성능에 기반한 커리큘럼의 다음 모듈 선택 등 가장 적절한 후속 조치가 결정됩니다. 이를 통해 시스템은 선형 대화 흐름을 넘어 컨텍스트에 따라 응답할 수 있습니다.

자동화된 데이터 및 문서 처리 파이프라인 내에서 라우팅은 분류 및 배포 기능으로 작동합니다. 이메일, 지원 티켓 또는 API 페이로드와 같은 수신 데이터는 콘텐츠, 메타데이터 또는 형식을 기반으로 분석됩니다. 그런 다음 시스템은 각 항목을 해당 워크플로로 보냅니다. 예를 들어, 영업 리드 수집 프로세스, JSON 또는 CSV 형식에 대한 특정 데이터 변환 함수 또는 긴급 이슈 에스컬레이션 경로 등이 있습니다.

여러 전문화된 도구 또는 에이전트가 관련된 복잡한 시스템에서 라우팅은 고수준 디스패처 역할을 합니다. 검색, 요약 및 정보 분석을 위한 별도의 에이전트로 구성된 연구 시스템은 현재 목표에 따라 작업을 가장 적합한 에이전트에게 할당하기 위해 라우터를 사용합니다. 마찬가지로 AI 코딩 도우미는 코드 조각을 올바른 전문화된 도구로 전달하기 전에 프로그래밍 언어와 사용자의 의도(디버깅, 설명 또는 번역)를 식별하기 위해 라우팅을 사용합니다.

궁극적으로 라우팅은 기능적으로 다양하고 컨텍스트 인식적인 시스템을 만드는 데 필수적인 논리적 중재 능력을 제공합니다. 이는 에이전트를 미리 정의된 시퀀스의 정적 실행자에서 변화하는 조건 하에서 작업을 완료하기 위한 가장 효과적인 방법을 결정할 수 있는 동적 시스템으로 변화시킵니다.

실습 코드 예제 (LangChain)

코드에서 라우팅을 구현하는 것은 가능한 경로와 어떤 경로를 선택할지 결정하는 논리를 정의하는 것을 포함합니다. LangChain 및 LangGraph와 같은 프레임워크는 이에 대한 특정 구성 요소 및 구조를 제공합니다. LangGraph의 상태 기반 그래프 구조는 라우팅 논리를 시각화하고 구현하는 데 특히 직관적입니다.

이 코드는 LangChain과 Google의 생성형 AI를 사용하여 간단한 에이전트와 같은 시스템을 시연합니다. 이는 사용자 요청의 의도(예약, 정보 또는 불분명)에 따라 사용자 요청을 서로 다른 시뮬레이션된 “하위 에이전트” 처리기로 라우팅하는 “조정자(coordinator)“를 설정합니다. 이 시스템은 언어 모델을 사용하여 요청을 분류한 다음 적절한 처리기 함수에 위임하여 다중 에이전트 아키텍처에서 흔히 볼 수 있는 기본 위임 패턴을 시뮬레이션합니다.

먼저 필요한 라이브러리를 설치해야 합니다.

pip install langchain langgraph google-cloud-aiplatform
langchain-google-genai google-adk deprecated pydantic

또한 선택한 언어 모델(예: OpenAI, Google Gemini, Anthropic)에 대한 API 키로 환경을 설정해야 합니다.

# Copyright (c) 2025 Marco Fago
# https://www.linkedin.com/in/marco-fago/
#
# This code is licensed under the MIT License.
# See the LICENSE file in the repository for the full license text.
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough,
RunnableBranch
# --- 구성 ---
# API 키 환경 변수가 설정되어 있는지 확인합니다(예:
GOOGLE_API_KEY)
try:
   llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash",
temperature=0)
   print(f"언어 모델 초기화됨: {llm.model}")
except Exception as e:
   print(f"언어 모델 초기화 오류: {e}")
   llm = None
# --- 시뮬레이션된 하위 에이전트 처리기 정의 (ADK 하위 에이전트에
상당) ---
def booking_handler(request: str) -> str:
   """예약 에이전트가 요청을 처리하는 것을 시뮬레이션합니다."""
   print("\n--- 예약 처리기로 위임 중 ---")
   return f"예약 처리기가 요청을 처리함: '{request}'. 결과:
시뮬레이션된 예약 작업."
def info_handler(request: str) -> str:
   """정보 에이전트가 요청을 처리하는 것을 시뮬레이션합니다."""
   print("\n--- 정보 처리기로 위임 중 ---")
   return f"정보 처리기가 요청을 처리함: '{request}'. 결과:
시뮬레이션된 정보 검색."
def unclear_handler(request: str) -> str:
   """위임할 수 없었던 요청을 처리합니다."""
   print("\n--- 불분명한 요청 처리 중 ---")
   return f"조정자가 요청을 위임할 수 없음: '{request}'.
명확히 해주십시오."
# --- 조정자 라우터 체인 정의 (ADK 조정자의 지침에 상당) ---
# 이 체인은 어떤 처리기로 위임할지 결정합니다.
coordinator_router_prompt = ChatPromptTemplate.from_messages([
   ("system", """사용자 요청을 분석하고 처리해야 할 전문 처리기를
결정하십시오.
    - 항공편 또는 호텔 예약을 관련하는 요청의 경우 'booker'를
      출력합니다.
    - 그 외의 모든 일반 정보 질문의 경우 'info'를 출력합니다.
    - 요청이 불분명하거나 두 범주 중 하나에 맞지 않는 경우
      'unclear'를 출력합니다.
    오직 한 단어만 출력하십시오: 'booker', 'info' 또는 'unclear'."""),
   ("user", "{request}")
])
if llm:
   coordinator_router_chain = coordinator_router_prompt | llm |
StrOutputParser()
# --- 위임 논리 정의 (ADK의 sub_agents 기반 자동 흐름에 상당) ---
# RunnableBranch를 사용하여 라우터 체인의 출력에 따라 라우팅합니다.
# RunnableBranch를 위한 분기 정의
branches = {
   "booker": RunnablePassthrough.assign(output=lambda x:
booking_handler(x['request']['request'])),
   "info": RunnablePassthrough.assign(output=lambda x:
info_handler(x['request']['request'])),
   "unclear": RunnablePassthrough.assign(output=lambda x:
unclear_handler(x['request']['request'])),
}
# RunnableBranch를 생성합니다. 원본 입력('request')을 해당 처리기에
라우팅하기 위해 라우터 체인의 출력을 사용합니다.
delegation_branch = RunnableBranch(
   (lambda x: x['decision'].strip() == 'booker', branches["booker"]),
# .strip() 추가됨
   (lambda x: x['decision'].strip() == 'info', branches["info"]),
# .strip() 추가됨
   branches["unclear"] # 'unclear' 또는 다른 출력을 위한 기본 분기
)
# 라우터 체인의 출력('decision')이 원본 입력('request')과 함께
# delegation_branch로 전달되도록 단일 실행 가능한 항목으로 결합합니다.
# delegation_branch로 전달됩니다.
coordinator_agent = {
   "decision": coordinator_router_chain,
   "request": RunnablePassthrough()
} | delegation_branch | (lambda x: x['output']) # 최종 출력을
추출
# --- 사용 예시 ---
def main():
   if not llm:
       print("\nLLM 초기화 실패로 실행 건너뜁니다.")
       return
   print("--- 예약 요청으로 실행 중 ---")
   request_a = "나를 위해 런던행 비행기를 예약해 줘."
   result_a = coordinator_agent.invoke({"request": request_a})
   print(f"최종 결과 A: {result_a}")
   print("\n--- 정보 요청으로 실행 중 ---")
   request_b = "이탈리아의 수도는 어디야?"
   result_b = coordinator_agent.invoke({"request": request_b})
   print(f"최종 결과 B: {result_b}")
   print("\n--- 불분명한 요청으로 실행 중 ---")
   request_c = "양자 물리학에 대해 알려줘."
   result_c = coordinator_agent.invoke({"request": request_c})
   print(f"최종 결과 C: {result_c}")
if __name__ == "__main__":
   main()

언급했듯이, 이 Python 코드는 LangChain 라이브러리와 Google의 생성형 AI 모델(구체적으로 gemini-2.5-flash)을 사용하여 간단한 에이전트와 같은 시스템을 구성합니다. 자세히 설명하면, booking_handler, info_handler 및 unclear_handler라는 세 가지 시뮬레이션된 하위 에이전트 처리기를 정의하며, 각 처리기는 특정 유형의 요청을 처리하도록 설계되었습니다.

핵심 구성 요소는 ChatPromptTemplate을 활용하여 언어 모델이 들어오는 사용자 요청을 ‘booker’, ‘info’ 또는 ‘unclear’의 세 가지 범주 중 하나로 분류하도록 지시하는 coordinator_router_chain입니다. 이 라우터 체인의 출력은 RunnableBranch에 의해 사용되어 원본 요청을 해당 처리기 함수에 위임합니다. RunnableBranch는 언어 모델의 결정을 확인하고 요청 데이터를 booking_handler, info_handler 또는 unclear_handler 중 하나로 전달합니다. coordinator_agent는 이러한 구성 요소를 결합하여 먼저 결정을 위해 요청을 라우팅한 다음 요청을 선택된 처리기로 전달합니다. 최종 출력은 처리기의 응답에서 추출됩니다.

main 함수는 세 가지 예제 요청을 사용하여 시스템 사용법을 시연하며, 서로 다른 입력이 시뮬레이션된 에이전트에 의해 어떻게 라우팅되고 처리되는지 보여줍니다. 언어 모델 초기화 오류에 대한 오류 처리가 포함되어 안정성을 보장합니다. 코드 구조는 중앙 조정자가 의도에 따라 작업을 전문화된 에이전트에 위임하는 기본 다중 에이전트 프레임워크를 모방합니다.

실습 코드 예제 (Google ADK)

이제 Google ADK 프레임워크 내에서 이러한 개념을 구현하는 예를 살펴보겠습니다. 에이전트 흐름을 구축하고 동시 실행을 활용하는 방법을 보여주기 위해, 우리는 LlmAgent 및 BaseAgent에서 파생된 TaskExecutor 에이전트와 SequentialAgent의 구조를 적용할 것입니다.

# Copyright (c) 2025 Marco Fago
#
# This code is licensed under the MIT License.
# See the LICENSE file in the repository for the full license text.
import uuid
from typing import Dict, Any, Optional
from google.adk.agents import Agent
from google.adk.runners import InMemoryRunner
from google.adk.tools import FunctionTool
from google.genai import types
from google.adk.events import Event
# --- Define Tool Functions ---
# These functions simulate the actions of the specialist agents.
def booking_handler(request: str) -> str:
   """
   Handles booking requests for flights and hotels.
   Args:
       request: The user's request for a booking.
   Returns:
       A confirmation message that the booking was handled.
   """
   print("-------------------------- Booking Handler Called
----------------------------")
   return f"Booking action for '{request}' has been simulated."
def info_handler(request: str) -> str:
   """
   Handles general information requests.
   Args:
request: The user's question.
   Returns:
       A message indicating the information request was handled.
   """
   print("-------------------------- Info Handler Called
----------------------------")
   return f"Information request for '{request}'. Result: Simulated
information retrieval."
def unclear_handler(request: str) -> str:
   """Handles requests that couldn't be delegated."""
   return f"Coordinator could not delegate request: '{request}'.
Please clarify."
# --- Create Tools from Functions ---
booking_tool = FunctionTool(booking_handler)
info_tool = FunctionTool(info_handler)
# Define specialized sub-agents equipped with their respective tools
booking_agent = Agent(
   name="Booker",
   model="gemini-2.0-flash",
   description="A specialized agent that handles all flight
           and hotel booking requests by calling the booking tool.",
   tools=[booking_tool]
)
info_agent = Agent(
   name="Info",
   model="gemini-2.0-flash",
   description="A specialized agent that provides general information
      and answers user questions by calling the info tool.",
   tools=[info_tool]
)
# Define the parent agent with explicit delegation instructions
coordinator = Agent(
   name="Coordinator",
   model="gemini-2.0-flash",
   instruction=(
       "You are the main coordinator. Your only task is to analyze
        incoming user requests "
       "and delegate them to the appropriate specialist agent.
        Do not try to answer the user directly.\n"
       "- For any requests related to booking flights or hotels,
         delegate to the 'Booker' agent.\n"
       "- For all other general information questions, delegate to
the 'Info' agent."
   ),
   description="A coordinator that routes user requests to the
     correct specialist agent.",
   # The presence of sub_agents enables LLM-driven delegation
(Auto-Flow) by default.
   sub_agents=[booking_agent, info_agent]
)
# --- Execution Logic ---
async
 def run_coordinator(runner: InMemoryRunner, request: str):
   """Runs the coordinator agent with a given request and
delegates."""
   print(f"\n--- Running Coordinator with request: '{request}' ---")
   final_result = ""
   try:
       user_id = "user_123"
       session_id = str(uuid.uuid4())
       await
 runner.session_service.create_session(
           app_name=runner.app_name, user_id=user_id,
session_id=session_id
       )
       for event in runner.run(
           user_id=user_id,
           session_id=session_id,
           new_message=types.Content(
               role='user',
               parts=[types.Part(text=request)]
           ),
       ):
           if event.is_final_response() and event.content:
               # Try to get text directly from event.content
               # to avoid iterating parts
               if hasattr(event.content, 'text') and
event.content.text:
                    final_result = event.content.text
               elif event.content.parts:
                   # Fallback: Iterate through parts and extract text
(might trigger warning)
                   text_parts = [part.text for part in
event.content.parts if part.text]
                   final_result = "".join(text_parts)
               # Assuming the loop should break after the final
response
               break
       print(f"Coordinator Final Response: {final_result}")
       return final_result
   except Exception as e:
       print(f"An error occurred while processing your request: {e}")
       return f"An error occurred while processing your request: {e}"
async
 def main():
   """Main function to run the ADK example."""
   print("--- Google ADK Routing Example (ADK Auto-Flow Style) ---")
   print("Note: This requires Google ADK installed and
authenticated.")
   runner = InMemoryRunner(coordinator)
   # Example Usage
   result_a = await run_coordinator(runner, "Book me a hotel in
Paris.")
   print(f"Final Output A: {result_a}")
   result_b = await run_coordinator(runner, "What is the highest
mountain in the world?")
   print(f"Final Output B: {result_b}")
   result_c = await run_coordinator(runner, "Tell me a random fact.")
# Should go to Info
   print(f"Final Output C: {result_c}")
   result_d = await run_coordinator(runner, "Find flights to Tokyo
next month.") # Should go to Booker
   print(f"Final Output D: {result_d}")
if __name__ == "__main__":
   import nest_asyncio
   nest_asyncio.apply()
   await main()               

이 코드는 Google ADK를 사용하여 간단한 에이전트 시스템을 구성하는 방법을 보여줍니다. 이 시스템은 Coordinator 에이전트를 중앙 집중식으로 사용하며, 이 코디네이터는 수신된 요청의 의도를 분류하여 Booker 또는 Info라는 두 가지 전문 하위 에이전트에게 작업을 위임합니다.

주요 구성 요소:

  1. 하위 에이전트 (Booker, Info): 이들은 각각 booking_handlerinfo_handler 함수를 래핑하는 FunctionTool을 사용하여 특정 작업을 수행합니다. 이들은 요청된 작업을 시뮬레이션하는 역할을 합니다.
  2. 코디네이터 에이전트: 이 LLM 에이전트는 사용자 요청을 분석하고, Booker 또는 Info 에이전트 중 적절한 대상을 선택하여 작업을 위임하는 지침을 받습니다.
  3. 실행 흐름: ADK의 InMemoryRunner는 코디네이터 에이전트를 실행합니다. 코디네이터는 수신된 요청을 기반으로 내부적으로 다음 작업을 결정하고, 해당 요청을 적절한 하위 에이전트에게 전달합니다.

이 구조는 라우팅 패턴을 시뮬레이션하여, 들어오는 쿼리의 성격에 따라 에이전트 시스템이 동적으로 실행 경로를 선택할 수 있도록 합니다. 코드가 성공적으로 실행되면, 최종 결과는 요청이 위임된 처리기에 따라 달라집니다.

라우팅 패턴에 대한 시각적 요약

그림 1: LLM을 라우터로 사용하는 라우터 패턴

주요 요점

  • 라우팅은 에이전트가 조건에 따라 실행 워크플로우의 다음 단계를 동적으로 결정할 수 있도록 합니다.
  • 이를 통해 에이전트는 다양한 입력에 응답하고 동작을 조정하여 선형 실행을 넘어서게 할 수 있습니다.
  • 라우팅 로직은 LLM을 사용하거나, 미리 정의된 규칙을 사용하거나, 임베딩 유사성을 사용하여 구현할 수 있습니다.
  • LangGraph 및 Google ADK와 같은 프레임워크는 에이전트 워크플로우 내에서 라우팅을 정의하고 관리하는 구조화된 방법을 제공합니다.

결론

결론적으로, 라우팅 패턴은 적응성이 뛰어나고 응답성이 뛰어난 에이전트형 시스템을 구축하는 데 중요한 단계입니다. 라우팅을 구현함으로써 우리는 단순한 선형 실행 흐름을 넘어 에이전트가 정보 처리 방법, 사용자 입력에 응답하는 방법, 사용 가능한 도구 또는 하위 에이전트를 활용하는 방법에 대해 지능적인 결정을 내릴 수 있도록 권한을 부여합니다.

라우팅이 고객 서비스 챗봇부터 복잡한 데이터 처리 파이프라인에 이르기까지 다양한 도메인에서 어떻게 적용될 수 있는지 살펴보았습니다. 들어오는 입력을 조건부로 워크플로우로 지시하는 능력은 동적 실행 흐름을 위한 필수적인 제어 메커니즘입니다.

LangChain과 Google ADK를 사용하는 코드 예제는 라우팅 로직을 구현하는 두 가지 효과적인 접근 방식을 보여줍니다. LangGraph의 그래프 기반 구조는 복잡한 다단계 워크플로우에 대한 명시적이고 시각적인 정의를 제공하는 반면, Google ADK는 도구 및 하위 에이전트 수준에서 위임을 처리하는 데 중점을 둡니다.

지능적으로 시나리오 간을 탐색하고 컨텍스트에 따라 맞춤형 응답이나 조치를 제공할 수 있는 에이전트를 구축하려면 라우팅 패턴을 마스터하는 것이 필수적입니다. 이는 다양하고 강력한 에이전트 애플리케이션을 구축하는 데 핵심적인 구성 요소입니다.

참고 자료


3장: 병렬화

병렬화 패턴 개요

이전 장에서 우리는 결정론적이고 선형적인 워크플로우를 실행하기 위한 순차적 처리 기술인 프롬프트 체이닝과 동적 의사 결정을 위한 라우팅을 탐구했습니다. 이러한 패턴은 필수적이지만, 많은 복잡한 에이전트 작업에는 다음 단계가 시작되기 전에 한 단계가 완료되기를 기다리는 대신 동시에 실행될 수 있는 여러 하위 작업이 포함됩니다. 이것이 병렬화(Parallelization) 패턴이 중요한 이유입니다.

병렬화는 LLM 호출, 도구 사용 또는 전체 하위 에이전트와 같은 여러 구성 요소를 동시에(concurrently) 실행하는 것을 포함합니다. 한 단계가 완료될 때까지 기다렸다가 다음 단계를 시작하는 대신, 병렬 실행을 통해 독립적인 작업을 동시에 실행할 수 있으므로, 독립적인 부분으로 분해될 수 있는 작업의 전체 실행 시간이 크게 단축됩니다.

복잡한 주제를 조사하고 그 결과를 요약하는 에이전트를 상상해 봅시다. 순차적 접근 방식은 다음을 수행할 수 있습니다.

    1. 소스 A 검색.
    1. 소스 A 요약.
    1. 소스 B 검색.
    1. 소스 B 요약.
    1. 요약 A와 B에서 최종 답변 종합.

병렬 접근 방식은 대신 다음을 수행할 수 있습니다.

    1. 소스 A 검색 소스 B 검색을 동시에 수행.
    1. 두 검색이 모두 완료되면 소스 A 요약 소스 B 요약 동시 수행.
    1. 요약 A와 B에서 최종 답변 종합(이 단계는 일반적으로 병렬 단계를 완료하기를 기다리므로 순차적임).

핵심 아이디어은 다른 부분의 출력에 의존하지 않는 워크플로우 부분을 식별하고 이를 병렬로 실행하는 것입니다. 이는 지연 시간이 있는 외부 서비스(API 또는 데이터베이스와 같은)를 다룰 때 특히 효과적인데, 여러 요청을 동시에 발행할 수 있기 때문입니다.

병렬화 구현에는 종종 비동기 실행 또는 멀티스레딩/멀티프로세싱을 지원하는 프레임워크가 필요합니다. 최신 에이전트 프레임워크는 비동기 작업을 염두에 두고 설계되어, 병렬로 실행될 수 있는 단계를 쉽게 정의할 수 있도록 합니다.

프레임워크는 병렬 실행을 위한 메커니즘을 제공합니다. LangChain의 LCEL(LangChain Expression Language)에서는 여러 실행 가능 구성 요소를 딕셔너리 또는 리스트 구조로 결합하여 병렬 실행을 달성할 수 있습니다. LangGraph는 단일 상태 전환에서 여러 노드를 시작할 수 있도록 병렬 분기를 구성할 수 있어 병렬화 구현에 매우 적합합니다. Google ADK는 에이전트의 기능과 상호 작용 모델을 구조화하기 위한 기본 메커니즘을 제공하여 병렬 실행을 위한 빌딩 블록을 제공합니다.

병렬화 패턴은 독립적인 조회, 계산 또는 외부 서비스와의 상호 작용과 관련된 작업을 다룰 때 에이전트 시스템의 효율성과 응답성을 개선하는 데 필수적입니다. 이는 복잡한 에이전트 워크플로우 성능을 최적화하는 핵심 기술입니다.

실용적인 응용 및 사용 사례

병렬화는 다양한 응용 분야에서 에이전트 성능을 최적화하기 위한 강력한 패턴입니다.

1. 정보 수집 및 연구:

여러 소스에서 동시에 정보 수집은 전형적인 사용 사례입니다.

  • 사용 사례: 회사를 조사하는 에이전트.
    • 병렬 작업: 뉴스 기사 검색, 주식 데이터 가져오기, 소셜 미디어 언급 확인, 회사 데이터베이스 쿼리를 동시에 수행합니다.
    • 이점: 순차적 조회보다 훨씬 빠르게 포괄적인 시각을 수집합니다.

2. 데이터 처리 및 분석:

다양한 분석 기술을 적용하거나 여러 데이터 세그먼트를 동시에 처리합니다.

  • 사용 사례: 고객 피드백을 분석하는 에이전트.
    • 병렬 작업: 일괄 처리된 피드백 항목에 대해 감정 분석 실행, 키워드 추출, 피드백 분류, 긴급 문제 식별을 동시에 수행합니다.
    • 이점: 다면적인 분석을 신속하게 제공합니다.

3. 다중 API 또는 도구 상호 작용:

다양한 유형의 정보를 수집하거나 다양한 작업을 수행하기 위해 여러 독립적인 API 또는 도구를 호출합니다.

  • 사용 사례: 여행 계획 에이전트.
    • 병렬 작업: 항공편 가격 확인, 호텔 가용성 검색, 지역 이벤트 조회, 추천 레스토랑 검색을 동시에 수행합니다.
    • 이점: 전체 여행 계획을 더 빠르게 제시합니다.

4. 여러 구성 요소를 포함하는 콘텐츠 생성:

복잡한 콘텐츠의 여러 부분을 병렬로 생성합니다.

  • 사용 사례: 마케팅 이메일을 작성하는 에이전트.
    • 병렬 작업: 제목 생성, 이메일 본문 초안 작성, 관련 이미지 찾기, 클릭 유도 문구 텍스트 생성을 동시에 수행합니다.
    • 이점: 최종 이메일을 더 효율적으로 조립합니다.

5. 유효성 검사 및 확인:

여러 독립적인 확인 또는 유효성 검사를 동시에 수행합니다.

  • 사용 사례: 사용자 입력을 확인하는 에이전트.
    • 병렬 작업: 이메일 형식 확인, 전화번호 유효성 검사, 데이터베이스를 통한 주소 확인, 악성 단어 확인을 동시에 수행합니다.
    • 이점: 입력 유효성에 대한 더 빠른 피드백을 제공합니다.

6. 멀티모달 처리:

동일한 입력의 텍스트, 이미지, 오디오와 같은 다양한 모달리티를 동시에 처리합니다.

  • 사용 사례: 텍스트와 이미지가 포함된 소셜 미디어 게시물을 분석하는 에이전트.
    • 병렬 작업: 텍스트의 감정과 키워드 분석 이미지의 개체와 장면 설명 분석을 동시에 수행합니다.
    • 이점: 다른 모달리티의 통찰력을 더 신속하게 통합합니다.

7. A/B 테스트 또는 여러 옵션 생성:

최고의 옵션을 선택하기 위해 응답 또는 출력의 여러 변형을 병렬로 생성합니다.

  • 사용 사례: 여러 창의적인 텍스트 옵션을 생성하는 에이전트.
    • 병렬 작업: 약간 다른 프롬프트나 모델을 사용하여 기사에 대한 세 가지 다른 제목을 동시에 생성합니다.
    • 이점: 최상의 옵션을 신속하게 비교하고 선택할 수 있습니다.

병렬화는 독립적인 작업을 병렬 실행하여 더 높은 성능을 가진 에이전트형 애플리케이션을 구축하는 데 필수적인 최적화 기술입니다.

실습 코드 예제 (LangChain)

LangChain 프레임워크 내에서 병렬 실행은 여러 실행 가능 구성 요소를 딕셔너리 또는 리스트 구조 내에서 결합하여 지원됩니다. 이 컬렉션이 체인의 후속 구성 요소에 입력으로 전달되면 LCEL 런타임은 포함된 실행 가능 개체들을 동시에 실행합니다.

이 구현은 LangChain을 사용하여 구성된 병렬 처리 워크플로우를 시연합니다. 이 워크플로우는 단일 사용자 쿼리에 응답하여 두 가지 독립적인 작업을 동시에 실행하도록 설계되었습니다. 이 병렬 프로세스는 별도의 체인 또는 함수로 인스턴스화되며, 그 결과는 후속 통합 단계에서 통합된 결과로 병합됩니다.

이 구현의 전제 조건은 핵심 LangChain 라이브러리 및 OpenAI와 같은 모델 공급자 라이브러리와 같은 필수 Python 패키지를 설치하는 것을 포함합니다. 또한, 선택한 언어 모델 서비스에 대한 유효한 API 키가 로컬 환경에서 인증을 위해 구성되어야 합니다.

import os
import asyncio
from typing import Optional
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import Runnable, RunnableParallel,
RunnablePassthrough
# --- 구성 ---
# API 키 환경 변수가 설정되어 있는지 확인하십시오 (예:
OPENAI_API_KEY)
try:
   llm: Optional[ChatOpenAI] = ChatOpenAI(model="gpt-4o-mini",
temperature=0.7)
except Exception as e:
   print(f"언어 모델 초기화 오류: {e}")
   llm = None
# --- 독립적인 체인 정의 ---
# 이 세 가지 체인은 병렬로 실행될 수 있는 구별된 작업을 나타냅니다.
summarize_chain: Runnable = (
   ChatPromptTemplate.from_messages([
       ("system", "다음 주제를 간결하게 요약하십시오:"),
       ("user", "{topic}")
   ])
   | llm
   | StrOutputParser()
)
questions_chain: Runnable = (
   ChatPromptTemplate.from_messages([
       ("system", "다음 주제에 대한 흥미로운 질문 세 가지를
생성하십시오:"),
       ("user", "{topic}")
   ])
   | llm
   | StrOutputParser()
)
terms_chain: Runnable = (
   ChatPromptTemplate.from_messages([
       ("system", "다음 주제에서 5-10개의 핵심 용어를 쉼표로 구분하여
식별하십시오:"),
       ("user", "{topic}")
   ])
   | llm
   | StrOutputParser()
)
# --- 병렬 + 종합 체인 구축 ---
# 1. 병렬로 실행할 작업 블록을 정의합니다. 이러한 결과는
원래 주제와 함께
# 다음 단계에 피드됩니다.
map_chain = RunnableParallel(
   {
       "summary": summarize_chain,
       "questions": questions_chain,
       "key_terms": terms_chain,
       "topic": RunnablePassthrough(), # 원래 주제를 통과시킴
   }
)
# 2. 병렬 결과를 최종 종합 프롬프트에 직접 결합하는 최종
종합 프롬프트를 정의합니다.
synthesis_prompt = ChatPromptTemplate.from_messages([
   ("system", """다음 정보를 기반으로:
    요약: {summary}
    관련 질문: {questions}
    핵심 용어: {key_terms}
    종합적인 답변을 생성하십시오."""),
   ("user", "원본 주제: {topic}")
])
# 3. 병렬 결과를 최종 종합 프롬프트에 직접 파이프한 다음 LLM 및
출력 파서로 연결하여 전체 병렬 체인을 구성합니다.
full_parallel_chain = map_chain | synthesis_prompt | llm |
StrOutputParser()
# --- 체인 실행 ---
async def run_parallel_example(topic: str) -> None:
   """
   특정 주제로 병렬 LangChain 예제를 비동기적으로 호출하고
종합된 결과를 인쇄합니다.
   Args:
       topic: LangChain 체인에 의해 처리될 입력 주제.
   """
   if not llm:
       print("LLM이 초기화되지 않아 예제를 실행할 수 없습니다.")
       return
   print(f"\n--- 주제 '{topic}'에 대한 병렬 LangChain 예제 실행
---")
   try:
       # `ainvoke`에 대한 입력은 단일 'topic' 문자열이며,
`map_chain`의 각 실행 가능 개체에 전달됩니다.
       response = await full_parallel_chain.ainvoke(topic)
       print("\n--- 최종 응답 ---")
       print(response)
   except Exception as e:
       print(f"\n체인 실행 중 오류가 발생했습니다: {e}")
if __name__ == "__main__":
   test_topic = "우주 탐사의 역사"
   # Python 3.7 이상에서는 asyncio.run이 비동기 함수를 실행하는
표준 방법입니다.
   asyncio.run(run_parallel_example(test_topic))

제공된 Python 코드는 LangChain 애플리케이션을 구현하며, 병렬 실행을 활용하여 주어진 주제를 효율적으로 처리하도록 설계되었습니다. 이 코드는 LangChain 라이브러리와 Google Gemini 모델을 사용하여 에이전트형 시스템을 구성합니다. 이 시스템은 세 가지 독립적인 체인을 정의합니다. 첫 번째 체인은 주제를 간결하게 요약하는 작업을 담당하고, 두 번째 체인은 주제와 관련된 흥미로운 질문 세 가지를 생성하고, 세 번째 체인은 주제에서 5-10개의 핵심 용어를 식별하도록 구성되어 있습니다. 각 체인은 고유한 시스템 메시지를 사용하여 특정 작업을 수행하도록 명시적으로 지시합니다.

이러한 독립적인 체인은 LangChain의 RunnableParallel을 사용하여 구성됩니다. 이 구조를 통해 세 가지 LLM 호출(요약, 질문, 용어)이 주제에 대해 동시에 실행되어 전체 실행 시간을 크게 단축합니다. 병렬 체인의 출력을 받은 후, 최종 단계는 이 세 가지 결과를 종합하여 포괄적인 답변을 생성하는 별도의 LLM 호출입니다. full_parallel_chain은 병렬 처리 단계와 최종 종합 단계를 순차적으로 연결합니다. 이 코드는 asyncio.run을 사용하여 전체 워크플로우를 관리하며, 이는 비동기 실행을 가능하게 합니다. 기본적으로 이 예제는 LangChain의 강력한 동시성 기능을 사용하여 효율성을 향상시키는 병렬화 패턴을 보여줍니다.

실습 코드 예제 (Google ADK)

이제 Google ADK를 사용하는 동안 이 개념을 설명하는 구체적인 예제를 살펴보겠습니다. 특히, ParallelAgentSequentialAgent의 개념을 적용하여 복잡한 워크플로우를 구축하는 방법을 시연할 것입니다.

from google.adk.agents import LlmAgent, ParallelAgent, SequentialAgent
from google.adk.tools import google_search
from google.adk.runners import InMemoryRunner
from google.adk.sessions import InMemorySessionService
from google.genai import types
import asyncio
import uuid
# --- 구성 ---
GEMINI_MODEL="gemini-2.0-flash"
# --- 1. 병렬 실행될 연구원 하위 에이전트 정의 ---
# 연구원 1: 재생 에너지
researcher_agent_1 = LlmAgent(
    name="RenewableEnergyResearcher",
    model=GEMINI_MODEL,
    instruction="""당신은 에너지 전문 AI 연구 보조원입니다.
'재생 에너지원'의 최신 발전에 대해 연구하십시오.
제공된 Google 검색 도구를 사용하십시오.
핵심 발견 사항을 간결하게(1-2문장) 요약하십시오.
요약만 출력하십시오.
""",
    description="재생 에너지원을 연구합니다.",
    tools=[google_search],
    output_key="renewable_energy_result" # 합병 에이전트용 상태에
결과 저장
)
# 연구원 2: 전기 자동차
researcher_agent_2 = LlmAgent(
    name="EVResearcher",
    model=GEMINI_MODEL,
    instruction="""당신은 운송 전문 AI 연구 보조원입니다.
'전기차 기술'의 최신 발전에 대해 연구하십시오.
제공된 Google 검색 도구를 사용하십시오.
핵심 발견 사항을 간결하게(1-2문장) 요약하십시오.
요약만 출력하십시오.
""",
    description="전기차 기술을 연구합니다.",
    tools=[google_search],
    output_key="ev_technology_result" # 합병 에이전트용 상태에
결과 저장
)
# 연구원 3: 탄소 포집
researcher_agent_3 = LlmAgent(
    name="CarbonCaptureResearcher",
    model=GEMINI_MODEL,
    instruction="""당신은 기후 솔루션 전문 AI 연구 보조원입니다.
'탄소 포집 방법'의 현재 상태를 연구하십시오.
제공된 Google 검색 도구를 사용하십시오.
핵심 발견 사항을 간결하게(1-2문장) 요약하십시오.
요약만 출력하십시오.
""",
    description="탄소 포집 방법을 연구합니다.",
    tools=[google_search],
    output_key="carbon_capture_result" # 합병 에이전트용 상태에
결과 저장
)
# --- 2. ParallelAgent 생성 (연구원들을 동시 실행) ---
# 이 에이전트는 연구원들의 동시 실행을 조정합니다.
# 모든 연구원들이 완료되고 결과를 상태에 저장하면 종료됩니다.
parallel_research_agent = ParallelAgent(
    name="ParallelWebResearchAgent",
    sub_agents=[researcher_agent_1, researcher_agent_2,
researcher_agent_3],
    description="정보를 수집하기 위해 여러 연구 에이전트를
병렬로 실행합니다."
)
# --- 3. 합병 에이전트 정의 (병렬 에이전트 *후* 실행) ---
# 이 에이전트는 병렬 에이전트가 상태에 저장한 결과를 가져와
단일 구조화된 응답으로 종합합니다.
merger_agent = LlmAgent(
    name="SynthesisAgent",
    model=GEMINI_MODEL, # 합성을 위해 더 강력한 모델이 필요할 수
있음
    instruction="""
당신은 연구 결과를 구조화된 보고서로 결합하는 AI 보조원입니다.
주요 작업은 다음 연구 요약을 명확하게 출처를 밝히며 종합하는
것입니다.
각 주제에 대해 머리글을 사용하여 응답을 구성하십시오.
보고서가 일관성 있고 핵심 요점을 원활하게 통합하도록
보장하십시오.
**중요: 당신의 최종 응답은 *오직* 아래 '입력 요약'에 제공된
정보에만 근거해야 합니다.
이 특정 요약 내용 외에는 외부 지식, 사실 또는 세부 정보를
추가하지 마십시오.**
* **재생 에너지:**
    {renewable_energy_result}
* **전기 자동차:**
    {ev_technology_result}
* **탄소 포집:**
    {carbon_capture_result}
**출력 형식:**
## 최신 지속 가능한 기술 발전에 대한 요약
### 재생 에너지 발견 사항
(RenewableEnergyResearcher의 발견 사항 기반)
[위에 제공된 재생 에너지 입력 요약만 종합하고 상세히 설명함.]
### 전기 자동차 발견 사항
(EVResearcher의 발견 사항 기반)
[위에 제공된 EV 입력 요약만 종합하고 상세히 설명함.]
### 탄소 포집 발견 사항
(CarbonCaptureResearcher의 발견 사항 기반)
[위에 제공된 탄소 포집 입력 요약만 종합하고 상세히 설명함.]
### 전반적인 결론
[*오직* 제시된 발견 사항만 연결하는 간략한(1-2문장) 결론 문장을
제공합니다.]
이 형식에 따라 구조화된 보고서만 출력하십시오. 이 구조 외부에
소개 또는 결론 문구를 포함하지 말고, 제공된 입력 요약 내용만
엄격하게 사용하십시오.
""",
    description="병렬 에이전트로부터 연구 결과를 종합하여
구조화되고 인용된 보고서를 작성하며, 제공된 입력에 엄격하게
근거합니다.",
    tools=[] # 병합에는 도구가 필요하지 않음
)
# --- 4. 순차적 에이전트 정의 (전체 흐름 조정) ---
# 이 메인 에이전트는 먼저 병렬 에이전트를 실행하여 상태를
채우고,
# 그런 다음 합병 에이전트를 실행하여 최종 출력을 생성합니다.
sequential_pipeline_agent = SequentialAgent(
    name="ResearchAndSynthesisPipeline",
    # 병렬 연구를 먼저 실행한 다음 합병
    sub_agents=[parallel_research_agent, merger_agent],
    description="병렬 연구를 조정하고 결과를 종합합니다."
)
root_agent = sequential_pipeline_agent

이 코드는 지속 가능한 기술 발전에 대한 연구 및 정보를 종합하기 위해 Google ADK를 사용하는 다중 에이전트 시스템을 정의합니다. 이 시스템은 전문 연구원 역할을 하는 세 가지 LlmAgent 인스턴스를 설정합니다. ResearcherAgent_1은 재생 에너지원에 중점을 두고, ResearcherAgent_2는 전기차 기술을 연구하며, ResearcherAgent_3은 탄소 포집 방법을 조사합니다. 각 연구원 에이전트는 GEMINI_MODEL을 사용하고 google_search 도구를 사용하도록 구성되어 있습니다. 이들은 발견 사항을 간결하게(1-2문장) 요약하고 이러한 요약을 세션 상태에 출력 키를 사용하여 저장하도록 지시받습니다.

그런 다음 ParallelAgent인 ParallelWebResearchAgent가 생성되어 이 세 연구원 에이전트를 병렬로 실행하도록 오케스트레이션합니다. 이를 통해 연구를 병렬로 수행하여 시간을 절약할 수 있습니다. ParallelAgent는 모든 하위 에이전트(연구원)가 완료되고 상태를 채우면 실행을 완료합니다.

다음으로, 연구 결과를 종합하는 MergerAgent(역시 LlmAgent)가 정의됩니다. 이 에이전트는 병렬 연구원들이 상태에 저장한 연구 요약을 입력으로 받아옵니다. 그 지침은 출력이 제공된 입력 요약에만 엄격하게 기반해야 하며 외부 지식 추가를 금지한다는 점을 강조합니다. MergerAgent는 결합된 발견 사항을 주제별 머리글이 있는 보고서로 구조화하고 간략한 전반적인 결론을 내리도록 설계되었습니다.

마지막으로, 전체 워크플로우를 조정하기 위해 ResearchAndSynthesisPipeline이라는 SequentialAgent가 생성됩니다. 기본 컨트롤러로서 이 메인 에이전트는 먼저 ParallelAgent를 실행하여 연구를 수행합니다. ParallelAgent가 완료되면 SequentialAgent는 수집된 정보를 종합하기 위해 MergerAgent를 실행합니다. sequential_pipeline_agent는 root_agent로 설정되어 이 다중 에이전트 시스템을 실행하는 진입점을 나타냅니다. 전체 프로세스는 여러 소스에서 정보를 병렬로 효율적으로 수집한 다음, 이를 단일 구조화된 보고서로 결합하도록 설계되었습니다.

병렬화 패턴에 대한 시각적 요약

그림 2: 병렬화 설계 패턴

주요 요점

다음은 주요 요점입니다.

  • 병렬화는 독립적인 작업을 동시에 실행하여 효율성을 개선하기 위한 패턴입니다.
  • 외부 I/O 작업(API 호출과 같은)과 관련된 작업을 처리할 때 특히 유용합니다.
  • 병렬화는 설계, 디버깅 및 시스템 로깅과 같은 핵심 개발 단계에 영향을 미칩니다.
  • LangChain의 LCEL에서는 RunnableParallel이 여러 실행 가능 항목을 나란히 실행하기 위한 핵심 구성 요소입니다.
  • Google ADK는 관리되는 에이전트 실행 환경을 통해 병렬화에 대한 네이티브 지원을 제공합니다.
  • 이 패턴은 복잡한 작업에 대한 전반적인 지연 시간을 줄이고 에이전트 시스템을 더 반응적으로 만드는 데 도움이 됩니다.

결론

병렬화 패턴은 독립적인 하위 작업을 동시에 실행하여 계산 워크플로우를 최적화하는 방법입니다. 이 접근 방식은 여러 모델 추론 또는 외부 서비스 호출과 관련된 복잡한 작업에서 전반적인 지연 시간을 줄입니다.

프레임워크는 이 패턴을 구현하기 위한 구별된 메커니즘을 제공합니다. LangChain에서는 RunnableParallel과 같은 구성 요소가 여러 처리 체인을 동시에 정의하고 실행하는 데 사용됩니다. 반대로, Google ADK와 같은 프레임워크는 코디네이터 에이전트가 다양한 하위 작업을 독립적인 에이전트에게 할당하고 이러한 에이전트가 병렬로 작동하도록 하여 다중 에이전트 위임에 의존합니다.

순차적(체이닝) 및 조건부(라우팅) 제어 흐름에 병렬 처리를 통합함으로써, 개발자는 다양한 작업을 효율적으로 관리하고 복잡한 워크플로우를 수행할 수 있는 정교하고 고성능의 계산 시스템을 구축할 수 있습니다.

참고 자료

병렬화 패턴 및 관련 개념에 대한 추가 자료는 다음과 같습니다.


4장: 반성

반성 패턴 개요

이전 장에서는 순차적 실행을 위한 프롬프트 체이닝, 동적 경로 선택을 위한 라우팅, 동시 태스크 실행을 위한 병렬화와 같은 기본 에이전트 패턴을 살펴보았습니다. 이러한 패턴은 에이전트가 더 유연하고 효율적으로 복잡한 작업을 수행할 수 있도록 합니다. 그러나 정교한 워크플로우를 통해서도 에이전트의 초기 출력이나 계획은 최적, 정확하거나 완전하지 않을 수 있습니다. 이때 반성(Reflection) 패턴이 중요해집니다.

반성 패턴은 에이전트가 자신의 작업, 출력 또는 내부 상태를 평가하고 그 평가를 사용하여 성능을 개선하거나 응답을 다듬는 것을 포함합니다. 이는 일종의 자체 수정 또는 자체 개선으로, 에이전트가 피드백, 내부 비판 또는 원하는 기준과의 비교를 기반으로 출력을 다듬거나 미래 행동을 조정할 수 있도록 합니다. 반성은 종종 초기 에이전트의 출력을 분석하는 별도의 에이전트의 지원을 받을 수 있습니다.

단일 체인에서 출력이 다음 단계로 직접 전달되거나, 라우팅이 경로를 선택하는 것과 달리, 반성은 피드백 루프를 도입합니다. 에이전트는 출력을 생성하는 데 그치지 않고, 그 출력을 검토(또는 그 출력을 생성한 프로세스)하고, 잠재적인 문제나 개선 영역을 식별한 다음, 그 통찰력을 사용하여 더 나은 버전을 생성하거나 자체 접근 방식을 수정합니다.

프로세스는 일반적으로 다음을 포함합니다.

    1. 실행: 에이전트가 작업을 수행하거나 초기 출력을 생성합니다.
    1. 평가/비판: 에이전트(종종 다른 LLM 호출 또는 규칙 세트 사용)가 이전 단계의 결과를 분석합니다. 이 평가는 사실적 정확성, 일관성, 스타일, 완전성 또는 기타 관련 기준에 대한 확인일 수 있습니다.
    1. 반성/개선: 비판을 바탕으로 에이전트는 어떻게 개선할지 결정합니다. 이는 개선된 출력을 생성하거나, 다음 단계에 대한 매개변수를 조정하거나, 심지어 전체 계획을 수정하는 것일 수 있습니다.
    1. 반복(선택 사항이지만 일반적): 개선된 출력 또는 조정된 접근 방식이 실행될 수 있으며, 만족스러운 결과가 나올 때까지 또는 중지 조건이 충족될 때까지 반성 프로세스가 반복될 수 있습니다.

반성 패턴의 핵심적이고 매우 효과적인 구현은 프로세스를 두 가지 별개의 논리적 역할, 즉 생산자(Producer)와 비평가(Critic)로 분리하는 것입니다. 이는 종종 “생성자-비평가(Generator-Critic)” 또는 “생산자-검토자(Producer-Reviewer)” 모델이라고 불립니다. 단일 에이전트가 자체 작업을 반성할 수 있지만, 두 가지 전문화된 에이전트(또는 뚜렷한 시스템 프롬프트를 가진 두 가지 별도 LLM 호출)를 사용하면 일반적으로 더 강력하고 편향되지 않은 결과를 얻을 수 있습니다.

    1. 생산자 에이전트: 이 에이전트의 주요 책임은 작업의 초기 실행을 수행하는 것입니다. 코드를 작성하든, 블로그 게시물을 작성하든, 계획을 수립하든, 콘텐츠 생성에 전적으로 집중합니다. 초기 프롬프트를 받아 첫 번째 버전의 출력을 생성합니다.
    1. 비평가 에이전트: 이 에이전트의 유일한 목적은 생산자가 생성한 출력을 평가하는 것입니다. 이는 다른 일련의 지침, 종종 별도의 페르소나(예: “당신은 선임 소프트웨어 엔지니어입니다”, “당신은 세심한 사실 확인자입니다”)를 받습니다. 비평가의 지침은 특정 기준(사실적 정확성, 코드 품질, 스타일 요구 사항, 완전성 등)에 따라 생산자의 작업을 분석하도록 안내합니다. 오류를 찾고, 개선 사항을 제안하고, 구조화된 피드백을 제공하는 데 중점을 둡니다.

이러한 관심사 분리는 에이전트가 자체 작업을 검토할 때 발생하는 “인지 편향”을 방지하기 때문에 강력합니다. 비평가 에이전트는 개선 영역이나 오류를 찾기 위해 전적으로 전념하여 출력에 새로운 관점을 가지고 접근합니다. 비평가의 피드백은 생산자 에이전트에게 다시 전달되어 개선된 버전의 출력을 생성하기 위한 지침으로 사용됩니다. LangChain 및 ADK 코드 예제 모두 이 두 가지 에이전트 모델을 구현하며, LangChain 예제는 비평가 페르소나를 만들기 위해 특정 “반성 프롬프트”를 사용하는 반면, ADK 예제는 생산자와 검토자 에이전트를 명시적으로 정의합니다.

반성 패턴의 구현은 피드백 결과에 따라 평가 결과에 기반한 조건부 전환 및 상태 관리를 지원하는 작업 흐름을 포함하는 워크플로우를 구조화할 필요가 있습니다. 단일 평가 및 개선 단계는 LangChain/LangGraph 또는 ADK 내에서 구현될 수 있지만, 진정한 반복적 반성은 에이전트의 행동에 대한 더 복잡한 조정을 필요로 합니다.

반성 패턴은 고품질 출력을 생성하고, 미묘한 작업을 처리하며, 자기 인식 및 적응성의 정도를 나타내는 에이전트를 구축하는 데 중요합니다. 이는 에이전트를 단순한 지침 실행자를 넘어 더 정교한 형태의 문제 해결 및 콘텐츠 생성으로 이동시킵니다.

더 나아가, 반성 패턴의 효과는 LLM이 대화의 메모리를 유지할 때 (8장 참조) 크게 향상됩니다. 이 대화 기록은 평가 단계에 대한 중요한 컨텍스트를 제공하여, 에이전트가 출력을 격리해서만 평가하는 것이 아니라 이전 상호 작용, 사용자 피드백 및 진화하는 목표라는 배경을 바탕으로 평가할 수 있도록 합니다. 이를 통해 에이전트는 과거의 비판으로부터 학습하고 과거의 오류를 피하여 보다 지능적이고 상황을 인식하는 개선을 이룰 수 있습니다. 메모리가 없으면 각 반성은 자체 포함된 이벤트가 되지만, 메모리가 있으면 각 주기가 이전 주기를 기반으로 구축되어 보다 지능적이고 맥락을 인식하는 개선으로 이어지는 누적 프로세스가 됩니다.

실용적인 응용 및 사용 사례

반성 패턴은 출력 품질, 정확성 또는 복잡한 제약 조건 준수가 중요한 시나리오에서 가치가 있습니다.

  1. 창의적인 글쓰기 및 콘텐츠 생성: 생성된 텍스트, 이야기, 시 또는 마케팅 카피를 개선합니다.
  • 사용 사례: 블로그 게시물을 작성하는 에이전트.
  • 반성: 초안 생성, 흐름, 어조 및 명확성에 대한 비판, 그런 다음 비판을 기반으로 다시 작성. 게시물이 품질 표준을 충족할 때까지 반복합니다.
  • 이점: 더 세련되고 효과적인 콘텐츠를 생성합니다. 2. 코드 생성 및 디버깅: 코드를 작성하고, 오류를 식별하고, 이를 수정합니다.
  • 사용 사례: Python 함수를 작성하는 에이전트.
  • 반성: 초기 코드를 작성하고, 테스트 또는 정적 분석을 실행하고, 오류 또는 비효율성 영역을 식별한 다음, 발견 사항을 기반으로 코드를 수정합니다.
  • 이점: 더 강력하고 기능적인 코드를 생성합니다. 3. 복잡한 문제 해결: 다단계 추론 작업에서 중간 단계 또는 제안된 솔루션을 평가합니다.
  • 사용 사례: 목표 달성을 위해 일련의 작업을 계획하는 에이전트.
  • 반성: 단계를 제안하고, 그것이 해결책에 더 가까워지는지 또는 모순을 도입하는지 평가하고, 필요한 경우 단계를 되돌리거나 다른 단계를 선택합니다.
  • 이점: 더 효과적이고 현실적인 계획을 개발합니다. 4. 요약 및 정보 종합:
  • 정확성, 완전성 및 간결성을 위해 요약을 개선합니다.
  • 사용 사례: 긴 문서를 요약하는 에이전트.
  • 반성: 초기 요약을 생성하고, 원본 문서의 주요 항목과 비교하여 정확성을 개선하거나 누락된 정보를 포함하도록 요약을 수정합니다.
  • 이점: 더 정확하고 포괄적인 요약을 만듭니다.

5. 계획 및 전략:

제안된 계획을 평가하고 잠재적인 결함이나 개선 사항을 식별합니다.

  • 사용 사례: 목표 달성을 위해 일련의 작업을 계획하는 에이전트.
  • 반성: 계획을 생성하고, 제약 조건에 대해 실행 가능성을 평가하거나 시뮬레이션하고, 평가를 기반으로 계획을 수정합니다.
  • 이점: 더 효과적이고 현실적인 계획을 개발합니다.

6. 대화 에이전트:

컨텍스트를 유지하거나 오해를 수정하거나 응답 품질을 개선하기 위해 이전 대화 턴을 검토합니다.

  • 사용 사례: 고객 지원 챗봇.
  • 반성: 사용자 응답 후, 대화 기록과 마지막으로 생성된 메시지를 검토하여 일관성을 유지하고 사용자의 최신 입력을 정확하게 다룹니다.
  • 이점: 더 자연스럽고 효과적인 대화로 이어집니다.

반성은 에이전트 시스템에 메타인지 계층을 추가하여 자체 출력 및 프로세스로부터 학습할 수 있게 하며, 이는 더 지능적이고, 신뢰할 수 있으며, 고품질의 결과를 초래합니다.

실습 코드 예제 (LangChain)

완전한 반복적 반성 프로세스의 구현은 상태 관리 및 순환 실행 메커니즘을 필요로 합니다. 이는 LangGraph와 같은 그래프 기반 프레임워크나 사용자 지정 절차 코드에서 기본적으로 처리되지만, 단일 반성 주기의 기본 원칙은 LCEL(LangChain Expression Language)을 사용하여 시연될 수 있습니다.

이 예제는 LangChain 라이브러리와 OpenAI의 GPT-4o 모델을 사용하여 반복적인 반성 루프를 구현합니다. 이 루프는 숫자의 팩토리얼을 계산하는 Python 함수를 생성하고 개선하여 반복적으로 개선합니다. 이 프로세스는 작업 프롬프트로 시작하여 초기 코드를 생성한 다음, 시뮬레이션된 선임 소프트웨어 엔지니어 역할에서 생성된 비판을 기반으로 코드를 반복적으로 반성하여, 비평 단계에서 코드가 완벽하다고 결정할 때까지 또는 최대 반복 횟수에 도달할 때까지 코드를 개선합니다. 마지막으로, 결과로 개선된 코드를 인쇄합니다.

먼저, 필요한 라이브러리를 설치해야 합니다.

pip install langchain langchain-community langchain-openai

또한, 선택한 언어 모델 서비스(예: OpenAI, Google Gemini, Anthropic)에 대한 API 키를 로컬 환경에 구성해야 합니다.

import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import SystemMessage, HumanMessage
# --- 구성 ---
# .env 파일에서 환경 변수 로드 (OPENAI_API_KEY용)
load_dotenv()
# API 키가 설정되어 있는지 확인
if not os.getenv("OPENAI_API_KEY"):
   raise ValueError(".env 파일에서 OPENAI_API_KEY를 찾을 수
없습니다. 추가해 주십시오.")
# 채팅 LLM 초기화. 더 나은 추론을 위해 gpt-4o를 사용합니다.
# 더 결정론적인 출력을 위해 낮은 온도가 사용됩니다.
llm = ChatOpenAI(model="gpt-4o", temperature=0.1)
def run_reflection_loop():
   """
   Python 함수를 점진적으로 개선하기 위해 다단계 AI 반성
루프를 시연합니다.
   """
   # --- 핵심 작업 ---
   task_prompt = """
   당신의 임무는 `calculate_factorial`라는 Python 함수를
작성하는 것입니다.
   이 함수는 다음을 수행해야 합니다.
   1. 'n'이라는 단일 정수를 입력으로 받아들입니다.
   2. 팩토리얼(n!)을 계산합니다.
   3. 함수가 수행하는 작업을 설명하는 명확한 독스트링을 포함합니다.
   4. 엣지 사례 처리: 0의 팩토리얼은 1입니다.
   5. 잘못된 입력 처리: 입력이 음수이면 ValueError를 발생시킵니다.
   """
   # --- 반성 루프 ---
   max_iterations = 3
   current_code = ""
   # 각 단계에서 컨텍스트를 제공하기 위해 대화 기록을 생성합니다.
   message_history = [HumanMessage(content=task_prompt)]
   for i in range(max_iterations):
       print("\n" + "="*25 + f" 반성 루프: {i + 1}차 시도 " +
"="*25)
       # --- 1. 생성 / 개선 단계 ---
       # 첫 번째 반복에서 생성하고, 후속 반복에서는 개선합니다.
       if i == 0:
           print("\n>>> 1단계: 초기 코드 생성...")
           # 첫 번째 메시지는 단순히 작업 프롬프트입니다.
           response = llm.invoke(message_history)
           current_code = response.content
       else:
           print("\n>>> 1단계: 이전 비판을 기반으로 코드 개선...")
           # 메시지 기록에는 이제 작업, 마지막 코드 및 마지막
비판이 포함됩니다.
           # 이전 비판 사항을 적용하도록 모델에 지시합니다.
           message_history.append(HumanMessage(content="비판 사항을
사용하여 코드를 개선하십시오."))
           response = llm.invoke(message_history)
           current_code = response.content
       print("\n--- 생성된 코드 (v" + str(i + 1) + ") ---\n" +
current_code)
       message_history.append(response) # 생성된 코드를 기록에
추가
       # --- 2. 반성 단계 ---
       print("\n>>> 2단계: 생성된 코드 반성...")
       # 비평가 에이전트에 대한 특정 프롬프트를 생성합니다.
       # 이는 모델이 선임 코드 검토자 역할을 하도록 요청합니다.
reflector_prompt = [
           SystemMessage(content="""
               당신은 Python의 전문가인 선임 소프트웨어
엔지니어입니다.
               당신의 역할은 철저한 코드 검토를 수행하는 것입니다.
               제공된 Python 코드를 원래 작업 요구 사항을 기반으로
엄격하게 평가하십시오.
               버그, 스타일 문제, 누락된 엣지 사례 및 개선 영역을
찾으십시오.
               코드가 완벽하고 모든 요구 사항을 충족하는 경우,
'CODE_IS_PERFECT'라는 단일 문구로 응답하십시오.
               그렇지 않은 경우 비판 목록을 글머리 기호로
제공하십시오.
           """),
           HumanMessage(content=f"원본 작업:\n{task_prompt}\n\n검토할
코드:\n{current_code}")
       ]
       critique_response = llm.invoke(reflector_prompt)
       critique = critique_response.content
       # --- 3. 중지 조건 ---
       if "CODE_IS_PERFECT" in critique:
           print("\n--- 비판 ---n추가적인 비판이 없습니다. 코드가
만족스럽습니다.")
           break
       print("\n--- 비판 ---n" + critique)
       # 다음 개선 루프를 위해 비판을 기록에 추가합니다.
       message_history.append(HumanMessage(content=f"이전 코드에
대한 비판:\n{critique}"))
   print("\n" + "="*30 + " 최종 결과 " + "="*30)
   print("\n반성 프로세스 후 최종 개선된 코드:\n")
   print(current_code)
if __name__ == "__main__":
   run_reflection_loop()

이 코드는 API 키 로드, 강력한 언어 모델 초기화 등 환경 설정으로 시작하여 GPT-4o와 같은 강력한 언어 모델을 사용하여 반복적인 출력 개선 루프를 시연합니다. 핵심 작업은 숫자 팩토리얼을 계산하고, docstring을 포함하며, 엣지 사례를 처리하고, 음수 입력에 대해 오류를 처리하는 Python 함수를 작성하는 것입니다. run_reflection_loop 함수는 반복적인 개선 프로세스를 조정합니다. 첫 번째 반복에서 모델은 작업 프롬프트를 기반으로 초기 코드를 생성합니다. 후속 반복에서는 이전 단계의 비판을 기반으로 코드를 개선합니다.

별도의 “비평가(reflector)” 역할(역시 언어 모델이 수행하지만 다른 시스템 프롬프트를 가짐)은 선임 소프트웨어 엔지니어처럼 작동하여 원래 작업 요구 사항에 대해 생성된 코드를 비판합니다. 이 비판은 문제에 대한 글머리 기호 목록 또는 문제가 발견되지 않았음을 나타내는 ‘CODE_IS_PERFECT’라는 문구로 제공됩니다. 비판에서 완벽하다고 표시되거나 최대 반복 횟수에 도달할 때까지 루프가 계속됩니다. 생성/개선 및 반성 단계 모두에 대한 컨텍스트를 제공하기 위해 대화 기록이 각 단계에서 유지 및 전달됩니다. 최종적으로 스크립트는 루프가 완료된 후 마지막으로 생성된 코드 버전을 출력합니다.

실습 코드 예제 (ADK)

이제 Google ADK를 사용하여 이 개념을 구현하는 방법을 보여주는 개념적 코드 예제를 살펴보겠습니다. 특히, 이 코드는 생성자-비평가 구조를 사용하여 초기 결과/계획을 생성하는 구성 요소와 이 초기 출력에 대한 비판적 피드백을 제공하여 생성자가 보다 정교하고 정확한 최종 출력으로 안내하는 다른 구성 요소를 보여줍니다.

from google.adk.agents import SequentialAgent, LlmAgent
# 첫 번째 에이전트가 초기 초안을 생성합니다.
generator = LlmAgent(
   name="DraftWriter",
   description="주어진 주제에 대한 초기 초안 콘텐츠를 생성합니다.",
   instruction="주제에 대한 짧고 유익한 단락을 작성하십시오.",
   output_key="draft_text" # 출력은 이 상태 키에 저장됩니다.
)
# 두 번째 에이전트는 첫 번째 에이전트의 초안을 비판합니다.
reviewer = LlmAgent(
   name="FactChecker",
   description="주어진 텍스트의 사실적 정확성을 검토하고 구조화된
비판을 제공합니다.",
   instruction="""
당신은 세심한 사실 확인자입니다.
1. 상태 키 'draft_text'에 제공된 텍스트를 읽으십시오.
2. 모든 주장의 사실적 정확성을 신중하게 확인하십시오.
3. 최종 출력은 두 가지 키를 포함하는 딕셔너리를 포함해야 합니다.
      - "status": 문자열, "ACCURATE" 또는 "INACCURATE" 중 하나.
      - "reasoning": 상태에 대한 명확한 설명을 제공하는 문자열,
발견된 특정 문제가 있으면 인용함.
""",
   output_key="review_output" # 구조화된 딕셔너리가 여기에
저장됩니다.
)
# SequentialAgent는 생성자가 검토자보다 먼저 실행되도록
보장합니다.
review_pipeline = SequentialAgent(
   name="WriteAndReview_Pipeline",
   sub_agents=[generator, reviewer]
)
# 실행 흐름:
# 1. generator 실행 -> state['draft_text']에 단락 저장.
# 2. reviewer 실행 -> state['draft_text']를 읽고 딕셔너리 출력을
state['review_output']에 저장.

이 코드는 Google ADK를 사용하여 텍스트를 생성하고 검토하는 파이프라인을 보여줍니다. 이들은 두 LlmAgent 인스턴스를 정의합니다. generator는 주어진 주제에 대한 초기 초안 단락을 생성하도록 설계되었습니다. 이 에이전트는 간결하고 유익한 글을 작성하도록 지시받고 출력을 draft_text 상태 키에 저장합니다. reviewer 에이전트는 generator가 생성한 텍스트에 대한 사실 확인자 역할을 합니다. 이 에이전트는 draft_text에서 텍스트를 읽고 사실적 정확성을 확인하도록 지시받습니다. 검토자의 출력은 두 가지 키를 포함하는 구조화된 딕셔너리입니다. status는 텍스트가 “ACCURATE” 또는 “INACCURATE”인지를 나타내며, reasoning은 상태에 대한 설명을 제공합니다. 이 딕셔너리는 review_output 상태 키에 저장됩니다. 이 두 에이전트의 실행 순서를 관리하기 위해 Review_pipeline이라는 SequentialAgent가 생성됩니다. 전반적인 실행 흐름은 생성자가 텍스트를 생성하고 이를 상태에 저장한 다음, 검토자가 이 텍스트를 상태에서 읽고 사실 확인을 수행하고 결과를 상태에 다시 저장한다는 것입니다. 이 파이프라인을 통해 별도의 에이전트를 사용하여 구조화된 콘텐츠 생성 및 검토 프로세스를 달성할 수 있습니다.

시각적 요약

그림 1: 반성 설계 패턴, 자체 반성

그림 2: 반성 설계 패턴, 생산자 및 비평가 에이전트

주요 요점

다음은 기억해야 할 핵심 사항입니다.

  • 반성 패턴의 주요 이점은 출력 자체를 반복적으로 수정하고 개선하여 품질, 정확성 및 복잡한 지침 준수를 크게 향상시킬 수 있다는 것입니다.

  • 실행, 평가/비판, 개선의 피드백 루프를 포함합니다. 반성은 고품질, 정확하거나 미묘한 출력이 필요한 작업에 필수적입니다.

  • 강력한 구현은 별도의 에이전트(또는 프롬프트 역할)가 초기 출력을 평가하여 객관성을 높이고 더 전문화되고 구조화된 피드백을 허용하는 생산자-비평가 모델입니다.

  • 그러나 이러한 이점은 증가된 지연 시간과 계산 비용을 수반하며, 이는 시간이 민감한 애플리케이션에는 최적이 아닙니다. 또한 이 패턴은 대화 기록을 포함하여 각 반복에서 대화 기록이 확장되므로 메모리 집약적입니다.

  • 완전한 반복적 반성은 일반적으로 상태 저장 워크플로우(LangGraph와 같은)를 필요로 하지만, 단일 반성 단계는 출력을 비판에 전달하고 후속 개선을 위해 LCEL을 사용하여 효과적으로 구현될 수 있습니다.

  • Google ADK는 순차적 워크플로우를 사용하여 한 에이전트의 출력을 다른 에이전트가 비판하도록 하여 정교한 피드백 및 개선 단계를 가능하게 합니다.

  • 이 패턴을 통해 에이전트는 자체 수정 능력을 갖고 시간이 지남에 따라 성능을 향상시킬 수 있습니다.

결론

결론적으로, 반성 패턴은 에이전트 워크플로우 내에서 자체 수정을 위한 중요한 메커니즘을 제공하여 단일 패스 실행을 넘어 반복적인 개선을 가능하게 합니다. 이는 에이전트가 출력을 생성하고, 특정 기준에 따라 이를 평가하고, 해당 평가를 사용하여 개선된 결과를 생성하는 피드백 루프를 생성함으로써 달성됩니다. 이러한 평가는 에이전트 자신이 수행할 수도 있고(자체 반성), 종종 더 효과적으로 별도의 비평가 에이전트가 수행하여 객관성을 높이고 구조화된 피드백을 제공하도록 할 수도 있습니다.

완전히 자율적인 다단계 반성 프로세스는 상태 관리를 위한 강력한 아키텍처를 필요로 하지만, 그 핵심 원칙은 단일 생성-비판-개선 주기로 효과적으로 시연될 수 있습니다. 제어 구조로서 반성은 다른 기본 패턴과 통합되어 더 강력하고 기능적으로 복잡한 에이전트형 시스템을 구성할 수 있습니다.

참고 자료

다음은 반성 패턴 및 관련 개념에 대한 추가 자료입니다.

5장: 도구 사용 (함수 호출)

이전 장에서 우리는 언어 모델과 내부 워크플로우 간의 상호 작용을 조정하고 정보 흐름을 관리하는 데 주로 중점을 둔 에이전트 패턴(체이닝, 라우팅, 병렬화, 반성)을 논의했습니다. 그러나 에이전트가 진정으로 유용하고 외부 시스템 또는 실제 세계와 상호 작용하려면 도구를 사용할 수 있는 기능이 필요합니다.

함수 호출이라고도 하는 도구 사용(Tool Use) 패턴은 에이전트가 외부 API, 데이터베이스, 서비스 또는 심지어 코드를 실행하는 것과 상호 작용할 수 있도록 합니다. 이는 에이전트의 핵심인 LLM이 사용자 요청 또는 현재 작업 상태에 따라 특정 외부 함수를 호출할 시점과 방법을 결정할 수 있도록 합니다.

프로세스는 일반적으로 다음을 포함합니다.

    1. 도구 정의: 외부 함수 또는 기능이 정의되고 LLM에 설명됩니다. 이 설명에는 함수의 목적, 이름 및 허용하는 매개변수와 해당 유형 및 설명이 포함됩니다.
    1. LLM 결정: LLM은 사용자 요청과 사용 가능한 도구 정의를 수신합니다. 요청에 대한 이해와 도구에 따라 LLM은 요청을 충족하기 위해 하나 이상의 도구를 호출해야 하는지 결정합니다.
    1. 함수 호출 생성: LLM이 도구 사용을 결정하면, 요청된 도구의 이름과 제공된 인수를 지정하는 구조화된 출력(종종 JSON 개체)을 생성합니다.
    1. 도구 실행: 에이전트 프레임워크 또는 오케스트레이션 계층은 이 구조화된 출력을 가로채고, 요청된 도구를 식별하고, 제공된 인수로 실제 외부 함수를 실행합니다.
    1. 관찰/결과: 도구 실행의 출력 또는 결과가 에이전트에 반환됩니다.
    1. LLM 처리(선택 사항이지만 일반적): LLM은 도구 출력을 컨텍스트로 수신하고 이를 사용하여 사용자에게 최종 응답을 구성하거나 다음 단계(다른 도구 호출, 반성 또는 최종 답변 제공)를 결정하는 데 사용합니다.

이 패턴은 LLM의 훈련 데이터 및 내부 기능의 한계를 깨고 최신 정보에 액세스하거나, 사용자별 데이터와 상호 작용하거나, 실제 세계의 조치를 트리거할 수 있도록 하기 때문에 근본적입니다. 함수 호출은 LLM의 추론 기능과 외부에서 사용할 수 있는 방대한 기능 배열 사이의 격차를 연결하는 기술적 메커니즘입니다.

“함수 호출”이 특정, 사전 정의된 코드 함수를 호출하는 것을 적절하게 설명하지만, 에이전트의 기능이 단순한 함수 실행을 넘어 확장될 수 있음을 인정하는 더 광범위한 “도구 호출” 개념을 고려하는 것이 유용합니다. “도구”는 전통적인 함수일 수 있지만, 외부 지식 기반에 대한 API 요청이나 다른 전문화된 에이전트에 대한 지침일 수도 있습니다. 이러한 관점은, 예를 들어, 기본 에이전트가 복잡한 데이터 분석 작업을 전용 “분석 에이전트”에 위임하거나 자체 API를 통해 외부 지식 기반을 쿼리하도록 하는 더 정교한 시스템을 구상할 수 있도록 합니다. “도구 호출”에 대한 생각은 이러한 다양한 디지털 리소스 및 다른 지능형 개체 생태계 전반에 걸쳐 에이전트의 전체 잠재력을 포착하는 데 더 적합합니다.

LangChain, LangGraph 및 Google Agent Developer Kit (ADK)와 같은 프레임워크는 도구를 정의하고 에이전트 워크플로우에 통합하기 위한 강력한 지원을 제공하며, 종종 Gemini 또는 OpenAI 시리즈와 같은 최신 LLM의 기본 함수 호출 기능을 활용합니다. 이러한 프레임워크의 “캔버스”에서 개발자는 도구를 정의한 다음 에이전트(일반적으로 LLM 에이전트)를 구성하여 이러한 도구를 인식하고 사용할 수 있도록 합니다.

도구 사용은 외부적으로 인식하고 상호 작용할 수 있는 강력하고 상호 작용적인 에이전트를 구축하는 데 초석이 되는 패턴입니다(그림 1 참조).

그림 1: 에이전트가 도구를 사용하는 일부 예시

실용적인 응용 및 사용 사례

도구 사용 패턴은 에이전트가 텍스트 생성 이상으로 외부 시스템과 상호 작용하거나 동적 정보를 검색해야 하는 거의 모든 시나리오에 적용 가능합니다.

1. 외부 소스에서 정보 검색: LLM의 훈련 데이터에 없는 실시간 데이터 또는 정보에 액세스합니다.
  • 사용 사례: 날씨 에이전트.
    • 도구: 위치를 입력받아 현재 날씨 조건을 반환하는 날씨 API.
    • 에이전트 흐름: 사용자가 “런던 날씨는 어때?”라고 묻습니다. LLM은 날씨 도구의 필요성을 식별하고, “런던”으로 도구를 호출합니다. 도구는 데이터를 반환하고, LLM은 데이터를 사용자 친화적인 응답으로 형식화합니다.
2. 데이터베이스 및 API와 상호 작용: 구조화된 데이터에 대한 쿼리, 업데이트 또는 기타 작업 수행.
  • 사용 사례: 전자상거래 에이전트.
    • 도구: 제품 재고 확인, 주문 상태 가져오기 또는 결제 처리를 위한 API 호출.
    • 에이전트 흐름: 사용자가 “제품 X 재고 있나요?”라고 묻습니다. LLM은 재고 API를 호출하고, 도구는 재고 수를 반환하며, LLM은 사용자에게 재고 상태를 알려줍니다.
3. 계산 및 데이터 분석 수행:

외부 계산기, 데이터 분석 라이브러리 또는 통계 도구 사용.

  • 사용 사례: 금융 에이전트.
    • 도구: 계산기 함수, 주식 시장 데이터 API, 스프레드시트 도구.
    • 에이전트 흐름: 사용자가 “AAPL의 현재 가격은 얼마이며 100주를 150달러에 샀을 때 잠재적 이익을 계산해 줘?”라고 묻습니다. LLM은 주식 API를 호출하여 현재 가격을 가져온 다음, 계산기 도구를 호출하여 결과를 얻고 응답을 형식화합니다.
4. 통신 전송:

이메일, 메시지 전송 또는 외부 통신 서비스에 대한 API 호출 수행.

  • 사용 사례: 개인 비서 에이전트.
    • 도구: 이메일 전송 API.
    • 에이전트 흐름: 사용자가 “내일 회의에 대해 존에게 이메일을 보내”라고 말합니다. LLM은 수신자, 제목, 요청에서 추출된 본문을 사용하여 이메일 도구를 호출합니다.
5. 코드 실행:

특정 작업을 수행하기 위해 코드 스니펫을 실행합니다.

  • 사용 사례: 코딩 지원 에이전트.
    • 도구: 코드 인터프리터.
    • 에이전트 흐름: 사용자가 Python 스니펫을 제공하고 “이 코드는 무엇을 하나요?”라고 묻습니다. LLM은 인터프리터 도구를 사용하여 코드를 실행하고 그 출력을 분석합니다.
6. 다른 시스템 또는 장치 제어:

스마트 홈 장치, IoT 플랫폼 또는 기타 연결된 시스템과 상호 작용합니다.

  • 사용 사례: 스마트 홈 에이전트.
    • 도구: 스마트 조명을 제어하는 API.
    • 에이전트 흐름: 사용자가 “거실 조명 꺼”라고 말합니다. LLM은 명령과 대상 장치를 사용하여 스마트 홈 도구를 호출합니다.

도구 사용은 언어 모델을 텍스트 생성기에서 디지털 또는 잠재적으로 물리적 세계에서 행동하고 추론할 수 있는 에이전트로 변환하는 것입니다.

실습 코드 예제 (LangChain)

LangChain 프레임워크 내에서 도구 사용을 구현하는 것은 두 단계 프로세스입니다. 먼저, Python 함수 또는 기타 실행 가능한 구성 요소를 캡슐화하여 간단한 함수를 정의합니다. 그런 다음 이러한 도구가 언어 모델에 바인딩되어, 사용자 쿼리를 충족하기 위해 외부 함수 호출을 생성해야 할 때 모델이 해당 기능을 활용할 수 있도록 구성됩니다.

다음 구현은 이 원칙을 시연합니다. 먼저 간단한 함수를 정의하여 정보 검색 도구를 시뮬레이션합니다. 그런 다음 에이전트가 구성되고 사용자 입력에 응답하여 이 도구를 활용하도록 구성됩니다. 이 예제의 실행에는 필수적인 코어 LangChain 라이브러리 및 모델 공급자 패키지를 설치해야 합니다. 또한, 로컬 환경에서 구성된 선택한 언어 모델 서비스(예: OpenAI, Google Gemini, Anthropic)로 적절한 인증이 필요한 전제 조건입니다.

import os, getpass
import asyncio
import nest_asyncio
from typing import List
from dotenv import load_dotenv
import logging
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool as langchain_tool
from langchain.agents import create_tool_calling_agent, AgentExecutor
# UNCOMMENT
# 사용자에게 안전하게 요청하고 환경 변수로 API 키를 설정하십시오
os.environ["GOOGLE_API_KEY"] = getpass.getpass("Google API 키를
입력하십시오: ")
os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API 키를
입력하십시오: ")
try:
  # 함수/도구 호출 기능이 있는 모델이 필요합니다.
  llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash",
temperature=0)
  print(f"✅ 언어 모델 초기화: {llm.model}")
except Exception as e:
  print(f" 언어 모델 초기화 오류: {e}")
  llm = None
# --- 도구 정의 ---
@langchain_tool
def search_information(query: str) -> str:
  """
  주어진 주제에 대한 사실 정보를 제공합니다. '프랑스의 수도' 또는
'런던 날씨?'와 같은 구문에 대한 답변을 찾는 데 이 도구를
사용하십시오.
  """
  print(f"\n---  도구 호출됨: 쿼리 '{query}'에 대한
search_information ---")
  # 사전 정의된 결과가 있는 검색 도구 시뮬레이션.
  simulated_results = {
      "weather in london": "런던의 날씨는 현재 흐리고 온도는
15°C입니다.",
"capital of france": "프랑스의 수도는 파리입니다.",
      "population of earth": "지구의 추정 인구는 약 80억 명입니다.",
      "tallest mountain": "에베레스트 산은 해발 가장 높은 산입니다.",
      "default": f"'{query}'에 대한 시뮬레이션된 검색 결과: 특정
정보를 찾을 수 없지만 주제는 흥미로워 보입니다."
  }
  result = simulated_results.get(query.lower(),
simulated_results["default"])
  print(f"--- 도구 결과: {result} ---")
  return result
tools = [search_information]
# --- 도구 호출 에이전트 생성 ---
if llm:
  # 이 프롬프트 템플릿은 에이전트의 내부 단계에 대한
`agent_scratchpad` 플레이스홀더가 필요합니다.
  agent_prompt = ChatPromptTemplate.from_messages([
      ("system", "당신은 도움이 되는 조수입니다."),
      ("human", "{input}"),
      ("placeholder", "{agent_scratchpad}"),
  ])
  # 에이전트 생성, LLM, 도구 및 프롬프트 연결.
  agent = create_tool_calling_agent(llm, tools, agent_prompt)
  # AgentExecutor는 에이전트를 호출하고 선택된 도구를 실행하는
런타임입니다.
  # 도구는 이미 에이전트에 바인딩되었으므로 여기에 'tools'
인수가 필요하지 않습니다.
  agent_executor = AgentExecutor(agent=agent, verbose=True,
tools=tools)
async def run_agent_with_tool(query: str):
  """에이전트 실행기를 쿼리로 호출하고 최종 응답을 인쇄합니다."""
  print(f"\n--- 도구와 함께 에이전트 실행: '{query}' ---")
  try:
      response = await agent_executor.ainvoke({"input": query})
      print("\n--- ✅ 최종 에이전트 응답 ---")
      print(response["output"])
  except Exception as e:
      print(f"\n에이전트 실행 중 오류가 발생했습니다: {e}")
async def main():
  """모든 에이전트 쿼리를 동시에 실행합니다."""
  tasks = [
      run_agent_with_tool("프랑스의 수도는 무엇입니까?"),
      run_agent_with_tool("런던 날씨는 어떻습니까?"),
      run_agent_with_tool("개에 대해 알려줘.") # 기본 도구 응답을
트리거해야 함
  ]
  await asyncio.gather(*tasks)
nest_asyncio.apply()
asyncio.run(main())

이 코드는 LangChain 라이브러리와 Google Gemini 모델을 사용하여 도구 호출 에이전트를 설정하는 방법을 보여줍니다. 특정 쿼리에 대한 사전 정의된 응답과 기타 쿼리에 대한 기본 응답을 시뮬레이션하는 사실적 답변을 제공하는 간단한 search_information 도구를 정의합니다. ChatGoogleGenerativeAI 모델이 초기화되어 도구 호출 기능이 있음을 확인합니다. 에이전트의 상호 작용을 안내하기 위해 ChatPromptTemplate이 생성됩니다. create_tool_calling_agent 함수를 사용하여 언어 모델, 도구 및 프롬프트를 결합하여 에이전트를 만듭니다. AgentExecutor가 에이전트의 실행 및 도구 호출을 관리하도록 설정됩니다. run_agent_with_tool 비동기 함수는 주어진 쿼리로 에이전트를 호출하고 결과를 인쇄하도록 정의됩니다. main 비동기 함수는 여러 쿼리를 준비하며, 이 쿼리들은 검색 도구의 특정 및 기본 응답을 모두 테스트하도록 설계되었습니다. 마지막으로, asyncio.run(main()) 호출은 모든 에이전트 작업을 동시에 실행합니다. 이 코드는 LLM 초기화 성공 확인을 포함하여 견고성을 위한 확인 기능을 포함합니다.

실습 코드 예제 (CrewAI)

이 코드는 CrewAI 프레임워크 내에서 함수 호출(도구)을 구현하는 방법을 보여주는 실용적인 예입니다. 이 예제는 시뮬레이션된 주가를 이 도구를 사용하여 검색하는 에이전트가 있는 간단한 시나리오를 설정합니다. 이 예제는 특히 도구를 사용하여 재정 정보를 검색하는 에이전트의 작업을 보여줍니다.

# pip install crewai langchain-openai
import os
from crewai import Agent, Task, Crew
from crewai.tools import tool
import logging
# --- 모범 사례: 로깅 구성 ---
# 기본 로깅 설정을 통해 승무원의 실행 추적에 도움이 됩니다.
logging.basicConfig(level=logging.INFO, format='%(asctime)s -
%(levelname)s - %(message)s')
# --- API 키 설정 ---
# 프로덕션의 경우, 런타임에 로드되거나 비밀 관리자를 통해 키
관리가 권장됩니다.
#
# 선택한 LLM 공급업체에 대한 환경 변수 설정 (예: OPENAI_API_KEY)
# os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY"
# os.environ["OPENAI_MODEL_NAME"] = "gpt-4o"
# --- 1. 리팩토링된 도구: 깨끗한 데이터 반환 ---
# 도구는 이제 원시 데이터(float)를 반환하거나 표준 Python
오류를 발생시킵니다.
# 이렇게 하면 재사용성이 향상되고 에이전트가 결과를 적절하게
처리하도록 강제됩니다.
@tool("주가 조회 도구")
def get_stock_price(ticker: str) -> float:
   """
   주어진 주식 티커 기호에 대한 최신 시뮬레이션 주가를
가져옵니다.
   가격을 float으로 반환합니다. 티커를 찾을 수 없는 경우 ValueError를
발생시킵니다.
   """
   logging.info(f"도구 호출: 티커 '{ticker}'에 대한
get_stock_price")
   simulated_prices = {
       "AAPL": 178.15,
       "GOOGL": 1750.30,
       "MSFT": 425.50,
   }
   price = simulated_prices.get(ticker.upper())
   if price is not None:
       return price
   else:
       # 특정 오류를 발생시키는 것이 문자열을 반환하는 것보다
낫습니다.
       # 에이전트는 예외를 처리하고 다음 조치를 결정할 수 있습니다.
       raise ValueError(f"티커 '{ticker.upper()}'에 대한 시뮬레이션된
가격을 찾을 수 없습니다.")
# --- 2. 에이전트 정의 ---
# 에이전트 정의는 동일하게 유지되지만, 이제 개선된 도구를
활용합니다.
financial_analyst_agent = Agent(
 role='선임 금융 분석가',
 goal='제공된 도구를 사용하여 주식 데이터를 분석하고 주요 가격을
보고합니다.',
 backstory="당신은 주식 정보를 찾기 위해 데이터 소스를 능숙하게
사용하는 숙련된 금융 분석가입니다. 당신은 명확하고 직접적인
답변을 제공합니다.",
 verbose=True,
 tools=[get_stock_price],
 # 위임 허용은 간단한 작업에는 필요하지 않지만 유용할 수
있습니다.
 allow_delegation=False,
)
# --- 3. 세련된 작업: 더 명확한 지침 및 오류 처리 ---
# 작업 설명은 에이전트가 성공적인 데이터 검색과 잠재적인 오류
모두에 반응하는 방법을 안내하도록 더 구체적입니다.
analyze_aapl_task = Task(
 description=(
     "Apple(티커: AAPL)의 현재 시뮬레이션된 주가는 얼마입니까? "
     "이를 찾기 위해 '주가 조회 도구'를 사용하십시오. "
     "가격을 검색할 수 없는 경우, 명확하게 보고해야 합니다."
 ),
 expected_output=(
     "시뮬레이션된 AAPL 주가를 명시하는 단일의 명확한 문장. "
     "예: 'AAPL의 시뮬레이션된 주가는 $178.15입니다.' "
     "가격을 찾을 수 없는 경우, 명확하게 명시하십시오."
 ),
 agent=financial_analyst_agent,
)
# --- 4. 크루 구성 ---
# 크루는 에이전트와 작업이 함께 작동하는 방식을 조정합니다.
financial_crew = Crew(
 agents=[financial_analyst_agent],
 tasks=[analyze_aapl_task],
 verbose=True # 프로덕션에서 더 상세한 로그를 위해 False로
설정
)
# --- 5. 메인 실행 블록 내에서 크루 실행 ---
# __name__ == "__main__": 블록을 사용하는 것은 표준 Python 모범
사례입니다.
def main():
   """크루를 실행하는 메인 함수."""
   # 시작하기 전에 API 키가 설정되어 있는지 확인합니다.
   if not os.environ.get("OPENAI_API_KEY"):
       print("오류: OPENAI_API_KEY 환경 변수가 설정되지
않았습니다.")
       print("실행하기 전에 설정하십시오.")
       return
   print("\n## 금융 크루 시작 ##")
   print("---------------------------------")
   # kickoff 메서드는 실행을 시작합니다.
   result = financial_crew.kickoff()
   print("\n---------------------------------")
   print("## 크루 실행 완료. ##")
   print("\n최종 결과:\n", result)
if __name__ == "__main__":
   main()

이 코드는 CrewAI 라이브러리를 사용하여 재무 분석 작업을 시뮬레이션하는 애플리케이션을 보여줍니다. 미리 정의된 티커에 대한 주가 조회를 시뮬레이션하는 사용자 지정 도구인 get_stock_price를 정의합니다. 이 도구는 유효한 티커에 대해서는 부동 소수점 숫자를 반환하고 유효하지 않은 티커에 대해서는 ValueError를 발생시키도록 설계되었습니다. 선임 금융 분석가 역할의 Crew.ai 에이전트인 financial_analyst_agent가 생성됩니다. 이 에이전트는 주식 가격을 조회하기 위해 get_stock_price 도구를 사용할 수 있도록 구성됩니다. 작업은 analyze_aapl_task로 정의되며, AAPL의 시뮬레이션된 주가를 조회하기 위해 도구를 사용하도록 에이전트에게 구체적으로 지시합니다. 작업 설명에는 도구 사용 시 성공 및 실패 사례를 모두 처리하는 방법에 대한 명확한 지침이 포함됩니다. 금융 분석가 에이전트와 analyze_aapl_task로 구성된 Crew가 조립됩니다. 에이전트와 크루 모두에 대해 verbose 설정이 활성화되어 실행 중에 상세한 로깅이 제공됩니다. 스크립스의 메인 부분은 kickoff() 메서드를 사용하여 크루의 작업을 실행합니다. 크루를 시작하기 전에 OPENAI_API_KEY 환경 변수가 설정되어 있는지 확인합니다. 크루 실행 결과(작업의 출력)가 콘솔에 인쇄됩니다. 이 코드는 도구 정의, 에이전트 및 작업을 설정하여 CrewAI에서 협업 워크플로우를 생성하는 방법을 보여줍니다. 또한 더 나은 추적을 위해 기본 로깅 구성을 포함합니다.

실습 코드 예제 (Google ADK)

이제 Google ADK 프레임워크 내에서 이러한 개념을 시연하는 개념적 코드 예제를 살펴보겠습니다. 특히, AgentTool을 사용하여 에이전트가 다른 에이전트의 기능을 도구로 사용하도록 허용하는 “도구로서의 에이전트” 패러다임을 보여줍니다.

from google.adk.agents import LlmAgent, BaseAgent, SequentialAgent
from google.adk.tools import agent_tool
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.tool_context import ToolContext
from google.genai import types
from google.adk.events import Event
from typing import AsyncGenerator
# 1. 핵심 기능을 위한 간단한 함수 도구.
# 이는 행동을 추론과 분리하는 모범 사례를 따릅니다.
def generate_image(prompt: str) -> dict:
   """
   텍스트 프롬프트를 기반으로 이미지를 생성합니다.
   Args:
       prompt: 생성할 이미지에 대한 자세한 설명.
   Returns:
       상태 및 생성된 이미지 바이트를 포함하는 딕셔너리.
   """
   print(f"도구: 프롬프트 '{prompt}'에 대한 이미지 생성 중")
   # 실제 구현에서는 이미지 생성 API를 호출합니다.
   # 이 예제에서는 모의 이미지 데이터를 반환합니다.
   mock_image_bytes = b"mock_image_data_for_a_cat_wearing_a_hat"
   return {
       "status": "success",
       # 도구는 원시 바이트를 반환하고, 에이전트는 파트 생성을
처리합니다.
       "image_bytes": mock_image_bytes,
       "mime_type": "image/png"
   }
# 2. ImageGeneratorAgent를 LlmAgent로 리팩토링.
# 이제 전달받은 입력을 올바르게 사용합니다.
image_generator_agent = LlmAgent(
   name="ImageGen",
   model="gemini-2.0-flash",
   description="텍스트 프롬프트를 기반으로 이미지를 생성합니다.",
   instruction=(
       "당신은 이미지 생성 전문가입니다. 사용자의 요청을 받아 "
       "`generate_image` 도구를 사용하여 이미지를 생성해야 합니다. "
       "사용자의 전체 요청은 도구의 'prompt' 인수로 사용되어야
합니다. "
       "도구가 이미지 바이트를 반환한 후에는 이미지를 출력해야
합니다."
   ),
   tools=[generate_image]
)
# 3. 수정된 에이전트를 AgentTool 래퍼로 감쌉니다.
# 여기에 있는 설명은 상위 에이전트가 보는 내용입니다.
image_tool = agent_tool.AgentTool(
   agent=image_generator_agent,
   description="생성할 이미지에 대한 상세한 프롬프트를
입력으로 사용하여 이미지를 생성하는 데 이 도구를 사용하십시오."
)
# 4. 상위 에이전트는 변경되지 않았습니다. 그 논리는 정확했습니다.
artist_agent = LlmAgent(
   name="Artist",
   model="gemini-2.0-flash",
   instruction=(
       "당신은 창의적인 예술가입니다. 먼저, 이미지에 대한 창의적이고
상세한 프롬프트를 발명하십시오. "
       "그런 다음 `ImageGen` 도구를 사용하여 당신의 프롬프트로
이미지를 생성하십시오."
   ),
   tools=[image_tool]
)

이 코드 조각은 Google ADK에서 에이전트 도구 래퍼를 사용하여 다른 에이전트의 기능을 도구로 활용하는 방법을 보여줍니다. 이는 계층적 에이전트 시스템의 예시입니다.

계층적 위임: artist_agent는 창의적인 예술가 역할을 하며, 이미지 생성을 위해 image_tool을 사용해야 합니다. 이 image_tool은 실제로는 image_generator_agent라는 또 다른 LLM 에이전트를 래핑합니다.

도구로서의 에이전트: image_toolimage_generator_agent를 호출하는 브리지 역할을 합니다. artist_agent가 이 도구를 호출하면, 이 도구는 image_generator_agent를 호출하고, 이 에이전트는 generate_image 함수를 사용하여 실제로 이미지를 생성합니다. 이 구조는 상위 수준의 창의적 계획과 하위 수준의 전문화된 실행을 분리하여 복잡한 작업을 구조화하는 방법을 보여줍니다.

이 예제는 ADK에서 에이전트 도구를 사용하여 다단계 에이전트 작업을 오케스트레이션하는 방법을 보여줍니다.

도구 사용 패턴에 대한 시각적 요약

그림 2: 도구 사용 설계 패턴

주요 요점

  • 도구 사용(함수 호출)은 에이전트가 외부 시스템과 상호 작용하고 동적 정보에 액세스할 수 있도록 합니다.
  • 여기에는 LLM이 이해할 수 있도록 명확한 설명과 매개변수로 도구를 정의하는 것이 포함됩니다.
  • LLM은 도구를 사용할 시점을 결정하고 구조화된 함수 호출을 생성합니다.
  • 에이전트 프레임워크는 실제 도구 호출을 실행하고 결과를 LLM에 다시 반환합니다.
  • 도구 사용은 에이전트가 실제 작업을 수행하고 최신 정보를 제공할 수 있도록 하는 데 필수적입니다.

결론

도구 사용 패턴은 대규모 언어 모델의 본질적인 텍스트 생성 기능을 넘어 기능 범위를 확장하는 중요한 아키텍처 원칙입니다. 모델에 외부 소프트웨어 및 데이터 소스와 인터페이스할 수 있는 기능을 제공함으로써 이 패러다임은 에이전트가 조치를 취하고, 계산을 실행하고, 다른 시스템에서 정보를 검색할 수 있도록 합니다. 이 프로세스에는 사용자 쿼리 충족에 필요하다고 판단될 때 외부 도구를 호출하도록 모델을 안내하는 것이 포함됩니다. LangChain, Google ADK 및 Crew AI와 같은 프레임워크는 이러한 외부 도구를 통합하는 데 도움이 되는 구조화된 추상화 및 구성 요소를 제공합니다. 이러한 프레임워크는 도구 사양을 모델에 노출하고 후속 도구 사용 요청을 구문 분석하는 프로세스를 관리합니다. 이를 통해 에이전트가 복잡한 에이전트형 워크플로우를 구축할 수 있는 기반이 마련됩니다.

참고 자료


6장: 계획

지능적인 행동은 종종 즉각적인 입력에 반응하는 것 이상을 포함합니다. 여기에는 복잡한 작업을 더 작고 관리하기 쉬운 단계로 나누고, 원하는 결과를 달성하기 위해 어떻게 진행할지 전략을 세우는 것이 필요합니다. 이것이 계획(Planning) 패턴이 중요해지는 지점입니다. 계획의 핵심은 에이전트 또는 에이전트 시스템이 초기 상태에서 목표 상태로 이동하기 위해 일련의 조치를 공식화할 수 있는 능력입니다. 이 계획은 미리 알려진 것이 아니라 요청에 응답하여 생성됩니다.

계획 패턴 개요

AI 맥락에서 계획 에이전트는 복잡한 목표를 위임받는 전문가라고 생각하는 것이 유용합니다. “팀 오프사이트 구성”을 요청하면 무엇을 해야 하는지(목표 및 제약 조건)를 정의하지만 어떻게 해야 하는지는 정의하지 않습니다. 에이전트의 핵심 임무는 그 목표를 달성하기 위한 경로를 자율적으로 계획하는 것입니다. 먼저 초기 상태(예: 예산, 참가자 수, 원하는 날짜)와 목표 상태(성공적으로 예약된 오프사이트)를 이해해야 하며, 그 다음 이 둘을 연결하는 최적의 행동 시퀀스를 발견해야 합니다. 계획은 경직된 스크립트가 아니라 단지 시작점에 불과합니다. 에이전트의 진정한 힘은 새로운 정보를 통합하고 장애물을 우회하기 위해 프로젝트를 조정할 수 있는 능력에 있습니다. 예를 들어, 선호하는 장소를 사용할 수 없게 되거나 선택한 케이터링 업체가 예약이 꽉 찼다면, 유능한 에이전트는 단순히 실패하지 않습니다. 그것은 조정합니다. 새로운 제약 조건을 등록하고, 옵션을 재평가하며, 대체 장소를 제안하거나 다른 날짜를 제안하여 새로운 계획을 공식화합니다.

그러나 유연성과 예측 가능성 사이의 상충 관계를 인식하는 것이 중요합니다. 동적 계획은 보편적인 해결책이 아니라 특정 도구입니다. 잘 이해되고 반복 가능한 해결책을 가진 문제의 경우, 에이전트를 미리 정의된 고정된 워크플로우로 제한하는 것이 더 효과적입니다. 이 접근 방식은 불확실성과 예측 불가능한 행동의 위험을 줄이기 위해 에이전트의 자율성을 제한하여 신뢰할 수 있고 일관된 결과를 보장합니다. 따라서 계획 에이전트와 단순한 작업 실행 에이전트를 사용할지 여부를 결정하는 것은 단 하나의 질문에 달려 있습니다. 즉, “어떻게”가 발견되어야 합니까, 아니면 이미 알려져 있습니까?

실용적인 응용 및 사용 사례

계획 패턴은 자율 시스템에서 핵심적인 계산 프로세스이며, 에이전트가 지정된 목표를 달성하기 위해 일련의 이산적인 실행 가능한 단계를 합성할 수 있도록 합니다. 이 프로세스는 복잡한 워크플로우를 조정하고 다양한 시스템과 상호 작용하는 데 필요한 논리적 프레임워크를 제공합니다.

절차적 작업 자동화 영역에서 계획은 워크플로우 조정을 위해 사용됩니다. 예를 들어, 신규 직원을 온보딩하는 비즈니스 프로세스는 시스템 계정 생성, 교육 모듈 할당, 다양한 부서와의 조정과 같은 지시적인 하위 작업 시퀀스로 분해될 수 있습니다. 에이전트는 이러한 단계를 논리적 순서로 실행하고 필요한 도구를 호출하거나 다양한 시스템과 상호 작용하여 종속성을 관리하는 계획을 생성합니다.

로보틱스 및 자율 탐색에서는 상태 공간 횡단에 계획이 기본이 됩니다. 시스템(물리적 로봇이든 가상 개체이든)은 초기 상태에서 목표 상태로 전환하기 위해 경로 또는 일련의 조치를 생성해야 합니다. 이는 환경 제약 조건(예: 장애물 회피 또는 교통 규칙 준수)을 준수하는 동시에 시간 또는 에너지 소비와 같은 메트릭을 최적화하는 것을 포함합니다.

이 패턴은 또한 구조화된 정보 종합에 중요합니다. 복잡한 출력(예: 연구 보고서)을 생성하도록 요청받을 때, 에이전트는 정보 수집, 데이터 요약, 콘텐츠 구조화 및 반복적 개선 단계를 포함하는 계획을 공식화할 수 있습니다. 유사하게, 다단계 문제 해결을 포함하는 고객 지원 시나리오에서 에이전트는 진단, 솔루션 구현 및 에스컬레이션을 위한 체계적인 계획을 생성하고 따를 수 있습니다.

본질적으로 계획 패턴은 에이전트가 단순한 반응적 행동을 넘어 목표 지향적 행동을 할 수 있도록 합니다. 이는 상호 의존적인 작업의 일관된 시퀀스를 요구하는 문제를 해결하는 데 필요한 논리적 프레임워크를 제공합니다.

실습 코드 예제 (Crew AI)

다음 섹션에서는 복잡한 쿼리에 응답하기 위해 먼저 다단계 계획을 공식화한 다음 해당 계획을 순차적으로 실행하는 에이전트를 활용하여 계획자 패턴을 Crew AI 프레임워크를 사용하여 구현하는 방법을 보여줍니다.

import os
from dotenv import load_dotenv
from crewai import Agent, Task, Crew, Process
from langchain_openai import ChatOpenAI
# 보안을 위해 .env 파일에서 환경 변수 로드
load_dotenv()
# 1. 명확성을 위해 언어 모델을 명시적으로 정의
llm = ChatOpenAI(model="gpt-4-turbo")
# 2. 명확하고 집중된 에이전트 정의
planner_writer_agent = Agent(
   role='기사 계획 및 작성자',
   goal='지정된 주제에 대한 간결하고 매력적인 요약을 계획하고 작성합니다.',
   backstory=(
       '당신은 전문 기술 작가이자 콘텐츠 전략가입니다. '
       '당신의 강점은 작성 전에 명확하고 실행 가능한 계획을 '
       '만드는 데 있으며, 최종 요약이 유익하면서도 이해하기 쉽도록 '
       '보장하는 것입니다.'
   ),
   verbose=True,
   allow_delegation=False,
   llm=llm # 특정 LLM을 에이전트에 할당
)
# 3. 더 구조화되고 구체적인 예상 출력을 가진 작업을 정의합니다.
topic = "강화 학습이 AI에서 중요한 이유"
high_level_task = Task(
   description=(
       f"1. '{topic}' 요약에 대한 글머리 기호 계획을 생성하십시오.\n"
       f"2. 계획에 따라 요약을 작성하십시오. 200단어 내외로 유지하십시오."
   ),
   expected_output=(
       "다음 두 가지 구별된 섹션을 포함하는 최종 보고서:\n\n"
       "### 계획\n"
       "- 요약의 주요 요점을 설명하는 글머리 기호 목록.\n\n"
       "### 요약\n"
       "- 주제에 대한 간결하고 잘 구조화된 요약."
   ),
   agent=planner_writer_agent,
)
# 명확한 프로세스로 크루 생성
crew = Crew(
   agents=[planner_writer_agent],
   tasks=[high_level_task],
   process=Process.sequential,
)
# 작업 실행
print("## 계획 및 작성 작업 실행 중 ##")
result = crew.kickoff()
print("\n\n---\n## 작업 결과 ##\n---")
print(result)

이 코드는 CrewAI 프레임워크를 사용하여 주어진 주제에 대한 요약을 계획하고 작성하는 AI 에이전트를 생성합니다. 필요한 라이브러리를 가져오고, .env 파일에서 API 키를 로드한 다음, 에이전트와 작업을 정의합니다.

planner_writer_agent는 기술 작가이자 콘텐츠 전략가 역할을 하도록 설정되어, 작성 전에 명확한 계획을 세우는 데 중점을 둡니다. high_level_task는 에이전트에게 먼저 계획을 생성하고 그 다음 200단어 요약을 작성하도록 지시합니다. 예상 출력은 “계획” 및 “요약”이라는 명확한 두 섹션으로 구분된 형식을 요구합니다.

크루는 에이전트와 작업을 포함하도록 조립되고, Process.sequential로 설정되어 작업이 순서대로 실행되도록 합니다. 최종적으로 crew.kickoff()를 호출하여 작업을 실행하고 그 결과를 인쇄합니다.

Google DeepResearch

Google Gemini DeepResearch(그림 1 참조)는 자율적인 정보 검색 및 종합을 위해 설계된 에이전트 기반 시스템입니다. 이는 복잡한 주제를 체계적으로 탐색하기 위해 Google 검색을 동적으로 반복적으로 쿼리하는 다단계 에이전트 파이프라인을 기반으로 작동합니다. 이 시스템은 대규모 코퍼스의 웹 기반 소스를 처리하고, 수집된 데이터를 관련성 및 지식 격차에 대해 평가하며, 이를 해결하기 위해 후속 검색을 수행하도록 구성되어 있습니다. 최종 출력은 출처를 인용하여 구조화된 다중 페이지 요약으로 통합됩니다.

더 나아가, 이 시스템의 작동은 단일 쿼리-응답 이벤트가 아니라 관리되는 장기 실행 프로세스입니다. 사용자가 복잡한 쿼리를 선언하고 조사를 승인할 수 있는 다단계 연구 계획을 생성하는 것으로 시작됩니다. 일단 계획이 승인되면, 에이전트 파이프라인은 반복적인 검색 및 분석 루프를 시작합니다. 여기에는 단순히 일련의 사전 정의된 검색을 실행하는 것 이상이 포함됩니다. 에이전트는 수집된 정보를 기반으로 쿼리를 동적으로 공식화하고 개선하며, 지식 격차를 식별하고, 데이터 포인트를 교차 확인하고, 불일치를 해결합니다.

그림 1: Google Deep Research 에이전트가 Google 검색 도구를 사용하여 실행 계획을 생성하는 모습.

이 프로세스의 핵심적인 아키텍처 구성 요소는 이 프로세스를 비동기적으로 관리하는 시스템의 능력입니다. 이러한 설계는 수백 개의 소스를 분석할 수 있는 조사 작업이 단일 지점 장애에 탄력적이며 사용자가 참여를 중단하고 완료 시 알림을 받을 수 있도록 보장합니다. 이 시스템은 또한 사용자가 제공한 문서를 통합하여 개인 소스 정보와 웹 기반 연구를 결합할 수 있습니다. 최종 출력은 단순히 발견 사항을 연결한 것이 아니라 논리적 섹션으로 콘텐츠를 구성하는 중요한 평가를 수행하는 합성 단계에서 일관된 내러티브로 정보를 구성하는 구조화된 다중 페이지 보고서입니다. 보고서는 종종 오디오 개요, 차트 및 원본 인용 소스에 대한 링크를 포함하여 대화형으로 설계되어 확인 및 추가 탐색이 가능합니다. 또한, 모델은 자체적으로 수행한 모든 검색을 소스 목록으로 명시적으로 반환하여 최종 종합 결과와 함께 제공하여 투명성을 높입니다.

이러한 체계적인 접근 방식은 수많은 정보 소스를 처리할 수 있는 능력에서 비롯되며, 이는 수동 연구자가 비슷한 시간 내에 달성할 수 있는 것보다 더 광범위한 분석 범위를 제공합니다. 이 광범위한 분석 범위는 덜 명백하지만 잠재적으로 중요한 정보의 발견 가능성을 높여 주제에 대한 더 강력하고 잘 뒷받침되는 이해로 이어집니다.

OpenAI Deep Research API는 복잡한 연구 작업을 자동화하도록 설계된 특수 도구입니다. 이 API는 고급 에이전트 모델을 활용하여 독립적으로 추론하고, 계획하고, 실제 소스의 정보를 종합합니다.

OpenAI Deep Research API

  • 특징: 구조화된 보고서, 내부 인용, 프로세스 투명성.
  • 예제: semaglutide의 경제적 영향에 대한 보고서 요청.
from openai import OpenAI
client = OpenAI(api_key="YOUR_OPENAI_API_KEY")
system_message = """당신은 구조화되고 데이터 기반의 보고서를
준비하는 전문 연구원입니다.
데이터가 풍부한 통찰력에 중점을 두고 신뢰할 수 있는 소스를
사용하며, 인라인 인용을 포함하십시오."""
user_query = "전 세계 의료 시스템에 대한 세마글루타이드의
경제적 영향에 대해 연구하십시오."
response = client.responses.create(
 model="o3-deep-research-2025-06-26",
 input=[
   {
     "role": "developer",
     "content": [{"type": "input_text", "text": system_message}]
   },
   {
     "role": "user",
     "content": [{"type": "input_text", "text": user_query}]
   }
 ],
 reasoning={"summary": "auto"},
 tools=[{"type": "web_search_preview"}]
)
final_report = response.output[-1].content[0].text
print(final_report)

시각적 요약

그림 4: 계획 설계 패턴

주요 요점

다음은 주요 요점입니다.

  • 계획은 에이전트가 복잡한 목표를 실행 가능한 순차적 단계로 분해할 수 있도록 합니다.
  • 이는 다단계 작업 자동화, 워크플로우 자동화 및 외부 데이터 소스와의 상호 작용에 필수적입니다.
  • LLM은 작업 설명에 기반하여 단계별 접근 방식을 생성하여 계획을 수행할 수 있습니다.
  • 명시적으로 작업을 계획 단계로 만들거나 복잡한 결과를 요청하는 것은 에이전트의 추론 능력을 향상시키는 데 도움이 됩니다.
  • Google Deep Research는 Google 검색 도구를 사용하여 얻은 소스를 기반으로 분석을 수행하는 에이전트의 예시입니다.

결론

결론적으로, 계획 패턴은 에이전트형 시스템을 단순한 반응형 응답자가 아닌 전략적, 목표 지향적 실행자로 승격시키는 필수적인 구성 요소입니다. 현대 대규모 언어 모델은 고수준 목표를 일관된 실행 가능한 단계로 자율적으로 분해할 수 있는 핵심 기능을 제공합니다. 이 패턴은 단순한 순차적 작업 실행부터 복잡한 시스템 구성에 이르기까지 확장됩니다. CrewAI 에이전트가 작성 계획을 생성하고 따르는 방식은 이 패턴의 적용을 보여줍니다. 궁극적으로 계획은 인간의 의도와 자동화된 실행 사이의 필수적인 다리를 제공하여 에이전트가 복잡한 워크플로우를 관리하고 포괄적인 종합 결과를 제공할 수 있도록 합니다.

참고 자료


7장: 멀티 에이전트 협업

단일 에이전트 아키텍처는 잘 정의된 문제에 효과적일 수 있지만, 복잡하고 다중 도메인 작업에 직면했을 때 그 능력이 종종 제약됩니다. 멀티 에이전트 협업 패턴은 시스템을 구별되고 전문화된 에이전트들의 협력적인 앙상블로 구조화함으로써 이러한 한계를 해결합니다. 이 접근 방식은 작업 분해(task decomposition) 원칙에 기반하며, 여기서 상위 수준의 목표는 개별적인 하위 문제들로 나뉩니다. 그런 다음 각 하위 문제는 해당 작업에 가장 적합한 특정 도구, 데이터 액세스 또는 추론 기능을 가진 에이전트에게 할당됩니다.

예를 들어, 복잡한 연구 질문은 정보 검색을 위한 연구 에이전트, 통계 처리를 위한 데이터 분석 에이전트, 최종 보고서 생성을 위한 종합 에이전트로 분해되어 할당될 수 있습니다. 이러한 시스템의 효능은 단순히 노동 분할에만 기인하는 것이 아니라, 에이전트 간 통신 메커니즘에 결정적으로 의존합니다. 이를 위해서는 표준화된 통신 프로토콜과 공유 온톨로지가 필요하며, 에이전트들이 데이터를 교환하고, 하위 작업을 위임하며, 최종 출력이 일관성을 유지하도록 행동을 조정할 수 있어야 합니다.

이 분산 아키텍처는 향상된 모듈성, 확장성 및 견고성을 포함한 여러 가지 이점을 제공하는데, 이는 단일 에이전트의 장애가 반드시 전체 시스템 장애를 유발하지 않기 때문입니다. 협업은 앙상블 내의 단일 에이전트의 잠재적 능력보다 집합적 성능이 뛰어난 시너지 효과를 낳을 수 있게 합니다.

멀티 에이전트 협업 패턴 개요

멀티 에이전트 협업 패턴은 여러 독립적이거나 준독립적인 에이전트들이 공통의 목표를 달성하기 위해 함께 작업하는 시스템을 설계하는 것을 포함합니다. 각 에이전트는 일반적으로 정의된 역할, 전체 목표에 부합하는 구체적인 목표, 그리고 잠재적으로 다른 도구 또는 지식 기반에 대한 접근 권한을 가집니다. 이 패턴의 강력함은 에이전트들 간의 상호작용과 시너지 효과에 있습니다.

협업은 다양한 형태로 나타날 수 있습니다.

  • 순차적 인계 (Sequential Handoffs): 한 에이전트가 작업을 완료하고 그 출력을 다음 에이전트에게 파이프라인의 다음 단계로 전달합니다 (계획 패턴과 유사하지만, 명시적으로 다른 에이전트들을 포함함).

  • 병렬 처리 (Parallel Processing): 여러 에이전트가 문제의 다른 부분에 대해 동시에 작업하고, 그 결과는 나중에 결합됩니다.

  • 토론 및 합의 (Debate and Consensus): 다양한 관점과 정보 출처를 가진 에이전트들이 옵션을 평가하기 위해 토론에 참여하여 궁극적으로 합의나 보다 정보에 입각한 결정에 도달하는 멀티 에이전트 협업입니다.

  • 계층적 구조 (Hierarchical Structures): 관리자 에이전트는 작업 도구 접근 권한이나 플러그인 기능에 따라 작업 에이전트들에게 동적으로 작업을 위임하고 그 결과를 종합할 수 있습니다. 각 에이전트는 또한 단일 에이전트가 모든 도구를 처리하는 대신 관련 도구 그룹을 처리할 수도 있습니다.

  • 전문가 팀 (Expert Teams): 서로 다른 도메인(예: 연구원, 작가, 편집자)의 전문 지식을 가진 에이전트들이 복잡한 출력을 생성하기 위해 협업합니다.

  • 비평가-검토자 (Critic-Reviewer): 에이전트들이 계획, 초안 또는 답변과 같은 초기 출력을 생성합니다. 그런 다음 두 번째 에이전트 그룹이 정책 준수, 보안, 규정 준수, 정확성, 품질 및 조직 목표와의 정렬에 대해 이 출력을 비판적으로 평가합니다. 원래 작성자 또는 최종 에이전트가 이 피드백을 기반으로 출력을 수정합니다. 이 패턴은 코드 생성, 연구 작성, 논리 확인 및 윤리적 정렬 보장에 특히 효과적입니다. 이 접근 방식의 장점은 향상된 견고성, 개선된 품질, 환각 또는 오류 가능성 감소를 포함합니다.

멀티 에이전트 시스템(그림 1 참조)은 근본적으로 에이전트 역할과 책임의 구분, 에이전트들이 정보를 교환하는 통신 채널의 확립, 그리고 그들의 협력 노력을 지시하는 작업 흐름 또는 상호작용 프로토콜의 공식화를 구성합니다. 그림 1: 멀티 에이전트 시스템의 예시

Crew AI 및 Google ADK와 같은 프레임워크는 에이전트, 작업 및 상호작용 절차의 사양을 위한 구조를 제공함으로써 이 패러다임을 용이하게 하도록 설계되었습니다. 이 접근 방식은 다양한 전문 지식이 필요하거나, 여러 개별 단계에 걸쳐 있거나, 동시 처리 및 에이전트 간 정보 확인의 이점을 활용해야 하는 과제에 특히 효과적입니다.

실제 애플리케이션 및 사용 사례

멀티 에이전트 협업은 수많은 도메인에 적용 가능한 강력한 패턴입니다.

  • 복잡한 연구 및 분석: 에이전트 팀이 연구 프로젝트에서 협력할 수 있습니다. 한 에이전트는 학술 데이터베이스 검색을 전문으로 하고, 다른 에이전트는 결과 요약을, 세 번째 에이전트는 동향 파악을, 네 번째 에이전트는 정보를 보고서로 종합하는 것을 전문으로 할 수 있습니다. 이는 인간 연구팀이 운영되는 방식과 유사합니다.

  • 소프트웨어 개발: 에이전트들이 소프트웨어 구축을 위해 협력하는 것을 상상해 보십시오. 한 에이전트는 요구 사항 분석가, 다른 에이전트는 코드 생성기, 세 번째 에이전트는 테스터,

  • 그리고 네 번째 에이전트는 문서 작성자가 될 수 있습니다. 그들은 구성 요소를 구축하고 검증하기 위해 서로에게 출력을 전달할 수 있습니다.

  • 창의적 콘텐츠 생성: 마케팅 캠페인을 만드는 것은 시장 조사 에이전트, 카피라이터 에이전트, 그래픽 디자인 에이전트(이미지 생성 도구 사용), 그리고 소셜 미디어 스케줄링 에이전트가 모두 함께 작업하는 것을 포함할 수 있습니다.

  • 재무 분석: 멀티 에이전트 시스템은 금융 시장을 분석할 수 있습니다. 에이전트들은 주식 데이터 가져오기, 뉴스 심리 분석, 기술적 분석 수행, 투자 권고안 생성 등을 전문으로 할 수 있습니다.

  • 고객 지원 에스컬레이션: 일선 지원 에이전트는 초기 문의를 처리하고, 필요할 때 전문 에이전트(예: 기술 전문가 또는 청구 전문가)에게 복잡한 문제를 에스컬레이션하여 문제 복잡성에 기반한 순차적 인계를 시연할 수 있습니다.

  • 공급망 최적화: 에이전트들은 공급망의 다른 노드(공급업체, 제조업체, 유통업체)를 나타낼 수 있으며, 변화하는 수요나 중단에 대응하여 재고 수준, 물류 및 스케줄링을 최적화하기 위해 협력할 수 있습니다.

  • 네트워크 분석 및 복구: 자율 운영은 특히 장애 지점을 찾는 데 있어 에이전트 아키텍처로부터 큰 이점을 얻습니다. 여러 에이전트가 협력하여 문제를 분류하고 복구하며 최적의 조치를 제안할 수 있습니다. 이러한 에이전트들은 기존 시스템의 이점을 활용하는 동시에 생성형 AI의 이점을 제공하기 위해 기존 머신러닝 모델 및 도구와 통합될 수도 있습니다.

전문 에이전트를 구분하고 그들의 상호 관계를 세심하게 조정하는 능력은 개발자가 향상된 모듈성, 확장성 및 단일 통합 에이전트에게는 불가능했을 복잡성을 다룰 수 있는 능력을 갖춘 시스템을 구축할 수 있도록 힘을 실어줍니다.

멀티 에이전트 협업: 상호 관계 및 통신 구조 탐색

에이전트들이 상호 작용하고 통신하는 복잡한 방식을 이해하는 것은 효과적인 멀티 에이전트 시스템을 설계하는 데 근본적입니다. 그림 2에 묘사된 바와 같이, 가장 단순한 단일 에이전트 시나리오에서부터 복잡하고 맞춤 설계된 협업 프레임워크에 이르기까지 다양한 상호 관계 및 통신 모델의 스펙트럼이 존재합니다. 각 모델은 고유한 장점과 과제를 제시하며, 멀티 에이전트 시스템의 전반적인 효율성, 견고성 및 적응성에 영향을 미칩니다.

  • 1. 단일 에이전트 (Single Agent): 가장 기본적인 수준에서 “단일 에이전트”는 다른 개체와의 직접적인 상호 작용이나 통신 없이 자율적으로 작동합니다. 이 모델은 구현 및 관리가 간단하지만, 그 능력은 본질적으로 개별 에이전트의 범위와 자원에 의해 제한됩니다. 이는 단일 자급자족 에이전트에 의해 해결 가능한 독립적인 하위 문제들로 분해될 수 있는 작업에 적합합니다.

  • 2. 네트워크 (Network): “네트워크” 모델은 여러 에이전트가 분산된 방식으로 서로 직접 상호 작용하는 협업을 향한 상당한 발전을 나타냅니다. 통신은 일반적으로 피어 투 피어로 발생하여 정보, 리소스 및 심지어 작업 공유를 허용합니다. 이 모델은 한 에이전트의 장애가 반드시 전체 시스템을 마비시키지 않으므로 복원력을 조성합니다. 그러나 크고 구조화되지 않은 네트워크에서 통신 오버헤드를 관리하고 일관된 의사 결정을 보장하는 것이 어려울 수 있습니다.

  • 3. 감독자 (Supervisor): “감독자” 모델에서는 전담 에이전트인 “감독자”가 일련의 하위 에이전트들의 활동을 감독하고 조정합니다. 감독자는 통신, 작업 할당 및 갈등 해결을 위한 중앙 허브 역할을 합니다. 이 계층적 구조는 명확한 권한 체계를 제공하며 관리 및 제어를 단순화할 수 있습니다. 그러나 이는 단일 장애 지점(감독자)을 도입하며, 감독자가 너무 많은 수의 하위 에이전트나 복잡한 작업으로 인해 압도될 경우 병목 현상이 될 수 있습니다.

  • 4. 도구로서의 감독자 (Supervisor as a Tool): 이 모델은 감독자의 역할이 직접적인 명령 및 제어보다는 다른 에이전트에게 리소스, 지침 또는 분석적 지원을 제공하는 것에 더 가깝도록 “감독자” 개념을 미묘하게 확장한 것입니다. 감독자는 다른 에이전트의 모든 행동을 지시하지 않으면서도 다른 에이전트가 작업을 더 효과적으로 수행할 수 있도록 도구, 데이터 또는 계산 서비스를 제공할 수 있습니다. 이 접근 방식은 엄격한 상향식 제어를 부과하지 않으면서 감독자의 역량을 활용하는 것을 목표로 합니다.

  • 5. 계층적 (Hierarchical): “계층적” 모델은 감독자 개념을 확장하여 다단계 조직 구조를 만듭니다. 이는 여러 수준의 감독자를 포함하며, 상위 수준 감독자가 하위 수준 감독자를 감독하고, 궁극적으로 최하위 계층에 운영 에이전트 집합이 존재합니다. 이 구조는 복잡한 문제를 하위 문제들로 분해할 수 있고, 각 하위 문제는 계층의 특정 계층에 의해 관리되는 경우에 적합합니다. 이는 정의된 경계 내에서 분산된 의사 결정을 허용하여 확장성과 복잡성 관리에 구조화된 접근 방식을 제공합니다. 그림 2: 에이전트들이 다양한 방식으로 통신하고 상호 작용함.

    6. 맞춤형 (Custom): “맞춤형” 모델은 멀티 에이전트 시스템 설계에서 궁극적인 유연성을 나타냅니다. 이는 주어진 문제 또는 애플리케이션의 특정 요구 사항에 정확하게 맞춰진 고유한 상호 관계 및 통신 구조를 생성할 수 있도록 합니다. 여기에는 이전에 언급된 모델들의 요소를 결합하는 하이브리드 접근 방식이나 환경의 고유한 제약 조건 및 기회에서 발생하는 완전히 새로운 설계가 포함될 수 있습니다. 맞춤형 모델은 종종 특정 성능 지수를 최적화하거나, 고도로 동적인 환경을 처리하거나, 도메인별 지식을 시스템 아키텍처에 통합해야 할 필요성에서 비롯됩니다. 맞춤형 모델을 설계하고 구현하는 것은 일반적으로 멀티 에이전트 시스템 원칙에 대한 깊은 이해와 통신 프로토콜, 조정 메커니즘 및 나타나는 행동에 대한 세심한 고려가 필요합니다.

요약하자면, 멀티 에이전트 시스템을 위한 상호 관계 및 통신 모델의 선택은 중요한 설계 결정입니다. 각 모델은 뚜렷한 장점과 단점을 제공하며, 최적의 선택은 작업의 복잡성, 에이전트 수, 원하는 자율성 수준, 견고성에 대한 필요성, 허용 가능한 통신 오버헤드와 같은 요소에 따라 달라집니다. 멀티 에이전트 시스템의 미래 발전은 이러한 모델들을 계속 탐색하고 개선할 것이며, 협력적 인텔리전스를 위한 새로운 패러다임을 개발할 것입니다.

실습 코드 (Crew AI)

이 Python 코드는 AI 트렌드에 대한 블로그 게시물을 생성하기 위해 CrewAI 프레임워크를 사용하여 AI 기반 크루를 정의합니다. 이는 .env 파일에서 API 키를 로드하여 환경을 설정하는 것으로 시작됩니다. 애플리케이션의 핵심은 두 에이전트를 정의하는 것입니다. 하나는 AI 트렌드를 찾고 요약하는 연구원이고, 다른 하나는 연구를 기반으로 블로그 게시물을 작성하는 작가입니다.

이에 따라 두 가지 작업이 정의됩니다. 하나는 트렌드를 연구하는 작업이고 다른 하나는 연구 작업의 출력에 의존하는 블로그 게시물을 작성하는 작업입니다. 그런 다음 이러한 에이전트와 작업이 크루로 조립되며, 작업이 순서대로 실행되는 순차적 프로세스가 지정됩니다. 크루는 에이전트, 작업 및 언어 모델(“gemini-2.0-flash” 모델로 구체적으로 지정)로 초기화됩니다. 메인 함수는 kickoff() 메서드를 사용하여 이 크루를 실행하여 원하는 출력을 생성하기 위해 에이전트 간의 협업을 조정합니다. 마지막으로 코드는 크루 실행의 최종 결과인 생성된 블로그 게시물을 인쇄합니다.

import os
from dotenv import load_dotenv
from crewai import Agent, Task, Crew, Process
from langchain_google_genai import ChatGoogleGenerativeAI
def setup_environment():
   """환경 변수를 로드하고 필수 API 키를 확인합니다."""
   load_dotenv()
   if not os.getenv("GOOGLE_API_KEY"):
       raise ValueError("GOOGLE_API_KEY를 찾을 수 없습니다. .env 파일에 설정해 주세요.")
def main():
   """
   최신 Gemini 모델을 사용하여 콘텐츠 생성을 위한 AI 크루를 초기화하고 실행합니다.
   """
   setup_environment()
   # 사용할 언어 모델을 정의합니다.
# 성능 및 기능 향상을 위해 Gemini 2.0 시리즈의 모델로 업데이트되었습니다.
   # 최첨단(미리 보기) 기능을 사용하려면 "gemini-2.5-flash"를 사용할 수 있습니다.
   llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash")
   # 특정 역할과 목표를 가진 에이전트 정의
   researcher = Agent(
       role='선임 연구 분석가',
       goal='AI의 최신 동향을 찾고 요약합니다.',
       backstory="당신은 주요 동향을 파악하고 정보를 종합하는 데 재주가 있는 숙련된 연구 분석가입니다.",
       verbose=True,
       allow_delegation=False,
   )
   writer = Agent(
       role='기술 콘텐츠 작가',
       goal='연구 결과를 바탕으로 명확하고 매력적인 블로그 게시물을 작성합니다.',
       backstory="당신은 복잡한 기술 주제를 접근하기 쉬운 콘텐츠로 변환할 수 있는 숙련된 작가입니다.",
       verbose=True,
       allow_delegation=False,
   )
   # 에이전트에 대한 작업 정의
   research_task = Task(
       description="2024-2025년 인공 지능의 상위 3가지 신흥 동향을 조사합니다. 실제 적용과 잠재적 영향에 중점을 둡니다.",
       expected_output="주요 요점과 출처를 포함한 상위 3가지 AI 동향에 대한 자세한 요약.",
       agent=researcher,
   )
   writing_task = Task(
       description="연구 결과를 바탕으로 500단어 블로그 게시물을 작성합니다. 게시물은 일반 독자가 이해하기 쉽고 매력적이어야 합니다.",
       expected_output="최신 AI 동향에 대한 완성된 500단어 블로그 게시물.",
       agent=writer,
       context=[research_task],
   )
   # 크루 생성
blog_creation_crew = Crew(
       agents=[researcher, writer],
       tasks=[research_task, writing_task],
       process=Process.sequential,
       llm=llm,
       verbose=2 # 상세한 크루 실행 로그를 위해 상세 모드 설정
   )
   # 크루 실행
   print("## Gemini 2.0 Flash로 블로그 생성 크루 실행 중... ##")
   try:
       result = blog_creation_crew.kickoff()
       print("\n------------------\n")
       print("## 크루 최종 출력 ##")
       print(result)
   except Exception as e:
       print(f"\n예상치 못한 오류가 발생했습니다: {e}")
if __name__ == "__main__":
   main()

이제 Google ADK 프레임워크 내에서 계층적, 병렬 및 순차적 조정 패러다임과 운영 도구로서의 에이전트 구현에 중점을 둔 추가 예제를 살펴보겠습니다.

실습 코드 (Google ADK)

다음 코드 예제는 부모-자식 관계를 생성하여 Google ADK 내에서 계층적 에이전트 구조를 설정하는 것을 보여줍니다. 코드는 LlmAgent와 BaseAgent에서 파생된 사용자 지정 TaskExecutor 에이전트의 두 가지 유형의 에이전트를 정의합니다. TaskExecutor는 특정 비-LLM 작업을 위해 설계되었으며 이 예제에서는 단순히 “작업이 성공적으로 완료되었습니다” 이벤트를 산출합니다. 지정된 모델과 친근한 인사 담당자 역할을 하라는 지침으로 LlmAgent인 greeter가 초기화됩니다. 사용자 지정 TaskExecutor는 task_doer로 인스턴스화됩니다. 조정자라고 불리는 부모 LlmAgent는 모델과 지침으로 생성됩니다. 조정자의 지침은 greeter에게 인사하도록 위임하고 task_doer에게 작업 실행을 위임하도록 안내합니다. greeter와 task_doer는 조정자의 하위 에이전트로 추가되어 부모-자식 관계를 설정합니다. 코드는 이 관계가 올바르게 설정되었는지 확인합니다. 마지막으로, 에이전트 계층 구조가 성공적으로 생성되었음을 나타내는 메시지를 인쇄합니다.

from google.adk.agents import LlmAgent, BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event
from typing import AsyncGenerator
# BaseAgent를 확장하여 사용자 지정 에이전트를 올바르게 구현합니다.
class TaskExecutor(BaseAgent):
   """사용자 지정, 비-LLM 동작을 가진 특수 에이전트입니다."""
   name: str = "TaskExecutor"
   description: str = "미리 정의된 작업을 실행합니다."
   async def _run_async_impl(self, context: InvocationContext) ->
AsyncGenerator[Event, None]:
       """작업에 대한 사용자 지정 구현 논리입니다."""
       # 여기에 사용자 지정 논리가 들어갑니다.
       # 이 예제에서는 간단한 이벤트를 산출하기만 합니다.
       yield Event(author=self.name, content="작업이 성공적으로 완료되었습니다.")
# 적절한 초기화로 개별 에이전트 정의
# LlmAgent는 모델을 지정해야 합니다.
greeter = LlmAgent(
   name="Greeter",
   model="gemini-2.0-flash-exp",
   instruction="당신은 친절한 인사 담당자입니다."
)
task_doer = TaskExecutor() # 구체적인 사용자 지정 에이전트 인스턴스화
# 부모 에이전트를 생성하고 하위 에이전트 할당
# 부모 에이전트의 설명과 지침은 위임 논리를 안내해야 합니다.
coordinator = LlmAgent(
   name="Coordinator",
   model="gemini-2.0-flash-exp",
   description="사용자에게 인사하고 작업을 실행할 수 있는 조정자입니다.",
   instruction="인사를 요청받으면 Greeter에게 위임하고, 작업을 수행하라는 요청을 받으면 TaskExecutor에게 위임하십시오.",
   sub_agents=[
       greeter,
       task_doer
   ]
)
# ADK 프레임워크는 부모-자식
관계를 자동으로 설정합니다.
# 초기화 후 확인하면 이 주장들은 통과될 것입니다.
assert greeter.parent_agent == coordinator
assert task_doer.parent_agent == coordinator
print("에이전트 계층 구조가 성공적으로 생성되었습니다.")

이 코드 발췌문은 Google ADK 내에서 반복적인 워크플로를 설정하기 위해 LoopAgent를 사용하는 것을 설명합니다. 코드는 두 에이전트인 ConditionChecker와 ProcessingStep을 정의합니다. ConditionChecker는 세션 상태에서 “status” 값을 확인하는 사용자 지정 에이전트입니다. “status”가 “completed”인 경우 ConditionChecker는 루프를 중지하기 위해 이벤트를 에스컬레이션합니다. 그렇지 않으면 루프를 계속하기 위해 이벤트를 산출합니다. ProcessingStep은 “gemini-2.0-flash-exp” 모델을 사용하는 LlmAgent입니다. 그 지침은 작업을 수행하고 최종 단계인 경우 세션 “status”를 “completed”로 설정하는 것입니다. StatusPoller라는 LoopAgent가 생성됩니다. StatusPoller는 max_iterations=10으로 구성됩니다. StatusPoller는 ProcessingStep과 ConditionChecker의 인스턴스 모두를 하위 에이전트로 포함합니다. LoopAgent는 최대 10번의 반복 동안 하위 에이전트들을 순차적으로 실행하며, ConditionChecker가 상태가 “completed”임을 발견하면 중지됩니다.

import asyncio
from typing import AsyncGenerator
from google.adk.agents import LoopAgent, LlmAgent, BaseAgent
from google.adk.events import Event, EventActions
from google.adk.agents.invocation_context import InvocationContext
# 모범 사례: 사용자 지정 에이전트를 완전하고 자체 설명적인 클래스로 정의합니다.
class ConditionChecker(BaseAgent):
   """세션 상태에서 'completed' 상태를 확인하는 사용자 지정 에이전트입니다."""
   name: str = "ConditionChecker"
   description: str = "프로세스가 완료되었는지 확인하고 루프 중지를 신호합니다."
   async def _run_async_impl(
       self, context: InvocationContext
   ) -> AsyncGenerator[Event, None]:
       """상태를 확인하고 루프를 계속하거나 중지하기 위해 이벤트를 산출합니다."""
       status = context.session.state.get("status", "pending")
is_done = (status == "completed")
       if is_done:
           # 조건이 충족되면 루프를 종료하기 위해 에스컬레이션합니다.
           yield Event(author=self.name,
actions=EventActions(escalate=True))
       else:
           # 루프를 계속하기 위해 간단한 이벤트를 산출합니다.
           yield Event(author=self.name, content="조건이 충족되지 않았으므로 루프를 계속합니다.")
# 수정: LlmAgent는 모델과 명확한 지침을 가져야 합니다.
process_step = LlmAgent(
   name="ProcessingStep",
   model="gemini-2.0-flash-exp",
   instruction="당신은 더 긴 프로세스의 단계입니다. 작업을 수행하십시오. 최종 단계인 경우 세션 상태를 'status'를 'completed'로 설정하여 업데이트하십시오."
)
# LoopAgent가 워크플로를 조정합니다.
poller = LoopAgent(
   name="StatusPoller",
   max_iterations=10,
   sub_agents=[
       process_step,
       ConditionChecker() # 잘 정의된 사용자 지정 에이전트 인스턴스화.
   ]
)
# 이제 이 폴러는 'process_step'을 실행한 다음
# 상태가 'completed'가 되거나 10회 반복이 지날 때까지 'ConditionChecker'를
# 반복적으로 실행합니다.

이 코드 발췌문은 Google ADK 내에서 선형 워크플로 구축을 위해 설계된 SequentialAgent 패턴을 설명합니다. 이 코드는 google.adk.agents 라이브러리를 사용하여 순차적 에이전트 파이프라인을 정의합니다. 파이프라인은 step1과 step2라는 두 개의 에이전트로 구성됩니다. step1은 “Step1_Fetch”로 이름 붙여지며 그 출력은 “data” 키 아래 세션 상태에 저장됩니다. step2는 “Step2_Process”로 이름 붙여지며 session.state[“data”]에 저장된 정보를 분석하고 요약을 제공하도록 지시됩니다. “MyPipeline”이라는 SequentialAgent가 이 하위 에이전트들의 실행을 조정합니다. 파이프라인이 초기 입력으로 실행되면 step1이 먼저 실행됩니다. step1의 응답은 “data” 키 아래 세션 상태에 저장됩니다. 그런 다음 step2가 실행되어 지침에 따라 상태에 배치된 정보를 활용합니다. 이 구조는 한 에이전트의 출력이 다음 에이전트의 입력이 되는 워크플로를 구축할 수 있도록 합니다. 이는 다단계 AI 또는 데이터 처리 파이프라인을 만드는 일반적인 패턴입니다.

from google.adk.agents import SequentialAgent, Agent
# 이 에이전트의 출력은 session.state["data"]에 저장됩니다.
step1 = Agent(name="Step1_Fetch", output_key="data")
# 이 에이전트는 이전 단계의 데이터를 사용할 것입니다.
# 이 데이터를 찾는 방법과 사용하는 방법을 지침으로 제공합니다.
step2 = Agent(
   name="Step2_Process",
   instruction="state['data']에서 찾은 정보를 분석하고 요약을 제공합니다."
)
pipeline = SequentialAgent(
   name="MyPipeline",
   sub_agents=[step1, step2]
)
# 파이프라인이 초기 입력으로 실행되면 Step1이 실행되고,
# 그 응답이 session.state["data"]에 저장된 후,
# Step2가 지침에 따라 상태의 정보를 사용하여 실행됩니다.

다음 코드 예제는 여러 에이전트 작업을 동시 실행할 수 있도록 하는 Google ADK 내의 ParallelAgent 패턴을 보여줍니다. data_gatherer는 weather_fetcher와 news_fetcher라는 두 하위 에이전트를 병렬로 실행하도록 설계되었습니다. weather_fetcher 에이전트는 주어진 위치의 날씨를 가져와 결과를 session.state[“weather_data”]에 저장하도록 지시됩니다. 마찬가지로 news_fetcher 에이전트는 주어진 주제에 대한 주요 뉴스를 검색하고 session.state[“news_data”]에 저장하도록 지시됩니다. 각 하위 에이전트는 “gemini-2.0-flash-exp” 모델을 사용하도록 구성됩니다. ParallelAgent는 이 하위 에이전트들의 실행을 조정하여 병렬로 작업할 수 있도록 합니다. weather_fetcher와 news_fetcher 모두의 결과는 에이전트 실행 완료 후 세션 상태에 수집되어 저장될 것입니다. 마지막으로,

예제는 에이전트 실행이 완료된 후 최종 상태에서 수집된 날씨 및 뉴스 데이터에 액세스하는 방법을 보여줍니다.

from google.adk.agents import Agent, ParallelAgent
# 가져오기 논리를 에이전트의 도구로 정의하는 것이 더 좋습니다.
# 이 예제의 단순성을 위해 에이전트의 지침 내에 논리를 포함할 것입니다.
# 실제 시나리오에서는 도구를 사용할 것입니다.
# 병렬로 실행될 개별 에이전트 정의
weather_fetcher = Agent(
   name="weather_fetcher",
   model="gemini-2.0-flash-exp",
   instruction="주어진 위치의 날씨를 가져와 날씨 보고서만 반환합니다.",
   output_key="weather_data" # 결과는 session.state["weather_data"]에 저장됩니다.
)
news_fetcher = Agent(
   name="news_fetcher",
   model="gemini-2.0-flash-exp",
   instruction="주어진 주제에 대한 주요 뉴스를 가져와 해당 뉴스 기사만 반환합니다.",
   output_key="news_data" # 결과는 session.state["news_data"]에 저장됩니다.
)
# 하위 에이전트를 조정하기 위한 ParallelAgent 생성
data_gatherer = ParallelAgent(
   name="data_gatherer",
   sub_agents=[
       weather_fetcher,
       news_fetcher
   ]
)

제공된 코드 세그먼트는 Google ADK 내에서 에이전트가 함수 호출과 유사한 방식으로 다른 에이전트의 기능을 활용할 수 있도록 하는 “도구로서의 에이전트” 패러다임을 보여줍니다. 특히, 이 코드는 Google의 LlmAgent 및 AgentTool 클래스를 사용하여 이미지 생성 시스템을 정의합니다. 이는 부모인 artist_agent와 하위 에이전트인 image_generator_agent로 구성됩니다. generate_image 함수는 모의 이미지 데이터를 반환하여 이미지 생성을 시뮬레이션하는 간단한 도구입니다. image_generator_agent는 텍스트 프롬프트를 기반으로 이 도구를 사용하는 책임을 집니다. artist_agent의 역할은 먼저 창의적인 이미지 프롬프트를 고안하는 것입니다. 그런 다음 AgentTool 래퍼를 통해 image_generator_agent를 호출합니다. AgentTool은 브리지 역할을 하여 한 에이전트가 다른 에이전트를 도구로 사용하도록 허용합니다. artist_agent가 image_tool을 호출하면 AgentTool은 artist가 발명한 프롬프트로 image_generator_agent를 호출합니다. image_generator_agent는 해당 프롬프트를 사용하여 generate_image 함수를 사용합니다. 마지막으로 생성된 이미지(또는 모의 데이터)는 에이전트를 통해 다시 반환됩니다. 이 아키텍처는 상위 수준 에이전트가 하위 수준의 전문화된 에이전트를 조정하여 작업을 수행하는 계층적 에이전트 시스템을 시연합니다.

from google.adk.agents import LlmAgent
from google.adk.tools import agent_tool
from google.genai import types
# 1. 핵심 기능에 대한 간단한 함수 도구입니다.
# 이는 조치와 추론을 분리하는 모범 사례를 따릅니다.
def generate_image(prompt: str) -> dict:
   """
   텍스트 프롬프트를 기반으로 이미지를 생성합니다.
   Args:
       prompt: 생성할 이미지에 대한 자세한 설명입니다.
   Returns:
       상태와 생성된 이미지 바이트를 포함하는 사전입니다.
   """
   print(f"도구: 프롬프트 '{prompt}'에 대한 이미지 생성 중")
   # 실제 구현에서는 이미지 생성 API를 호출할 것입니다.
   # 이 예제에서는 모의 이미지 데이터를 반환합니다.
   mock_image_bytes = b"mock_image_data_for_a_cat_wearing_a_hat"
   return {
       "status": "success",
       # 도구는 원시 바이트를 반환하며, 에이전트가 부분 생성을 처리합니다.
       "image_bytes": mock_image_bytes,
       "mime_type": "image/png"
   }
# 2. ImageGeneratorAgent를 LlmAgent로 리팩토링합니다.
# 이제 전달된 입력을 올바르게 사용합니다.
image_generator_agent = LlmAgent(
   name="ImageGen",
   model="gemini-2.0-flash",
   description="상세한 텍스트 프롬프트를 기반으로 이미지를 생성합니다.",
   instruction=(
       "당신은 이미지 생성 전문가입니다. 당신의 임무는 사용자의 요청을 받아 "
       "`generate_image` 도구를 사용하여 이미지를 생성하는 것입니다. "
       "사용자의 전체 요청은 도구의 'prompt' 인수로 사용되어야 합니다. "
       "도구가 이미지 바이트를 반환한 후에는 반드시 이미지를 출력해야 합니다."
   ),
   tools=[generate_image]
)
# 3. 수정된 에이전트를 AgentTool로 래핑합니다.
# 여기에 있는 설명은 부모 에이전트가 보는 내용입니다.
image_tool = agent_tool.AgentTool(
   agent=image_generator_agent,
   description="이 도구를 사용하여 이미지를 생성하십시오. 입력은 원하는 이미지에 대한 설명적인 프롬프트여야 합니다."
)
# 4. 부모 에이전트는 변경되지 않았습니다. 그 논리는 올바르게 작동했습니다.
artist_agent = LlmAgent(
   name="Artist",
   model="gemini-2.0-flash",
   instruction=(
       "당신은 창의적인 예술가입니다. 먼저 이미지에 대한 창의적이고 상세한 프롬프트를 만드십시오. "
       "그런 다음 `ImageGen` 도구를 사용하여 당신의 프롬프트를 사용하여 이미지를 생성하십시오."
   ),
   tools=[image_tool]
)

한눈에 보기

무엇을: 복잡한 문제는 종종 단일의 단일체 LLM 기반 에이전트의 능력을 초과합니다. 단일 에이전트는 다면적인 작업의 모든 부분을 해결하는 데 필요한 다양하고 전문화된 기술이나 특정 도구 액세스가 부족할 수 있습니다. 이러한 한계는 전체 시스템의 효율성을 감소시키는 병목 현상을 만듭니다. 결과적으로, 정교하고 다중 도메인 목표를 다루는 것은 비효율적이며 불완전하거나 차선의 결과로 이어질 수 있습니다.

왜: 멀티 에이전트 협업 패턴은 여러 협력 에이전트들로 시스템을 생성하여 표준화된 해결책을 제공합니다. 복잡한 문제는 더 작고 관리하기 쉬운 하위 문제로 분해됩니다. 그런 다음 각 하위 문제는 해당 문제를 해결하는 데 필요한 정확한 도구와 기능을 갖춘 전문 에이전트에게 할당됩니다. 이러한 에이전트들은 정의된 통신 프로토콜과 순차적 인계, 병렬 작업 스트림 또는 계층적 위임과 같은 상호작용 모델을 통해 함께 작동합니다. 이 에이전트 기반의 분산 접근 방식은 시너지 효과를 창출하여, 단일 에이전트에게는 불가능했을 결과를 그룹이 달성할 수 있도록 합니다.

경험 법칙: 작업이 단일 에이전트에게 너무 복잡하고 전문적인 기술이나 도구가 필요한 별개의 하위 작업으로 분해될 수 있을 때 이 패턴을 사용하십시오. 이는 복잡한 연구 및 분석, 소프트웨어 개발 또는 창의적 콘텐츠 생성과 같이 다양한 전문 지식이나 여러 단계가 필요한 문제에 이상적입니다.

시각적 요약

그림 3: 멀티 에이전트 설계 패턴

주요 요점

  • 멀티 에이전트 협업은 공통의 목표를 달성하기 위해 함께 작업하는 여러 에이전트를 포함합니다.
  • 이 패턴은 전문화된 역할, 분산된 작업 및 에이전트 간 통신을 활용합니다.
  • 협업은 순차적 인계, 병렬 처리, 토론 또는 계층적 구조와 같은 형태를 취할 수 있습니다.
  • 이 패턴은 다양한 전문 지식이나 여러 단계가 필요한 복잡한 문제에 이상적입니다.

결론

본 장에서는 멀티 에이전트 협업 패턴을 탐구하며, 시스템 내에서 여러 전문 에이전트를 조정하는 것의 이점을 시연했습니다. 우리는 다양한 협업 모델을 검토했으며, 복잡하고 다면적인 문제를 다양한 도메인에 걸쳐 해결하는 데 있어 이 패턴의 필수적인 역할을 강조했습니다. 에이전트 협업에 대한 이해는 자연스럽게 그들의 외부 환경과의 상호 작용에 대한 탐구로 이어집니다.

참고 문헌


5장: 도구 사용 (함수 호출)

이전 장에서 우리는 언어 모델과 내부 워크플로우 간의 상호 작용을 조정하고 정보 흐름을 관리하는 데 주로 중점을 둔 에이전트 패턴(체이닝, 라우팅, 병렬화, 반성)을 논의했습니다. 그러나 에이전트가 진정으로 유용하고 외부 시스템 또는 실제 세계와 상호 작용하려면 도구를 사용할 수 있는 기능이 필요합니다.

함수 호출이라고도 하는 도구 사용(Tool Use) 패턴은 에이전트가 외부 API, 데이터베이스, 서비스 또는 심지어 코드를 실행하는 것과 상호 작용할 수 있도록 합니다. 이는 에이전트의 핵심인 LLM이 사용자 요청 또는 현재 작업 상태에 따라 특정 외부 함수를 호출할 시점과 방법을 결정할 수 있도록 합니다.

프로세스는 일반적으로 다음을 포함합니다.

    1. 도구 정의: 외부 함수 또는 기능이 정의되고 LLM에 설명됩니다. 이 설명에는 함수의 목적, 이름 및 허용하는 매개변수와 해당 유형 및 설명이 포함됩니다.
    1. LLM 결정: LLM은 사용자 요청과 사용 가능한 도구 정의를 수신합니다. 요청에 대한 이해와 도구에 따라 LLM은 요청을 충족하기 위해 하나 이상의 도구를 호출해야 하는지 결정합니다.
    1. 함수 호출 생성: LLM이 도구 사용을 결정하면, 요청된 도구의 이름과 제공된 인수를 지정하는 구조화된 출력(종종 JSON 개체)을 생성합니다.
    1. 도구 실행: 에이전트 프레임워크 또는 오케스트레이션 계층은 이 구조화된 출력을 가로채고, 요청된 도구를 식별하고, 제공된 인수로 실제 외부 함수를 실행합니다.
    1. 관찰/결과: 도구 실행의 출력 또는 결과가 에이전트에 반환됩니다.
    1. LLM 처리(선택 사항이지만 일반적): LLM은 도구 출력을 컨텍스트로 수신하고 이를 사용하여 사용자에게 최종 응답을 구성하거나 다음 단계(다른 도구 호출, 반성 또는 최종 답변 제공)를 결정하는 데 사용합니다. 이 패턴은 LLM의 훈련 데이터 및 내부 기능의 한계를 깨고 최신 정보에 액세스하거나, 사용자별 데이터와 상호 작용하거나, 실제 세계의 조치를 트리거할 수 있도록 하기 때문에 근본적입니다. 함수 호출은 LLM의 추론 기능과 외부에서 사용할 수 있는 방대한 기능 배열 사이의 격차를 연결하는 기술적 메커니즘입니다. “함수 호출”이 특정, 사전 정의된 코드 함수를 호출하는 것을 적절하게 설명하지만, 에이전트의 기능이 단순한 함수 실행을 넘어 확장될 수 있음을 인정하는 더 광범위한 “도구 호출” 개념을 고려하는 것이 유용합니다. “도구”는 전통적인 함수일 수 있지만, 외부 지식 기반에 대한 API 요청이나 다른 전문화된 에이전트에 대한 지침일 수도 있습니다. 이러한 관점은 기본 에이전트가 복잡한 데이터 분석 작업을 전용 “분석 에이전트”에 위임하거나 자체 API를 통해 외부 지식 기반을 쿼리하도록 하는 더 정교한 시스템을 구상할 수 있도록 합니다. “도구 호출”에 대한 생각은 이러한 다양한

6장: 계획

지능적인 행동은 종종 즉각적인 입력에 반응하는 것 이상을 포함합니다. 여기에는 복잡한 작업을 더 작고 관리하기 쉬운 단계로 나누고, 원하는 결과를 달성하기 위해 어떻게 진행할지 전략을 세우는 것이 필요합니다. 이것이 계획(Planning) 패턴이 중요해지는 지점입니다. 계획의 핵심은 에이전트 또는 에이전트 시스템이 초기 상태에서 목표 상태로 이동하기 위해 일련의 조치를 공식화할 수 있는 능력입니다. 이 계획은 미리 알려진 것이 아니라 요청에 응답하여 생성됩니다.

계획 패턴 개요

AI 맥락에서 계획 에이전트는 복잡한 목표를 위임받는 전문가라고 생각하는 것이 유용합니다. “팀 오프사이트 구성”을 요청하면 무엇을 해야 하는지(목표 및 제약 조건)를 정의하지만 어떻게 해야 하는지는 정의하지 않습니다. 에이전트의 핵심 임무는 그 목표를 달성하기 위한 경로를 자율적으로 계획하는 것입니다. 먼저 초기 상태(예: 예산, 참가자 수, 원하는 날짜)와 목표 상태(성공적으로 예약된 오프사이트)를 이해해야 하며, 그 다음 이 둘을 연결하는 최적의 행동 시퀀스를 발견해야 합니다. 계획은 경직된 스크립트가 아니라 단지 시작점에 불과합니다. 에이전트의 진정한 힘은 새로운 정보를 통합하고 장애물을 우회하기 위해 프로젝트를 조정할 수 있는 능력에 있습니다. 예를 들어, 선호하는 장소를 사용할 수 없게 되거나 선택한 케이터링 업체가 예약이 꽉 찼다면, 유능한 에이전트는 단순히 실패하지 않습니다. 그것은 조정합니다. 새로운 제약 조건을 등록하고, 옵션을 재평가하며, 대체 장소를 제안하거나 다른 날짜를 제안하여 새로운 계획을 공식화합니다.

그러나 유연성과 예측 가능성 사이의 상충 관계를 인식하는 것이 중요합니다. 동적 계획은 보편적인 해결책이 아니라 특정 도구입니다. 잘 이해되고 반복 가능한 해결책을 가진 문제의 경우, 에이전트를 미리 정의된 고정된 워크플로우로 제한하는 것이 더 효과적입니다. 이 접근 방식은 불확실성과 예측 불가능한 행동의 위험을 줄이기 위해 에이전트의 자율성을 제한하여 신뢰할 수 있고 일관된 결과를 보장합니다. 따라서 계획 에이전트와 단순한 작업 실행 에이전트를 사용할지 여부를 결정하는 것은 단 하나의 질문에 달려 있습니다. 즉, “어떻게”가 발견되어야 합니까, 아니면 이미 알려져 있습니까?

실용적인 응용 및 사용 사례

계획 패턴은 자율 시스템에서 핵심적인 계산 프로세스이며, 에이전트가 지정된 목표를 달성하기 위해 일련의 이산적인 실행 가능한 단계를 합성할 수 있도록 합니다. 이 프로세스는 고수준 목표를 이산적인 하위 문제 또는 하위 목표의 구조화된 시퀀스로 변환하여, 필요한 경우 도구를 호출하거나 다양한 시스템과 상호 작용하여 복잡한 워크플로우를 조정할 수 있도록 합니다.

절차적 작업 자동화 영역에서 계획은 워크플로우 조정을 위해 사용됩니다. 예를 들어, 신규 직원을 온보딩하는 비즈니스 프로세스는 시스템 계정 생성, 교육 모듈 할당, 다양한 부서와의 조정과 같은 지시적인 하위 작업 시퀀스로 분해될 수 있습니다. 에이전트는 이러한 단계를 논리적 순서로 실행하고 필요한 도구를 호출하거나 다양한 시스템과 상호 작용하여 종속성을 관리하는 계획을 생성합니다.

로보틱스 및 자율 탐색에서는 상태 공간 횡단에 계획이 기본이 됩니다. 시스템(물리적 로봇이든 가상 개체이든)은 초기 상태에서 목표 상태로 전환하기 위해 경로 또는 일련의 조치를 생성해야 합니다. 이는 환경 제약 조건(예: 장애물 회피 또는 교통 규칙 준수)을 준수하는 동시에 시간 또는 에너지 소비와 같은 메트릭을 최적화하는 것을 포함합니다.

이 패턴은 또한 구조화된 정보 종합에 중요합니다. 복잡한 출력(예: 연구 보고서)을 생성하도록 요청받을 때, 에이전트는 정보 수집, 데이터 요약, 콘텐츠 구조화 및 반복적 개선 단계를 포함하는 계획을 공식화할 수 있습니다. 유사하게, 다단계 문제 해결을 포함하는 고객 지원 시나리오에서 에이전트는 진단, 솔루션 구현 및 에스컬레이션을 위한 체계적인 계획을 생성하고 따를 수 있습니다.

본질적으로 계획 패턴은 에이전트가 단순한 반응적 행동을 넘어 목표 지향적 행동을 할 수 있도록 합니다. 이는 상호 의존적인 작업의 일관된 시퀀스를 요구하는 문제를 해결하는 데 필요한 논리적 프레임워크를 제공합니다.

실습 코드 예제 (Crew AI)

다음 섹션에서는 복잡한 쿼리에 응답하기 위해 먼저 다단계 계획을 공식화한 다음 해당 계획을 순차적으로 실행하는 에이전트를 활용하여 계획자 패턴을 Crew AI 프레임워크를 사용하여 구현하는 방법을 보여줍니다.

import os
from dotenv import load_dotenv
from crewai import Agent, Task, Crew, Process
from langchain_openai import ChatOpenAI
# 보안을 위해 .env 파일에서 환경 변수 로드
load_dotenv()
# 1. 명확성을 위해 언어 모델을 명시적으로 정의
llm = ChatOpenAI(model="gpt-4-turbo")
# 2. 명확하고 집중된 에이전트 정의
planner_writer_agent = Agent(
   role='기사 계획 및 작성자',
   goal='지정된 주제에 대한 간결하고 매력적인 요약을 계획하고 작성합니다.',
   backstory=(
       '당신은 전문 기술 작가이자 콘텐츠 전략가입니다. '
       '당신의 강점은 작성 전에 명확하고 실행 가능한 계획을 '
       '만드는 데 있으며, 최종 요약이 유익하면서도 이해하기 쉽도록 '
       '보장하는 것입니다.'
   ),
   verbose=True,
   allow_delegation=False,
   llm=llm # 특정 LLM을 에이전트에 할당
)
# 3. 더 구조화되고 구체적인 예상 출력을 가진 작업을 정의합니다.
topic = "강화 학습이 AI에서 중요한 이유"
high_level_task = Task(
   description=(
       f"1. '{topic}' 요약에 대한 글머리 기호 계획을 생성하십시오.\n"
       f"2. 계획에 따라 요약을 작성하십시오. 200단어 내외로 유지하십시오."
   ),
   expected_output=(
       "다음 두 가지 구별된 섹션을 포함하는 최종 보고서:\n\n"
       "### 계획\n"
       "- 요약의 주요 요점을 설명하는 글머리 기호 목록.\n\n"
       "### 요약\n"
       "- 주제에 대한 간결하고 잘 구조화된 요약."
   ),
   agent=planner_writer_agent,
)
# 명확한 프로세스로 크루 생성
crew = Crew(
   agents=[planner_writer_agent],
   tasks=[high_level_task],
   process=Process.sequential,
)
# 작업 실행
print("## 계획 및 작성 작업 실행 중 ##")
result = crew.kickoff()
print("\n\n---\n## 작업 결과 ##\n---")
print(result)

이 코드는 CrewAI 프레임워크를 사용하여 주어진 주제에 대한 요약을 계획하고 작성하는 AI 에이전트를 생성합니다. 이 코드는 필요한 라이브러리를 가져오고, .env 파일에서 API 키를 로드한 다음, 에이전트와 작업을 정의합니다.

planner_writer_agent는 기술 작가이자 콘텐츠 전략가 역할을 하도록 설정되어, 작성 전에 명확한 계획을 세우는 데 중점을 둡니다. high_level_task는 에이전트에게 먼저 계획을 생성하고 그 다음 200단어 요약을 작성하도록 지시합니다. 예상 출력은 “계획” 및 “요약”이라는 명확한 두 섹션으로 구분된 형식을 요구합니다.

크루는 에이전트와 작업을 포함하도록 조립되고, Process.sequential로 설정되어 작업이 순서대로 실행되도록 합니다. 최종적으로 crew.kickoff()를 호출하여 작업을 실행하고 그 결과를 인쇄합니다.

Google DeepResearch

Google Gemini DeepResearch(그림 1 참조)는 자율적인 정보 검색 및 종합을 위해 설계된 에이전트 기반 시스템입니다. 이는 복잡한 주제를 체계적으로 탐색하기 위해 Google 검색을 동적으로 반복적으로 쿼리하는 다단계 에이전트 파이프라인을 기반으로 작동합니다. 이 시스템은 대규모 코퍼스의 웹 기반 소스를 처리하고, 수집된 데이터를 관련성 및 지식 격차에 대해 평가하며, 이를 해결하기 위해 후속 검색을 수행하도록 구성되어 있습니다. 최종 출력은 출처를 인용하여 구조화된 다중 페이지 요약으로 통합됩니다.

더 나아가, 이 시스템의 작동은 단일 쿼리-응답 이벤트가 아니라 관리되는 장기 실행 프로세스입니다. 이 프로세스는 사용자가 복잡한 쿼리를 선언하고 조사를 승인할 수 있는 다단계 연구 계획을 생성하는 것으로 시작됩니다. 일단 계획이 승인되면, 에이전트 파이프라인은 반복적인 검색 및 분석 루프를 시작합니다. 여기에는 단순히 일련의 사전 정의된 검색을 실행하는 것 이상이 포함됩니다. 에이전트는 수집된 정보를 기반으로 쿼리를 동적으로 공식화하고 개선하며, 지식 격차를 식별하고, 데이터 포인트를 교차 확인하고, 불일치를 해결합니다.

그림 1: Google Deep Research 에이전트가 Google 검색 도구를 사용하여 실행 계획을 생성하는 모습.

이 프로세스의 핵심적인 아키텍처 구성 요소는 이 프로세스를 비동기적으로 관리하는 시스템의 능력입니다. 이러한 설계는 수백 개의 소스를 분석할 수 있는 조사 작업이 단일 지점 장애에 탄력적이며 사용자가 참여를 중단하고 완료 시 알림을 받을 수 있도록 보장합니다. 이 시스템은 또한 사용자가 제공한 문서를 통합하여 개인 소스 정보와 웹 기반 연구를 결합할 수 있습니다. 최종 출력은 단순히 발견 사항을 연결한 것이 아니라 논리적 섹션으로 콘텐츠를 구성하는 중요한 평가를 수행하는 합성 단계에서 일관된 내러티브로 정보를 구성하는 구조화된 다중 페이지 보고서입니다. 보고서는 종종 오디오 개요, 차트 및 원본 인용 소스에 대한 링크를 포함하여 대화형으로 설계되어 확인 및 추가 탐색이 가능합니다. 또한, 모델은 최종 종합 결과와 함께 수행한 모든 검색을 소스 목록으로 명시적으로 반환하여 투명성을 높입니다.

이러한 체계적인 접근 방식은 수많은 정보 소스를 처리할 수 있는 능력에서 비롯되며, 이는 수동 연구자가 비슷한 시간 내에 달성할 수 있는 것보다 더 광범위한 분석 범위를 제공합니다. 이 광범위한 분석 범위는 덜 명백하지만 잠재적으로 중요한 정보의 발견 가능성을 높여 주제에 대한 더 강력하고 잘 뒷받침되는 이해로 이어집니다.

OpenAI Deep Research API는 복잡한 연구 작업을 자동화하도록 설계된 특수 도구입니다. 이 API는 고급 에이전트 모델을 활용하여 독립적으로 추론하고, 계획하고, 실제 소스의 정보를 종합합니다. 단순한 Q&A 모델과 달리, 높은 수준의 쿼리를 받아들여 자율적으로 하위 질문으로 분해하고, 내장된 도구를 사용하여 웹 검색을 수행하며, 인용이 포함된 구조화된 최종 보고서를 제공합니다. API는 이 전체 프로세스에 대한 직접적인 프로그래밍 액세스를 제공하며, 고품질 종합 및 빠른 응답을 위한 모델을 사용합니다.

OpenAI Deep Research API

  • 특징: 구조화된 보고서, 내부 인용, 프로세스 투명성.
  • 예제: semaglutide의 경제적 영향에 대한 보고서 요청.
from openai import OpenAI
client = OpenAI(api_key="YOUR_OPENAI_API_KEY")
system_message = """당신은 구조화되고 데이터 기반의 보고서를
준비하는 전문 연구원입니다.
데이터가 풍부한 통찰력에 중점을 두고 신뢰할 수 있는 소스를
사용하며, 인라인 인용을 포함하십시오."""
user_query = "전 세계 의료 시스템에 대한 세마글루타이드의
경제적 영향에 대해 연구하십시오."
response = client.responses.create(
 model="o3-deep-research-2025-06-26",
 input=[
   {
     "role": "developer",
     "content": [{"type": "input_text", "text": system_message}]
   },
   {
     "role": "user",
     "content": [{"type": "input_text", "text": user_query}]
   }
 ],
 reasoning={"summary": "auto"},
 tools=[{"type": "web_search_preview"}]
)
final_report = response.output[-1].content[0].text
print(final_report)

시각적 요약

그림 4: 계획 설계 패턴

주요 요점

다음은 주요 요점입니다.

  • 계획은 에이전트가 복잡한 목표를 실행 가능한 순차적 단계로 분해할 수 있도록 합니다.
  • 이는 다단계 작업 자동화, 워크플로우 자동화 및 외부 데이터 소스와의 상호 작용에 필수적입니다.
  • LLM은 작업 설명에 기반하여 단계별 접근 방식을 생성하여 계획을 수행할 수 있습니다.
  • 명시적으로 작업을 계획 단계로 만들거나 복잡한 결과를 요청하는 것은 에이전트의 추론 능력을 향상시키는 데 도움이 됩니다.
  • Google Deep Research는 Google 검색 도구를 사용하여 얻은 소스를 기반으로 분석을 수행하는 에이전트의 예시입니다.

결론

결론적으로, 계획 패턴은 에이전트형 시스템을 단순한 반응형 응답자가 아닌 전략적, 목표 지향적 실행자로 승격시키는 필수적인 구성 요소입니다. 현대 대규모 언어 모델은 고수준 목표를 일관된 실행 가능한 단계로 자율적으로 분해할 수 있는 핵심 기능을 제공합니다. 이 패턴은 단순한 순차적 작업 실행부터 복잡한 시스템 구성에 이르기까지 확장됩니다. CrewAI 에이전트가 작성 계획을 생성하고 따르는 방식은 이 패턴의 적용을 보여줍니다. 궁극적으로 계획은 인간의 의도와 자동화된 실행 사이의 필수적인 다리를 제공하여 에이전트가 복잡한 워크플로우를 관리하고 포괄적인 종합 결과를 제공할 수 있도록 합니다.

참고 자료


7장: 다중 에이전트 협업

개별 AI 에이전트는 종종 복잡하고 다면적인 문제를 다룰 때 제한된 능력에 직면합니다. 이러한 한계를 극복하기 위해 **에이전트 간 통신(A2A, Inter-Agent Communication)**은 다양한 AI 에이전트가 원활하게 조정하고, 작업을 위임하며, 정보를 교환할 수 있도록 합니다.

Google의 A2A 프로토콜은 이러한 통신을 용이하게 하기 위해 설계된 개방형 표준으로, LangGraph, CrewAI 또는 Google ADK와 같은 다양한 프레임워크로 개발된 에이전트 간의 상호 운용성을 보장합니다.

다중 에이전트 협업 패턴 개요

다중 에이전트 협업 패턴은 여러 독립적이거나 반독립적인 에이전트가 공통 목표를 달성하기 위해 함께 작동하도록 시스템을 설계하는 것을 포함합니다. 각 에이전트는 일반적으로 정의된 역할, 전반적인 목표와 일치하는 특정 목표, 그리고 잠재적으로 다른 도구 또는 지식 기반에 대한 액세스 권한을 가집니다. 이 패턴의 힘은 이러한 에이전트 간의 상호 작용과 시너지 효과에 있습니다.

협업은 다음과 같은 다양한 형태로 나타날 수 있습니다.

순차적 인계: 한 에이전트가 작업을 완료하고 그 출력을 파이프라인의 다음 단계에 대해 다른 에이전트에게 전달합니다(계획 패턴과 유사하지만 명시적으로 다른 에이전트 포함).

  • 병렬 처리: 여러 에이전트가 문제의 다른 부분에서 동시에 작업하고 그 결과는 나중에 결합됩니다.
  • 토론 및 합의: 다양한 관점과 정보 소스를 가진 에이전트가 옵션을 평가하고, 궁극적으로 합의 또는 더 정보에 입각한 결정을 내리기 위해 논쟁에 참여하는 다중 에이전트 협업.
  • 계층적 구조: 관리자 에이전트가 도구 액세스 또는 플러그인 기능에 따라 동적으로 작업 에이전트에게 작업을 위임하고 그 결과를 종합할 수 있습니다. 각 에이전트도 단일 에이전트가 모든 도구를 처리하는 대신 관련 도구 그룹을 처리할 수 있습니다.
  • 전문가 팀: 다양한 도메인에서 전문 지식을 가진 에이전트(예: 연구원, 작가, 편집자)가 복잡한 출력을 생성하기 위해 협력합니다.
  • 비평가-검토자: 에이전트는 계획, 초안 또는 답변과 같은 초기 출력을 생성합니다. 두 번째 에이전트 그룹은 정책 준수, 보안, 규정 준수, 정확성, 품질 및 조직 목표와의 정렬에 대해 이 출력을 비판적으로 평가합니다. 원래 생성자 또는 최종 에이전트가 이 피드백을 기반으로 출력을 수정합니다. 이 패턴은 코드 생성, 연구 작문, 논리 확인 및 윤리적 정렬 보장에서 특히 효과적입니다. 이 접근 방식의 장점은 향상된 강력함, 개선된 품질 및 할루시네이션 또는 오류 가능성 감소입니다.

다중 에이전트 시스템(그림 1 참조)은 본질적으로 에이전트 역할 및 책임의 구획화, 에이전트가 정보를 교환하는 통신 채널 설정, 협력적 노력 지시를 위한 작업 흐름 또는 상호 작용 프로토콜 공식화로 구성됩니다.

Crew AI 및 Google ADK와 같은 프레임워크는 에이전트의 사양, 작업 및 상호 작용 절차를 정의할 수 있는 구조를 제공하여 이러한 패러다임을 용이하게 합니다. 이 접근 방식은 다양한 전문 지식, 여러 이산 단계 활용 또는 동시 처리 및 에이전트 간 정보 확인의 이점을 필요로 하는 과제에 특히 효과적입니다.

실용적인 응용 및 사용 사례

다중 에이전트 협업은 모듈성, 확장성 및 향상된 지능을 가능하게 하므로 다양한 도메인에 적용할 수 있는 강력한 패턴입니다.

  • 복잡한 연구 및 분석: 여러 에이전트 팀이 연구 프로젝트를 공동으로 수행할 수 있습니다. 한 에이전트는 학술 데이터베이스 검색을 전문으로 하고, 다른 에이전트는 요약, 다른 에이전트는 추세 식별, 네 번째 에이전트는 정보를 보고서로 종합하는 역할을 할 수 있습니다. 이는 인간 연구팀이 운영하는 방식과 유사합니다.
  • 소프트웨어 개발: 여러 에이전트가 소프트웨어 구축에 협력하는 것을 상상해 보십시오. 한 에이전트는 요구사항 분석가, 다른 에이전트는 코드 생성기, 세 번째 에이전트는 테스터, 네 번째 에이전트는 문서 작성자가 될 수 있습니다. 이들은 출력을 서로 전달하여 구성 요소를 구축하고 확인합니다.
  • 창의적인 콘텐츠 생성: 마케팅 캠페인 생성에는 시장 조사 에이전트, 카피라이터 에이전트, 그래픽 디자인 에이전트(이미지 생성 도구 사용), 소셜 미디어 예약 에이전트가 모두 함께 작업할 수 있습니다.
  • 재무 분석: 다중 에이전트 시스템은 금융 시장을 분석할 수 있습니다. 에이전트는 주식 데이터 가져오기, 뉴스 감성 분석, 기술적 분석 수행, 투자 권장 사항 생성 등을 전문으로 할 수 있습니다.
  • 고객 지원 에스컬레이션: 최전선 지원 에이전트가 초기 문의를 처리하고, 문제 복잡성에 따라 전문 에이전트(예: 기술 전문가 또는 청구 전문 에이전트)에게 문제를 에스컬레이션할 수 있으며, 이는 문제 복잡성에 따른 순차적 인계를 시연합니다.
  • 공급망 최적화: 에이전트는 공급업체, 제조업체, 유통업체와 같은 공급망의 다양한 노드를 나타내며 수요 또는 중단 변화에 대응하여 재고 수준, 물류 및 일정을 최적화하기 위해 협력할 수 있습니다.
  • 네트워크 분석 및 복구: 자율 운영은 특히 장애 지점 찾기에서 에이전트 아키텍처의 큰 이점을 얻습니다. 여러 에이전트가 문제를 분류하고 해결하기 위해 협력하여 최적의 조치를 제안할 수 있습니다. 이러한 에이전트는 기존 시스템을 활용하는 동시에 기존 머신러닝 모델 및 도구를 통합할 수 있으며, 동시에 생성형 AI의 이점을 제공합니다.

전문화된 에이전트를 분할하고 상호 관계를 세심하게 조정하는 능력은 개발자가 향상된 모듈성, 확장성 및 단일 통합 에이전트의 잠재 능력을 초과하는 복잡성을 다룰 수 있는 능력을 갖춘 시스템을 구축할 수 있도록 권한을 부여합니다.

다중 에이전트 협업: 상호 관계 및 통신 구조 탐색

복잡한 다중 에이전트 시스템을 설계하는 데 있어 에이전트가 상호 작용하고 통신하는 정교한 방식을 이해하는 것은 근본적으로 중요합니다. 그림 2에 묘사된 바와 같이, 가장 단순한 단일 에이전트 시나리오에서부터 복잡하고 사용자 지정 설계된 협업 프레임워크에 이르기까지 상호 관계 및 통신 모델의 스펙트럼이 존재합니다. 각 모델은 고유한 장점과 과제를 제시하며, 이는 다중 에이전트 시스템의 전반적인 효율성, 강력함 및 적응성에 영향을 미칩니다.

  • 1. 단일 에이전트: 가장 기본적인 수준에서 “단일 에이전트”는 다른 개체와의 직접적인 상호 작용이나 통신 없이 자율적으로 작동합니다. 이 모델은 구현 및 관리가 간단하지만, 그 능력은 본질적으로 개별 에이전트의 범위와 자원으로 제한됩니다. 단일하고 자급자족하는 에이전트에 의해 해결될 수 있는 독립적인 하위 문제들로 분해될 수 있는 작업에 적합합니다.
  • 2. 네트워크: “네트워크” 모델은 여러 에이전트가 탈중앙화된 방식으로 서로 직접 상호 작용하는 협업을 향한 중요한 단계를 나타냅니다. 통신은 일반적으로 피어 투 피어(peer-to-peer)로 발생하여 정보, 리소스 및 심지어 작업의 공유를 허용합니다. 이 모델은 한 에이전트의 실패가 전체 시스템을 마비시키지 않으므로 탄력성을 조성합니다. 그러나 대규모의 구조화되지 않은 네트워크에서 통신 오버헤드를 관리하고 일관된 의사 결정을 보장하는 것은 어려울 수 있습니다.
  • 3. 감독자(Supervisor): 전용 에이전트인 “감독자”가 하위 에이전트 그룹의 활동을 감독하고 조정합니다. 감독자는 통신, 작업 할당 및 갈등 해결을 위한 중앙 허브 역할을 합니다. 이 계층적 구조는 명확한 권한 라인을 제공하며 관리를 단순화할 수 있습니다. 그러나 감독자에게 단일 실패 지점이 발생하며, 감독자가 너무 많은 하위 에이전트나 복잡한 작업으로 압도되면 병목 현상이 될 수 있습니다.
  • 4. 도구로서의 감독자: 이 모델은 감독자의 역할이 다른 에이전트에게 모든 조치를 직접 명령하고 제어하는 것이 아니라, 다른 에이전트에게 리소스, 지침 또는 분석 지원을 제공하는 것으로 초점을 옮기는 “감독자” 개념의 미묘한 확장입니다. 감독자는 다른 에이전트가 작업을 더 효과적으로 수행할 수 있도록 도구, 데이터 또는 계산 서비스를 제공할 수 있지만, 모든 조치를 지시하지는 않습니다. 이 접근 방식은 감독자의 능력을 활용하면서도 엄격한 상향식 제어를 부과하지 않도록 목표합니다.
  • 5. 계층적(Hierarchical): “계층적” 모델은 감독자 개념을 확장하여 다층적인 조직 구조를 생성합니다. 여기에는 여러 수준의 감독자가 포함되며, 상위 수준의 감독자가 하위 수준의 감독자를 감독하고, 궁극적으로 가장 낮은 계층에 운영 에이전트 컬렉션이 있습니다. 이 구조는 복잡한 문제를 하위 문제로 분해하고 각 하위 문제를 특정 계층이 관리하도록 함으로써 확장성 및 복잡성 관리에 대한 구조화된 접근 방식을 제공합니다. 이는 정의된 경계 내에서 분산된 의사 결정을 허용합니다.

그림 2: 에이전트가 다양한 방식으로 통신하고 상호 작용합니다.

  • 6. 사용자 지정(Custom): “사용자 지정” 모델은 다중 에이전트 시스템 설계에서 궁극적인 유연성을 나타냅니다. 이는 주어진 문제 또는 애플리케이션의 특정 요구 사항에 정확하게 맞게 조정된 고유한 상호 관계 및 통신 구조를 생성할 수 있도록 합니다. 여기에는 이전에 언급된 모델의 요소를 결합하거나, 시스템 설계의 고유한 제약 조건 및 기회에서 발생하는 완전히 새로운 디자인이 포함될 수 있습니다. 사용자 지정 모델은 일반적으로 특정 성능 메트릭을 최적화하거나, 통신 프로토콜, 조정 메커니즘 및 잠재적 동작을 신중하게 고려하여 설계 및 구현에 대한 깊은 이해를 요구합니다.

요약하자면, 다중 에이전트 시스템의 상호 관계 및 통신 모델 선택은 중요한 설계 결정입니다. 각 모델은 고유한 장점과 단점을 제공하며, 최적의 선택은 작업의 복잡성, 에이전트 수, 원하는 자율성 수준, 강력함에 대한 필요성, 허용 가능한 통신 오버헤드와 같은 요소에 따라 달라집니다. 다중 에이전트 시스템에 대한 미래의 발전은 이러한 모델을 계속 탐색하고 개선할 가능성이 높으며, 협력적 지능을 위한 새로운 패러다임을 개발할 것입니다.

"""

또 한번 아래 내용은 파이썬 코딩이 들어가 있는데 인포그래픽에 추가해라.

"""

실습 코드 예제 (Crew AI)

이 Python 코드는 AI 기반 크루를 사용하여 AI 트렌드에 대한 블로그 게시물을 생성하도록 CrewAI 프레임워크를 사용합니다. 이 예제는 환경을 설정하고, .env 파일에서 API 키를 로드한 다음, 연구원이 AI 트렌드를 찾고 요약하도록 하고, 작가 에이전트가 연구를 기반으로 블로그 게시물을 작성하도록 하는 두 에이전트를 정의합니다.

연구 작업의 출력이 글쓰기 작업의 입력이 되도록 두 작업이 정의됩니다. 이 에이전트와 작업은 작업이 순차적으로 실행되도록 지정하는 Crew에 조립됩니다. 크루는 에이전트, 작업 및 언어 모델(구체적으로 “gemini-2.0-flash” 모델)과 함께 초기화됩니다. 메인 함수는 kickoff() 메서드를 사용하여 이 크루를 실행하여 원하는 출력을 생성하기 위해 에이전트 간의 협업을 조정합니다. 마지막으로, 코드는 크루 실행의 최종 결과를 인쇄합니다.

import os
from dotenv import load_dotenv
from crewai import Agent, Task, Crew, Process
from langchain_google_genai import ChatGoogleGenerativeAI
def setup_environment():
   """환경 변수를 로드하고 필수 API 키를 확인합니다."""
   load_dotenv()
   if not os.getenv("GOOGLE_API_KEY"):
       raise ValueError("GOOGLE_API_KEY가 없습니다. .env 파일에
설정하십시오.")
def main():
   """
   최신 Gemini 모델을 사용하여 콘텐츠 생성을 위한 AI 크루를
초기화하고 실행합니다.
   """
   setup_environment()
   # 사용할 언어 모델 정의.
# 최신 Gemini 모델 시리즈를 사용하여 성능 향상을 위해 업데이트되었습니다.
   # 최첨단(미리 보기) 기능을 위해 "gemini-2.5-flash"를 사용할 수
있습니다.
   llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash")
   # 특정 역할과 목표를 가진 에이전트 정의
   researcher = Agent(
       role='선임 연구 분석가',
       goal='AI의 최신 트렌드를 찾고 요약합니다.',
       backstory="당신은 주요 트렌드를 파악하고 정보를 종합하는
재능이 있는 숙련된 연구 분석가입니다.",
       verbose=True,
       allow_delegation=False,
   )
   writer = Agent(
       role='기술 콘텐츠 작가',
       goal='연구 결과를 기반으로 명확하고 매력적인 블로그 게시물을
작성합니다.',
       backstory="당신은 복잡한 기술 주제를 접근하기 쉬운 콘텐츠로
번역할 수 있는 숙련된 작가입니다.",
       verbose=True,
       allow_delegation=False,
   )
   # 에이전트에 대한 작업 정의
   research_task = Task(
       description="2024-2025년 AI의 상위 3가지 신흥 트렌드를
연구하십시오. 실제 응용 프로그램과 잠재적 영향에 중점을
두십시오.",
       expected_output="주요 요점과 소스가 포함된 상위 3가지 AI
트렌드에 대한 상세한 요약.",
       agent=researcher,
   )
   writing_task = Task(
       description="연구 결과를 기반으로 500단어 블로그 게시물을
작성하십시오. 게시물은 일반 청중이 이해하기 쉽고 매력적이어야
합니다.",
       expected_output="최신 AI 트렌드에 대한 완전한 500단어 블로그
게시물.",
       agent=writer,
       context=[research_task],
   )
   # 크루 생성
blog_creation_crew = Crew(
       agents=[researcher, writer],
       tasks=[research_task, writing_task],
       process=Process.sequential,
       llm=llm,
       verbose=2 # 상세한 크루 실행 로그를 위해 상세도 설정
   )
   # 크루 실행
   print("## Gemini 2.0 Flash를 사용하여 블로그 생성 크루 실행...
##")
   try:
       result = blog_creation_crew.kickoff()
       print("\n------------------\n")
       print("## 크루 최종 출력 ##")
       print(result)
   except Exception as e:
       print(f"\n예상치 못한 오류 발생: {e}")
if __name__ == "__main__":
   main()

이제 Google ADK 프레임워크 내에서 계층적, 병렬, 순차적 조정 패러다임과 운영 도구로서의 에이전트 구현을 구현하는 특정 예제를 검토하는 데 주의를 기울이겠습니다.

실습 코드 예제 (Google ADK)

다음 코드 예제는 계층적 에이전트 구조를 설정하는 방법을 보여줍니다. 여기에는 BaseAgent에서 파생된 사용자 지정 TaskExecutorLlmAgent의 두 가지 유형의 에이전트가 포함됩니다. TaskExecutor는 특정, 비-LLM 작업을 위해 설계되었으며 이 예제에서는 단순히 “작업이 성공적으로 완료되었습니다” 이벤트를 생성합니다. greeter라는 LlmAgent는 지정된 모델과 친근한 인사말을 하는 지침을 사용하여 초기화됩니다. task_doer라는 사용자 지정 TaskExecutor 인스턴스가 생성됩니다. 다음으로, coordinator라는 부모 LlmAgent가 모델과 지침을 사용하여 생성됩니다. 코디네이터의 지침은 인사 요청을 Greeter에게 위임하고 작업 실행을 TaskExecutor에게 위임하도록 안내합니다. Greeter와 TaskDoer는 코디네이터의 하위 에이전트로 추가되어 부모-자식 관계를 설정합니다. 코드는 이 관계가 올바르게 설정되었는지 확인하는 어설션(assertion)을 수행합니다. 마지막으로, 계층적 에이전트 구조가 성공적으로 생성되었음을 나타내는 메시지를 인쇄합니다.

from google.adk.agents import LlmAgent, BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event
from typing import AsyncGenerator
# 사용자 지정 에이전트를 BaseAgent에서 확장하여 올바르게
구현합니다.
class TaskExecutor(BaseAgent):
   """사용자 지정 비-LLM 동작을 가진 전문 에이전트입니다."""
   name: str = "TaskExecutor"
   description: str = "미리 정의된 작업을 실행합니다."
   async def _run_async_impl(self, context: InvocationContext) ->
AsyncGenerator[Event, None]:
       """작업에 대한 사용자 지정 구현 논리."""
       # 여기에 사용자 지정 논리가 들어갑니다.
       # 이 예제의 경우 간단한 이벤트를 생성합니다.
       yield Event(author=self.name, content="작업이 성공적으로
완료되었습니다.")
# 개별 에이전트 정의, 올바른 초기화 포함
# LlmAgent는 모델을 지정해야 합니다.
greeter = LlmAgent(
   name="Greeter",
   model="gemini-2.0-flash-exp",
   instruction="당신은 친근한 인사 담당자입니다."
)
task_doer = TaskExecutor() # 사용자 지정 에이전트 인스턴스화
# 부모 에이전트를 만들고 하위 에이전트를 할당합니다.
# 부모 에이전트의 설명과 지침은 위임 논리를 안내해야 합니다.
coordinator = LlmAgent(
   name="Coordinator",
   model="gemini-2.0-flash-exp",
   description="사용자 요청을 Greeter에게 위임하고 작업을
TaskExecutor에게 위임할 수 있는 코디네이터입니다.",
   instruction="인사를 요청받으면 Greeter에게 위임하십시오. 작업을
실행하도록 요청받으면 TaskExecutor에게 위임하십시오.",
   sub_agents=[
       greeter,
       task_doer
   ]
)
# ADK 프레임워크는 자동으로 부모-자식 관계를 설정합니다.
# 이 어설션들은 초기화 후 확인되면 통과할 것입니다.
assert greeter.parent_agent == coordinator
assert task_doer.parent_agent == coordinator
print("에이전트 계층 구조가 성공적으로 생성되었습니다.")

이 코드는 Google ADK 내에서 계층적 에이전트 구조를 설정하는 방법을 보여줍니다. 이 코드는 BaseAgent에서 파생된 TaskExecutor라는 사용자 지정 에이전트를 정의합니다. 이 에이전트는 비-LLM 작업을 나타내며, 성공적으로 완료되었음을 나타내는 이벤트를 생성합니다. 그런 다음 greeter라는 표준 LlmAgenttask_doer라는 TaskExecutor 인스턴스를 만듭니다. 이러한 하위 에이전트는 coordinator라는 부모 LlmAgent에 할당되어 계층 구조를 형성합니다. ADK는 이러한 관계를 자동으로 설정하며, 코드는 이 관계가 올바르게 설정되었는지 확인하는 어설션을 포함합니다. 이 예제는 ADK를 사용하여 단순한 비-LLM 기능과 LLM 추론 능력을 결합하는 방법을 보여줍니다.

다음 코드 발췌는 Google ADK 프레임워크 내에서 반복적 워크플로우를 설정하기 위해 LoopAgent를 활용하는 방법을 설명합니다. 이 코드는 두 에이전트, ConditionChecker 및 ProcessingStep을 정의합니다. ConditionChecker는 세션 상태에서 “status” 값을 확인하는 사용자 지정 에이전트입니다. “status”가 “completed”이면 ConditionChecker는 루프를 중지하기 위해 이벤트를 에스컬레이션합니다. 그렇지 않으면 루프를 계속하도록 이벤트를 생성합니다. ProcessingStep은 “gemini-2.0-flash-exp” 모델을 사용하는 LlmAgent입니다. 그 지침은 작업을 수행하고 최종 단계인 경우 세션 상태를 “completed”로 설정하는 것입니다. StatusPoller라는 LoopAgent가 생성됩니다. StatusPoller는 ProcessingStep과 ConditionChecker 인스턴스를 하위 에이전트로 포함합니다. LoopAgent는 이 두 하위 에이전트를 순차적으로 실행하며, 상태가 “completed”로 설정되거나 10회 반복 횟수에 도달할 때까지 반복합니다.

import asyncio
from typing import AsyncGenerator
from google.adk.agents import LoopAgent, LlmAgent, BaseAgent
from google.adk.events import Event, EventActions
from google.adk.agents.invocation_context import InvocationContext
# 모범 사례: 사용자 지정 에이전트를 완전하고 자체 설명적인
클래스로 정의합니다.
class ConditionChecker(BaseAgent):
   """세션 상태에서 'completed' 상태를 확인하는 사용자 지정
에이전트입니다."""
   name: str = "ConditionChecker"
   description: str = "프로세스가 완료되었는지 확인하고 루프를
중지하도록 신호를 보냅니다."
   async def _run_async_impl(
       self, context: InvocationContext
   ) -> AsyncGenerator[Event, None]:
       """상태를 확인하고 루프를 계속하거나 중지하도록 이벤트를
생성합니다."""
       status = context.session.state.get("status", "pending")
       is_done = (status == "completed")
       if is_done:
           # 조건이 충족되면 루프를 종료하도록 에스컬레이션합니다.
           yield Event(author=self.name,
actions=EventActions(escalate=True))
       else:
           # 루프를 계속하기 위해 간단한 이벤트를 생성합니다.
           yield Event(author=self.name, content="조건이 충족되지
않아 루프를 계속합니다.")
# 처리는 상태를 저장하도록 지시하는 모델이 포함된 LlmAgent여야
합니다.
process_step = LlmAgent(
   name="ProcessingStep",
   model="gemini-2.0-flash-exp",
   instruction="당신은 더 긴 프로세스의 한 단계입니다. 작업을
수행하십시오. 최종 단계인 경우 세션 상태를 'completed'로 설정하여
업데이트하십시오."
)
# LoopAgent는 워크플로우를 조정합니다.
poller = LoopAgent(
   name="StatusPoller",
   max_iterations=10,
   sub_agents=[
       process_step,
       ConditionChecker() # 잘 정의된 사용자 지정 에이전트
인스턴스화.
   ]
)
# 이 폴러는 'process_step'을 실행하고 'ConditionChecker'를
반복적으로 실행합니다.

다음 코드 발췌는 선형 워크플로우를 구축하기 위해 엔지니어링된 Google ADK 내의 SequentialAgent 패턴을 설명합니다. 이 코드는 두 에이전트인 step1과 step2를 정의합니다. step1은 “Step1_Fetch”로 이름이 지정되고 그 출력은 “data” 키 아래에 세션 상태에 저장됩니다. step2는 “Step2_Process”로 이름이 지정되며, session.state[“data”]에 저장된 정보를 분석하고 요약을 제공하도록 지시받습니다. SequentialAgent인 “MyPipeline”은 이러한 하위 에이전트의 실행을 조정합니다. 파이프라인이 초기 입력으로 실행되면 Step1이 먼저 실행됩니다. Step1의 응답은 “data”로 세션 상태에 저장된 다음 Step2가 지침에 따라 상태의 정보(data)를 사용하여 실행됩니다. 이 구조는 한 에이전트의 출력이 다음 에이전트의 입력이 되는 워크플로우를 구축할 수 있도록 합니다.

from google.adk.agents import SequentialAgent, Agent
# 이 에이전트의 출력은 session.state["data"]에 저장됩니다.
step1 = Agent(name="Step1_Fetch", output_key="data")
# 이 에이전트는 이전 단계의 데이터를 사용합니다.
# 이전 단계의 데이터를 사용하는 방법을 지시합니다.
step2 = Agent(
   name="Step2_Process",
   instruction="state['data']에서 찾은 정보를 분석하고 요약을
제공하십시오."
)
pipeline = SequentialAgent(
   name="MyPipeline",
   sub_agents=[step1, step2]
)
# 파이프라인이 초기 입력으로 실행되면 Step1이 실행되고,
응답은 session.state["data"]에 저장된 다음 Step2가 지침에
따라 실행됩니다.

다음 코드 예제는 여러 에이전트 작업을 동시에 실행할 수 있는 Google ADK 내의 ParallelAgent 패턴을 보여줍니다. 데이터 수집기(data_gatherer)는 두 하위 에이전트, weather_fetcher 및 news_fetcher를 병렬로 실행하도록 설계되었습니다. weather_fetcher 에이전트는 주어진 위치에 대한 날씨를 가져와 결과를 session.state[“weather_data”]에 저장하도록 지시받습니다. 유사하게, news_fetcher 에이전트는 주어진 주제에 대한 상위 뉴스 기사를 검색하고 해당 스토리를 session.state[“news_data”]에 저장하도록 지시받습니다. 각 하위 에이전트는 “gemini-2.0-flash-exp” 모델을 사용하도록 구성됩니다. ParallelAgent는 이러한 하위 에이전트의 실행을 조정하여 병렬로 작동할 수 있도록 합니다. 두 에이전트의 결과는 수집되어 최종 상태에 저장될 것입니다. 마지막으로, 예제는 에이전트 실행이 완료된 후 최종 상태에서 수집된 날씨 및 뉴스 데이터에 액세스하는 방법을 보여줍니다.

from google.adk.agents import Agent, ParallelAgent
# 실제 시나리오에서는 도구를 사용하는 것이 더 좋습니다.
# 이 예제의 단순성을 위해 로직을 에이전트 지침에 포함합니다.
# 병렬로 실행될 개별 에이전트 정의
weather_fetcher = Agent(
   name="weather_fetcher",
   model="gemini-2.0-flash-exp",
   instruction="주어진 위치에 대한 날씨를 가져와 날씨 보고서만
반환하십시오.",
   output_key="weather_data" # 결과는 session.state["weather_data"]에
저장됩니다
)
news_fetcher = Agent(
   name="news_fetcher",
   model="gemini-2.0-flash-exp",
   instruction="주어진 주제에 대한 상위 뉴스 기사를 가져와
해당 스토리만 반환하십시오.",
   output_key="news_data" # 결과는 session.state["news_data"]에
저장됩니다
)
# 하위 에이전트를 조정하기 위해 ParallelAgent 생성
data_gatherer = ParallelAgent(
   name="data_gatherer",
   sub_agents=[
       weather_fetcher,
       news_fetcher
   ]
)

마지막으로, 이 코드는 Google ADK 내에서 에이전트를 도구로 사용하는 패러다임을 보여주며, 한 에이전트가 다른 에이전트의 기능을 도구처럼 활용할 수 있도록 합니다. 이 코드는 두 에이전트, artist_agent와 image_generator_agent를 사용하여 이미지 생성 시스템을 정의합니다. 이미지 생성의 핵심 기능을 시뮬레이션하는 간단한 도구인 generate_image 함수가 있습니다. artist_agent는 창의적인 이미지 프롬프트를 발명하고 그 다음 image_tool 래퍼를 통해 image_generator_agent에게 작업을 위임합니다. 이 구조는 계층적 에이전트 시스템을 만드는 데 사용될 수 있으며, 상위 에이전트가 하위 에이전트의 전문 기능을 활용할 수 있도록 합니다.

from google.adk.agents import LlmAgent
from google.adk.tools import agent_tool
from google.genai import types
# 1. 핵심 기능을 위한 간단한 함수 도구.
# 이는 행동을 추론과 분리하는 모범 사례를 따릅니다.
def generate_image(prompt: str) -> dict:
   """
   텍스트 프롬프트를 기반으로 이미지를 생성합니다.
   Args:
       prompt: 생성할 이미지에 대한 자세한 설명.
   Returns:
       상태 및 생성된 이미지 바이트를 포함하는 딕셔너리.
   """
   print(f"도구: 프롬프트 '{prompt}'에 대한 이미지 생성 중")
   # 실제 구현에서는 이미지 생성 API를 호출합니다.
   # 이 예제에서는 모의 이미지 데이터를 반환합니다.
   mock_image_bytes = b"mock_image_data_for_a_cat_wearing_a_hat"
   return {
       "status": "success",
       # 도구는 원시 바이트를 반환하고, 에이전트는 파트 생성을
처리합니다.
       "image_bytes": mock_image_bytes,
       "mime_type": "image/png"
   }
# 2. ImageGeneratorAgent를 LlmAgent로 리팩토링.
# 이제 전달받은 입력을 올바르게 사용합니다.
image_generator_agent = LlmAgent(
   name="ImageGen",
   model="gemini-2.0-flash",
   description="텍스트 프롬프트를 기반으로 이미지를 생성합니다.",
   instruction=(
       "당신은 이미지 생성 전문가입니다. 사용자의 요청을 받아 "
       "`generate_image` 도구를 사용하여 이미지를 생성해야 합니다. "
       "사용자의 전체 요청은 도구의 'prompt' 인수로 사용되어야
합니다. "
       "도구가 이미지 바이트를 반환한 후에는 이미지를 출력해야
합니다."
   ),
   tools=[generate_image]
)
# 3. 수정된 에이전트를 AgentTool 래퍼로 감쌉니다.
# 여기에 있는 설명은 상위 에이전트가 보는 내용입니다.
image_tool = agent_tool.AgentTool(
   agent=image_generator_agent,
   description="생성할 이미지에 대한 상세한 프롬프트를
입력으로 사용하여 이미지를 생성하는 데 이 도구를 사용하십시오."
)
# 4. 상위 에이전트는 변경되지 않았습니다. 그 논리는 정확했습니다.
artist_agent = LlmAgent(
   name="Artist",
   model="gemini-2.0-flash",
   instruction=(
       "당신은 창의적인 예술가입니다. 먼저, 이미지에 대한 창의적이고
상세한 프롬프트를 발명하십시오. "
       "그런 다음 `ImageGen` 도구를 사용하여 당신의 프롬프트로
이미지를 생성하십시오."
   ),
   tools=[image_tool]
)

제 8장: 메모리 관리

효과적인 메모리 관리는 지능형 에이전트가 정보를 유지하는 데 매우 중요합니다. 에이전트가 효율적으로 작동하려면 인간과 마찬가지로 다양한 유형의 메모리가 필요합니다. 이 장에서는 에이전트의 즉각적인(단기) 및 영구적인(장기) 메모리 요구 사항을 구체적으로 다루면서 메모리 관리를 심층적으로 탐구합니다.

메모리관리 개요

에이전트 시스템에서 메모리란 에이전트가 과거 상호 작용, 관찰 및 학습 경험으로부터 정보를 유지하고 활용하는 능력을 의미합니다. 이러한 능력은 에이전트가 정보에 입각한 결정을 내리고, 대화의 맥락을 유지하며, 시간이 지남에 따라 개선될 수 있도록 합니다. 에이전트 메모리는 일반적으로 두 가지 주요 유형으로 분류됩니다.

  • 단기 메모리(컨텍스트 메모리): 작업 기억과 유사하게 현재 처리 중이거나 최근에 액세스한 정보를 보관합니다. 대규모 언어 모델(LLM)을 사용하는 에이전트의 경우 단기 메모리는 주로 컨텍스트 창 내에 존재합니다. 이 창에는 현재 상호 작용의 최근 메시지, 에이전트 응답, 도구 사용 결과 및 에이전트 성찰이 포함되며, 이 모든 것이 LLM의 후속 응답 및 동작에 정보를 제공합니다. 컨텍스트 창은 제한된 용량을 가지므로 에이전트가 직접 액세스할 수 있는 최근 정보의 양이 제한됩니다. 효율적인 단기 메모리 관리는 오래된 대화 부분을 요약하거나 주요 세부 사항을 강조하는 기술을 통해 이 제한된 공간 내에 가장 관련성 높은 정보를 유지하는 것을 포함합니다. ‘긴 컨텍스트’ 창을 가진 모델의 등장은 이 단기 메모리의 크기를 확장하여 단일 상호 작용 내에 더 많은 정보를 보유할 수 있게 했습니다. 그러나 이 컨텍스트는 여전히 일시적이며 세션이 종료되면 손실되며, 매번 처리하는 것은 비용이 많이 들고 비효율적일 수 있습니다. 따라서 에이전트가 진정한 영속성을 달성하고, 과거 상호 작용에서 정보를 검색하며, 지속적인 지식 기반을 구축하려면 별도의 메모리 유형이 필요합니다.
  • 장기 메모리(영구 메모리): 이는 에이전트가 다양한 상호 작용, 작업 또는 장기간에 걸쳐 유지해야 하는 정보를 저장하는 저장소 역할을 하며, 장기 지식 기반과 유사합니다. 데이터는 일반적으로 에이전트의 즉각적인 처리 환경 외부, 종종 데이터베이스, 지식 그래프 또는 벡터 데이터베이스에 저장됩니다. 벡터 데이터베이스에서는 정보가 수치 벡터로 변환되어 저장되며, 이를 통해 에이전트는 정확한 키워드 일치가 아닌 의미론적 유사성을 기반으로 데이터를 검색할 수 있으며, 이를 의미론적 검색(semantic search)이라고 합니다. 에이전트가 장기 메모리에서 정보가 필요할 때 외부 저장소를 쿼리하고, 관련 데이터를 검색하고, 이를 단기 컨텍스트에 통합하여 즉시 사용함으로써 이전 지식과 현재 상호 작용을 결합합니다.

실제 응용 프로그램 및 사용 사례

메모리 관리는 에이전트가 정보를 추적하고 시간이 지남에 따라 지능적으로 수행하는 데 매우 중요합니다. 이는 에이전트가 기본적인 질의응답 능력을 능가하는 데 필수적입니다. 응용 분야는 다음과 같습니다.

  • 챗봇 및 대화형 AI: 대화 흐름을 유지하는 것은 단기 메모리에 달려 있습니다. 챗봇은 일관된 응답을 제공하기 위해 이전 사용자 입력을 기억해야 합니다. 장기 메모리는 챗봇이 사용자 기본 설정, 과거 문제 또는 이전 토론을 회상하여 개인화되고 지속적인 상호 작용을 제공할 수 있도록 합니다.
  • 작업 지향 에이전트: 다단계 작업을 관리하는 에이전트는 이전 단계, 현재 진행 상황 및 전반적인 목표를 추적하기 위해 단기 메모리가 필요합니다. 이 정보는 작업의 컨텍스트 또는 임시 저장소에 있을 수 있습니다. 장기 메모리는 즉각적인 컨텍스트에 없는 특정 사용자 관련 데이터에 액세스하는 데 중요합니다.
  • 개인화된 경험: 맞춤형 상호 작용을 제공하는 에이전트는 장기 메모리를 사용하여 사용자 기본 설정, 과거 행동 및 개인 정보를 저장하고 검색합니다. 이를 통해 에이전트는 응답 및 제안을 조정할 수 있습니다.
  • 학습 및 개선: 에이전트는 과거 상호 작용에서 학습하여 성능을 개선할 수 있습니다. 성공적인 전략, 실수 및 새로운 정보는 장기 메모리에 저장되어 향후 조정이 용이해집니다. 강화 학습 에이전트는 학습된 전략이나 지식을 이러한 방식으로 저장합니다.
  • 정보 검색 (RAG): 질문 답변을 위해 설계된 에이전트는 장기 지식 기반, 즉 검색 증강 생성(RAG) 내에 구현되는 경우가 많은 지식 기반에 액세스합니다. 에이전트는 관련 문서를 검색하여 응답에 정보를 제공합니다.
  • 자율 시스템: 로봇이나 자율 주행차는 지도, 경로, 물체 위치 및 학습된 동작에 대한 메모리가 필요합니다. 이는 즉각적인 주변 환경에 대한 단기 메모리와 일반적인 환경 지식에 대한 장기 메모리를 포함합니다.

메모리는 에이전트가 기록을 유지하고, 학습하고, 상호 작용을 개인화하며, 복잡하고 시간 의존적인 문제를 관리할 수 있게 합니다.

실습 코드: Google Agent Developer Kit (ADK)의 메모리 관리

Google Agent Developer Kit(ADK)는 실용적인 응용을 위한 구성 요소를 포함하여 컨텍스트 및 메모리를 관리하기 위한 구조화된 방법을 제공합니다. 정보 유지가 필요한 에이전트를 구축하려면 ADK의 세션(Session), 상태(State) 및 메모리(Memory)에 대한 확고한 이해가 필수적입니다.

인간 상호 작용에서와 마찬가지로 에이전트는 일관되고 자연스러운 대화를 수행하기 위해 이전 교환 내용을 회상할 수 있어야 합니다. ADK는 세 가지 핵심 개념과 관련 서비스를 통해 컨텍스트 관리를 단순화합니다.

에이전트와의 모든 상호 작용은 고유한 대화 스레드로 간주될 수 있습니다. 에이전트는 이전 상호 작용의 데이터에 액세스해야 할 수 있습니다. ADK는 이를 다음과 같이 구성합니다.

  • 세션(Session): 특정 상호 작용에 대한 메시지 및 작업(이벤트)을 기록하고 해당 대화와 관련된 임시 데이터(상태)도 저장하는 개별 채팅 스레드입니다.
  • 상태(State) (session.state): 세션 내에 저장되며 현재 활성 채팅 스레드와 관련된 정보만 포함합니다.
  • 메모리(Memory): 즉각적인 대화 범위를 넘어서는 데이터 검색을 위한 리소스로서, 다양한 과거 채팅 또는 외부 소스에서 가져온 정보의 검색 가능한 저장소입니다.

ADK는 복잡하고 상태 저장 방식이며 컨텍스트를 인식하는 에이전트를 구축하는 데 필수적인 핵심 구성 요소를 관리하기 위한 전용 서비스를 제공합니다. SessionService는 채팅 스레드(Session 객체)의 시작, 기록 및 종료를 처리하여 채팅 스레드를 관리하고, MemoryService는 장기 지식(Memory)의 저장 및 검색을 감독합니다.

SessionService 및 MemoryService는 모두 다양한 구성 옵션을 제공하여 사용자가 응용 프로그램 요구 사항에 따라 저장 방법을 선택할 수 있도록 합니다. 테스트 목적으로 인메모리 옵션을 사용할 수 있지만, 응용 프로그램 다시 시작 시 데이터가 영구적으로 유지되지는 않습니다. 영구적인 저장 및 확장을 위해서는 ADK가 데이터베이스 및 클라우드 기반 서비스도 지원합니다.

세션(Session): 각 채팅 추적

ADK의 Session 객체는 개별 채팅 스레드를 추적하고 관리하도록 설계되었습니다. 에이전트와의 대화가 시작되면 SessionService는 google.adk.sessions.Session으로 표현되는 Session 객체를 생성합니다. 이 객체는 특정 대화 스레드와 관련된 모든 데이터(ID, app_name, user_id와 같은 고유 식별자, Event 객체로서의 이벤트의 연대순 기록, 세션별 임시 데이터를 저장하는 상태 저장 영역, 마지막 업데이트 시간을 나타내는 타임스탬프)를 캡슐화합니다. 개발자는 일반적으로 SessionService를 통해 Session 객체와 간접적으로 상호 작용합니다. SessionService는 새 세션 시작, 이전 세션 재개, 세션 활동 기록(상태 업데이트 포함), 활성 세션 식별 및 세션 데이터 제거 관리를 포함하여 대화 세션의 수명 주기를 관리할 책임이 있습니다. ADK는 세션 기록 및 임시 데이터에 대한 다양한 저장 메커니즘을 가진 여러 SessionService 구현을 제공하는데, 예를 들어 InMemorySessionService는 테스트에는 적합하지만 응용 프로그램 다시 시작 시 데이터 영구성을 제공하지는 않습니다.

# 예시: InMemorySessionService 사용
# 이는 응용 프로그램 다시 시작 시 데이터 영구성이 필요하지 않은
로컬 개발 및 테스트에 적합합니다.
from google.adk.sessions import InMemorySessionService
session_service = InMemorySessionService()

직접 관리하는 데이터베이스에 안정적으로 저장하려면 DatabaseSessionService가 있습니다.

# 예시: DatabaseSessionService 사용
# 이는 영구적인 저장을 요구하는 프로덕션 또는 개발에 적합합니다.
# 데이터베이스 URL(예: SQLite, PostgreSQL 등)을 구성해야 합니다.
# 필요 사항: pip install google-adk[sqlalchemy] 및 데이터베이스
드라이버(예: PostgreSQL의 경우 psycopg2)
from google.adk.sessions import DatabaseSessionService
# 로컬 SQLite 파일 사용 예시:
db_url = "sqlite:///./my_agent_data.db"
session_service = DatabaseSessionService(db_url=db_url)

Google Cloud에서 확장을 위해 Vertex AI 인프라를 사용하는 VertexAiSessionService도 있습니다.

# 예시: VertexAiSessionService 사용
# 이는 Google Cloud Platform에서 확장을 위해 적합하며,
세션 관리를 위해 Vertex AI 인프라를 활용합니다.
# 필요 사항: pip install google-adk[vertexai] 및 GCP 설정/인증
from google.adk.sessions import VertexAiSessionService
PROJECT_ID = "your-gcp-project-id" # GCP 프로젝트 ID로 바꾸세요
LOCATION = "us-central1" # 원하는 GCP 위치로 바꾸세요
# 이 서비스와 함께 사용되는 app_name은 Reasoning Engine ID 또는
이름에 해당해야 합니다.
REASONING_ENGINE_APP_NAME =
"projects/your-gcp-project-id/locations/us-central1/reasoningEngines/
your-engine-id" # Reasoning Engine 리소스 이름으로 바꾸세요
session_service = VertexAiSessionService(project=PROJECT_ID,
location=LOCATION)
# 이 서비스를 사용할 때 서비스 메서드에 REASONING_ENGINE_APP_NAME을
전달합니다:
# session_service.create_session(app_name=REASONING_ENGINE_APP_NAME,
...)
# session_service.get_session(app_name=REASONING_ENGINE_APP_NAME,
...)
# session_service.append_event(session, event,
app_name=REASONING_ENGINE_APP_NAME)
# session_service.delete_session(app_name=REASONING_ENGINE_APP_NAME,
...)

적절한 SessionService를 선택하는 것은 에이전트의 상호 작용 기록과 임시 데이터가 저장되는 방식과 그 영구성(지속성)을 결정하므로 중요합니다.

각 메시지 교환은 순환 프로세스를 수반합니다. 메시지를 수신하면 Runner가 SessionService를 사용하여 세션을 검색하거나 설정하고, 에이전트는 세션의 컨텍스트(상태 및 기록된 상호 작용)를 사용하여 메시지를 처리하고, 에이전트는 응답을 생성하고 상태를 업데이트할 수 있으며, Runner는 이를 이벤트로 캡슐화하고, session_service.append_event() 메서드는 새 이벤트를 기록하고 저장소에서 상태를 업데이트합니다. 그런 다음 세션은 다음 메시지를 기다립니다. 이상적으로는 상호 작용이 종료될 때 세션을 종료하기 위해 delete_session 메서드를 사용해야 합니다. 이 프로세스는 SessionService가 세션별 기록 및 임시 데이터를 관리하여 연속성을 유지하는 방법을 보여줍니다.

상태(State): 세션의 스크래치패드

ADK에서 세션(채팅 스레드) 각각에는 해당 특정 대화 기간 동안 에이전트의 임시 작업 메모리와 유사한 상태 구성 요소가 포함됩니다. session.events는 전체 채팅 기록을 기록하는 반면, session.state는 활성 채팅과 관련된 동적 데이터 포인트를 저장하고 업데이트합니다.

근본적으로 session.state는 키-값 쌍으로 데이터를 저장하는 사전처럼 작동합니다. 핵심 기능은 에이전트가 일관된 대화에 필수적인 사용자 기본 설정, 작업 진행 상황, 증분 데이터 수집 또는 후속 에이전트 동작에 영향을 미치는 조건부 플래그와 같은 세부 정보를 유지하고 관리할 수 있도록 하는 것입니다.

상태의 구조는 직렬화 가능한 Python 유형(문자열, 숫자, 부울, 목록 및 이러한 기본 유형을 포함하는 사전 포함)의 값과 쌍을 이루는 문자열 키로 구성됩니다. 상태는 대화 전반에 걸쳐 동적으로 진화합니다. 이러한 변경 사항의 영구성은 구성된 SessionService에 따라 달라집니다.

데이터 범위를 정의하고 영구성을 유지하기 위해 키 접두사를 사용하여 상태 구성을 달성할 수 있습니다. 접두사가 없는 키는 세션별로 지정됩니다.

  • user: 접두사는 사용자 ID와 연결된 데이터를 모든 세션에 걸쳐 연관시킵니다.
  • app: 접두사는 응용 프로그램의 모든 사용자 간에 공유되는 데이터를 나타냅니다.
  • temp: 접두사는 현재 처리 턴에만 유효하며 영구적으로 저장되지 않는 데이터를 나타냅니다.

에이전트는 단일 session.state 사전을 통해 모든 상태 데이터에 액세스합니다. SessionService는 데이터 검색, 병합 및 영구성을 처리합니다. 세션 기록에 이벤트를 추가할 때(session_service.append_event()를 통해) 상태가 업데이트되어야 합니다. 이는 정확한 추적을 보장하고, 영구 서비스에 올바르게 저장하며, 상태 변경을 안전하게 처리하도록 합니다.

  1. 간단한 방법: output_key 사용 (에이전트 텍스트 응답용): 에이전트의 최종 텍스트 응답을 상태에 직접 저장하려는 경우 가장 쉬운 방법입니다. LlmAgent를 설정할 때 사용할 output_key만 알려주면 됩니다. Runner는 이를 감지하고 이벤트를 추가할 때 응답을 상태에 저장하는 데 필요한 조치를 자동으로 생성합니다. output_key를 통해 상태 업데이트를 보여주는 코드 예시를 살펴보겠습니다.
# Google Agent Developer Kit (ADK)에서 필요한 클래스 가져오기
from google.adk.agents import LlmAgent
from google.adk.sessions import InMemorySessionService, Session
from google.adk.runners import Runner
from google.genai.types import Content, Part
# output_key를 사용하여 LlmAgent 정의.
greeting_agent = LlmAgent(
   name="Greeter",
   model="gemini-2.0-flash",
   instruction="짧고 친근한 인사를 생성하세요.",
   output_key="last_greeting"
)
# --- Runner 및 세션 설정 ---
app_name, user_id, session_id = "state_app", "user1", "session1"
session_service = InMemorySessionService()
runner = Runner(
   agent=greeting_agent,
   app_name=app_name,
   session_service=session_service
)
session = session_service.create_session(
   app_name=app_name,
   user_id=user_id,
   session_id=session_id
)
print(f"초기 상태: {session.state}")
# --- 에이전트 실행 ---
user_message = Content(parts=[Part(text="Hello")])
print("\n--- 에이전트 실행 중 ---")
for event in runner.run(
   user_id=user_id,
   session_id=session_id,
   new_message=user_message
):
   if event.is_final_response():
     print("에이전트가 응답했습니다.")
# --- 업데이트된 상태 확인 ---
# runner가 모든 이벤트를 처리한 *후* 상태를 올바르게 확인합니다.
updated_session = session_service.get_session(app_name, user_id,
session_id)
print(f"\n에이전트 실행 후 상태: {updated_session.state}")

내부적으로 Runner는 output_key를 확인하고 append_event를 호출할 때 state_delta와 함께 필요한 조치를 자동으로 생성합니다.

  1. 표준 방법: EventActions.state_delta 사용 (더 복잡한 업데이트용): 여러 키를 한 번에 업데이트하거나 텍스트가 아닌 것을 저장하거나 user: 또는 app:와 같은 특정 범위를 대상으로 하거나 에이전트의 최종 텍스트 응답과 연결되지 않은 업데이트를 수행해야 하는 등 더 복잡한 작업을 수행해야 하는 경우, 상태 변경 사항의 사전(state_delta)을 수동으로 빌드하고 이를 Event를 추가할 때 EventActions 내에 포함합니다. 한 가지 예를 살펴보겠습니다.
import time
from google.adk.tools.tool_context import ToolContext
from google.adk.sessions import InMemorySessionService
# --- 권장되는 도구 기반 접근 방식 정의 ---
def log_user_login(tool_context: ToolContext) -> dict:
   """
   사용자 로그인 이벤트 발생 시 세션 상태를 업데이트합니다.
   이 도구는 사용자 로그인과 관련된 모든 상태 변경 사항을
캡슐화합니다.
   Args:
       tool_context: ADK에서 자동으로 제공되며 세션 상태에 대한
액세스를 제공합니다.
   Returns:
       작업 성공을 확인하는 사전입니다.
   """
   # 제공된 컨텍스트를 통해 상태에 직접 액세스합니다.
   state = tool_context.state
   # 현재 값 또는 기본값을 가져온 다음 상태를 업데이트합니다.
   # 이렇게 하면 훨씬 깔끔해지고 논리가 공존합니다.
   login_count = state.get("user:login_count", 0) + 1
   state["user:login_count"] = login_count
   state["task_status"] = "active"
   state["user:last_login_ts"] = time.time()
   state["temp:validation_needed"] = True
   print("`log_user_login` 도구 내에서 상태가 업데이트되었습니다.")
   return {
       "status": "success",
       "message": f"사용자 로그인이 추적되었습니다. 총 로그인 수:
{login_count}."
   }
# --- 사용 시연 ---
# 실제 응용 프로그램에서는 LLM 에이전트가 이 도구를 호출하기로
결정합니다.
# 여기서는 시연 목적으로 직접 호출을 시뮬레이션합니다.
# 1. 설정
session_service = InMemorySessionService()
app_name, user_id, session_id = "state_app_tool", "user3",
"session3"
session = session_service.create_session(
   app_name=app_name,
   user_id=user_id,
   session_id=session_id,
   state={"user:login_count": 0, "task_status": "idle"}
)
print(f"초기 상태: {session.state}")
# 2. 도구 호출 시뮬레이션 (실제 앱에서는 ADK Runner가 이를
수행)
# 이 독립형 예시를 위해 ToolContext를 수동으로 생성합니다.
from google.adk.tools.tool_context import InvocationContext
mock_context = ToolContext(
   invocation_context=InvocationContext(
       app_name=app_name, user_id=user_id, session_id=session_id,
       session=session, session_service=session_service
   )
)
# 3. 도구 실행
log_user_login(mock_context)
# 4. 업데이트된 상태 확인
updated_session = session_service.get_session(app_name, user_id,
session_id)
print(f"도구 실행 후 상태: {updated_session.state}")
# 예상 출력은 "이전" 사례와 동일한 상태 변경을 표시하지만
# 코드 구성은 훨씬 더 깔끔합니다.

그리고 더 강력합니다.

이 코드는 사용자 세션 상태를 관리하기 위한 도구 기반 접근 방식을 시연합니다. 이 코드는 사용자가 로그인할 때 세션 상태를 업데이트하는 책임이 있는 도구 역할을 하는 log_user_login이라는 함수를 정의합니다.

이 함수는 ADK에서 제공하는 ToolContext 객체를 받아 세션의 상태 사전(dictionary)에 액세스하고 수정합니다. 도구 내에서 사용자 로그인 수(user:login_count)를 증가시키고, 작업 상태(task_status)를 “active”로 설정하고, 사용자 마지막 로그인 시간(user:last_login_ts)을 기록하고, 임시 플래그 temp:validation_needed를 추가합니다.

시연 부분의 코드는 이 도구가 사용되는 방식을 시뮬레이션합니다. 인메모리 세션 서비스를 설정하고 일부 미리 정의된 상태로 초기 세션을 생성합니다. 그런 다음 ADK Runner가 도구를 실행하는 환경을 모방하기 위해 ToolContext를 수동으로 생성합니다. 이 모의 컨텍스트를 사용하여 log_user_login 함수가 호출됩니다. 마지막으로 코드는 세션을 다시 검색하여 도구 실행으로 인해 상태가 업데이트되었음을 보여줍니다. 이 목표는 도구 내에 상태 변경 사항을 캡슐화하여 도구 외부에서 상태를 직접 조작하는 것보다 코드를 더 깔끔하고 체계적으로 만드는 방법을 보여주는 것입니다.

세션을 검색한 후 session.state 사전을 직접 수정하는 것은 표준 이벤트 처리 메커니즘을 우회하므로 강력히 권장되지 않습니다. 이러한 직접적인 변경 사항은 세션의 이벤트 기록에 기록되지 않으며, 선택한 SessionService에 의해 영구적으로 저장되지 않을 수 있으며, 타임스탬프와 같은 필수 메타데이터를 업데이트하지 않고 동시성 문제를 일으킬 수 있습니다. 세션 상태를 업데이트하는 권장 방법은 LlmAgentoutput\_key 매개변수(에이전트의 최종 텍스트 응답에만 해당)를 사용하거나 session\_service.append\_event()를 통해 이벤트를 추가할 때 EventActions.state\_delta 내에 상태 변경 사항을 포함하는 것입니다. session.state는 주로 기존 데이터를 읽는 데 사용되어야 합니다.

상태를 설계할 때 단순하게 유지하고, 기본 데이터 유형을 사용하고, 키에 명확한 이름을 부여하고 접두사를 올바르게 사용하고, 깊은 중첩을 피하고, 항상 append_event 프로세스를 통해 상태를 업데이트하는 것을 기억하십시오.

메모리(Memory): MemoryService를 사용한 장기 지식

에이전트 시스템에서 세션 구성 요소는 단일 대화에 특정한 현재 채팅 기록(이벤트) 및 임시 데이터(상태)에 대한 기록을 유지합니다. 그러나 에이전트가 여러 상호 작용에 걸쳐 정보를 유지하거나 외부 데이터에 액세스하려면 장기 지식 관리가 필요합니다. 이는 MemoryService를 통해 촉진됩니다.

# 예시: InMemoryMemoryService 사용
# 이는 응용 프로그램 다시 시작 시 데이터 영구성이 필요하지 않은
로컬 개발 및 테스트에 적합합니다.
# 메모리 내용은 앱이 중지될 때 손실됩니다.
from google.adk.memory import InMemoryMemoryService
memory_service = InMemoryMemoryService()

세션과 상태는 단일 채팅 세션에 대한 단기 메모리로 개념화될 수 있는 반면, MemoryService에서 관리하는 장기 지식은 영구적이며 검색 가능한 저장소 역할을 합니다. 이 저장소에는 여러 과거 상호 작용 또는 외부 소스의 정보가 포함될 수 있습니다. BaseMemoryService 인터페이스에 의해 정의된 MemoryService는 이 검색 가능한 장기 지식을 관리하기 위한 표준을 설정합니다. 주요 기능에는 세션에서 콘텐츠를 추출하고 add_session_to_memory 메서드를 사용하여 저장하는 정보 추가와, 에이전트가 저장소를 쿼리하고 search_memory 메서드를 사용하여 관련 데이터를 수신할 수 있도록 하는 정보 검색이 포함됩니다.

ADK는 이 장기 지식 저장소를 만드는 데 사용할 수 있는 여러 구현을 제공합니다. InMemoryMemoryService는 테스트 목적으로 적합한 임시 저장 솔루션을 제공하지만 응용 프로그램 다시 시작 시 데이터가 보존되지는 않습니다. 프로덕션 환경의 경우 일반적으로 VertexAiRagMemoryService가 사용됩니다. 이 서비스는 Google Cloud의 검색 증강 생성(RAG) 서비스를 활용하여 확장 가능하고 영구적이며 의미론적 검색 기능을 활성화합니다(RAG에 대한 14장 참조).

# 예시: VertexAiRagMemoryService 사용
# 이는 GCP에서 확장을 위해 적합하며, 영구적인 검색 가능한
메모리를 위해 Vertex AI RAG(검색 증강 생성)를 활용합니다.
# 필요 사항: pip install google-adk[vertexai], GCP 설정/인증
# 및 Vertex AI RAG 코퍼스.
from google.adk.memory import VertexAiRagMemoryService
# Vertex AI RAG 코퍼스의 리소스 이름
RAG_CORPUS_RESOURCE_NAME =
"projects/your-gcp-project-id/locations/us-central1/ragCorpora/your-c
orpus-id" # 코퍼스 리소스 이름으로 바꾸세요
# 검색 동작에 대한 선택적 구성
SIMILARITY_TOP_K = 5 # 검색할 상위 결과 수
VECTOR_DISTANCE_THRESHOLD = 0.7 # 벡터 유사성 임계값
memory_service = VertexAiRagMemoryService(
   rag_corpus=RAG_CORPUS_RESOURCE_NAME,
   similarity_top_k=SIMILARITY_TOP_K,
   vector_distance_threshold=VECTOR_DISTANCE_THRESHOLD
)
# 이 서비스를 사용할 때 add_session_to_memory 및 search_memory와
같은 메서드는 지정된 Vertex AI RAG 코퍼스와 상호 작용합니다.

실습 코드: LangChain 및 LangGraph의 메모리 관리

LangChain 및 LangGraph에서 메모리는 자연스러운 느낌의 대화형 응용 프로그램을 만드는 데 중요한 구성 요소입니다. 이를 통해 AI 에이전트는 과거 상호 작용의 정보를 기억하고, 피드백으로부터 학습하며, 사용자 기본 설정에 적응할 수 있습니다. LangChain의 메모리 기능은 저장된 기록을 참조하여 현재 프롬프트를 풍부하게 한 다음, 향후 사용을 위해 최신 교환을 기록하는 기반을 제공합니다. 에이전트가 더 복잡한 작업을 처리함에 따라 이 기능은 효율성과 사용자 만족도 모두에 필수적이 됩니다.

단기 메모리: 이는 스레드 범위 지정되므로 단일 세션 또는 스레드 내에서 진행 중인 대화를 추적합니다. 즉각적인 컨텍스트를 제공하지만 전체 기록은 LLM의 컨텍스트 창에 부담을 주어 오류나 성능 저하를 유발할 수 있습니다. LangGraph는 에이전트 상태의 일부로 단기 메모리를 관리하며, 이는 체크포인터를 통해 영구적으로 저장되어 언제든지 스레드를 재개할 수 있습니다.

장기 메모리: 이는 세션 전반에 걸쳐 사용자별 또는 애플리케이션 수준의 데이터를 저장하며, 대화 스레드 간에 공유됩니다. 사용자 지정 “네임스페이스”에 저장되며 모든 스레드에서 언제든지 다시 불러올 수 있습니다. LangGraph는 장기 메모리를 저장하고 다시 불러오기 위한 저장소를 제공하여 에이전트가 영구적으로 지식을 유지할 수 있도록 합니다.

LangChain은 공식 체인 내의 수동 제어부터 자동 통합에 이르기까지 대화 기록 관리를 위한 다양한 도구를 제공합니다.

ChatMessageHistory: 수동 메모리 관리. 공식 체인 외부에서 대화 기록에 대한 직접적이고 간단한 제어를 위해서는 ChatMessageHistory 클래스가 이상적입니다. 이를 통해 대화 교환을 수동으로 추적할 수 있습니다.

from langchain.memory import ChatMessageHistory
# 기록 객체 초기화
history = ChatMessageHistory()
# 사용자 및 AI 메시지 추가
history.add_user_message("저는 다음 주에 뉴욕으로 갑니다.")
history.add_ai_message("좋습니다! 환상적인 도시입니다.")
# 메시지 목록에 액세스
print(history.messages)

ConversationBufferMemory: 체인을 위한 자동 메모리. 메모리를 체인에 직접 통합하기 위해 ConversationBufferMemory는 일반적인 선택입니다. 대화의 버퍼를 유지하고 프롬프트에 사용할 수 있도록 합니다. 다음 두 가지 주요 매개변수로 동작을 사용자 지정할 수 있습니다.

  • memory_key: 채팅 기록이 포함될 프롬프트의 변수 이름을 지정하는 문자열입니다. 기본값은 “history”입니다.
  • return_messages: 기록의 형식을 결정하는 부울입니다.
    • False인 경우(기본값), 형식화된 단일 문자열을 반환하며, 이는 표준 LLM에 이상적입니다.
    • True인 경우, 메시지 객체 목록을 반환하며, 이는 채팅 모델에 권장되는 형식입니다.
from langchain.memory import ConversationBufferMemory
# 메모리 초기화
memory = ConversationBufferMemory()
# 대화 턴 저장
memory.save_context({"input": "날씨가 어때요?"}, {"output":
"오늘은 맑습니다."})
# 메모리를 문자열로 로드
print(memory.load_memory_variables({}))

이 메모리를 LLMChain에 통합하면 모델은 대화 기록에 액세스하고 컨텍스트에 적합한 응답을 제공할 수 있습니다.

from langchain_openai import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.memory import ConversationBufferMemory
# 1. LLM 및 프롬프트 정의
llm = OpenAI(temperature=0)
template = """당신은 유능한 여행사입니다.
이전 대화:
{history}
새 질문: {question}
응답:"""
prompt = PromptTemplate.from_template(template)
# 2. 메모리 구성
# 메모리 키 "history"는 프롬프트의 변수와 일치합니다.
memory = ConversationBufferMemory(memory_key="history")
# 3. 체인 빌드
conversation = LLMChain(llm=llm, prompt=prompt, memory=memory)
# 4. 대화 실행
response = conversation.predict(question="항공편을 예약하고 싶습니다.")
print(response)
response = conversation.predict(question="참고로 제 이름은 Sam입니다.")
print(response)
response = conversation.predict(question="내 이름이 다시 뭐였지?")
print(response)

채팅 모델에서 향상된 효과를 위해 return\_messages=True를 설정하여 구조화된 메시지 객체 목록을 사용하는 것이 좋습니다.

from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import (
   ChatPromptTemplate,
   MessagesPlaceholder,
SystemMessagePromptTemplate,
   HumanMessagePromptTemplate,
)
# 1. 채팅 모델 및 프롬프트 정의
llm = ChatOpenAI()
prompt = ChatPromptTemplate(
   messages=[
       SystemMessagePromptTemplate.from_template("당신은 친근한
보조원입니다."),
       MessagesPlaceholder(variable_name="chat_history"),
       HumanMessagePromptTemplate.from_template("{question}")
   ]
)
# 2. 메모리 구성
# return_messages=True는 채팅 모델에 필수적입니다.
memory = ConversationBufferMemory(memory_key="chat_history",
return_messages=True)
# 3. 체인 빌드
conversation = LLMChain(llm=llm, prompt=prompt, memory=memory)
# 4. 대화 실행
response = conversation.predict(question="안녕하세요, 저는 Jane입니다.")
print(response)
response = conversation.predict(question="내 이름을 기억하니?")
print(response)

장기 메모리 유형: 장기 메모리를 통해 시스템은 다른 대화 전반에 걸쳐 정보를 유지하여 더 깊은 수준의 컨텍스트와 개인화를 제공할 수 있습니다. 이는 인간의 메모리와 유사하게 세 가지 유형으로 나눌 수 있습니다.

  • 의미 메모리: 사실 기억하기: 이는 사용자 기본 설정이나 도메인 지식과 같은 특정 사실과 개념을 유지하는 것과 관련됩니다. 이는 에이전트 응답의 기반을 마련하여 보다 개인화되고 관련성 높은 상호 작용으로 이어지는 데 사용됩니다. 이 정보는 지속적으로 업데이트되는 사용자 “프로필”(JSON 문서) 또는 개별 사실 문서 “컬렉션”으로 관리될 수 있습니다.

  • 일화 메모리: 경험 기억하기: 이는 과거 사건이나 행동을 기억하는 것과 관련됩니다. AI 에이전트의 경우 일화 메모리는 작업을 수행하는 방법을 기억하는 데 자주 사용됩니다. 실제로는 종종 다음과 같이 구현됩니다.

  • 몇 가지 예시 프롬프팅(few-shot example prompting)을 통해 에이전트는 과거 성공적인 상호 작용 시퀀스에서 학습하여 작업을 올바르게 수행합니다.

  • 절차 기억: 규칙 기억하기: 이는 작업을 수행하는 방법의 메모리로, 에이전트의 핵심 지침 및 동작이며 종종 시스템 프롬프트에 포함됩니다. 에이전트가 자체 프롬프트를 수정하여 적응하고 개선하는 것은 일반적입니다. 효과적인 기술은 “성찰(Reflection)“인데, 에이전트에게 현재 지침과 최근 상호 작용을 제공하고 자체 지침을 개선하도록 요청하는 것입니다.

아래 코드는 에이전트가 LangGraph BaseStore에 저장된 절차적 메모리를 업데이트하기 위해 성찰을 사용하는 방법을 시연하는 유사 코드입니다.

# 에이전트 지침을 업데이트하는 노드
def update_instructions(state: State, store: BaseStore):
   namespace = ("instructions",)
   # 저장소에서 현재 지침을 검색합니다.
   current_instructions = store.search(namespace)[0]
   # 대화에 대해 성찰하고 새로운 개선된 지침을 생성하도록 LLM에
요청하는 프롬프트를 생성합니다.
   prompt = prompt_template.format(
       instructions=current_instructions.value["instructions"],
       conversation=state["messages"]
   )
   # LLM에서 새 지침을 가져옵니다.
   output = llm.invoke(prompt)
   new_instructions = output['new_instructions']
   # 업데이트된 지침을 저장소에 다시 저장합니다.
   store.put(("agent_instructions",), "agent_a", {"instructions":
new_instructions})
# 지침을 사용하여 응답을 생성하는 노드
def call_model(state: State, store: BaseStore):
   namespace = ("agent_instructions", )
   # 저장소에서 최신 지침을 검색합니다.
   instructions = store.get(namespace, key="agent_a")[0]
   # 검색된 지침을 사용하여 프롬프트를 형식화합니다.
   prompt =
prompt_template.format(instructions=instructions.value["instructions"
])
   # ... 응용 프로그램 논계속

LangGraph는 장기 메모리를 저장소의 JSON 문서로 저장합니다. 각 메모리는 사용자 지정 네임스페이스(폴더와 유사) 및 고유 키(파일 이름과 유사) 아래에 구성됩니다. 이 계층적 구조를 통해 정보 구성을 쉽게 하고 검색할 수 있습니다. 다음 코드는 InMemoryStore를 사용하여 메모리를 넣고, 가져오고, 검색하는 방법을 시연합니다.

from langgraph.store.memory import InMemoryStore
# 실제 임베딩 함수를 위한 자리 표시자
def embed(texts: list[str]) -> list[list[float]]:
   # 실제 응용 프로그램에서는 적절한 임베딩 모델을 사용합니다.
   return [[1.0, 2.0] for _ in texts]
# 인메모리 저장소 초기화. 프로덕션의 경우 데이터베이스 기반
저장소를 사용합니다.
store = InMemoryStore(index={"embed": embed, "dims": 2})
# 특정 사용자와 응용 프로그램 컨텍스트을 위한 네임스페이스 정의
user_id = "my-user"
application_context = "chitchat"
namespace = (user_id, application_context)
# 1. 저장소에 메모리 넣기
store.put(
   namespace,
   "a-memory", # 이 메모리의 키
   {
       "rules": [
           "사용자는 짧고 직접적인 언어를 좋아합니다",
           "사용자는 영어 및 python만 사용합니다",
       ],
       "my-key": "my-value",
   },
)
# 2. 네임스페이스 및 키로 메모리 가져오기
item = store.get(namespace, "a-memory")
print("검색된 항목:", item)
# 3. 필터링 및 쿼리를 사용하여 네임스페이스 내에서 메모리 검색
# 벡터 유사성을 쿼리로 정렬합니다.
items = store.search(
namespace,
   filter={"my-key": "my-value"},
   query="language preferences"
)
print("검색 결과:", items)

Vertex Memory Bank

Memory Bank는 Vertex AI Agent Engine의 관리형 서비스로, 에이전트에게 영구적인 장기 메모리를 제공합니다. 이 서비스는 Gemini 모델을 사용하여 대화 기록을 비동기적으로 분석하여 주요 사실과 사용자 기본 설정을 추출합니다.

이 정보는 사용자 ID와 같은 정의된 범위별로 영구적으로 저장되며, 새 데이터를 통합하고 모순을 해결하도록 지능적으로 업데이트됩니다. 새 세션을 시작할 때 에이전트는 전체 데이터 회수 또는 임베딩을 사용한 유사성 검색을 통해 관련 메모리를 검색합니다. 이 프로세스를 통해 에이전트는 세션 전반에 걸쳐 연속성을 유지하고 회상된 정보를 기반으로 응답을 개인화할 수 있습니다.

에이전트의 러너는 먼저 초기화되는 VertexAiMemoryBankService와 상호 작용합니다. 이 서비스는 에이전트의 대화 중에 생성된 메모리의 자동 저장을 처리합니다. 각 메모리에는 향후 정확한 검색을 보장하기 위해 고유한 USER_ID 및 APP_NAME으로 태그가 지정됩니다.

from google.adk.memory import VertexAiMemoryBankService
agent_engine_id = agent_engine.api_resource.name.split("/")[-1]
memory_service = VertexAiMemoryBankService(
   project="PROJECT_ID",
   location="LOCATION",
   agent_engine_id=agent_engine_id
)
session = await session_service.get_session(
   app_name=app_name,
   user_id="USER_ID",
   session_id=session.id
)
await memory_service.add_session_to_memory(session)

Memory Bank는 Google ADK와의 원활한 통합을 제공하여 즉각적인 즉시 사용 가능한 환경을 제공합니다. LangGraph 및 CrewAI와 같은 다른 에이전트 프레임워크 사용자의 경우 Memory Bank는 직접 API 호출을 통해 지원을 제공합니다. 이러한 통합을 시연하는 온라인 코드 예제를 관심 있는 독자가 쉽게 사용할 수 있습니다.

한눈에 보기

무엇을: 에이전트 시스템은 복잡한 작업을 수행하고 일관된 경험을 제공하기 위해 과거 상호 작용의 정보를 기억할 필요가 있습니다. 메모리 메커니즘이 없으면 에이전트는 상태가 없어져 대화 컨텍스트를 유지하거나 경험으로부터 학습하거나 사용자에게 응답을 개인화할 수 없습니다. 이는 근본적으로 단순한 단일 단계 상호 작용으로 그들의 능력을 제한하며, 다단계 프로세스 또는 진화하는 사용자 요구 사항을 처리하는 데 실패합니다. 핵심 문제는 단일 대화의 즉각적인 임시 정보와 시간이 지남에 따라 수집된 방대한 영구 지식 모두를 효과적으로 관리하는 방법입니다.

: 표준화된 솔루션은 단기 및 장기 저장소를 구별하는 이중 구성 요소 메모리 시스템을 구현하는 것입니다. 단기, 컨텍스트 메모리는 LLM의 컨텍스트 창 내에서 최근 상호 작용 데이터를 보유하여 대화 흐름을 유지합니다. 지속되어야 하는 정보를 위해 장기 메모리 솔루션은 벡터 저장소와 같은 외부 데이터베이스를 사용하여 효율적인 의미론적 검색을 수행합니다. Google ADK와 같은 에이전트 프레임워크는 대화 스레드에 대한 Session 및 임시 채팅 데이터에 대한 State, 검색 가능한 장기 지식에 대한 전용 MemoryService와 같이 이를 관리하기 위한 특정 구성 요소를 제공합니다. 전용 MemoryService는 장기 지식 기반과 인터페이스하여 에이전트가 관련 과거 정보를 검색하고 현재 컨텍스트에 통합할 수 있도록 합니다.

경험 법칙: 에이전트가 단일 질문에 답하는 것 이상을 해야 할 때 이 패턴을 사용하십시오. 대화 전반에 걸쳐 컨텍스트를 유지하거나, 다단계 작업에서 진행 상황을 추적하거나, 사용자 기본 설정 및 기록을 회상하여 상호 작용을 개인화해야 하는 에이전트에게 필수적입니다. 에이전트가 과거의 성공, 실패 또는 새로 획득한 정보를 기반으로 학습하거나 적응해야 하는 경우 메모리 관리를 구현해야 합니다.

시각적 요약

그림 1: 메모리 관리 설계 패턴

주요 시사점

메모리 관리에 대한 주요 사항을 신속하게 요약하면 다음과 같습니다.

  • 메모리는 에이전트가 상황을 추적하고, 학습하고, 상호 작용을 개인화하는 데 매우 중요합니다.

  • 대화형 AI는 단일 채팅 내의 즉각적인 컨텍스트를 위한 단기 메모리와 여러 세션에 걸친 영구 지식을 위한 장기 메모리 모두에 의존합니다.

  • 단기 메모리(즉각적인 것)는 일시적이며 종종 LLM의 컨텍스트 창이나 프레임워크가 컨텍스트를 전달하는 방식에 의해 제한됩니다.

  • 장기 메모리(남아 있는 것)는 외부 저장소(벡터 데이터베이스와 같은)를 사용하여 여러 채팅에 걸쳐 정보를 저장하고 액세스하며 검색을 통해 액세스합니다.

  • ADK와 같은 프레임워크에는 메모리를 관리하기 위해 Session(채팅 스레드), State(임시 채팅 데이터), MemoryService(검색 가능한 장기 지식)와 같은 특정 부분이 있습니다.

  • ADK의 SessionService는 채팅 세션의 전체 수명(이벤트 및 임시 데이터 포함)을 처리합니다.

  • ADK의 session.state는 임시 채팅 데이터에 대한 사전입니다. 접두사(user:, app:, temp:)는 데이터가 속한 위치와 지속되는지 여부를 알려줍니다.

  • ADK에서는 이벤트를 추가할 때 EventActions.state_delta 또는 output_key를 사용하여 상태를 업데이트해야 하며, 상태 사전을 직접 변경해서는 안 됩니다.

  • ADK의 MemoryService는 정보를 장기 저장소에 넣고 에이전트가 이를 검색할 수 있도록 하여 종종 도구를 사용합니다.

  • LangChain은 프롬프트에 단일 대화의 기록을 자동으로 삽입하여 에이전트가 즉각적인 컨텍스트를 회상할 수 있도록 하는 ConversationBufferMemory와 같은 실용적인 도구를 제공합니다.

  • LangGraph는 특정 사용자 및 애플리케이션 컨텍스트에 따라 저장되는 네임스페이스에 정보를 저장하고 검색하여 다른 세션 전반에 걸쳐 지식을 유지하는 고급 장기 메모리를 활성화합니다.

  • Memory Bank는 관리형 서비스로, 사용자별 정보를 자동으로 추출, 저장 및 회상하여 Google ADK, LangGraph 및 CrewAI와 같은 프레임워크 전반에서 개인화되고 지속적인 대화를 활성화하는 영구적인 장기 메모리를 에이전트에 제공합니다.

결론

이 장에서는 에이전트 시스템을 위한 메모리 관리의 매우 중요한 작업에 대해 다루며, 단기적인 컨텍스트와 오래 지속되는 지식의 차이점을 보여주었습니다. 우리는 이러한 유형의 메모리가 설정되는 방식과 더 스마트한 에이전트를 구축하는 데 사용되는 곳을 살펴보았습니다. Google ADK가 세션, 상태 및 MemoryService와 같이 이를 처리하기 위해 제공하는 특정 조각을 자세히 살펴보았습니다. 이제 에이전트가 단기 및 장기적으로 기억하는 방법을 다루었으므로 에이전트가 학습하고 적응하는 방법을 계속 살펴볼 수 있습니다. 다음 패턴인 “학습 및 적응”은 에이전트가 새로운 경험이나 데이터에 따라 사고방식이나 행동 방식을 변경하는 방법에 대해 설명합니다.

참고 자료

  1. ADK 메모리, https://google.github.io/adk-docs/sessions/memory/

제 9장: 학습 및 적응

학습과 적응은 인공 지능 에이전트의 기능을 향상시키는 데 핵심적인 역할을 합니다. 이러한 프로세스를 통해 에이전트는 미리 정의된 매개변수를 넘어 자율적으로 경험과 환경 상호 작용을 통해 개선될 수 있습니다. 학습하고 적응함으로써 에이전트는 지속적인 수동 개입 없이 새로운 상황을 효과적으로 관리하고 성능을 최적화할 수 있습니다. 이 장에서는 에이전트 학습 및 적응의 기반이 되는 원칙과 메커니즘을 자세히 살펴봅니다.

전반적인 그림

에이전트는 새로운 경험과 데이터에 따라 사고, 행동 또는 지식을 변경하여 학습하고 적응합니다. 이를 통해 에이전트는 단순히 지침을 따르는 것에서 시간이 지남에 따라 더 스마트해지도록 발전할 수 있습니다.

  • 강화 학습: 에이전트는 행동을 시도하고 긍정적인 결과에 대해 보상을 받고 부정적인 결과에 대해 페널티를 받아 변화하는 상황에서 최적의 행동을 학습합니다. 로봇을 제어하거나 게임을 하는 에이전트에 유용합니다.
  • 지도 학습: 에이전트는 레이블이 지정된 예제를 통해 학습하여 입력과 원하는 출력을 연결하여 의사 결정 및 패턴 인식과 같은 작업을 가능하게 합니다. 이메일을 분류하거나 추세를 예측하는 에이전트에 이상적입니다.
  • 비지도 학습: 에이전트는 레이블이 지정되지 않은 데이터에서 숨겨진 연결 및 패턴을 발견하여 통찰력, 조직 및 환경의 정신적 지도를 생성하는 데 도움을 줍니다. 특정 지침 없이 데이터를 탐색하는 에이전트에 유용합니다.
  • LLM 기반 에이전트의 몇 가지 예시/제로샷 학습: LLM을 활용하는 에이전트는 최소한의 예시 또는 명확한 지침으로 새로운 작업에 신속하게 적응하여 새로운 명령이나 상황에 빠르게 응답할 수 있습니다.
  • 온라인 학습: 에이전트는 동적 환경에서 실시간 반응 및 지속적인 적응에 필수적인 새 데이터로 지식을 지속적으로 업데이트합니다. 연속적인 데이터 스트림을 처리하는 에이전트에 중요합니다.
  • 메모리 기반 학습: 에이전트는 과거 경험을 회상하여 유사한 상황에서 현재 행동을 조정하여 컨텍스트 인식 및 의사 결정을 향상시킵니다. 메모리 회상 기능이 있는 에이전트에 효과적입니다.

에이전트는 학습을 기반으로 전략, 이해 또는 목표를 변경하여 적응합니다. 이는 예측 불가능하거나, 변화하거나, 새로운 환경에 있는 에이전트에게 매우 중요합니다.

**근접 정책 최적화(PPO)**는 로봇 관절 제어 또는 게임 내 캐릭터 제어와 같이 연속적인 행동 범위를 가진 환경에서 에이전트를 훈련하는 데 사용되는 강화 학습 알고리즘입니다. 주요 목표는 에이전트의 정책이라고 하는 의사 결정 전략을 안정적이고 안정적으로 개선하는 것입니다.

PPO의 핵심 아이디어는 에이전트의 정책을 작고 신중한 업데이트로 만드는 것입니다. 이는 성능을 붕괴시킬 수 있는 급격한 변경을 피합니다. 작동 방식은 다음과 같습니다.

    1. 데이터 수집: 에이전트는 현재 정책을 사용하여 환경과 상호 작용하고(예: 게임 플레이) 경험 배치(상태, 행동, 보상)를 수집합니다.
    1. “대리” 목표 평가: PPO는 잠재적인 정책 업데이트가 예상 보상을 어떻게 변경할지 계산합니다. 그러나 단순히 이 보상을 최대화하는 대신 특수 “클리핑된” 목적 함수를 사용합니다.
    1. “클리핑” 메커니즘: 이것이 PPO 안정성의 핵심입니다. 현재 정책 주변에 “신뢰 영역” 또는 안전 영역을 만듭니다. 알고리즘은 현재 전략과 너무 다른 업데이트를 만드는 것이 금지됩니다. 이 클리핑은 안전 브레이크 역할을 하여 에이전트가 학습을 무효화하는 크고 위험한 단계를 밟는 것을 방지합니다.

요약하자면 PPO는 성능을 개선하는 것과 알려진 작동 전략에 가까이 머무르는 것 사이의 균형을 맞추어 훈련 중 치명적인 실패를 방지하고 보다 안정적인 학습으로 이어지게 합니다.

**직접 선호도 최적화(DPO)**는 대규모 언어 모델(LLM)을 인간의 선호도에 맞추기 위해 특별히 설계된 더 새로운 방법입니다. 이 작업에 PPO를 사용하는 것보다 더 간단하고 직접적인 대안을 제공합니다.

DPO를 이해하려면 먼저 전통적인 PPO 기반 정렬 방법을 이해하는 것이 도움이 됩니다.

  • PPO 접근 방식(2단계 프로세스):
      1. 보상 모델 훈련: 먼저, 인간이 다양한 LLM 응답을 평가하거나 비교하는 곳에서 인간 피드백 데이터를 수집합니다(예: “응답 A가 응답 B보다 낫다”). 이 데이터는 보상 모델을 훈련하는 데 사용되며, 보상 모델의 작업은 새로운 응답에 대해 인간이 어떤 점수를 줄지 예측하는 것입니다.
      1. PPO를 사용한 미세 조정: 다음으로 LLM은 PPO를 사용하여 미세 조정됩니다. LLM의 목표는 보상 모델에서 가장 높은 점수를 받는 응답을 생성하는 것입니다. 보상 모델은 훈련 게임에서 “심사위원” 역할을 합니다.

이 2단계 프로세스는 복잡하고 불안정할 수 있습니다. 예를 들어, LLM은 허점을 찾아 나쁜 응답에 대해 높은 점수를 얻기 위해 보상 모델을 “해킹”하는 방법을 학습할 수 있습니다.

  • DPO 접근 방식(직접 프로세스): DPO는 보상 모델을 완전히 건너뜁니다. 인간의 선호도를 보상 점수로 변환하고 그 점수를 최적화하는 대신, DPO는 선호도 데이터를 사용하여 LLM의 정책을 직접 업데이트합니다.
  • 이는 선호도 데이터를 직접 사용하여 언어 모델을 최적화하여 보상 모델을 훈련하고 사용하는 복잡성과 잠재적 불안정성을 피합니다. 이는 정렬 프로세스를 보다 효율적이고 강력하게 만듭니다.

실제 응용 프로그램 및 사용 사례

적응형 에이전트는 경험적 데이터를 기반으로 하는 반복적인 업데이트를 통해 가변 환경에서 향상된 성능을 나타냅니다.

  • 개인화된 비서 에이전트는 개별 사용자 행동의 종단 분석을 통해 상호 작용 프로토콜을 개선하여 응답 생성을 고도로 최적화합니다.
  • 거래 봇 에이전트는 실시간 고해상도 시장 데이터를 기반으로 모델 매개변수를 동적으로 조정하여 의사 결정 알고리즘을 최적화하고 재정적 수익을 극대화하며 위험 요소를 완화합니다.
  • 응용 프로그램 에이전트는 관찰된 사용자 행동을 기반으로 UI 및 기능을 동적으로 수정하여 사용자 참여 및 시스템 직관성을 높입니다.
  • 로봇 및 자율 주행 차량 에이전트는 센서 데이터 및 과거 작업 분석을 통합하여 탐색 및 응답 기능을 향상시켜 다양한 환경 조건에서 안전하고 효율적인 작동을 가능하게 합니다.
  • 사기 탐지 에이전트는 새로 식별된 사기 패턴으로 예측 모델을 개선하여 시스템 보안을 향상시키고 재정적 손실을 최소화합니다.
  • 추천 에이전트는 사용자 기본 설정 학습 알고리즘을 활용하여 콘텐츠 선택 정밀도를 개선하여 고도로 개인화되고 컨텍스트에 적합한 추천을 제공합니다.
  • 게임 AI 에이전트는 전략 알고리즘을 동적으로 조정하여 게임 복잡성과 도전을 증가시켜 플레이어 참여를 향상시킵니다.
  • 지식 기반 학습 에이전트: 에이전트는 검색 증강 생성(RAG)을 활용하여 문제 설명 및 입증된 솔루션의 지식 기반을 유지할 수 있습니다(14장 참조). 성공적인 전략과 직면한 과제를 저장함으로써 에이전트는 의사 결정 중에 이 데이터에 참조하여 이전에 성공적인 패턴을 적용하거나 알려진 함정을 피함으로써 새로운 상황에 더 효과적으로 적응할 수 있습니다.

사례 연구: 자가 개선 코딩 에이전트 (SICA)

Maxime Robeyns, Laurence Aitchison 및 Martin Szummer가 개발한 자가 개선 코딩 에이전트(SICA)는 에이전트 기반 학습의 발전을 나타내며, 에이전트가 자체 소스 코드를 수정하는 능력을 시연합니다. 이는 한 에이전트가 다른 에이전트를 훈련하는 기존 접근 방식과 대조적으로, SICA는 수정자와 수정되는 엔티티 역할을 모두 수행하며 다양한 코딩 과제 전반에서 성능을 개선하기 위해 코드베이스를 반복적으로 개선합니다.

SICA의 자가 개선은 반복적인 주기로 작동합니다(그림 1 참조). 초기에는 SICA가 과거 버전의 아카이브와 벤치마크 테스트에서의 성능을 검토합니다. 성공, 시간 및 계산 비용을 고려한 가중 공식에 따라 계산된 최고 성능 점수를 가진 버전을 선택합니다. 그런 다음 선택된 버전은 다음 차례의 자체 수정을 수행합니다. 아카이브를 분석하여 잠재적인 개선 사항을 식별한 다음 코드베이스를 직접 수정합니다. 수정된 에이전트는 벤치마크를 테스트하고 결과를 아카이브에 기록합니다. 이 프로세스는 반복되어 과거 성능으로부터 직접 학습이 가능하게 합니다. 이러한 자가 개선 메커니즘을 통해 SICA는 전통적인 훈련 패러다임 없이도 기능을 발전시킬 수 있습니다.

그림 1: SICA의 자가 개선, 과거 버전을 기반으로 학습하고 적응

SICA는 코드 편집 및 탐색에서 발전을 이루는 상당한 자가 개선을 겪었습니다. 초기에는 SICA가 코드 변경에 대해 기본 파일 덮어쓰기 접근 방식을 사용했습니다. 이후 더 지능적이고 상황적인 편집이 가능한 “스마트 편집기”를 개발했습니다. 이는 diff를 통합하여 대상 수정을 수행하고 패턴 기반 편집을 수행하는 “Diff-Enhanced Smart Editor”와 처리 요구 사항을 줄이기 위한 “Quick Overwrite Tool”로 발전했습니다.

SICA는 또한 효율성을 위해 코드의 구조적 맵(AST) 구문을 사용하여 효율성을 위한 “최소 Diff 출력 최적화” 및 “컨텍스트 인식 Diff 최소화”를 구현했습니다. 또한 “SmartEditor 입력 정규화 도구”가 추가되었습니다. 탐색 측면에서 SICA는 코드베이스 내에서 정의를 식별하기 위해 코드의 구조적 맵(AST)을 사용하는 “AST 기호 로케이터”를 독립적으로 생성했습니다. 나중에 빠른 검색과 AST 확인을 결합한 “하이브리드 기호 로케이터”가 개발되었습니다. 이는 검색 속도를 향상시키기 위해 관련 코드 섹션에 초점을 맞추기 위해 “하이브리드 기호 로케이터의 최적화된 AST 구문 분석”을 통해 추가로 최적화되었습니다(그림 2 참조).

그림 2: 반복에 걸친 성능. 주요 개선 사항은 해당 도구 또는 에이전트 수정 사항으로 주석 처리되어 있습니다. (Maxime Robeyns, Martin Szummer, Laurence Aitchison 제공)

SICA의 아키텍처는 기본 파일 작업, 명령 실행 및 산술 계산을 위한 기본 도구 키트로 구성됩니다. 여기에는 결과 제출 및 특수 하위 에이전트(코딩, 문제 해결 및 추론) 호출 메커니즘이 포함됩니다. 이러한 하위 에이전트는 복잡한 작업을 분해하고 특히 확장된 메타 개선 반복 중 LLM의 컨텍스트 길이를 관리합니다.

비동기 감독자, 또 다른 LLM은 SICA의 동작을 모니터링하여 루프 또는 정체와 같은 잠재적인 문제를 식별합니다. SICA와 통신하며 필요한 경우 실행을 중지할 수 있습니다. 감독자는 패턴 및 비효율성을 식별할 수 있도록 통화 그래프 및 메시지 및 도구 작업 로그를 포함하여 SICA의 작업에 대한 자세한 보고서를 수신합니다.

SICA의 LLM은 단기 메모리인 컨텍스트 창 내에서 정보를 구조화된 방식으로 구성하며, 이는 작동에 매우 중요합니다. 이 구조에는 에이전트 목표를 정의하는 시스템 프롬프트, 도구 및 하위 에이전트 문서, 시스템 지침이 포함됩니다. 코어 프롬프트에는 문제 진술 또는 지침, 열린 파일의 내용 및 디렉터리 맵이 포함됩니다. 도우미 메시지는 에이전트의 단계별 추론, 도구 및 하위 에이전트 호출 기록 및 결과를 기록하며 감독자 통신도 기록합니다. 이러한 구성은 효율적인 정보 흐름을 촉진하여 LLM 작동을 향상시키고 처리 시간 및 비용을 줄입니다. 초기에는 파일 변경 사항이 수정 사항만 보여주는 diff로 기록된 다음 주기적으로 통합되었습니다.

SICA: 코드 살펴보기: SICA 구현을 더 깊이 살펴보면 기능의 기반이 되는 몇 가지 주요 설계 선택 사항이 나타납니다. 논의한 바와 같이 시스템은 코딩 에이전트, 문제 해결 에이전트 및 추론 에이전트와 같은 여러 하위 에이전트를 포함하는 모듈식 아키텍처로 구축됩니다. 이러한 하위 에이전트는 복잡한 작업을 분해하고 특히 확장된 메타 개선 반복 중에 컨텍스트 길이를 효율적으로 관리하기 위해 기본 에이전트가 도구 호출처럼 호출됩니다.

이 프로젝트는 도구 사용 및 기타 에이전트 작업에 대해 훈련 후 LLM을 사후 훈련하는 데 관심이 있는 사람들을 위한 강력한 프레임워크를 제공하기 위해 적극적으로 개발되고 있으며, 전체 코드는 https://github.com/MaximeRobeyns/self\_improving\_coding\_agent/ GitHub 저장소에서 추가 탐색 및 기여를 위해 제공됩니다.

보안을 위해 프로젝트는 Docker 컨테이너화를 강력하게 강조하며, 에이전트는 전용 Docker 컨테이너 내에서 실행됩니다. 이는 호스트 머신으로부터 격리를 제공하여 에이전트가 셸 명령을 실행할 수 있는 잠재적인 파일 시스템 조작 위험을 완화하기 때문에 중요한 조치입니다.

투명성과 제어를 보장하기 위해 시스템은 이벤트 버스 및 에이전트의 통화 그래프에서 이벤트를 시각화하는 대화형 웹 페이지를 통해 강력한 관측 가능성을 제공합니다. 이를 통해 사용자는 개별 이벤트를 검사하고, 감독자 메시지를 읽고, 더 명확한 이해를 위해 하위 에이전트 추적을 축소하여 에이전트의 작업에 대한 포괄적인 통찰력을 얻을 수 있습니다.

핵심 지능 측면에서 에이전트 프레임워크는 다양한 제공업체의 LLM 통합을 지원하여 특정 작업에 가장 적합한 모델을 실험할 수 있도록 합니다. 마지막으로 중요한 구성 요소는 주 에이전트와 동시에 실행되는 LLM인 비동기 감독자입니다. 이 감독자는 병적인 편차 또는 정체를 위해 에이전트의 동작을 주기적으로 평가하고 알림을 보내거나 필요한 경우 에이전트의 실행을 취소할 수 있습니다. 통화 그래프 및 LLM 메시지, 도구 호출 및 응답의 이벤트 스트림을 포함하여 시스템 상태의 자세한 텍스트 표현을 수신하므로 비효율적인 패턴이나 반복되는 작업을 감지할 수 있습니다.

SICA의 초기 구현에서 주목할 만한 과제는 메타 개선 반복마다 자가 제안하는 독창적이고, 혁신적이며, 실행 가능하고, 매력적인 수정을 위해 LLM 기반 에이전트에게 프롬프트를 제공하는 것이었습니다. 특히 개방형 학습 및 LLM 에이전트의 진정한 창의성을 육성하는 데 있어 이러한 제한 사항은 현재 연구에서 조사의 주요 영역으로 남아 있습니다.

AlphaEvolve 및 OpenEvolve

AlphaEvolve는 알고리즘을 발견하고 최적화하기 위해 설계된 Google의 AI 에이전트입니다. 여기에는 LLM(특히 Gemini 모델(Flash 및 Pro)), 자동화된 평가 시스템 및 진화 알고리즘 프레임워크의 조합이 사용됩니다. 이 시스템은 이론 수학과 실용적인 컴퓨팅 응용 프로그램 모두를 발전시키는 것을 목표로 합니다.

AlphaEvolve는 Gemini 모델 앙상블을 사용합니다. Flash는 광범위한 초기 알고리즘 제안을 생성하는 데 사용되며, Pro는 보다 심층적인 분석 및 개선을 제공합니다. 제안된 알고리즘은 미리 정의된 기준에 따라 자동으로 평가 및 채점됩니다. 이 평가는 반복적으로 솔루션을 개선하는 데 사용되는 피드백을 제공하여 최적화되고 새로운 알고리즘으로 이어집니다.

실용적인 컴퓨팅에서 AlphaEvolve는 Google의 인프라 내에서 배포되었습니다. 데이터 센터 스케줄링에서 개선을 입증하여 전역 컴퓨팅 리소스 사용량을 0.7% 감소시켰습니다. 또한 향후 텐서 처리 장치(TPU)의 Verilog 코드 최적화 제안을 통해 하드웨어 설계에 기여했습니다. 또한 AlphaEvolve는 Gemini 아키텍처의 핵심 커널에서 23% 속도 향상 및 FlashAttention의 저수준 GPU 명령에 대한 최대 32.5% 최적화를 포함하여 AI 성능을 가속화했습니다.

기초 연구 분야에서 AlphaEvolve는 행렬 곱셈을 위한 새로운 알고리즘, 특히 이전에 알려진 솔루션을 능가하는 48개의 스칼라 곱셈을 사용하는 4x4 복소수 행렬에 대한 방법을 발견하는 데 기여했습니다. 광범위한 수학 연구에서 키싱 넘버(kissing number problem) 문제의 발전과 같은 예시를 포함하여 75%의 경우 50개 이상의 열린 문제에 대한 기존의 최첨단 솔루션을 재발견하고 20%의 경우 기존 솔루션을 개선했습니다.

OpenEvolve는 LLM(그림 3 참조)을 활용하여 코드를 반복적으로 최적화하는 진화 코딩 에이전트입니다. 이는 LLM 기반 코드 생성, 평가 및 선택 파이프라인을 오케스트레이션하여 광범위한 작업에 대해 프로그램을 지속적으로 개선합니다. OpenEvolve의 주요 측면은 단일 함수에만 국한되지 않고 전체 코드 파일을 진화시킬 수 있다는 것입니다. 이 에이전트는 다중 프로그래밍 언어 지원 및 모든 LLM에 대한 OpenAI 호환 API 호환성을 제공하여 다재다능하도록 설계되었습니다. 또한 다중 목적 최적화를 통합하고 유연한 프롬프트 엔지니어링을 허용하며 복잡한 코딩 과제를 효율적으로 처리하기 위해 분산 평가가 가능합니다.

그림 3: OpenEvolve 내부 아키텍처는 컨트롤러에 의해 관리됩니다. 이 컨트롤러는 프로그램 샘플러, 프로그램 데이터베이스, 평가자 풀 및 LLM 앙상블과 같은 여러 핵심 구성 요소를 오케스트레이션합니다. 주요 기능은 학습 및 적응 프로세스를 촉진하여 코드 품질을 향상시키는 것입니다.

이 코드 조각은 진화 최적화를 수행하기 위해 OpenEvolve 라이브러리를 사용합니다. 초기 프로그램, 평가 파일 및 구성 파일에 대한 경로로 OpenEvolve 시스템을 초기화합니다. evolve.run(iterations=1000) 줄은 1000회 반복 실행하여 프로그램의 개선된 버전을 찾기 위해 진화 프로세스를 시작합니다. 마지막으로 진화 중에 발견된 최고의 프로그램의 메트릭을 소수점 이하 네 자리까지 형식화하여 출력합니다.

from openevolve import OpenEvolve

# 시스템 초기화
evolve = OpenEvolve(
   initial_program_path="path/to/initial_program.py",
   evaluation_file="path/to/evaluator.py",
   config_path="path/to/config.yaml"
)
# 진화 실행
best_program = await evolve.run(iterations=1000)
print(f"최고 프로그램 메트릭:")
for name, value in best_program.metrics.items():
   print(f" {name}: {value:.4f}")

한눈에 보기

무엇을: AI 에이전트는 동적이고 예측 불가능한 환경에서 작동하는 경우가 많으며, 여기서는 미리 프로그래밍된 논리가 불충분할 수 있습니다. 성능 저하의 원인이 될 수 있는 새로운 상황에 직면했을 때 그들의 성능은 저하될 수 있습니다. 경험으로부터 학습할 수 있는 능력이 없으면 에이전트는 전략을 최적화하거나 상호 작용을 개인화할 수 없습니다. 이러한 경직성은 효율성을 제한하고 복잡하고 실제 시나리오에서 진정한 자율성을 달성하는 것을 방해합니다.

왜: 표준화된 솔루션은 학습 및 적응 메커니즘을 통합하여 정적 에이전트를 동적이고 진화하는 시스템으로 변환하는 것입니다. 이를 통해 에이전트는 새로운 데이터와 상호 작용을 기반으로 지식과 행동을 자율적으로 개선할 수 있습니다. 에이전트 시스템은 강화 학습에서 SICA와 같은 자가 수정과 같은 고급 기술에 이르기까지 다양한 방법을 사용할 수 있습니다. Google의 AlphaEvolve와 같은 고급 시스템은 LLM과 진화 알고리즘을 활용하여 복잡한 문제에 대한 완전히 새롭고 더 효율적인 솔루션을 발견합니다. 지속적으로 학습함으로써 에이전트는 지속적인 수동 재프로그래밍 없이도 새로운 작업을 마스터하고, 성능을 향상시키며, 변화하는 조건에 적응할 수 있습니다.

경험 법칙: 동적이고 불확실하거나 개인적인 손길이 필요한 환경에서 작동해야 하는 에이전트를 구축할 때 이 패턴을 사용하십시오. 개인화, 지속적인 성능 개선 및 새로운 상황에 자율적으로 대처하는 능력이 필요한 응용 분야에 필수적입니다. 에이전트 시스템을 설계할 때 학습 및 적응 메커니즘을 통합하여 에이전트가 경험을 통해 개선되도록 해야 합니다.

시각적 요약

그림 4: 학습 및 적응 패턴

주요 시사점

주요 시사점은 다음과 같습니다.

  • 학습 및 적응은 에이전트가 경험을 사용하여 수행하는 작업에 더 능숙해지고 새로운 상황을 처리하는 것에 관한 것입니다.

  • “적응”은 학습으로 인해 발생하는 에이전트 행동 또는 지식의 가시적인 변화입니다.

  • SICA, 자가 개선 코딩 에이전트는 과거 성능을 기반으로 코드를 수정하여 자가 개선합니다. 이는 스마트 편집기 및 AST 기호 로케이터와 같은 도구로 이어졌습니다.

  • 특수 “하위 에이전트”와 “감독자”를 갖는 것은 이러한 자가 개선 시스템이 큰 작업을 관리하고 궤도를 유지하는 데 도움이 됩니다.

  • LLM의 “컨텍스트 창”이 설정되는 방식(시스템 프롬프트, 코어 프롬프트 및 도우미 메시지로)은 에이전트가 얼마나 효율적으로 작동하는지에 매우 중요합니다.

  • 이 패턴은 항상 변화하고, 불확실하거나, 개인적인 손길이 필요한 환경에서 작동해야 하는 에이전트에게 매우 중요합니다.

  • 학습하는 에이전트를 구축하는 것은 종종 머신 러닝 도구와 연결하고 데이터 흐름을 관리하는 것을 포함합니다.

  • 에이전트 시스템은 기본 코딩 도구를 갖추고 자율적으로 자체를 편집하여 벤치마크 작업에서 성능을 향상시킬 수 있습니다.

  • AlphaEvolve는 Google의 AI 에이전트로, LLM과 진화 프레임워크를 활용하여 자율적으로 알고리즘을 발견하고 최적화하여 기초 연구 및 실용적인 컴퓨팅 응용 프로그램 모두를 크게 향상시킵니다.

결론

이 장에서는 인공 지능에서 학습 및 적응의 중요한 역할에 대해 검토했습니다. AI 에이전트는 지속적인 데이터 획득 및 경험을 통해 성능을 향상시킵니다. 자가 개선 코딩 에이전트(SICA)는 자가 개선 코딩 에이전트(SICA)는 코드 수정을 통해 기능을 자율적으로 개선함으로써 이를 보여주는 예입니다.

우리는 아키텍처, 응용 프로그램, 계획, 다중 에이전트 협업, 메모리 관리 및 학습 및 적응을 포함한 에이전트 AI의 기본 구성 요소를 검토했습니다. 학습 원칙은 특히 다중 에이전트 시스템에서 조정된 개선을 위해 중요합니다. 이를 달성하기 위해 튜닝 데이터는 각 참여 에이전트의 개별 입력 및 출력을 캡처하는 전체 상호 작용 궤적을 정확하게 반영해야 합니다.

이러한 요소들은 Google의 AlphaEvolve와 같은 중요한 발전에 기여합니다. 이 AI 시스템은 LLM, 자동화된 평가 및 진화적 접근 방식을 통해 알고리즘을 독립적으로 발견하고 개선하여 과학 연구 및 계산 기술의 발전을 주도합니다. 이러한 패턴은 정교한 AI 시스템을 구성하기 위해 결합될 수 있습니다. AlphaEvolve와 같은 개발은 AI 에이전트에 의한 자율적인 알고리즘 발견 및 최적화가 달성 가능하다는 것을 보여줍니다.

참고 자료


제 10장: 모델 컨텍스트 프로토콜

LLM이 에이전트로서 효과적으로 기능하려면 그 기능이 멀티모달 생성을 넘어 확장되어야 합니다. 현재 데이터 액세스, 외부 소프트웨어 활용 및 특정 운영 작업을 실행하는 것을 포함하여 외부 환경과의 상호 작용이 필요합니다. 모델 컨텍스트 프로토콜(MCP)은 LLM이 외부 리소스와 인터페이스하기 위한 표준화된 인터페이스를 제공하여 이러한 필요성을 다룹니다. 이 프로토콜은 일관되고 예측 가능한 통합을 촉진하기 위한 핵심 메커니즘 역할을 합니다.

MCP 패턴 개요

모든 LLM이 사용자 지정 통합 없이 모든 외부 시스템, 데이터베이스 또는 도구에 연결할 수 있는 범용 어댑터를 상상해 보십시오. 그것이 본질적으로 모델 컨텍스트 프로토콜(MCP)입니다. 이는 Gemini, OpenAI의 GPT 모델, Mixtral 및 Claude와 같은 모든 LLM이 외부 응용 프로그램, 데이터 소스 및 도구와 통신하는 방식을 표준화하기 위해 설계된 개방형 표준입니다. LLM이 컨텍스트를 얻고, 작업을 실행하고, 다양한 시스템과 상호 작용하는 방법을 단순화하는 범용 연결 메커니즘이라고 생각하십시오.

MCP는 클라이언트-서버 아키텍처에서 작동합니다. 이는 데이터(리소스라고 함), 대화형 템플릿(본질적으로 프롬프트임), 실행 가능한 함수(도구라고 함)가 MCP 서버에 의해 노출되는 방식과 이를 MCP 클라이언트(LLM 호스트 응용 프로그램 또는 AI 에이전트 자체일 수 있음)가 소비하는 방식을 정의합니다. 이러한 표준화된 접근 방식은 LLM을 다양한 운영 환경에 통합하는 복잡성을 극적으로 줄입니다.

그러나 MCP는 “에이전트 인터페이스”에 대한 계약이며, 그 효과는 노출하는 기본 API 설계에 크게 의존합니다. 개발자가 기존의 레거시 API를 수정 없이 단순히 래핑하는 위험이 있는데, 이는 에이전트에게 최적이 아닐 수 있습니다. 예를 들어, 티켓팅 시스템의 API가 전체 티켓 세부 정보를 한 번에 하나씩만 검색하도록 허용하는 경우, 우선순위 티켓 요약을 요청받은 에이전트는 대량에서 느리고 부정확할 것입니다. 진정으로 효과적이려면 비결정적 에이전트가 효율적으로 작동하도록 지원하기 위해 필터링 및 정렬과 같은 결정적 기능을 갖추도록 기본 API가 개선되어야 합니다. 이는 에이전트가 결정적 워크플로를 마법처럼 대체하지 않으며, 성공을 위해 더 강력한 결정적 지원이 종종 필요함을 강조합니다.

또한, MCP는 에이전트가 이해하기에 본질적으로 아직 이해하기 어려운 입력이나 출력을 가진 API를 래핑할 수도 있습니다. API는 반환하는 데이터 형식이 에이전트 친화적일 때만 유용하며, 이는 MCP 자체가 보장하지 않습니다. 예를 들어, 소비하는 에이전트가 PDF 콘텐츠를 구문 분석할 수 없는 경우 파일을 PDF로 반환하는 문서 저장소에 대한 MCP 서버를 만드는 것은 거의 유용하지 않습니다. 더 나은 접근 방식은 에이전트가 실제로 읽고 처리할 수 있는 Markdown과 같은 텍스트 버전을 반환하는 API를 먼저 만드는 것입니다. 이는 단순히 연결뿐만 아니라 진정한 호환성을 보장하기 위해 교환되는 데이터의 특성도 고려해야 함을 시연합니다.

MCP 대 도구 함수 호출

모델 컨텍스트 프로토콜(MCP)과 도구 함수 호출은 LLM이 외부 기능(도구 포함)과 상호 작용하고 작업을 실행할 수 있도록 하는 별개의 메커니즘입니다. 둘 다 텍스트 생성을 넘어 LLM의 기능을 확장하는 역할을 하지만, 접근 방식과 추상화 수준에서 차이가 있습니다.

도구 함수 호출은 LLM이 특정, 미리 정의된 도구 또는 함수에 직접 요청하는 것으로 생각할 수 있습니다. 이 맥락에서 “도구”와 “함수”라는 단어를 상호 교환적으로 사용한다는 점에 유의하십시오. 이 상호 작용은 1대1 통신 모델로 특징지어지며, 여기서 LLM은 외부 작업을 요구하는 사용자 의도에 대한 이해를 기반으로 요청을 형식화합니다. 그런 다음 응용 프로그램 코드가 이 요청을 실행하고 결과를 LLM에 반환합니다. 이 프로세스는 종종 독점적이며 다양한 LLM 공급업체에 따라 다릅니다.

반면, 모델 컨텍스트 프로토콜(MCP)은 LLM이 외부 기능(도구 및 시스템)을 검색, 통신 및 활용하기 위한 표준화된 인터페이스로 작동합니다. 이는 다양한 도구 및 시스템과 상호 작용을 용이하게 하고 모든 규격 준수 도구가 모든 규격 준수 LLM에 액세스할 수 있는 생태계를 구축하는 것을 목표로 하는 개방형 프로토콜 역할을 합니다. 이는 다양한 시스템 및 구현 전반에 걸쳐 상호 운용성, 구성 가능성 및 재사용성을 촉진합니다. 연합 모델을 채택함으로써 우리는 상호 운용성을 크게 개선하고 기존 자산의 가치를 잠금 해제합니다. 이 전략은 기본 시스템을 비용이 많이 드는 재작성 없이 독립적으로 계속 작동하지만, LLM에 의해 오케스트레이션되는 새로운 응용 프로그램 및 워크플로에 구성될 수 있도록 하여 이질적이고 레거시 서비스를 MCP 준수 인터페이스로 래핑하는 것만으로도 현대 생태계로 가져올 수 있게 합니다.

MCP와 도구 함수 호출 간의 근본적인 차이점에 대한 분석은 다음과 같습니다.

기능도구
함수
호출
모델
컨텍스트
프로토콜
(MCP)
표준화독점적이며
공급업체별입니다.
형식 및 구현은
LLM
공급업체에 따라
다릅니다.
다양한 LLM 및 도구 간의 상호 운용성을 촉진하는 개방형 표준 프로토콜입니다.
범위LLM이 특정, 미리 정의된 함수의 실행을 요청하기 위한 직접적인 메커니즘입니다.LLM과 외부 도구가 서로를 검색하고 통신하는 방법에 대한 더 광범위한 프레임워크입니다.
아키텍처LLM과 응용 프로그램의
도구 처리 논리 간의
1대1 상호 작용입니다.
LLM 기반 응용 프로그램(클라이언트)이 다양한 MCP 서버(도구)에 연결하고 활용할 수 있는 클라이언트-서버 아키텍처입니다.
검색LLM은 특정 대화의
컨텍스트 내에서 어떤
도구가 사용 가능한지
명시적으로 알려집니다.
사용 가능한 도구를 동적으로 검색할 수 있도록 합니다. MCP 클라이언트는 서버를 쿼리하여 제공하는 기능을 확인할 수 있습니다.
재사용성도구 통합은 종종 사용 중인 특정 응용 프로그램 및 LLM과 긴밀하게 결합됩니다.독립적인 “MCP 서버” 개발을 촉진하며, 이는 규격 준수 응용 프로그램에서 액세스할 수 있습니다.

도구 함수 호출을 특정 작업 세트에 대해 맞춤 제작된 특정 렌치 및 드라이버와 같은 특정 도구 세트를 AI에 제공하는 것으로 생각하십시오. 이는 고정된 작업 세트를 가진 작업장에 효율적입니다. 반면, MCP(모델 컨텍스트 프로토콜)는 보편적인 표준화된 전원 콘센트 시스템을 만드는 것과 같습니다. 도구 자체를 제공하지는 않지만, 모든 제조업체의 규격 준수 도구가 연결되어 작동하도록 허용하여 동적이며 끊임없이 확장되는 작업장을 가능하게 합니다.

요약하자면, 함수 호출은 몇 가지 특정 기능에 대한 직접적인 액세스를 제공하는 반면, MCP는 LLM이 광범위한 외부 리소스를 검색하고 사용하는 표준화된 통신 프레임워크입니다. 간단한 응용 프로그램의 경우 특정 도구로 충분하며, 복잡하고 상호 연결된 AI 시스템을 적응시켜야 하는 경우에는 MCP와 같은 보편적인 표준이 필수적입니다.

MCP에 대한 추가 고려 사항

MCP는 강력한 프레임워크를 제시하지만, 철저한 평가는 주어진 사용 사례에 대한 적합성에 영향을 미치는 몇 가지 중요한 측면을 고려해야 합니다. 세부 사항을 자세히 살펴보겠습니다.

  • 도구 대 리소스 대 프롬프트: 이러한 구성 요소의 특정 역할 이해가 중요합니다. 리소스는 정적 데이터(예: PDF 파일, 데이터베이스 레코드)입니다. 도구는 동작을 수행하는 실행 가능한 함수(예: 이메일 전송, API 쿼리)입니다. 프롬프트는 LLM이 리소스 또는 도구와 상호 작용하는 방법을 안내하는 템플릿으로, 상호 작용이 구조화되고 효과적임을 보장합니다.

  • 검색 가능성: MCP의 주요 이점 중 하나는 MCP 클라이언트가 동적을 쿼리할 수 있다는 것입니다. 서버에 제공하는 도구 및 리소스를 확인합니다. 이 “적시” 검색 메커니즘은 새 기능에 적응해야 하는 에이전트에게 강력합니다.

  • 보안: 모든 프로토콜을 통해 도구와 데이터를 노출하는 데에는 강력한 보안 조치가 필요합니다. MCP 구현에는 클라이언트가 어떤 서버에 액세스할 수 있는지, 그리고 어떤 특정 작업을 수행할 수 있는지 제어하기 위한 인증 및 권한 부여가 포함되어야 합니다.

  • 구현: MCP는 개방형 표준이지만 구현이 복잡할 수 있습니다. 그러나 공급업체는 이 프로세스를 단순화하기 시작했습니다. 예를 들어, Anthropic 또는 FastMCP와 같은 일부 모델 공급업체는 SDK를 제공하여 보일러플레이트 코드의 많은 부분을 추상화하여 개발자가 MCP 클라이언트 및 서버를 더 쉽게 생성하고 연결할 수 있도록 합니다.

  • 오류 처리: 포괄적인 오류 처리 전략이 중요합니다. 프로토콜은 오류(예: 도구 실행 실패, 서버를 사용할 수 없음, 잘못된 요청)가 LLM에 다시 통신되는 방식을 정의해야 하므로 LLM이 실패를 이해하고 잠재적으로 대안적인 접근 방식을 시도할 수 있습니다.

  • 로컬 대 원격 서버: MCP 서버는 에이전트와 동일한 머신에서 로컬로 배포되거나 다른 서버에서 원격으로 배포될 수 있습니다. 로컬 서버는 민감한 데이터에 대한 속도와 보안을 위해 선택될 수 있는 반면, 원격 서버

  • 아키텍처는 조직 전반에서 일반적인 도구의 공유 및 확장 가능한 액세스를 허용합니다.

  • 주문형 대 일괄 처리: MCP는 주문형, 대화식 세션과 더 큰 규모의 일괄 처리를 모두 지원할 수 있습니다. 선택은 실시간 대화 에이전트와 즉각적인 도구 액세스가 필요한지 또는 데이터를 일괄 처리하는 데이터 분석 파이프라인이 필요한지에 따라 달라집니다.

  • 전송 메커니즘: 프로토콜은 통신을 위한 기본 전송 계층도 정의합니다. 로컬 상호 작용의 경우 효율적인 프로세스 간 통신을 위해 STDIO(표준 입력/출력)를 통한 JSON-RPC를 사용합니다. 원격 연결의 경우 지속적이고 효율적인 클라이언트-서버 통신을 가능하게 하기 위해 Streamable HTTP 및 서버 전송 이벤트(SSE)와 같은 웹 친화적인 프로토콜을 활용합니다.

모델 컨텍스트 프로토콜은 클라이언트-서버 모델을 사용하여 정보 흐름을 표준화합니다. 구성 요소 상호 작용을 이해하는 것이 MCP의 고급 에이전트 동작에 핵심입니다.

    1. 대규모 언어 모델 (LLM): 핵심 지능. 사용자 요청을 처리하고, 계획을 공식화하며, 외부 정보에 액세스하거나 작업을 수행해야 할 때를 결정합니다.
    1. MCP 클라이언트: LLM 주변의 응용 프로그램 또는 래퍼입니다. 중개자 역할을 하며 LLM의 의도를 MCP 표준을 준수하는 공식 요청으로 변환합니다. 사용 가능한 도구 및 리소스를 검색, 연결 및 통신하는 책임이 있습니다.
    1. MCP 서버: 외부 세계로 가는 관문입니다. 승인된 MCP 클라이언트에게 도구, 리소스 및 프롬프트 세트를 노출합니다. 각 서버는 일반적으로 회사 내부 데이터베이스 연결 또는 이메일 서비스 또는 공개 API와 같은 특정 도메인을 담당합니다.
    1. 선택적 타사(3P) 서비스: MCP 서버가 관리하고 노출하는 실제 외부 도구, 응용 프로그램 또는 데이터 소스를 나타냅니다. 요청된 작업을 수행하는 최종 엔드포인트입니다(예: 독점 데이터베이스 쿼리, SaaS 플랫폼과 상호 작용 또는 공개 날씨 API 호출).

상호 작용 흐름은 다음과 같습니다.

  1. 검색: MCP 클라이언트는 LLM을 대신하여 MCP 서버에 쿼리하여 제공하는 기능(도구, 리소스 및 프롬프트)을 확인합니다. 서버는 사용 가능한 도구(예: send_email), 리소스(예: customer_database) 및 프롬프트를 나열하는 매니페스트로 응답합니다.
    1. 요청 공식화: LLM은 발견된 도구 중 하나를 사용해야 한다고 결정합니다. 예를 들어 이메일을 보내기로 결정합니다. 사용해야 할 도구(send_email)와 필요한 매개변수(수신자, 제목, 본문)를 지정하여 요청을 공식화합니다.
    1. 클라이언트 통신: MCP 클라이언트는 LLM의 공식화된 요청을 적절한 MCP 서버에 표준화된 호출로 보냅니다.
    1. 서버 실행: MCP 서버는 요청을 수신합니다. 클라이언트를 인증하고, 요청을 확인하고, 지정된 작업을 실행합니다(예: 이메일 API의 send() 함수 호출).
    1. 응답 및 컨텍스트 업데이트: 실행 후 MCP 서버는 표준화된 응답을 MCP 클라이언트에 다시 보냅니다. 이 응답은 작업이 성공했는지 여부와 관련 출력(예: 전송된 이메일에 대한 확인 ID)을 나타냅니다. 클라이언트는 이 결과를 LLM에 다시 전달하여 컨텍스트를 업데이트하고 작업의 다음 단계로 진행할 수 있도록 합니다.

실제 응용 프로그램 및 사용 사례

MCP는 AI/LLM의 기능을 크게 확장하여 다양성과 강력함을 향상시킵니다. 다음은 아홉 가지 주요 사용 사례입니다.

  • 데이터베이스 통합: MCP를 통해 LLM 및 에이전트는 구조화된 데이터베이스의 구조화된 데이터에 원활하게 액세스하고 상호 작용할 수 있습니다. 예를 들어, 데이터베이스용 MCP 툴박스를 사용하여 에이전트는 Google BigQuery 데이터 세트에 쿼리하여 실시간 정보를 검색하고, 보고서를 생성하거나, 자연어 명령에 따라 레코드를 업데이트할 수 있습니다.
  • 생성 미디어 오케스트레이션: MCP는 에이전트가 고급 생성 미디어 서비스와 통합할 수 있도록 합니다. MCP Tools for Genmedia Services를 통해 에이전트는 Google의 Imagen(이미지 생성), Google의 Veo(비디오 제작), Google의 Chirp 3 HD(사실적인 음성) 또는 Google의 Lyria(음악 작곡)를 포함하는 워크플로를 오케스트레이션하여 AI 응용 프로그램 내에서 동적 콘텐츠 생성을 허용할 수 있습니다.
  • 외부 API 상호 작용: MCP는 LLM이 외부 API를 호출하고 응답을 수신하는 표준화된 방법을 제공합니다. 이는 에이전트가 실시간 날씨 데이터를 가져오고, 주가를 가져오고, 이메일을 보내거나, CRM 시스템과 상호 작용하여 핵심 언어 모델을 훨씬 능가하는 기능을 확장할 수 있음을 의미합니다.
  • 추론 기반 정보 추출: LLM의 강력한 추론 기술을 활용하여 MCP는 기존 검색 및 검색 시스템을 능가하는 효과적인 쿼리 종속 정보 추출을 용이하게 합니다. 기존 검색 도구가 전체 문서를 반환하는 대신 에이전트는 텍스트를 분석하고 복잡한 질문에 직접 답하는 정확한 조항, 수치 또는 문을 추출할 수 있습니다.
  • 사용자 지정 도구 개발: 개발자는 사용자 지정 도구를 빌드하고 MCP 서버(예: FastMCP 사용)를 통해 노출할 수 있습니다. 이를 통해 전문 내부 기능 또는 독점 시스템을 표준화되고 쉽게 소비할 수 있는 형식으로 LLM 및 기타 에이전트가 사용할 수 있도록 하여 LLM 자체를 수정할 필요 없이 사용할 수 있습니다.
  • 표준화된 LLM-대-응용 프로그램 통신: MCP는 LLM과 상호 작용하는 응용 프로그램 간의 일관된 통신 계층을 보장합니다. 이는 통합 오버헤드를 줄이고, 다양한 LLM 공급업체 및 호스트 응용 프로그램 간의 상호 운용성을 촉진하며, 복잡한 에이전트 시스템 개발을 단순화합니다.
  • 복잡한 워크플로 오케스트레이션: 다양한 MCP 노출 도구 및 데이터 소스를 결합하여 에이전트는 고도로 복잡한 다단계 워크플로를 오케스트레이션할 수 있습니다. 예를 들어, 에이전트는 데이터베이스에서 고객 데이터를 검색하고, 개인화된 마케팅 이미지를 생성하고, 맞춤형 이메일을 초안 작성한 다음, 서로 다른 MCP 서비스와 상호 작용하여 이메일을 보낼 수 있습니다.
  • IoT 장치 제어: MCP는 LLM이 IoT(사물 인터넷) 장치와 상호 작용하는 것을 용이하게 할 수 있습니다. 에이전트는 MCP를 사용하여 스마트 홈 기기, 산업용 센서 또는 로봇에 명령을 보내 물리적 시스템의 자연어 제어 및 자동화를 활성화할 수 있습니다.
  • 금융 서비스 자동화: 금융 서비스에서 MCP는 LLM이 다양한 금융 데이터 소스, 거래 플랫폼 또는 규정 준수 시스템과 상호 작용하도록 활성화할 수 있습니다. 에이전트는 시장 데이터를 분석하고, 거래를 실행하고, 개인화된 재무 조언을 생성하거나, 규정 보고서를 자동화할 수 있으며, 이 모든 과정에서 안전하고 표준화된 통신을 유지합니다.

요컨대, 모델 컨텍스트 프로토콜(MCP)은 에이전트가 데이터베이스, API 및 웹 리소스에서 실시간 정보에 액세스할 수 있도록 합니다. 또한 에이전트가 이메일 전송, 레코드 업데이트, 장치 제어 및 복잡한 작업 실행과 같은 작업을 수행하고 다양한 소스의 데이터를 통합 및 처리할 수 있도록 허용합니다. 또한 MCP는 AI 응용 프로그램을 위한 미디어 생성 도구를 지원합니다.

ADK를 사용한 실습 코드 예시

이 섹션에서는 로컬 MCP 서버에 연결하여 ADK 에이전트가 로컬 파일 시스템과 상호 작용할 수 있도록 하여 파일 시스템 작업을 수행할 수 있도록 하는 방법을 간략하게 설명합니다.

MCPToolset을 사용한 에이전트 설정

파일 시스템 상호 작용을 위해 에이전트를 구성하려면 agent.py 파일을 생성해야 합니다(예: ./adk\_agent\_samples/mcp\_agent/agent.py에 위치). MCPToolsetLlmAgent 객체의 tools 목록 내에서 인스턴스화됩니다. args 목록에서 "/path/to/your/folder"를 로컬 시스템의 절대 경로로 바꾸는 것이 중요합니다. 이 디렉토리는 MCP 서버가 액세스할 수 있는 루트여야 합니다. 이 디렉터리는 에이전트가 수행하는 파일 시스템 작업의 루트가 됩니다.

import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset,
StdioServerParameters
# 이 에이전트 스크립트와 동일한 디렉터리 내에
'mcp_managed_files'라는 폴더에 대한 신뢰할 수 있는 절대 경로를
만듭니다.
# 이는 에이전트가 시연 목적으로 즉시 작동하도록 보장합니다.
# 프로덕션의 경우 더 영구적이고 안전한 위치를 가리키게 됩니다.
TARGET_FOLDER_PATH =
os.path.join(os.path.dirname(os.path.abspath(__file__)),
"mcp_managed_files")
# 에이전트가 필요하기 전에 대상 디렉터리가 있는지 확인합니다.
os.makedirs(TARGET_FOLDER_PATH, exist_ok=True)
root_agent = LlmAgent(
   model='gemini-2.0-flash',
   name='filesystem_assistant_agent',
   instruction=(
       '파일 관리를 도와드립니다. 파일 목록 표시, 파일 읽기 및
파일 쓰기가 가능합니다. '
       f'다음 디렉터리에서 작동 중입니다: {TARGET_FOLDER_PATH}'
   ),
   tools=[
       MCPToolset(
           connection_params=StdioServerParameters(
               command='npx',
               args=[
                   "-y", # npx의 인수로 자동 설치 확인
                   "@modelcontextprotocol/server-filesystem",
                   # 이것은 폴더에 대한 절대 경로여야 합니다.
TARGET_FOLDER_PATH,
               ],
           ),
           # 선택 사항: MCP 서버에서 노출되는 도구를 필터링할 수
있습니다.
           # 예를 들어 읽기만 허용하려면:
           # tool_filter=['list_directory', 'read_file']
       )
   ],
)

npx(npm 버전 5.2.0 이상에 번들로 제공됨)는 npm 레지스트리에서 Node.js 패키지를 직접 실행할 수 있는 유틸리티입니다. 이를 통해 전역 설치나 프로젝트 환경 내에 설치할 필요 없이 많은 커뮤니티 MCP 서버를 실행할 수 있습니다.

__init__.py 파일을 생성하면 agent.py 파일이 Agent Development Kit(ADK)에서 검색 가능한 Python 패키지의 일부로 인식되도록 하는 데 필요합니다. 이 파일은 agent.py와 동일한 디렉터리에 있어야 합니다.

# ./adk_agent_samples/mcp_agent/__init__.py
from . import agent

물론, 사용 가능한 다른 지원되는 명령도 있습니다. 예를 들어 python3에 연결하는 것은 다음과 같이 달성할 수 있습니다.

connection_params = StdioConnectionParams(
 server_params={
     "command": "python3",
     "args": ["./agent/mcp_server.py"],
     "env": {
       "SERVICE_ACCOUNT_PATH":SERVICE_ACCOUNT_PATH,
       "DRIVE_FOLDER_ID": DRIVE_FOLDER_ID
     }
 }
)

UVX는 Python의 컨텍스트에서 uv를 사용하여 임시의 격리된 Python 환경에서 명령을 실행하는 명령줄 도구를 나타냅니다. 본질적으로 이는 전역적으로 또는 프로젝트 환경 내에 설치할 필요 없이 Python 도구 및 패키지를 실행할 수 있도록 합니다. MCP 서버를 통해 실행할 수 있습니다.

connection_params = StdioConnectionParams(
 server_params={
   "command": "uvx",
   "args": ["mcp-google-sheets@latest"],
   "env": {
     "SERVICE_ACCOUNT_PATH":SERVICE_ACCOUNT_PATH,
     "DRIVE_FOLDER_ID": DRIVE_FOLDER_ID
   }
 }
)

MCP 서버가 생성되면 다음 단계는 ADK Web을 사용하여 서버에 연결하는 것입니다.

ADK 웹으로 MCP 서버 연결

시작하려면 ‘adk web’을 실행합니다. 터미널에서 mcp_agent의 상위 디렉터리(예: adk_agent_samples)로 이동하여 다음을 실행합니다.

cd ./adk_agent_samples # 또는 해당 상위 디렉터리
adk web

ADK 웹 UI가 브라우저에 로드되면 에이전트 메뉴에서 filesystem\_assistant\_agent를 선택합니다. 다음으로 다음과 같은 프롬프트로 실험해 보세요.

  • “이 폴더의 내용을 보여주세요.”
  • sample.txt 파일을 읽어보세요.” (이는 sample.txt가 TARGET_FOLDER_PATH에 위치한다고 가정합니다.)
  • another\_file.md에 무엇이 있나요?”

FastMCP를 사용한 MCP 서버 생성

FastMCP는 MCP 서버 개발을 간소화하기 위해 설계된 고급 Python 프레임워크입니다. 프로토콜 복잡성을 단순화하는 추상화 계층을 제공하여 개발자가 핵심 논리에 집중할 수 있도록 합니다.

이 라이브러리를 사용하면 간단한 Python 데코레이터를 사용하여 도구, 리소스 및 프롬프트를 신속하게 정의할 수 있습니다. 상당한 이점은 Python 함수 서명, 유형 힌트 및 설명 문자열을 지능적으로 해석하여 필요한 AI 모델 인터페이스 사양을 구성하는 자동 스키마 생성입니다. 이 자동화는 수동 구성을 최소화하고 인간 오류를 줄입니다.

기본 도구 생성 외에도 FastMCP는 서버 구성 및 프록시와 같은 고급 아키텍처 패턴을 촉진합니다. 이는 복잡한 다중 구성 요소 시스템의 모듈식 개발과 기존 서비스를 AI에서 액세스할 수 있는 프레임워크로의 원활한 통합을 가능하게 합니다. 또한 FastMCP에는 효율적이고 분산되며 확장 가능한 AI 기반 응용 프로그램을 위한 최적화 기능이 포함되어 있습니다.

서버 설정(FastMCP 사용)

예를 들어 서버에서 제공하는 간단한 “인사” 도구를 고려해 보겠습니다. ADK 에이전트 및 기타 MCP 클라이언트는 활성화된 후 HTTP를 사용하여 이 도구와 상호 작용할 수 있습니다.

# fastmcp_server.py
# 이 스크립트는 FastMCP를 사용하여 간단한 MCP 서버를 만드는
방법을 시연합니다.
# 이 서버는 인사를 생성하는 단일 도구를 노출합니다.
# 1. FastMCP 설치 확인:
# pip install fastmcp
from fastmcp import FastMCP, Client
# FastMCP 서버 초기화.
mcp_server = FastMCP()
# 간단한 도구 함수 정의.
# `@mcp_server.tool` 데코레이터는 이 Python 함수를 MCP 도구로
등록합니다.
# 설명 문자열은 LLM을 위한 도구 설명이 됩니다.
@mcp_server.tool
def greet(name: str) -> str:
    """
    개인화된 인사를 생성합니다.
    Args:
name: 인사할 사람의 이름입니다.
    Returns:
        인사 문자열입니다.
    """
    return f"Hello, {name}! Nice to meet you."
# 또는 스크립트에서 실행하려는 경우:
if __name__ == "__main__":
    mcp_server.run(
        transport="http",
        host="127.0.0.1",
        port=8000
    )

이 Python 스크립트는 이름이 지정된 사람을 인사를 하는 단일 함수인 greet를 정의합니다. @tool() 데코레이터는 이 함수 위에 있어 이 함수를 AI 또는 다른 프로그램이 사용할 수 있는 도구로 자동 등록합니다. 함수의 설명 문자열과 유형 힌트는 FastMCP에서 에이전트에게 도구가 작동하는 방식, 필요한 입력 및 반환할 내용을 알려주는 데 사용됩니다.

스크립트가 실행되면 localhost:8000에서 요청을 수신하는 FastMCP 서버가 시작됩니다. 이렇게 하면 greet 함수가 네트워크 서비스로 사용 가능해집니다. 그런 다음 에이전트를 이 서버에 연결하고 greet 도구를 사용하여 큰 작업의 일부로 인사를 생성하도록 구성할 수 있습니다. 서버는 수동으로 중지될 때까지 계속 실행됩니다.

FastMCP 서버를 ADK 에이전트로 사용하기

ADK 에이전트는 실행 중인 FastMCP 서버를 사용하기 위해 MCP 클라이언트로 설정될 수 있습니다. 이를 위해서는 FastMCP 서버의 네트워크 주소(일반적으로 http://localhost:8000)로 HttpSeverParameters를 구성해야 합니다.

tool_filter 매개변수를 포함하여 에이전트의 도구 사용을 서버에서 제공하는 특정 도구(‘greet’ 등)로 제한할 수 있습니다. ‘Greet John Doe’와 같은 요청으로 프롬프트가 주어지면 에이전트에 내장된 LLM은 MCP를 통해 사용 가능한 ‘greet’ 도구를 식별하고 인수인 ‘John Doe’로 이를 호출하고 서버 응답을 반환합니다. 이 프로세스는 MCP를 통해 노출되는 사용자 정의 도구와 ADK 에이전트와의 통합을 보여줍니다.

이 구성을 설정하려면 에이전트 파일(예: ./adk_agent_samples/fastmcp_client_agent/에 위치한 agent.py)이 필요합니다. 이 파일은 ADK 에이전트를 인스턴스화하고 HttpServerParameters를 사용하여 작동 중인 FastMCP 서버에 대한 연결을 설정합니다.

# ./adk_agent_samples/fastmcp_client_agent/agent.py
import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset,
HttpServerParameters
# FastMCP 서버 주소 정의.
# 이전 예시에서 정의한 fastmcp_server.py가 이 포트에서 실행 중인지
확인합니다.
FASTMCP_SERVER_URL = "http://localhost:8000"
root_agent = LlmAgent(
   model='gemini-2.0-flash', # 또는 선호하는 모델
   name='fastmcp_greeter_agent',
   instruction='당신은 이름을 가진 사람들에게 인사할 수 있는
친근한 비서입니다. "greet" 도구를 사용하세요.',
   tools=[
       MCPToolset(
           connection_params=HttpServerParameters(
               url=FASTMCP_SERVER_URL,
           ),
           # 선택 사항: MCP 서버에서 노출되는 도구를 필터링합니다
           # 이 예시에서는 'greet'만 예상합니다.
           tool_filter=['greet']
       )
   ],
)

스크립트는 gemini 언어 모델을 사용하는 Fastmcp_greeter_agent라는 에이전트를 정의합니다. 이 에이전트에게는 사람들에게 인사하는 것이 목표인 친근한 비서 역할을 하라는 특정 지침이 제공됩니다. 결정적으로, 이 코드는 에이전트가 작업을 수행하기 위해 도구를 사용하여 장착합니다. 이전 예시에서 정의한 FastMCP 서버가 실행 중인 localhost:8000에 연결하도록 MCPToolset을 구성합니다. 에이전트는 해당 서버에서 호스팅되는 greet 도구에 액세스할 수 있도록 구체적으로 허가됩니다. 본질적으로 이 코드는 시스템의 클라이언트 측을 설정하여 사람들에게 인사하는 것이 목표이며 이를 달성하기 위해 정확히 어떤 외부 도구를 사용해야 하는지 아는 지능적인 에이전트를 생성합니다.

fastmcp_client_agent 디렉터리 내에 __init__.py 파일을 생성해야 합니다. 이는 에이전트가 ADK에서 검색 가능한 Python 패키지로 인식되도록 보장합니다.

시작하려면 새 터미널을 열고 python fastmcp\_server.py를 실행하여 FastMCP 서버를 시작합니다. 다음으로 fastmcp\_client\_agent의 상위 디렉터리(예: adk\_agent\_samples)로 이동하여 adk web을 실행합니다. 브라우저에서 ADK 웹 UI가 로드되면 에이전트 메뉴에서 fastmcp\_greeter\_agent를 선택합니다. 그런 다음 “Greet John Doe”와 같은 프롬프트를 입력하여 테스트할 수 있습니다. 에이전트는 FastMCP 서버에서 greet 도구를 사용하여 응답을 생성합니다.

한눈에 보기

무엇을: 효과적인 에이전트가 되기 위해 LLM은 단순한 텍스트 생성을 넘어 기능을 확장해야 합니다. 그들은 현재 데이터에 액세스하고 외부 소프트웨어를 활용하기 위해 외부 환경과 상호 작용할 수 있는 능력이 필요합니다. 표준화된 통신 방법이 없으면 LLM과 외부 도구 또는 데이터 소스 간의 각 통합은 사용자 지정되고 복잡하며 재사용할 수 없는 노력이 됩니다. 이 임시 방식은 확장성을 저해하고 복잡하고 상호 연결된 AI 시스템 구축을 어렵고 비효율적으로 만듭니다.

왜: 모델 컨텍스트 프로토콜(MCP)은 LLM과 외부 시스템 간의 표준화된 인터페이스 역할을 하여 표준화된 솔루션을 제공합니다. 이는 LLM이 외부 기능이 검색되고 사용되는 방식을 정의하는 개방형 표준화 프로토콜을 수립합니다. 클라이언트-서버 모델에서 작동하면서 MCP는 서버가 모든 규격 준수 클라이언트에게 도구, 데이터 리소스 및 대화형 프롬프트를 노출하도록 허용합니다. LLM 기반 응용 프로그램은 이러한 클라이언트로 작동하여 사용 가능한 리소스를 동적으로 검색하고 예측 가능한 방식으로 상호 작용합니다. 이러한 표준화된 접근 방식은 상호 운용 가능하고 재사용 가능한 구성 요소의 생태계를 조성하여 복잡한 에이전트 워크플로 개발을 극적으로 단순화합니다.

경험 법칙: 다양한 외부 도구, 데이터 소스 및 API와 상호 작용해야 하는 복잡하고 확장 가능한 또는 엔터프라이즈급 에이전트 시스템을 구축할 때 MCP(모델 컨텍스트 프로토콜)를 사용하십시오. 이는 서로 다른 LLM 및 도구 간의 상호 운용성이 우선 순위이고 에이전트가 재배포 없이 새로운 기능에 동적으로 액세스해야 할 때 이상적입니다. 고정되고 제한된 수의 사전 정의된 함수가 있는 간단한 응용 프로그램의 경우 직접 도구 함수 호출로 충분할 수 있습니다.

시각적 요약

그림 1: 모델 컨텍스트 프로토콜

주요 시사점

다음은 주요 시사점입니다.

  • 모델 컨텍스트 프로토콜(MCP)은 LLM과 외부 응용 프로그램, 데이터 소스 및 도구 간의 표준화된 통신을 용이하게 하는 개방형 표준입니다.
  • 이는 리소스, 프롬프트 및 도구를 노출하고 소비하는 방법을 정의하는 클라이언트-서버 아키텍처를 사용합니다.
  • ADK는 기존 MCP 서버를 활용하는 것과 ADK 도구를 MCP 서버를 통해 노출하는 것을 모두 지원합니다.
  • FastMCP는 특히 Python으로 구현된 도구를 노출하기 위해 MCP 서버의 개발 및 관리를 단순화합니다.
  • MCP Tools for Genmedia Services는 에이전트가 Google Cloud의 생성 미디어 기능(Imagen, Veo, Chirp 3 HD, Lyria)과 통합할 수 있도록 합니다.
  • MCP는 LLM 및 에이전트가 실세계 시스템과 상호 작용하고, 동적 정보에 액세스하고, 텍스트 생성을 넘어 작업을 수행할 수 있도록 합니다.

결론

모델 컨텍스트 프로토콜(MCP)은 대규모 언어 모델(LLM)과 외부 시스템 간의 통신을 용이하게 하는 개방형 표준입니다. 이는 클라이언트-서버 아키텍처를 사용하여 LLM이 표준화된 도구를 통해 리소스에 액세스하고, 프롬프트를 활용하고, 작업을 실행할 수 있도록 합니다. MCP를 통해 LLM은 데이터베이스와 상호 작용하고, 생성 미디어 워크플로를 관리하고, IoT 장치를 제어하고, 금융 서비스를 자동화할 수 있습니다. 실용적인 예시는 파일 시스템 서버 및 FastMCP로 구축된 서버와 통신하도록 에이전트를 구성하는 방법을 보여주며, ADK와의 통합을 시연합니다. MCP는 기본 언어 능력을 넘어서 상호 작용하는 대화형 AI 에이전트를 개발하는 데 핵심 구성 요소입니다.

참고 자료


제 11장: 목표 설정 및 모니터링

AI 에이전트가 진정으로 효과적이고 목적을 갖기 위해서는 단순히 정보를 처리하거나 도구를 사용하는 능력 이상의 것이 필요합니다. 그들은 실제로 성공하고 있는지 추적할 수 있는 명확한 방향 감각과 수단을 가져야 합니다. 이것이 목표 설정 및 모니터링 패턴이 들어오는 지점입니다. 에이전트에게 작업할 특정 목표를 부여하고, 이러한 목표를 달성하기 위해 진행 상황을 추적하고 목표가 달성되었는지 판단할 수 있는 수단을 갖추는 것입니다.

목표 설정 및 모니터링 패턴 개요

여행 계획을 세우는 것을 상상해 보십시오. 그냥 충동적으로 목적지에 나타나지 않습니다. 당신은 어디로 가고 싶은지(목표 상태), 어디에서 출발하는지(초기 상태), 사용 가능한 옵션(교통, 경로, 예산)을 고려하고 티켓 예약, 가방 챙기기, 공항/역으로 이동, 탑승, 도착, 숙소 찾기 등 일련의 단계를 매핑합니다. 종종 의존성과 제약 조건을 고려하는 이 단계별 프로세스는 에이전트 시스템에서 계획이라고 하는 것을 근본적으로 의미합니다.

AI 에이전트의 맥락에서 계획은 일반적으로 고수준 목표를 취하고 자율적으로 또는 반자율적으로 일련의 중간 단계 또는 하위 목표를 생성하는 에이전트가 포함됩니다. 이러한 단계는 순차적으로 또는 도구 사용, 라우팅 또는 다중 에이전트 협업과 같은 다른 패턴을 포함할 수 있는 보다 복잡한 흐름으로 실행될 수 있습니다. 계획 메커니즘은 정교한 검색 알고리즘, 논리적 추론 또는 작업에 대한 교육 데이터 및 이해를 기반으로 그럴듯하고 효과적인 계획을 생성하기 위해 대규모 언어 모델(LLM)의 기능을 활용할 수 있습니다.

좋은 계획 기능은 에이전트가 간단한 단일 단계 쿼리가 아닌 문제를 다룰 수 있도록 합니다. 이는 다면적인 요청을 처리하고, 변경되는 상황에 적응하기 위해 재계획하고, 복잡한 워크플로를 오케스트레이션할 수 있도록 합니다. 이는 많은 고급 에이전트 동작의 기반을 형성하는 기초 패턴이며, 간단한 반응형 시스템을 정의된 목표를 향해 적극적으로 작업할 수 있는 시스템으로 전환합니다.

실제 응용 프로그램 및 사용 사례

목표 설정 및 모니터링 패턴은 복잡하고 실제적인 시나리오에서 자율적이고 안정적으로 작동해야 하는 에이전트를 구축하는 데 필수적입니다. 다음은 몇 가지 실제 응용 분야입니다.

  • 고객 지원 자동화: 에이전트의 목표는 “고객의 청구 문의 해결”일 수 있습니다. 대화를 모니터링하고, 데이터베이스 항목을 확인하고, 도구를 사용하여 청구를 조정합니다. 성공은 청구 변경 사항을 확인하고 긍정적인 고객 피드백을 받는 것으로 모니터링됩니다. 문제가 해결되지 않으면 에스컬레이션됩니다.
  • 개인화된 학습 시스템: 학습 에이전트는 “학생들의 대수학 이해도 향상”이라는 목표를 가질 수 있습니다. 학생의 연습 진행 상황을 모니터링하고, 교육 자료를 조정하고, 정확도 및 완료 시간과 같은 성능 지표를 추적하여 학생이 어려움을 겪으면 접근 방식을 조정합니다.
  • 프로젝트 관리 도우미: 에이전트는 “Y 날짜까지 프로젝트 마일스톤 X가 완료되도록 보장”하도록 맡겨질 수 있습니다. 작업 상태, 팀 통신 및 리소스 가용성을 모니터링하고, 지연을 표시하고, 목표가 위험에 처한 경우 수정 조치를 제안합니다.
  • 자동화된 거래 봇: 거래 에이전트의 목표는 “위험 허용 범위 내에서 포트폴리오 이익 극대화”일 수 있습니다. 시장 데이터, 현재 포트폴리오 가치 및 위험 지표를 지속적으로 모니터링하여 조건이 목표와 일치할 때 거래를 실행하고 위험 임계값이 초과되면 전략을 조정합니다.
  • 로봇 공학 및 자율 주행차: 자율 주행차의 주요 목표는 “A에서 B까지 승객을 안전하게 수송하는 것”입니다. 주변 환경(다른 차량, 보행자, 신호등), 자체 상태(속도, 연료) 및 계획된 경로의 진행 상황을 지속적으로 모니터링하여 목표를 안전하고 효율적으로 달성하기 위해 운전 동작을 조정합니다.
  • 콘텐츠 중재: 에이전트의 목표는 “플랫폼 X에서 유해한 콘텐츠 식별 및 제거”일 수 있습니다. 수신 콘텐츠를 모니터링하고, 분류 모델을 적용하며, 모호한 사례를 인간 검토자에게 에스컬레이션하여 필터링 기준을 조정하고, 오탐/음성률을 추적합니다.

이 패턴은 동적 조건에 적응하고, 신뢰할 수 있는 결과를 달성하고, 지능적인 자체 관리를 위한 필요한 프레임워크를 제공하기 위해 자율적이고 안정적으로 작동해야 하는 에이전트에게 근본적입니다.

실습 코드 예시

목표 설정 및 모니터링 패턴을 설명하기 위해 LangChain 및 OpenAI API를 사용하는 예시가 있습니다. 이 Python 스크립트는 지정된 코딩 문제에 대한 솔루션을 생성하고 개선하기 위해 설계된 자율 AI 에이전트를 개괄적으로 설명합니다. 핵심 기능은 목표 품질 벤치마크 준수를 보장하면서 단일 코드를 한 번만 생성하는 것이 아니라 생성, 자체 평가 및 개선의 반복 주기로 들어가는 것입니다.

에이전트의 성공은 생성된 코드가 초기 목표를 성공적으로 충족하는지 여부에 대한 자체 AI 기반 판단으로 측정됩니다. 최종 출력은 이 개선 프로세스의 정점인 주석이 달린 사용 준비가 된 Python 파일입니다.

종속성:

pip install langchain_openai openai python-dotenv
OPENAI_API_KEY가 포함된 .env 파일

이 스크립트를 이해하는 가장 좋은 방법은 지정된 코딩 문제를 해결하기 위해 할당된 자율 AI 프로그래머로 상상하는 것입니다(그림 1 참조). 프로세스는 상세한 프로젝트 브리핑을 AI에 제공하는 것으로 시작되며, 이는 해결해야 하는 특정 코딩 문제입니다.

# MIT 라이선스
# Copyright (c) 2025 Mahtab Syed
# https://www.linkedin.com/in/mahtabsyed/
"""
실습 코드 예시 - 반복 2
- 목표 설정 및 모니터링 패턴을 설명하기 위해 LangChain 및 OpenAI
API를 사용하는 예시가 있습니다.
목표: 지정된 목표에 따라 사용 사례에 대한 코드를 작성할 수
있는 AI 에이전트 구축:
- 코딩 문제(사용 사례)를 입력으로 허용하거나 입력으로 제공할 수
있습니다.
- 목표 목록(예: "단순함", "테스트됨", "엣지 케이스 처리")을
코드로 허용하거나 입력으로 제공할 수 있습니다.
- LLM(예: GPT-4o)을 사용하여 목표가 충족될 때까지 Python 코드를
생성하고 개선합니다. (최대 5회 반복 사용, 이는 설정된 목표를
기반으로 할 수 있음)
- 목표 충족 여부를 확인하기 위해 LLM에 이를 판단하고 코드만
True 또는 False로 응답하도록 요청하여 반복 중지를 더 쉽게
합니다.
- 최종 코드를 깨끗한 파일 이름과 헤더 주석이 있는 .py 파일에
저장합니다.
"""
import os
import random
import re
from pathlib import Path
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv, find_dotenv
# 환경 변수 로드
_ = load_dotenv(find_dotenv())
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
if not OPENAI_API_KEY:
   raise EnvironmentError("❌ OPENAI_API_KEY 환경 변수를 설정해
주세요.")
# ✅ OpenAI LLM 초기화
print(" OpenAI LLM(gpt-4o) 초기화 중...")
llm = ChatOpenAI(
   model="gpt-4o", # gpt-4o에 액세스할 수 없는 경우 다른
OpenAI LLM 사용
   temperature=0.3,
   openai_api_key=OPENAI_API_KEY,
)
# --- 유틸리티 함수 ---
def generate_prompt(
   use_case: str, goals: list[str], previous_code: str = "",
feedback: str = ""
) -> str:
   print(" 코드 생성을 위한 프롬프트 구성 중...")
   base_prompt = f"""
당신은 정보 검색 에이전트입니다. 귀하의 목표는 다음 사용 사례를
기반으로 Python 코드를 작성하는 것입니다.
사용 사례: {use_case}
귀하의 목표는 다음과 같습니다:
{chr(10).join(f"- {g.strip()}" for g in goals)}
"""
   if previous_code:
       print(" 개선을 위해 이전 코드를 프롬프트에 추가 중.")
       base_prompt += f"\n이전에 생성된 코드:\n{previous_code}"
   if feedback:
       print(" 수정을 위해 피드백 포함 중.")
       base_prompt += f"\n이전 버전에 대한 피드백:\n{feedback}\n"
   base_prompt += "\n코드만 반환하십시오. 코드 외부의 주석이나
설명은 포함하지 마십시오."
   return base_prompt
def get_code_feedback(code: str, goals: list[str]) -> str:
   print(" 목표에 대해 코드 검토 중...")
   feedback_prompt = f"""
당신은 Python 코드 검토자입니다. 아래에 코드 조각이 표시됩니다.
다음 목표를 기반으로 이 코드를 비판하고 목표가 충족되었는지
여부를 확인하십시오.
{chr(10).join(f"- {g.strip()}" for g in goals)}
이 코드를 비판하고 목표가 충족되었는지 여부를 확인하십시오.
명확성, 단순성, 정확성, 엣지 케이스 처리 또는 테스트 범위에
대한 개선이 필요한지 언급하십시오.
코드:
{code}
"""
   return llm.invoke(feedback_prompt)
def goals_met(feedback_text: str, goals: list[str]) -> bool:
   """
   피드백 텍스트를 기반으로 목표가 충족되었는지 여부를 평가하기
위해 LLM을 사용합니다.
   LLM 출력에서 구문 분석된 True 또는 False를 반환합니다.
   """
   review_prompt = f"""
당신은 AI 검토자입니다.
다음은 목표입니다:
{chr(10).join(f"- {g.strip()}" for g in goals)}
여기에 코드에 대한 피드백이 있습니다:
\"\"\"
{feedback_text}
\"\"\"
위의 피드백을 기반으로 목표가 충족되었습니까?
단 하나의 단어만 응답하십시오: True 또는 False.
"""
   response = llm.invoke(review_prompt).content.strip().lower()
   return response == "true"
def clean_code_block(code: str) -> str:
   lines = code.strip().splitlines()
   if lines and lines[0].strip().startswith("```"):
       lines = lines[1:]
   if lines and lines[-1].strip() == "```":
       lines = lines[:-1]
   return "\n".join(lines).strip()
def add_comment_header(code: str, use_case: str) -> str:
   comment = f"# 이 Python 프로그램은 다음 사용 사례를
구현합니다:\n# {use_case.strip()}\n"
   return comment + "\n" + code
def to_snake_case(text: str) -> str:
   text = re.sub(r"[^a-zA-Z0-9 ]", "", text)
   return re.sub(r"\s+", "_", text.strip().lower())
def save_code_to_file(code: str, use_case: str) -> str:
   print(" 최종 코드를 파일에 저장 중...")
   summary_prompt = (
       f"다음 사용 사례를 10자 이내의 단일 소문자 단어 또는
구문으로 요약하여 Python 파일 이름에 적합하도록 만드세요:\n\n{use_case}"
   )
   raw_summary = llm.invoke(summary_prompt).content.strip()
   short_name = re.sub(r"[^a-zA-Z0-9_]", "", raw_summary.replace("
", "_").lower())[:10]
   random_suffix = str(random.randint(1000, 9999))
   filename = f"{short_name}_{random_suffix}.py"
   filepath = Path.cwd() / filename
   with open(filepath, "w") as f:
       f.write(code)
   print(f"✅ 코드가 저장된 위치: {filepath}")
   return str(filepath)
# --- 주요 에이전트 함수 ---
def run_code_agent(use_case: str, goals_input: str, max_iterations:
int = 5) -> str:
   goals = [g.strip() for g in goals_input.split(",")]
   print(f"\n 사용 사례: {use_case}")
   print(" 목표:")
   for g in goals:
       print(f" - {g}")
   previous_code = ""
   feedback = ""
   for i in range(max_iterations):
       print(f"\n=== 반복 {i + 1} / {max_iterations} ===")
       prompt = generate_prompt(use_case, goals, previous_code,
feedback if isinstance(feedback, str) else feedback.content)
       print(" 코드 생성 중...")
       code_response = llm.invoke(prompt)
       raw_code = code_response.content.strip()
       code = clean_code_block(raw_code)
       print("\n 생성된 코드:\n" + "-" * 50 + f"\n{code}\n" +
"-" * 50)
       print("\n 피드백 검토를 위해 코드 제출 중...")
       feedback = get_code_feedback(code, goals)
       feedback_text = feedback.content.strip()
       print("\n 수신된 피드백:\n" + "-" * 50 +
f"\n{feedback_text}\n" + "-" * 50)
       if goals_met(feedback_text, goals):
           print("✅ LLM이 목표가 충족되었음을 확인했습니다. 반복
중지.")
           break
       print(" 목표가 완전히 충족되지 않았습니다. 다음 반복을
준비 중...")
       previous_code = code
   final_code = add_comment_header(code, use_case)
   return save_code_to_file(final_code, use_case)
# --- CLI 테스트 실행 ---
if __name__ == "__main__":
   print("\n AI 코드 생성 에이전트에 오신 것을 환영합니다")
   # 예시 1
   use_case_input = "주어진 양의 정수에 대한 BinaryGap을 찾는
코드를 작성하세요"
   goals_input = "이해하기 쉬운 코드, 기능적으로 정확함, 포괄적인
엣지 케이스 처리, 양의 정수 입력만 허용, 몇 가지 예제와 함께
결과 출력"
   run_code_agent(use_case_input, goals_input)
   # 예시 2
   # use_case_input = "현재 디렉터리와 그 안에 중첩된 모든
디렉터리의 파일을 세고 총 개수를 출력하는 코드를 작성하세요"
   # goals_input = (
   # "이해하기 쉬운 코드, 기능적으로 정확함, 포괄적인 엣지 케이스
처리, 성능 권장 사항 무시, unittest 또는 pytest와 같은 테스트
스위트 사용 권장 사항 무시"
   # )
   # run_code_agent(use_case_input, goals_input)
   # 예시 3
   # use_case_input = "단어 문서 또는 docx 파일을 명령줄 입력으로
받아 열고 단어 및 문자를 세어 모두 출력하는 코드를 작성하세요"
   # goals_input = "이해하기 쉬운 코드, 기능적으로 정확함, 엣지
케이스 처리"
   # run_code_agent(use_case_input, goals_input)

이 간략한 설명과 함께, 최종 코드가 충족해야 하는 엄격한 품질 체크리스트를 제공하는데, 이는 최종 코드가 충족해야 하는 목표를 나타냅니다.

AI 프로그래머에게 이 할당량이 주어지면 작업을 시작하고 첫 번째 코드 초안을 생성합니다. 그러나 이 초기 버전을 즉시 제출하는 대신, 중요한 단계를 수행하기 위해 잠시 멈춥니다. 바로 엄격한 자체 검토입니다. 자신의 창작물을 제공된 품질 체크리스트의 모든 항목과 세심하게 비교하여 자체 품질 보증 검사관 역할을 합니다. 이 검사 후, 자체 진행 상황에 대해 간단하고 편견 없는 평결을 내립니다. 모든 표준을 충족하면 “참”이고, 부족하면 “거짓”입니다.

평결이 “거짓”인 경우 AI는 포기하지 않습니다. 자체 비판에서 얻은 통찰력을 사용하여 약점을 파악하고 코드를 지능적으로 다시 작성하는 사려 깊은 수정 단계에 들어갑니다. 이 초안 작성, 자체 검토 및 개선의 순환은 각 반복에서 AI가 목표에 더 가까워지도록 목표로 하며, AI가 “참” 상태에 도달하거나 미리 정의된 시도 횟수에 도달할 때까지 반복됩니다. 마감 시한을 두고 작업하는 개발자와 유사합니다. 이 최종 검사를 통과하면 스크립트는 세련된 솔루션을 패키징하고, 유용한 주석을 추가하고, 깨끗하고 새로운 Python 파일에 저장하여 사용할 준비가 되도록 합니다.

주의 사항 및 고려 사항: 이것이 예시이며 실제 프로덕션 코드는 아님을 유의하는 것이 중요합니다. 실제 응용 프로그램의 경우 몇 가지 요소를 고려해야 합니다. LLM은 목표의 의도된 의미를 완전히 이해하지 못할 수 있으며 자신의 성능을 성공적으로 평가하지 못할 수 있습니다. 목표가 잘 이해되더라도 모델은 환각을 일으킬 수 있습니다. 코드를 작성하고 품질을 심사하는 동일한 LLM을 사용하는 경우 잘못된 방향으로 가고 있음을 발견하기가 더 어려울 수 있습니다.

궁극적으로 LLM은 마법처럼 완벽한 코드를 생성하지 않습니다. 생성된 코드를 실행하고 테스트해야 합니다. 또한, 이 예시의 “모니터링”은 기본적이며 프로세스가 영원히 실행될 수 있는 잠재적인 위험을 만듭니다.

전문 코드 검토자로 행동하여 깨끗하고, 정확하며, 간단한 코드를 생성하는 데 깊이 헌신하십시오. 당신의 핵심 임무는 모든 제안이 현실과 모범 사례에 근거하도록 보장하여 코드 “환각”을 제거하는 것입니다.

코드 조각을 제공하면 다음을 수행해야 합니다.

  • — 오류 식별 및 수정: 논리적 결함, 버그 또는 잠재적인 런타임 오류를 지적합니다.
  • — 단순화 및 리팩터링: 정확성을 희생하지 않고 코드를 더 읽기 쉽고, 효율적이며, 유지 관리가 용이하도록 변경 사항을 제안합니다.
  • — 명확한 설명 제공: 제안된 모든 변경 사항에 대해 변경 사항이 정리 코드, 성능 또는 보안 원칙을 참조하여 왜 개선된 것인지 설명합니다.
  • — 수정된 코드 제공: 제안된 변경 사항의 “이전” 및 “이후”를 보여주어 개선 사항을 명확하게 합니다.

귀하의 피드백은 직접적이고 건설적이어야 하며 항상 코드 품질을 향상시키는 것을 목표로 해야 합니다.

더 강력한 접근 방식은 특정 역할을 에이전트 승무원에게 할당하여 이러한 우려 사항을 분리하는 것입니다. 예를 들어, 저는 Gemini를 사용하여 AI 에이전트의 개인 승무원을 구축했는데, 각 에이전트는 특정 역할을 가지고 있습니다.

  • 동료 프로그래머: 코드 작성 및 브레인스토밍 지원.
  • 코드 검토자: 오류를 포착하고 개선 사항 제안.
  • 문서 작성자: 명확하고 간결한 문서 생성.
  • 테스트 작성자: 포괄적인 단위 테스트 생성.
  • 프롬프트 개선 도구: AI와의 상호 작용 최적화.

이 다중 에이전트 시스템에서 프로그래머 에이전트와 별개의 엔티티로 작동하는 코드 검토자는 예시의 심사위원과 유사한 프롬프트를 가지고 있으며, 이는 객관적인 평가를 크게 향상시킵니다. 이 구조는 테스트 작성자 에이전트가 프로그래머 에이전트가 생성한 코드에 대한 단위 테스트를 작성해야 하는 필요성을 충족시키기 때문에 더 나은 관행으로 자연스럽게 이어집니다.

저는 관심 있는 독자에게 이러한 보다 정교한 제어를 추가하고 코드를 프로덕션 준비가 된 상태에 가깝게 만드는 과제를 맡깁니다.

AlphaEvolve 및 OpenEvolve

AlphaEvolve는 알고리즘을 발견하고 최적화하기 위해 설계된 Google의 AI 에이전트입니다. LLM(특히 Gemini 모델(Flash 및 Pro)), 자동화된 평가 시스템 및 진화 알고리즘 프레임워크의 조합을 활용합니다. 이 시스템은 이론 수학 및 실용적인 컴퓨팅 응용 프로그램 모두 발전을 목표로 합니다.

AlphaEvolve는 Gemini 모델 앙상블을 사용합니다. Flash는 광범위한 초기 알고리즘 제안을 생성하는 데 사용되며, Pro는 보다 심층적인 분석 및 개선을 제공합니다. 제안된 알고리즘은 미리 정의된 기준에 따라 자동으로 평가 및 채점됩니다. 이 평가는 반복적으로 솔루션을 개선하는 데 사용되는 피드백을 제공하여 최적화되고 새로운 알고리즘으로 이어집니다.

실용적인 컴퓨팅에서 AlphaEvolve는 Google 인프라 내에서 배포되었습니다. 데이터 센터 스케줄링에서 개선을 입증하여 전역 컴퓨팅 리소스 사용량을 0.7% 감소시켰습니다. 또한 향후 TPU의 Verilog 코드 최적화 제안을 통해 하드웨어 설계에 기여했습니다. 또한 AlphaEvolve는 Gemini 아키텍처의 핵심 커널에서 23% 속도 향상 및 FlashAttention의 저수준 GPU 명령에 대한 최대 32.5% 최적화를 포함하여 AI 성능을 가속화했습니다.

기초 연구 분야에서 AlphaEvolve는 행렬 곱셈을 위한 새로운 알고리즘을 발견하는 데 기여했으며, 여기에는 이전에 알려진 솔루션을 능가하는 48개의 스칼라 곱셈을 사용하는 4x4 복소수 행렬에 대한 방법이 포함됩니다. 광범위한 수학 연구에서 키싱 넘버 문제의 발전과 같은 예시를 포함하여 75%의 경우 50개 이상의 열린 문제에 대한 기존의 최첨단 솔루션을 재발견하고 20%의 경우 기존 솔루션을 개선했습니다.

OpenEvolve는 LLM(그림 3 참조)을 활용하여 코드를 반복적으로 최적화하는 진화 코딩 에이전트입니다. 이는 LLM 기반 코드 생성, 평가 및 선택 파이프라인을 오케스트레이션하여 광범위한 작업에 대해 프로그램을 지속적으로 개선합니다. OpenEvolve의 주요 측면은 단일 함수에만 국한되지 않고 전체 코드 파일을 진화시킬 수 있다는 것입니다. 이 에이전트는 다중 프로그래밍 언어 지원 및 모든 LLM에 대한 OpenAI 호환 API 호환성을 제공하여 다재다능하도록 설계되었습니다. 또한 다중 목적 최적화를 통합하고 유연한 프롬프트 엔지니어링을 허용하며 복잡한 코딩 과제를 효율적으로 처리하기 위해 분산 평가가 가능합니다.

그림 3: OpenEvolve 내부 아키텍처는 컨트롤러에 의해 관리됩니다. 이 컨트롤러는 프로그램 샘플러, 프로그램 데이터베이스, 평가자 풀 및 LLM 앙상블과 같은 여러 핵심 구성 요소를 오케스트레이션합니다. 주요 기능은 학습 및 적응 프로세스를 촉진하여 코드 품질을 향상시키는 것입니다.

이 코드 조각은 진화 최적화를 수행하기 위해 OpenEvolve 라이브러리를 사용합니다. 초기 프로그램, 평가 파일 및 구성 파일에 대한 경로로 OpenEvolve 시스템을 초기화합니다. evolve.run(iterations=1000) 줄은 1000회 반복 실행하여 프로그램의 개선된 버전을 찾기 위해 진화 프로세스를 시작합니다. 마지막으로 진화 중에 발견된 최고의 프로그램의 메트릭을 소수점 이하 네 자리까지 형식화하여 출력합니다.

from openevolve import OpenEvolve

# 시스템 초기화
evolve = OpenEvolve(
   initial_program_path="path/to/initial_program.py",
   evaluation_file="path/to/evaluator.py",
   config_path="path/to/config.yaml"
)
# 진화 실행
best_program = await evolve.run(iterations=1000)
print(f"최고 프로그램 메트릭:")
for name, value in best_program.metrics.items():
   print(f" {name}: {value:.4f}")

한눈에 보기

무엇을: AI 에이전트는 종종 동적이고 예측 불가능한 환경에서 작동하며, 여기서는 미리 프로그래밍된 논리가 불충분할 수 있습니다. 에이전트의 성능은 초기 설계 시 예상하지 못한 새로운 상황에 직면했을 때 저하될 수 있습니다. 경험으로부터 학습할 수 있는 능력이 없으면 에이전트는 전략을 최적화하거나 상호 작용을 개인화할 수 없습니다. 이러한 경직성은 효율성을 제한하고 복잡하고 실제 시나리오에서 진정한 자율성을 달성하는 것을 방해합니다.

왜: 표준화된 솔루션은 학습 및 적응 메커니즘을 통합하여 정적 에이전트를 동적이고 진화하는 시스템으로 변환하는 것입니다. 이를 통해 에이전트는 새로운 데이터와 상호 작용을 기반으로 지식과 행동을 자율적으로 개선할 수 있습니다. 에이전트 시스템은 강화 학습에서 자가 수정과 같은 고급 기술에 이르기까지 다양한 방법을 사용하여 이러한 발전을 이룰 수 있습니다. Google의 AlphaEvolve와 같은 고급 시스템은 LLM과 진화 알고리즘을 활용하여 복잡한 문제에 대한 완전히 새롭고 더 효율적인 솔루션을 발견합니다. 지속적으로 학습함으로써 에이전트는 지속적인 수동 재프로그래밍 없이도 새로운 작업을 마스터하고, 성능을 향상시키며, 변화하는 조건에 적응할 수 있습니다.

경험 법칙: 동적이고 불확실하거나 개인적인 손길이 필요한 환경에서 작동해야 하는 AI 에이전트를 구축할 때 이 패턴을 사용하십시오. 개인화, 지속적인 성능 개선 및 새로운 상황에 자율적으로 대처하는 능력이 필요한 응용 분야에 필수적입니다.

시각적 요약

그림 4: 학습 및 적응 패턴

주요 시사점

주요 시사점은 다음과 같습니다.

  • 학습 및 적응은 에이전트가 경험을 사용하여 수행하는 작업에 더 능숙해지고 새로운 상황을 처리하는 것에 관한 것입니다.

  • “적응”은 학습으로 인해 발생하는 에이전트 행동 또는 지식의 가시적인 변화입니다.

  • SICA, 자가 개선 코딩 에이전트는 과거 성능을 기반으로 코드를 수정하여 자가 개선합니다. 이는 스마트 편집기 및 AST 기호 로케이터와 같은 도구로 이어졌습니다.

  • 특수 “하위 에이전트”와 “감독자”를 갖는 것은 이러한 자가 개선 시스템이 큰 작업을 관리하고 궤도를 유지하는 데 도움이 됩니다.

  • LLM의 “컨텍스트 창”이 설정되는 방식(시스템 프롬프트, 코어 프롬프트 및 도우미 메시지로)은 에이전트가 얼마나 효율적으로 작동하는지에 매우 중요합니다.

  • 이 패턴은 항상 변화하고, 불확실하거나, 개인적인 손길이 필요한 환경에서 작동해야 하는 에이전트에게 매우 중요합니다.

  • 학습하는 에이전트를 구축하는 것은 종종 머신 러닝 도구와 연결하고 데이터 흐름을 관리하는 것을 포함합니다.

  • 에이전트 시스템은 기본 코딩 도구를 갖추고 자율적으로 자체를 편집하여 벤치마크 작업에서 성능을 향상시킬 수 있습니다.

  • AlphaEvolve는 Google의 AI 에이전트로, LLM과 진화 프레임워크를 활용하여 자율적으로 알고리즘을 발견하고 최적화하여 기초 연구 및 실용적인 컴퓨팅 응용 프로그램 모두를 크게 향상시킵니다.

결론

이 장에서는 인공 지능에서 학습 및 적응의 중요한 역할에 대해 검토했습니다. AI 에이전트는 지속적인 데이터 획득 및 경험을 통해 성능을 향상시킵니다. 자가 개선 코딩 에이전트(SICA)는 코드 수정을 통해 기능을 자율적으로 개선함으로써 이를 보여주는 예입니다.

우리는 아키텍처, 응용 프로그램, 계획, 다중 에이전트 협업, 메모리 관리 및 학습 및 적응을 포함한 에이전트 AI의 기본 구성 요소를 검토했습니다. 학습 원칙은 특히 다중 에이전트 시스템에서 조정된 개선을 위해 중요합니다. 이를 달성하기 위해 튜닝 데이터는 각 참여 에이전트의 개별 입력 및 출력을 캡처하는 전체 상호 작용 궤적을 정확하게 반영해야 합니다.

이러한 요소들은 Google의 AlphaEvolve와 같은 중요한 발전에 기여합니다. 이 AI 시스템은 LLM, 자동화된 평가 및 진화적 접근 방식을 통해 알고리즘을 독립적으로 발견하고 개선하여 과학 연구 및 계산 기술의 발전을 주도합니다. 이러한 패턴은 정교한 AI 시스템을 구성하기 위해 결합될 수 있습니다. AlphaEvolve와 같은 개발은 AI 에이전트에 의한 자율적인 알고리즘 발견 및 최적화가 달성 가능하다는 것을 보여줍니다.

참고 자료


참고 자료


제 10장: 모델 컨텍스트 프로토콜

LLM이 에이전트로서 효과적으로 기능하려면 그 기능이 멀티모달 생성을 넘어 확장되어야 합니다. 현재 데이터 액세스, 외부 소프트웨어 활용 및 특정 운영 작업을 실행하는 것을 포함하여 외부 환경과의 상호 작용이 필요합니다. 모델 컨텍스트 프로토콜(MCP)은 LLM이 외부 리소스와 인터페이스하기 위한 표준화된 인터페이스를 제공하여 이러한 필요성을 다룹니다. 이 프로토콜은 일관되고 예측 가능한 통합을 촉진하기 위한 핵심 메커니즘 역할을 합니다.

MCP 패턴 개요

모든 LLM이 사용자 지정 통합 없이 모든 외부 시스템, 데이터베이스 또는 도구에 연결할 수 있는 범용 어댑터를 상상해 보십시오. 그것이 본질적으로 모델 컨텍스트 프로토콜(MCP)입니다. 이는 Gemini, OpenAI의 GPT 모델, Mixtral 및 Claude와 같은 모든 LLM이 외부 응용 프로그램, 데이터 소스 및 도구와 통신하는 방식을 표준화하기 위해 설계된 개방형 표준입니다. LLM이 컨텍스트를 얻고, 작업을 실행하고, 다양한 시스템과 상호 작용하는 방법을 단순화하는 범용 연결 메커니즘이라고 생각하십시오.

MCP는 클라이언트-서버 아키텍처에서 작동합니다. 이는 데이터(리소스라고 함), 대화형 템플릿(본질적으로 프롬프트임), 실행 가능한 함수(도구라고 함)가 MCP 서버에 의해 노출되는 방식과 이를 MCP 클라이언트(LLM 호스트 응용 프로그램 또는 AI 에이전트 자체일 수 있음)가 소비하는 방식을 정의합니다. 이러한 표준화된 접근 방식은 LLM을 다양한 운영 환경에 통합하는 복잡성을 극적으로 줄입니다.

그러나 MCP는 “에이전트 인터페이스”에 대한 계약이며, 그 효과는 노출하는 기본 API 설계에 크게 의존합니다. 개발자가 기존의 레거시 API를 수정 없이 단순히 래핑하는 위험이 있는데, 이는 에이전트에게 최적이 아닐 수 있습니다. 예를 들어, 티켓팅 시스템의 API가 전체 티켓 세부 정보를 한 번에 하나씩만 검색하도록 허용하는 경우, 우선순위 티켓 요약을 요청받은 에이전트는 대량에서 느리고 부정확할 것입니다. 진정으로 효과적이려면 비결정적 에이전트가 효율적으로 작동하도록 지원하기 위해 필터링 및 정렬과 같은 결정적 기능을 갖추도록 기본 API가 개선되어야 합니다. 이는 에이전트가 결정적 워크플로를 마법처럼 대체하지 않으며, 성공을 위해 더 강력한 결정적 지원이 종종 필요함을 강조합니다.

또한, MCP는 에이전트가 이해하기에 본질적으로 아직 이해하기 어려운 입력이나 출력을 가진 API를 래핑할 수도 있습니다. API는 반환하는 데이터 형식이 에이전트 친화적일 때만 유용하며, 이는 MCP 자체가 보장하지 않습니다. 예를 들어, 소비하는 에이전트가 PDF 콘텐츠를 구문 분석할 수 없는 경우 파일을 PDF로 반환하는 문서 저장소에 대한 MCP 서버를 만드는 것은 거의 유용하지 않습니다. 더 나은 접근 방식은 에이전트가 실제로 읽고 처리할 수 있는 Markdown과 같은 텍스트 버전을 반환하는 API를 먼저 만드는 것입니다. 이는 단순히 연결뿐만 아니라 진정한 호환성을 보장하기 위해 교환되는 데이터의 특성도 고려해야 함을 시연합니다.

MCP 대 도구 함수 호출

모델 컨텍스트 프로토콜(MCP)과 도구 함수 호출은 LLM이 외부 기능(도구 포함)과 상호 작용하고 작업을 실행할 수 있도록 하는 별개의 메커니즘입니다. 둘 다 텍스트 생성을 넘어 LLM의 기능을 확장하는 역할을 하지만, 접근 방식과 추상화 수준에서 차이가 있습니다.

도구 함수 호출은 LLM이 특정, 미리 정의된 도구 또는 함수에 직접 요청하는 것으로 생각할 수 있습니다. 이 맥락에서 “도구”와 “함수”라는 단어를 상호 교환적으로 사용한다는 점에 유의하십시오. 이 상호 작용은 1대1 통신 모델로 특징지어지며, 여기서 LLM은 외부 작업을 요구하는 사용자 의도에 대한 이해를 기반으로 요청을 형식화합니다. 그런 다음 응용 프로그램 코드가 이 요청을 실행하고 결과를 LLM에 반환합니다. 이 프로세스는 종종 독점적이며 다양한 LLM 공급업체에 따라 다릅니다.

반면, 모델 컨텍스트 프로토콜(MCP)은 LLM이 외부 기능(도구 및 시스템)을 검색, 통신 및 활용하기 위한 표준화된 인터페이스로 작동합니다. 이는 다양한 도구 및 시스템과 상호 작용을 용이하게 하고 모든 규격 준수 도구가 모든 규격 준수 LLM에 액세스할 수 있는 생태계를 구축하는 것을 목표로 하는 개방형 프로토콜 역할을 합니다. 이는 다양한 시스템 및 구현 전반에 걸쳐 상호 운용성, 구성 가능성 및 재사용성을 촉진합니다. 연합 모델을 채택함으로써 우리는 상호 운용성을 크게 개선하고 기존 자산의 가치를 잠금 해제합니다. 이 전략은 기본 시스템을 비용이 많이 드는 재작성 없이 독립적으로 계속 작동하지만, LLM에 의해 오케스트레이션되는 새로운 응용 프로그램 및 워크플로에 구성될 수 있도록 하여 이질적이고 레거시 서비스를 MCP 준수 인터페이스로 래핑하는 것만으로도 현대 생태계로 가져올 수 있게 합니다.

MCP와 도구 함수 호출 간의 근본적인 차이점에 대한 분석은 다음과 같습니다.

기능도구
함수
호출
모델
컨텍스트
프로토콜
(MCP)
표준화독점적이며
공급업체별입니다.
형식 및 구현은
LLM
공급업체에 따라
다릅니다.
다양한