요즘IT
위시켓
새로 나온
인기요즘 작가들컬렉션
물어봐
새로 나온
인기
요즘 작가들
컬렉션
물어봐
개발
AI
IT서비스
기획
디자인
비즈니스
프로덕트
커리어
트렌드
스타트업
서비스 전체보기
위시켓요즘IT
고객 문의
02-6925-4867
10:00-18:00주말·공휴일 제외
yozm_help@wishket.com
요즘IT
요즘IT 소개작가 지원
기타 문의
콘텐츠 제안하기광고 상품 보기
요즘IT 슬랙봇크롬 확장 프로그램
이용약관
개인정보 처리방침
청소년보호정책
㈜위시켓
대표이사 : 박우범
서울특별시 강남구 테헤란로 211 3층 ㈜위시켓
사업자등록번호 : 209-81-57303
통신판매업신고 : 제2018-서울강남-02337 호
직업정보제공사업 신고번호 : J1200020180019
제호 : 요즘IT
발행인 : 박우범
편집인 : 노희선
청소년보호책임자 : 박우범
인터넷신문등록번호 : 서울,아54129
등록일 : 2022년 01월 23일
발행일 : 2021년 01월 10일
© 2013 Wishket Corp.
로그인
요즘IT 소개
콘텐츠 제안하기
광고 상품 보기
개발

자바 vs. 파이썬: 더 나은 에이전트 개발 언어는?

안영회
12분
1시간 전
85
에디터가 직접 고른 실무 인사이트 매주 목요일에 만나요.
newsletter_profile0명 뉴스레터 구독 중

본문은 로드 존슨(Rod Johnson)의 글 <Build Better Agents in Java vs Python: Embabel vs LangGraph>를 번역한 글입니다. 로드 존슨은 스프링 프레임워크를 만든 것으로 유명한 개발자죠. 그는 최근 Embabel이란 프로젝트를 만들고, 에이전트 프레임워크를 개발하고 있습니다. 필자에게 허락받고 번역과 게재를 진행했습니다.


많은 사람이 생성형 AI 애플리케이션을 구축할 때는 파이썬이 가장 자연스러운 언어라고 잘못 알고 있습니다. 데이터 과학도 아닌데요.

 

그러니 이미 JVM을 사용하고 있다면, 사실 JVM에서 생성형 AI를 다루는 것이 당연합니다. 반면 둘 중 어느 쪽도 아니라면, 이제부터 보여드릴 공정한 비교 결과에 어쩌면 놀라워 할 수도 있겠습니다.

 

이전 블로그들에서 저는 CrewAI와 Pydantic AI의 예시를 가져와 Java와 Embabel로 다시 구현하면서, 같은 기능을 두고 더 깔끔하고 확장 가능한 코드를 만들어 본 적 있습니다.

 

오늘은 LangGraph의 차례입니다. LangGraph는 첫 번째이자, 동시에 가장 인기 있는 파이썬 기반 생성형 AI 프레임워크인 LangChain에서 나온 확장 프레임워크입니다. LangChain의 인기와 LangGraph의 약속은 회사를 주목받는 대상으로 만들었으며, 덕분에 회사는 최근 11억 달러의 평가액으로 시리즈 B 투자를 유치했습니다. 그 프레임워크는 이런 평가대로 정말 Java가 할 수 없는 것들을 할 수 있을까요?

 

 

LangGraph 아키텍처

LangGraph는 코드를 이용하여 다수 에이전트를 사용하는 업무 프로그램을 구현할 수 있게 합니다. 이 프레임워크는 유한 상태 기계(Finite State Machine)를 사용하여 작업 순서를 정렬합니다. 노드(상태)는 함수이고, 엣지(전이)는 자동으로 결정되거나 함수 호출 결과에 따라 동적으로 결정되기도 합니다.

 

이러한 노드와 엣지들은 긴 흐름을 여러 단계로 나누어 실행을 더 결정론적으로 만들고 작업 흐름을 이해하기 쉽게 만듭니다.

 

무엇보다 상태 기계는 작업 흐름을 정의하는 가장 명확한 방법이므로 이 개념을 가장 먼저 적용한 LangChain이 현재의 시장 위치를 차지하는 것이 놀랄 일은 아닙니다. 빠른 시장 출시에 따른 것이지요. 하지만 이것이 최선의 모델일까요, 아니면 아직 명확히 드러나지 않은 더 나은 해결책이 있을까요? 최근 저희는 여러 번 상태 기계를 구축한 끝에  목표 지향 행동 계획(GOAP)을 기반으로 하는 새로운 접근법을 정착시켰습니다.

 

때때로 먼저 움직이지 않고 다른 사람들의 성공과 실패에서 배우는 것이 최선일 수 있습니다.

 

 

패턴 대결

저는 오랫동안 Embabel과 LangGraph 사이의 체계적인 비교를 하고 싶었습니다. 공정을 기하기 위해 제가 만든 예시를 선택하지 않았고, 대신 Hamza Boulahia의 ‘LangGraph 에이전트 디자인 패턴’에 관한 최근 블로그의 내용을 다시 구현했습니다.

 

지난 몇 년간 AI 시스템을 구축하면서 배운 것이 있다면 패턴이 중요하다는 사실입니다. 전통적인 소프트웨어를 설계하든 대형 언어 모델(LLM) 에이전트로 실험하든, 작업 흐름을 구성하는 방식이야말로 결과물이 얼마나 견고하고 유연하며 확장 가능한지를 결정합니다.

 

Boulahia는 6가지 에이전트 디자인 패턴을 시연합니다:

 

  • 프롬프트 사슬 구성: 복잡한 일들을 관리할 수 있는 단계로 나누기
  • 제어 흐름을 위한 경로 설정(routing)과 병렬화
  • 리플렉션(Reflection): 에이전트가 결과를 비판적으로 살피고 개선
  • 외부 시스템과 통합하기 위한 도구 사용
  • 계획 수립(Planning): 목적을 명확하게 실행 가능한 순서로 구조화
  • 여러 에이전트의 협업

 

각 패턴을 하나씩 살펴보면서 자바 프로그램에서 Embabel을 사용해 표현력을 올리는 방식들을 보여드리겠습니다.

 

 

프롬프트 사슬 구성

프롬프트 사슬 구성은 필수적인 패턴으로 한 모델의 결과가 다른 모델의 입력이 되는 구조입니다. 이렇게 하면 일의 흐름을 예측하기 좋습니다. 이제 다룰 예제에서 에이전트는 사용자 입력을 기반으로 주제를 제안하고 각 주제에 대해 여러 개의 블로그 제목을 생성할 겁니다.

 

LangGraph 구현

예제는 LLM을 호출할 때 기본 LangChain API를 사용하며, 작업 흐름 관리를 위해서 LangGraph를 사용합니다.

 

여타 작업 흐름 엔진(workflow engine)처럼 LangGraph를 사용하면 LangChain 만 사용할 때와 달리 프로세스 상태를 유지하기만 해도 좋습니다. 이를 State 클래스로 표현합니다.

 

작업 흐름은 노드와 엣지를 통해 정의됩니다. LLM을 호출하는 Python 메서드가 노드가 됩니다.

 

 

작업 흐름은 코드로 구현합니다. 노드는 메서드에 매핑되고 엣지가 메서드를 연결합니다. 예제에서 엣지는 간단하게 다음 상태로 전환하게 만듭니다. 작업 흐름이 하드코딩된 문자열로 표현되기 때문에 컴파일 할 때 유효성 검사가 거의 이루어지지 않습니다.

 

 

작업 흐름의 실행은 아래와 같은 코드로 합니다.

 

 

실행한 결과는 다음과 같습니다.

 

 

Embabel 구현

사소한 부분이긴 하지만, 사실 위의 예시 일부는 좋지 않은 설계이기 때문에 이를 그대로 구현하기는 어렵습니다. 특히, 지금의 예시는 도메인에 대한 모델링 부족하다 할 수 있습니다. 파이썬으로 생성형 AI를 다룰 때는 도메인 모델링을 과소평가하는 경향이 있는데, 이는 심각한 실수입니다. State 클래스의 모든 필드는 문자열로 모델링되어 있으므로 구조가 있어야 합니다. 특히 주제를 하나로 문자열로 표현하는 일은 오해를 낳기 쉽습니다.

 

이 문제를 해결하는 형태로 최소한의 변경을 했습니다. 도메인 모델과 행위 그리고 목적으로 표현된 Embabel 구현 방식을 택했습니다.

 

도메인 객체는 Java 레코드이며, 요구사항에서 암시된 구조를 올바르게 나타냅니다.

 

 

코드를 좀 더 자세히 해설해 보겠습니다. 우선 @Agent 애노테이션은 클래스가 Spring에 의해 생성되고 주입되며, Embabel이 처리하게 합니다. @Action 메서드는 단계를 나타내고, 목적은 원하는 출력입니다.

 

Actor 필드는 하이퍼파라미터를 주고 LLM 모델을 선택하는 일과 명령어를 결합합니다.

 

extractTopics와 generateBlogTitles 메서드는 LangGraph 노드 메서드에 해당합니다. 그러나 작업 흐름은 매우 다르게 구성되어 있습니다.

 

이처럼 Embabel에서는 코드에서 작업 흐름을 지정할 필요가 없습니다. 프레임워크 스스로  코드를 분석하여 실행 경로를 계획합니다.  특히 실행 로직은 타입의 흐름에서 올바른 실행 계획을 추론할 수 있습니다. 예를 들어, 작업 흐름 범위에서 Topics와 BlogTitles 객체를 사용할 수 있어야만 마찬가지로 목적을 달성할 수 있다는 것을 알 수 있습니다. 그에 따라 메서드 호출 순서를 올바르게 결정할 수 있습니다. 이와 같은 계획 단계는 결정론적입니다.

 

마지막으로, generateBlogTitles 단계를 병렬 작업으로 모델링하여 요구사항을 더 자연스럽게 반영하고 LLM과의 상호 작용을 더 간결하고 집중력있게 만듭니다.

 

출력 결과는 다음과 같습니다. LangGraph 예제 출력과 달리 요구사항을 정확하게 반영한 구조입니다.

 

 

비교

두 구현 모두 흐름을 작고 초점이 분명한 단계로 나누어 예측을 쉽게 합니다. 다만, Embabel 버전에는 코드가 조금 더 많습니다. 다중성cardinality을 올바르게 모델링하려는 코드가 추가됩니다.

 

그 대신 Embabel 구현은 여러 측면에서 우수하다고 느껴집니다. 먼저, 타입 안전성 측면이 그렇습니다. 오용 가능성이 있는 문자열과 대비되는 제대로 된 도메인 모델이 있어 테스트하기 쉬워집니다. 그리고 추가할 수 있는 구조 때문에 병렬 작업도 가능합니다.

 

그 외에도 Embabel 구현에서는 사소하지만 LangGraph 보다 나은 몇 가지 개선 사항이 있습니다.

 

  • LLM 선택과 페르소나 외부화: Embabel과 Spring을 사용하면, Actor 정의를 application.yml 파일로 간단하게 외부화할 수 있습니다. 파이썬에 부족한 복잡한 옵션 구성이 가능합니다.
  • 주제와 블로그 제목 유지: 적절히 구조화된 도메인 객체가 있기 때문에, 원할 때 쉽게 내용을 유지할 수 있습니다.

 

 

경로 선택(Routing)

또 다른 기본 패턴은 작업 흐름 안에서의 경로 선택입니다.

 

LangGraph 구현

여기 LangGraph 구현이 있습니다. 먼저 감정 분석을 수행한 다음 응답을 생성하기 위해 적절한 함수를 선택합니다:

 

 

LangGraph에서 항상 그렇듯이 작업 흐름은 확인할 수 없는 문자열로 구축됩니다. 예제 코드에서 “classification”에 오타가 났는데, 전부 같이 틀렸기 때문에 다행히 문제는 없습니다. 그래도 이렇게 취약할 때는 타이핑이 매우 중요해집니다.

 

다음 예시는 함수의 출력을 다음 상태 이름에 매핑하는 식의 더 정교한 LangGraph 상태 전환을 보여주는데, 여기서도 역시 오류가 발생하기 쉬운 마법 문자열이 보너스로 더해집니다:

 

 

다음과 같은 코드로 이를 실행합니다.

 

 

Embabel 구현

노드와 엣지를 명시적으로 연결하는 일은 이렇게 간단한 일을 너무 복잡하게 만듭니다. 좀 더 자연스러운 방식으로 단계를 표현해 봅시다.

 

이때는 단계가 다르기 때문에 LLM과 관련된 공통 시스템 메시지가 아니라 매번 초점이 맞춰진 프롬프트를 사용할 수 있습니다.

 

먼저 Sentiment의 타입을 만들겠습니다. 이는 흐름을 데이터 타입으로 표현할 수 있게 도와줍니다:

 

 

이제 우리는 classify 메소드가 Sentiment 객체를 반환 하도록 구현을 시작할 수 있습니다. Embabel은 스스로 Sentiment가 하위 타입을 갖는지 알아차리며,그에 맞춰 적절한 경로를 계획합니다.

 

한편 Encourage와  help 메소드는 각각 하나의 Sentiment 하위 유형을 취합니다. 파이썬 함수와 마찬지로 LLM을 호출하여 응답을 생성하는 구조입니다.

 

 

비교

Embabel을 이용해 데이터 타입을 통해 실행 경로를 정의하는 것이 LangChain 상태 머신보다  자연스럽고 오류가 적다고 생각합니다. 다시 한번, Embabel 내부에서 스스로 계획하는 기능이 흐름을 파악하기 때문에 직접 흐름을 정의할 필요가 없습니다.

 

 

병렬화

LLM 상호 작용이 일어나면, 동작이 느려질 수 있습니다. 따라서 병렬화는 생성형 AI 애플리케이션에서 중요합니다.

 

LangGraph는 “평행 엣지(parallel edges)”를 통해 작업을 병렬화할 수 있게 합니다.

 

 

Embabel에서는 병렬화를 위해 두 가지 선택이 가능합니다:

 

  • 블로그 제목 예시에서 본 parallelMap 메서드를 사용한 프로그래밍 방식.
  • 자동 구조도 있습니다. embabel.agent.platform.process-type=CONCURRENT를 설정하면, Embabel 스스로 계획하여 서로 의존하지 않는 작업을 대상으로 현재 목표를 달성하기 위해 필요한 경로를 병렬 실행합니다.

 

 

리플렉션(Reflection)

리플렉션은 다양한 이름으로 불리는 중요한 보편적 패턴입니다. 앤트로픽은 “Building Effective Agents”에서 이 패턴을 Evaluator-optimizer라고 부릅니다.

 

패턴의 핵심 아이디어는 LLM 호출을 한 차례 사용하여 다른 LLM의 출력에 대한 피드백을 제공하고 결과가 만족스러울 때까지 반복하는 것입니다.

 

여기 LangGraph 구현이 있습니다. 사용자가 지정한 작업에 응답하여 텍스트를 초안하고 만족스러울 때까지 비판합니다.

 

 

패턴의 가치에 빗대어 볼 때 이상적인 구현과는 거리가 멉니다. 일단, 종료를 보장할 수 없습니다.

 

개발자 관점에서 이런 류의 상태 머신은 일반적인 작업흐름을 표현하는데 이상적이지 않습니다. 공정하게 따지면, Embabel의 GOAP 계획도 마찬가지입니다. FSM이나 GOAP을 쓴다면 조잡한 계획에 잘 맞지만, 세밀한 작업을 정의할 수 있다면 정교한 프레임워크 지원으로 더 쉽게 할 수 있습니다.

 

따라서 Embabel은 일반적인 패턴에 대해 바로 직접 만들지 않고 가져다 쓸 수 있도록 구현하는 방식을 제공합니다. (물론 자신만의 것을 만들 수도 있습니다.) 구현을 원하는 경우 GOAP 계획을 내보내는 fluent API*를 제공하기 때문에 표준적인 방식으로 단계를 관리할 수 있습니다.

*fluent API: 메서드 체이닝으로 코드를 자연어처럼 읽히게 만드는 API 설계 패턴

 

RepeatUntilAcceptableBuilder를 사용하여 프로그래밍 방식으로 Agent를 만들고 @Configuration 클래스에서 Spring bean으로 노출하면, Embabel이 자동으로 배포합니다:

 

 

자바로 작성한 Embabel 코드는 LangGraph를 사용한 파이썬 코드보다 훨씬 적은 코드를 가지고 있습니다. 그 덕분에 타입을 보장하고 많은 도구를 활용할 수 있어 흐름을 망칠 일이 없습니다. 만일 maxIterations를 초과하면 루프가 종료되고, 지금까지 본 최고 점수의 결과를 반환합니다. 또한, RepeatUntilAcceptableBuilder가 매개변수화되어 있고 타입을 보장하기 때문에 자신의 결과 타입과 피드백 타입을 사용할 수 있습니다. 이 시나리오에서 LangGraph는 매우 좋지 않은 성능을 보여준다고 할 수 있겠습니다.

 

 

도구 사용

도구 사용은 모든 생성형 AI 프레임워크의 필수 요소이며 쉬워야 합니다.

 

LangGraph를 사용하면 Python에서 도구를 정의하고 LLM에 바인딩할 수 있습니다:

 

 

간단한 예시입니다. 다만, 이 예시는 생성된 Python 코드를 샌드박스 없이 실행하기 때문에 위험할 수 있습니다.

 

Embabel로 같은 처리를 한다면 매 번 PromptRunner를 사용하여 간단하게 도구를 추가할 수 있습니다. MCP가 지원하는 잘 알려진 도구 그룹을 정의하거나 Java 객체로 지정할 수 있습니다:

 

 

도구 객체를 사용하는 기능은 특히 중요합니다. 이들은 데이터베이스에서 검색한 도메인 객체 또는 스프링으로 에이전트에 주입한 서비스일 수 있습니다. 파이썬에서도 그렇게 할 수 있지만, 자바 객체의 경우가 유용한 비즈니스 기능을 가질 가능성이 더 높다는 점에서 중요한 차이가 있습니다.

 

Embabel은 또한 LlmReference와 같은 개념으로 도구 그룹화를 제공하며, 이는 도구를 프롬프트 요소와 결합하여 런타임에 정교한 구성을 가능하게 합니다.

 

 

계획 수립

계획을 수립하는 과정을 다룬 LangGraph 블로그 예시는 이전 두 단계의 실행에 따라 최종 단계가 달라지는 형태를 포함합니다. 이것은 작업흐름 구축 과정에서다음과 같이 표현됩니다.

 

 

Embabel에서는 두 데이터 타입을 모두 사용하는 메서드를 간단히 정의합니다:

 

 

이 방식이 제가 느끼기에 더 명확하고 타입 활용에 안전하며 테스트하기 쉽습니다.

 

LangChain 예시는 또한 계획의 단계를 하드코딩하지만, 그 코드는 LLM이 만들 수 있다고 언급합니다. Embabel에서 역시 LLM이 명령 객체를 인스턴스화하도록 하여 이를 구현할 수 있습니다. 그러나 LLM에 의존하여 계획을 수행하면 흐름 예측이 훨씬 어려워지므로 주의깊게 수행해야 합니다.

 

 

다중 에이전트 협업

마지막 패턴은 감독자 노드에서 다른 에이전트로의 전달 예시입니다. 이것은 Google ADK와 같은 프레임워크에서 흔한 패턴입니다. 상태 머신이나 GOAP 액션을 통해 단계를 표현하는 것보다 덜 결정론적이므로, 우리가 권장하는 경향이 없는 패턴입니다. 이러한 동작을 달성하기 위해 Embabel은 에이전트를 도구로 사용할 수 있게 합니다.

 

 

전체 코드

전체 코드는 Embabel langgraph-patterns 저장소에서 찾을 수 있습니다.

 

누군가는 원본 블로그가 LangGraph 사용을 제대로 보여준 것이 아니라고 주장할 수 있습니다. 하지만 이 글은 제가 보기에 에이전트에 대한 건전한 이해를 바탕으로 한 잘 쓴 글이며, 미디엄Medium에서 수백 개의 박수를 받았습니다. 다른 LangGraph 애플리케이션에서 동일한 결함을 찾기는 어렵지 않으며, 문제가 저자가 아니라 프레임워크, 언어 및 생태계에 있음을 시사합니다.

 

 

마치며

이제 자바 커뮤니티는 파이썬 기반 에이전트의 성장을 따라잡아야 한다는 강박을 멈추고, 생성형 AI 에이전트를 구축하기 위한 해결책뿐만 아니라 모든 플랫폼에서 최고의 해결책을 가지고 있음을 보여줄 때입니다.

 

파이썬 프레임워크가 보여주는 평범한 아이디어 아래 안주하는 것을 멈추고 우리가 써온 언어를 기반으로 생각할 때입니다. 파이썬은 황제들만 갖출 수 있는 옷을 입지 않았습니다. 여러분이 JVM 개발자이지만, 상사가 파이썬으로 에이전트를 작성하라고 말하면, 비즈니스 애플리케이션, 생성형 AI 또는 기타 분야의 플랫폼으로서 파이썬의 약점을 지적할 준비를 하세요. 그리고 일반적인 패턴을 그대로 쓰면서도 파이썬보다 자바가 나아 보일 수 있는 방법을 보여줄 준비를 하세요.

 

파이썬은 모든 개발자가 능숙하게 쓰면 좋은 중요한 언어입니다. 많은 작업에 훌륭한 성능을 보여줍니다. 다만, 이 언어는 꽤 중요한 비즈니스 애플리케이션을 구축할 때 인기 있는 언어가 아니었으며, 여기에는 그럴만한 이유가 있습니다.

 

JVM의 견고성은 잘 알려져 있습니다. 생태계 — 특히 파이썬에서는 대안이 없는 Spring이 — 이러한 견고함을 강력하게 뒷받침합니다. Embabel은 이를 바탕으로 하며, 모든 파이썬 프레임워크보다 우수한 개념을 제공한다고 저는 믿습니다. 나머지는 여러분에게 달려 있습니다. 오늘 자바 에이전트 템플릿에서 첫 번째 에이전트를 만들어 보세요.