22. LangGraph 기본과 응용

LLM 시대, LangChain(랭체인)으로 배우는 AI 소프트웨어 개발

by AI개발자
gaebalai-blog_ai-v3-1.jpg

(1) LangGraph(랭그래프)란?

LangGraph(랭그래프)는 LangChain(랭체인)을 기반으로 하는 에이전트 프레임워크입니다. LangGraph(랭그래프)의 주요 특징은 복잡한 제어구조를 가진 워크플로우를 유연하게 표현할 수 있다는 점입니다. 이는 LangGraph(랭그래프)가 워크플로우 표현에 그래프를 채택하고 있기 때문입니다. 여기서 그래프란, 노드(정점)과 엣지(변)로 이루어지는 수학적 구조로 요소간에 관계성을 표현하는데 사용됩니다.


LangChain(랭체인)이 주로 다루는 체인구조는 선형 워크플로우 표현에 적합한 특수한 그래프 구조입니다. 그러나, 체인에서 반복적이고 복잡한 제어구조를 표현하는 것은 어렵습니다. 예를 들어, 특정 조건이 충족될 때까지 처리를 반복하는 경우 체인 구조에서 제대로 표현할 수 없습니다. 반면에 LangGraph(랭그래프)는 체인뿐만 아니라 일반적인 그래프 구조를 사용하여 복잡한 제어구조를 표현할 수 있습니다. LangGraph(랭그래프)가 다루는 일반적인 그래프 구조에는 노드에 엣지 연결에 제한이 없습니다. 이 때문에 다양한 구조를 유연하게 표현할 수 있습니다. 예를 들어, 다음과 같은 제어구조를 표현할 수 있게 됩니다.


반복처리: 가장자리를 사용하여 이전 노드로 돌아가 루프를 표현함

분기처리: 하나의 노드에서 복수의 엣지를 내는 것으로 조건분기를 표현함


LangGraph(랭그래프)는 에이전트와 도구의 처리를 노드로 표현하고 노드 간의 연결을 엣지로 표현합니다. 가장자리는 다음에 실행할 노드를 지정합니다. LangGraph(랭그래프)는 노드와 엣지를 결합하여 멀티 에이전트 구현에 적합한 워크플로우를 표현합니다.


예를 들어, 아래 그림은 LangGraph(랭그래프)를 사용하여 소프트웨어 개발팀을 멀티에이전트로 구현하는 그래프입니다. 리더(Leader), 프로그래머(Programmer), 테스터(TestWriter), 평가자(Evaluator)의 4가지 에이전트가 노드로 표현됩니다.


또한, 에이전트가 사용하는 도구(Tool)도 노드로 표현됩니다. 에이전트와 에이전트와 도구간의 관계를 가장자리로 표시됩니다. 여기에서는 그래프에서 어떤 에이전트도 리더(Leader)와 관계하고, 평가자(Evaluator)는 도구와 관계하는 것을 읽을 수 있습니다. 또한, 화살표가 순환하고 있는 것으로부터 반복처리가 표현되고 있는 것도 확인할 수 있습니다. 점선은 분기처리를 나타냅니다. 이 그래프의 상세한 동작은 나중에 다시 설명합니다.


llm-langchain80-1.png 소프트웨어 개발 워크플로우



① LangGraph API

LangGraph(랭그래프)는 이름에서 알 수 있듯이 그래프를 조작하는 API를 제공합니다. 아래 표는 LangGraph(랭그래프)의 주요 API를 나열합니다. 이러한 API를 사용하면 AI에이전트에 필요한 기능을 구현할 수 있습니다. 이번에는 이런 API를 사용하여 LangGraph(랭그래프)가 에이전트를 구현하는 방법을 설명합니다.


LangGraph 관련 API

llm-langchain80-2.png



② LangGraph 사용예시

개발자는 LangGraph(랭그래프)를 사용하여 다양한 워크플로우를 그래프로 정의할 수 있습니다. LangGraph(랭그래프)는 에이전트 시스템에 국한되지 않고, 범용 워크플로우를 정의하는데 사용할 수 있습니다. 여기에서는 LLM이나 에이전트와 직접 관련이 없는 예시로서 목표금액이 모이는 저금통을 LangGraph(랭그래프)로 구현해 봅시다.

이 돼지저금통은 넣은 돈만큼만 모으는 간단한 것입니다. 목표금액에 도달하면 돼지저금통을 열 수 있습니다. 이 구현은 위 표에서 표시된 API 사용법을 설명합니다. 구현한 코드는 아래 코드로 나와 있습니다.


LangGraph를 이용한 돼지저금통 구현(src/langgraph/piggy_bank.py)

llm-langchain80-3.png

아래 그림은 이 돼지 저금통의 워크플로우를 보여줍니다. 이 그래프 노드는 다음과 같습니다.


__start__ (시작): 시작

Deposit (입금가능): 돈을 투입

Full (목표 금액 달성): 목표 금액 달성 통지

__end__ (종료): 종료


llm-langchain81.png 돼지저금통 워크플로우 그래프

__start__ 및 __end__는 워크플로우의 시작과 끝을 나타내는 특수노드입니다. __start__ 및 __end__는 LangGraph(랭그래프)에서 예약한 노드명입니다. __start__에서 Deposit로 전환하고 목표금액에 도달하면 Full로 전환합니다. Full처리가 끝나면 __end__로 전환하여 워크플로우를 종료합니다. Deposit노드와 Full노드에서는 미리 정해진 처리를 실시합니다. Deposit노드는 사용자로부터 입금을 수락합니다. 한편, Full노드는 목표금액에 도달했음을 사용자에게 알립니다.


현재 저장된 금액은 이 워크플로우 전체에서 공유해야 하는 상태입니다. Depoit노드에서 이루어지는 입금접수는 이 금액을 갱신합니다. 반면에 Full노드에서 발생하는 목표 금액 달성 알림을 이 금액을 참조합니다. LangGraph(랭그래프)의 그래프는 이러한 상태를 관리하는 메커니즘을 제공합니다. 이 때문에 LangGraph(랭그래프)의 그래프를 상태그래프(StateGraph)라고도 합니다.


▣ 모듈 가져오기

우선 LangGraph 관련 모듈을 가져옵니다.

llm-langchain81-1.png

여기서 Annotated는 형힌트를 부여하는 함수이고, TypedDict는 사전형의 형힌트를 정의하는 클래스입니다. operator는 연산자를 함수로 취급하는 모듈이고 functools는 함수형 프로그래밍을 지원하는 모듈입니다. 여기서 노드에서 실행되는 함수를 부분적으로 적용하는데 사용됩니다. StateGraph는 상태그래프를 정의하기 위한 클래스, END는 end노드를 나타내는 정수입니다.


▣ 상태 정의

다음 워크플로우 전체에서 공유되는 상태를 정의합니다. 상태필드와 형을 TypedDict로 정의합니다. 돼지 저금통의 상태정의는 다음과 같습니다.

llm-langchain81-2.png

여기에서는 total, count, last_deposit의 3개 int형 필드를 가지는 상태를 정의하고 있습니다. total은 현재 저축금액, count는 입금횟수를 나타냅니다. 또한 last_deposit은 마지막으로 입금한 금액을 나타냅니다. total과 count는 Annotated에서 연산자 add를 사용하여 업데이트됨을 나타냅니다. 이렇게 하면 총합 및 count가 업데이트될 때마다 자동으로 추가됩니다. 반면에 last_deposit은 업데이트할 떄 덮어씁니다. 이 때문에 Annotated에 의한 수식은 불필요합니다.


▣ 노드함수정의

다음으로 각 노드의 처리를 함수로 정의합니다. LangGraph(랭그래프)는 각 노드의 처리를 Python함수 또는 Runnable객체로 정의합니다. 이 함소 또는 Runnable객체의 인수는 상태정의에 지정된 유형과 일치해야 합니다. 또한 반환값은 상태를 업데이트하기 위해 사전을 반환해야 합니다. 그러면, 각 처리를 정의하는 것으로 실제로 확인해 갑니다.

Deposit노드는 사용자의 입금을 수락하고 total, count, last_deposit을 업데이트합니다.

llm-langchain81-3.png

deposit함수는 PiggyBankState형의 상태를 입력으로 합니다. 입력이 되는 상태를 일절 사용하지 않습니다. 그 경우 LangGraph(랭그래프)의 사양에 따라 상태를 인수로 받아야 합니다. 반면에 상태 업데이트는 deposit함수로 수행됩니다. 입금액을 사용자에게 입력하고 total, count, last_deposit을 업데이트합니다. deposit함수는 total, count, last_deposit의 3필드를 가진 사전을 반환하여 상태를 업데이트합니다. total에는 amount값이, count에는 1이 각각 현재의 값에 더해지게 되어 있습니다. 이것은 상태 정의로 Annotated를 사용해 지정한 연산자 add에 의해 실현됩니다. 반면에 last_deposit는 amount로 덮어씁니다. 이는 상태 정의로 특별히 지정하고 있지 않기 때문입니다. 다음은 Full노드의 처리를 정의합니다.

llm-langchain81-4.png

이 프로세스는 목표금액에 도달했음을 사용자에게 알립니다. 또한, total을 0으로 설정합니다. total은 차등업데이트되므로 0을 설정(가산)해도 total은 불변입니다. LangGraph(랭그래프)에서도 적어도 하나의 상태를 업데이트해야 합니다. 따라서 total에 0을 지정합니다.


▣ 조건 검사 함수 정의

LangGraph(랭그래프)에서는 조건에 따라 노드간의 전환을 제어할 수 있습니다. 이 전환을 결정하는 것이 조건검사 함수입니다. 상태전이의 조건검사 함수는 노드 함수와 마찬가지로 상태를 인수로 사용합니다. 조건검사 함수의 반환값은 다음 상태를 결정합니다. 다음은 check_goal함수의 정의입니다.

llm-langchain81-5.png

여기서는 total이 목표금액이상이면 'full'을 반환하고, 그렇지 않으면 'continue'를 반환합니다. 목표금액인 goal은 이 함수의 인수로 받습니다. 그러나, LangGraph(랭그래프)에서 조건검사함수는 상태만을 인수로 설정해야 합니다. 이 때문에 후 처리에서는 이 함수를 부분적용해 goal를 고정한 함수를 작성합니다.


▣ 상태 그래프 정의

그런 다음 워크플로우를 상태그래프로 정의합니다. StateGraph클래스를 사용하여 이 상태 그래프를 정의합니다.

llm-langchain81-6.png

여기에서는 StateGraph클래스의 인스턴ㅅ스를 작성해, 각 노드와 그 처리를 추가하고 있습니다. add_note메소드로 각 노드와 처리를 추가합니다. add_node메소드의 첫번째 인수는 노드명이고, 두번째 인수는 해당 노드에서 실행하는 함수 또는 Runnable객체입니다. 여기에서는 Deposit노드에서 deposit함수를 설정하고 Full노드에서 finalize함수를 실행합니다.


add_conditional_edges 및 add_edge 메소드 호출은 노드간의 전환을 정의합니다. add_conditional_edges 메소드는 조건부 전이를 정의합니다. 첫번째 인수는 전환소스노드, 두번째 인수는 조건검사함수, 세번째 인수는 전환대상노드를 지정하는 사전입니다. 조건검사 함수가 반환하는 값에 따라 전환대상노드가 결정됩니다. 여기서는 Deposit노드에서 check_goal함수를 사용하여 Full노드로 전환할지 또는 Deposit노드에 머무를지 결정합니다. check_goal함수는 상태 이외에 goal을 인수를 사용합니다. 이 때문에,, 이 함수를 부분적용해서 상태만을 인수로 하는 함수를 작성하고 나서 add_conditional_edges메소드에 건네주고 있습니다. 부분 적용에는 functools.partial함수를 사용합니다.


add_edges메소드는 무조건 전이를 정의합니다. 여기서는 Full노드에서 END노드로 전환하도록 설정합니다. END노드는 워크플로우의 끝을 나타내는 특수노드입니다. 또한 set_entry_point메소드에서 워크플로우의 진입점을 설정합니다. 이것은 노드로부터의 천이처를 지정하는 것에 상당합니다. 여기에서는 Deposit노드를 진입점으로 설정합니다. 마지막으로 compile 메소드로 상태 그래프를 컴파일합니다. 컴파일하면 LangChain(랭체인)의 Runnable객체가 생성됩니다.

llm-langchain81-7.png

300원, 400원, 500원의 3회 입금(사용자에 의한 입력)으로 목표금액인 1000원에 이르렀습니다. 처음 3행은 Deposit노드에서의 입금처리를 보여줍니다. 그 다음 행들은 Full노드에서 목표 금액 달성 통지를 보여줍니다. 마지막 행은 최종 상태를 나타냅니다.



(2) LangGraph의 응용

이번에는 LangGraph(랭그래프)를 사용하여 대표적인 에이전트 아키텍처를 구현하는 방법을 소개합니다.


단일 에이전트: 자연어 쉘 인터페이스

수평 아키텍처: 에이전트간 대화 시뮬레이션

수직 아키텍처: 에이전트 팀에 의한 소프트웨어 개발


단일 에이전트는 도구를 호출하기 위한 간단한 에이전트를 구현합니다. 도구로는 쉘 명령을 호출하는 도구를 사용합니다. 사용자가 자연어로 명령을 입력하면 에이전트는 해당 명령을 해석하고 해당 쉘 명령을 실행합니다. 예를 들어, 사용자가 자연어로 날짜와 시간을 쿼리하면 에이전트가 date명령을 실행하고 날짜와 시간을 반환합니다.


수평 아키텍처는 여러 에이전트가 협력하여 작동하는 시뮬레이션을 구현합니다. 여기에서는 방문판매 시뮬레이션을 예를 들어보겠습니다. 방문판매원과 주부의 두 대리인이 대화를 나눠 상담을 진행합니다. 에이전트 간의 대화는 멀티 에이전트 처리의 기본이 되기 때문에 수평 아키텍처의 구현예로서 적합합니다.


수직 아키텍처에서는 여러 에이전트가 협력하여 소프트웨어 개발을 수행하는 시나리오를 구현합니다. 여기에서 프로젝트리더, 프로그래머, 테스트 작성자 및 평가지의 4가지 에이전트가 각 역할을 담당하고 프로젝트를 진행합니다. 프로젝트 리더이외의 에이전트는 프로젝트 리더의 지시에 따라 작업을 진행하는 수직 아키텍처를 구현합니다. 대화의 기본은 수평 아키텍처와 동일하지만, 리더가 다음 작업을 지시한다는 점이 다릅니다.


이들은 모두 간단한 예이지만, 이러한 아키텍처를 결합하여 더 복잡한 시스템을 구축할 수 있어야 합니다. 예를 들어, 대규모 소프트웨어 개발 프로젝트를 실현하기 위해 수평아키텍처와 수직 아키텍처를 결합할 수 있습니다.


① 단일 에이전트 구축: 자연어 쉘 인터페이스

LangGraph(랭그래프)를 사용하여 자연어 쉘 인터페이스를 사용하여 단일 에이전트를 구축하는 방법을 소개합니다. 이 인터페이스에서는 사용자가 자연어로 명령어를 입력하면 에이전트가 그 명령어를 해석해 대응하는 쉘 명령어를 실행합니다. 예를 들어, 사용자가 '현재 날짜와 시간을 말해줘'라고 입력하면 에이전트는 date명령을 실행하여 현재 날짜와 시간을 반환합니다.


아래 그림은 이 자연어 쉘 인터페이스의 워크플로우를 상태그래프로 보여줍니다. 이 그래프는 Agent와 Tool의 2개 노드로 구성되어 있습니다. Agent노드는 사용자로부터 자연어 입력을 받고 입력을 기반으로 적절한 도구를 호출합니다. Tool노드는 실제로 쉘명령을 실행하고 그 결과를 Agent노드에 반환합니다. Agent노드는 Tool노드에서 받은 결과를 사용자에게 반환합니다.


llm-langchain82.png 자연어 쉘 인터페이스 워크플로우

이제 LangGraph(랭그래프)를 사용하여 이 자연어 쉘 인터페이스를 구현해 보겠습니다. 전체 프로그램은 아래 코드와 같습니다.


자연어 쉘 인터페이스 구현(src/langgraph/shell.py)

llm-langchain82-1.png

▣ 모듈 가져오기

우선 필요한 모듈을 가져옵니다.

llm-langchain82-2.png

여기서는 LangGraph(랭그래프) 외에 LangChain(랭체인) 관련 모듈 및 subprocess 모듈을 가져옵니다.


▣ 도구 정의

다음 쉘 명령을 실행하기 위한 도구를 정의합니다. 참고로 환경에 맞추어 '리눅스 쉘 명령'의 OS명을 다시 작성해 주세요. Windows환경인 경우, 'Linux'를 'Windows'로 변경합니다.

llm-langchain82-3.png

여기서는 exec_command라는 도구를 정의합니다. 이 도구는 인수로 쉘명령을 수신하고 명령을 실행한 결과를 리턴합니다. 도구를 정의하려면 @tool 데코레이터를 사용합니다.


▣ 에이전트 상태 정의

다음으로 에이전트의 상태를 정의합니다.

llm-langchain82-4.png

여기서는 messages라는 필드가 있는 AgentState형을 정의합니다. messages는 에이전트와 사용자 상호작용을 저장하는 필드입니다. Annotated를 사용하여 messages형을 Sequence[BaseMessage]로 지정하고 업데이트 시, operator.add를 사용하여 요소를 추가하도록 지정합니다. 또한, messages를 상태의 일부로 정의하는 것은 LangGraph(랭그래프)의 정석입니다.


▣ 노드 함수 정의

다음 각 노드에서 수행하는 작업을 함수로 정의합니다.

llm-langchain82-5.png

여기서는 agent_node와 tool_node라는 2가지 함수를 정의합니다. 또한 사전에 agent_node함수에서 사용할 ChatOpenAI 인스턴스를 생성하고 exec_command도구를 바인딩합니다.

agent_node함수는 에이전트 노드 처리를 구현합니다. 현재 상태로 유지되는 메시지 목록을 LLM에 전달하고 결과를 새 메시지 목록으로 반환합니다. 반환된 메시지 목록은 현재 메시지 목록의 끝에 연결됩니다.

tool_node함수는 도구 노드 처리를 구현합니다. 에이전트 노드로부터 받은 메시지에 포함된 도구 호출을 실행해, 그 결과를 도구 메시지로서 에이전트에 돌려줍니다. 도구 호출을 포함하는 메시지는 메시지 목록의 마지막 테일 메시지입니다.


▣ 조건검사 함수 정의

다음은 노드간의 전환을 결정하는 조건검사 함수를 구현합니다.

llm-langchain82-6.png

should_continue함수는 입력상태에서 도구를 호출해야 하는지 여부를 결정합니다. 마지막 메시지가 도구호출을 포함하면 'tool'을 그렇지 않으면 'end'를 반환합니다. 메시지가 도구호출을 포함하는지 여부는 메시지의 tool_calls필드를 보면 알 수 있습니다.


▣ 상태그래프 정의

다음 StateGraph를 사용하여 상태그래프를 정의합니다.

llm-langchain82-7.png

Agent노드와 Tool 노드를 추가하고 should_continue함수를 처리하여 조건부 엣지를 추가합니다. should_continue가 'tool'을 반환하면 Tool노드로 'end'를 반환하면 종료노드(END)로 전환합니다. Tool노드에서 무조건 Agent노드로 전환합니다. 마지막으로 진입점을 Agent노드로 설정하고 그래프를 컴파일합니다.


▣ 상태그래프 실행

마지막으로 상태그래프를 실행합니다.

llm-langchain82-8.png

사용자가 자연어로 쿼리를 입력하고 쿼리를 HumanMessage로 그래프에 전달합니다. 그래프는 쿼리에 응답할 때까지 Agent노드와 Tool노드를 전환하고 최종응답을 사용자에게 반환합니다.


이 프로그램을 실행하면 사용자는 자연어로 쉘명령을 실행할 수 있습니다. 다음 실행 예시로 앞서 언급했듯이 환경에 맞게 도구정의 부분에 있는 리눅스쉘명령의 OS명을 다시 작성합니다.

llm-langchain82-9.png

여기서 사용자가 '현재 시간은?'을 입력하면 에이전트가 date명령을 실행하여 현재시간을 반환합니다. 그외에도 'IP주소는?', '운영체제 버전은?'등 다야한 쿼리로 대응할 수 있습니다.



② 수평 아키텍처 구축: 방문판매 시뮬레이션

LangGraph(랭그래프)를 사용하여 수평 아키텍처에서 에이전트 간의 대화방법을 구현하는 방법을 소개합니다. 소재로는 방문판매 시뮬레이션을 다룹니다. 방문판매원과 주부의 2에이전트가 등장해 대화를 통해 상담을 진행시켜 나갑니다. 아래 그림은 이 시뮬레이션의 상태 그래프를 보여줍니다. 방문판매원(salesman) 노드와 주부(SHED)노드의 2노드가 번갈아 전이됨으로써 교대로 상담원이 발언하고 대화가 진행됩니다. Salesman 노드를 경험있는 방문판매원 에이전트를 SHED노드는 견고한 주부 에이전트를 나타냅니다. 방문판매원이 영업을 종료한다고 판단되면 종료노드로 전화합니다.


llm-langchain83.png 방문판매 시뮬레이션 워크폴로우


방문판매 시뮬레이션 구현(src/langgraph/sales.py)

llm-langchain83-1.png

▣ 모듈 가져오기

우선 필요한 모듈을 가져옵니다.

llm-langchain83-2.png

여기서는 LangGraph(랭그래프) 외에 LangChain(랭체인) 관련 모듈을 가져옵니다.


▣ 프롬프트 정의

다음올 각 에이전트에 대한 프롬프트를 정의합니다.

llm-langchain83-3.png

여기에서는 방문판매원을 위한 프롬프트와 주부를 위한 프롬프트를 정의합니다. 방문팜내원을 위한 프롬프트에서는 방문판매원의 이름과 성격, 영업종료 조건등을 지정합니다. 주부 프롬프트에서는 주부의 성격을 지정합니다. 에이전트의 작용은 주어진 역할과 페르소나에 달려 있다는 것을 알고 있습니다. 위 프롬프트를 변경하고 에이전트 동작이 어떻게 바뀌는지 확인해 볼 수 있습니다.


▣ 체인 정의

다음 LLM과 프롬프트를 결합하여 체인을 정의합니다.

llm-langchain83-4.png


여기에서는 LLM은 gpt-4o-mini를 사용하고 앞서 정의한 프롬프트와 결합하여 체인을 정의합니다.


▣ 상태 정의

다음 에이전트의 상태를 정의합니다.

llm-langchain83-5.png

여기서 messages필드가 있는 AgentState형을 정의합니다. messages는 상담원간의 대화를 저장하는 필드입니다.


▣ 노드 함수 정의

다음으로 각 노드의 처리를 정의합니다. 여기서는 두 에이전트의 공통처리를 함수로 정의하고 이를 부분적으로 적용하여 각 에이전트에 대해 노드 함수를 정의합니다.

llm-langchain83-6.png

agent_node함수는 에이전트 노드 처리를 구현합니다. 에이전트의 상태를 받고 체인을 호출하여 다음 메시지를 생성합니다. 생성된 메시지를 AIMessage객체로 변환하고 다른 이름으로 반환합니다. salesman_node와 shed_node는 agent_node함수를 부분적으로 적용하여 각 에이전트에 대한 노드 함수를 정의합니다.


▣ 조건검사 함수 정의

다음으로 노드간의 전환을 결정하기 위해 조건검사 함수를 구현합니다.

llm-langchain83-7.png

route함수는 방문판매원의 마지막 메시지에 'FINISH'가 포함되어 있으면 'finish'를, 그렇지 않으면 'continue'를 반환합니다.


▣ 상태그래프 정의

상태 그래프를 정의합니다.

llm-langchain83-8.png

여기에서는 Salesman 노드와 SHED노드를 추가하고 route함수를 사용하여 조건부 엣지를 추가합니다. route함수가 'continue'를 반환하면 SHED노드로 'finish'를 반환하면 END 노드로 전환합니다. SHED노드에서 무조건 Salesman노드로 전환합니다. 또한 진입점에 Salesman노드를 설정하고 그래프를 컴파일합니다.


▣ 상태그래프 실행

llm-langchain83-9.png

초기 상태로 빈 메시지를 전달하여 워크플로우를 실행합니다. 방문판매원과 주부가 대화를 반복하고 방문판매원이 영업이 종료한다고 판단한 결과시뮬레이션이 종료됩니다.

llm-langchain83-10.png

실행결과로부터 방문판매원과 주부가 번갈아 대화를 반복하고 방문판매원이 영업을 종료한다는 시나리오가 실현되고 있는 것을 알 수 있습니다.



③ 수직 아키텍처 구축: 에이전트 팀에 의한 소프트웨어 개발

LangGraph(랭그래프)를 사용하여 여러 에이전트가 협력하여 소프트웨어 개발을 수행하는 시나리오를 구현합니다. 프로젝트리더, 프로그래머, 테스트라이터, 평가자의 4개 에이전트가 등장해 각각의 역할을 담당해 프로젝트를 진행해 갑니다. 이는 리더가 다른 에이전트에게 지시를 내리고 지시에 따라 각 에이전트가 작동하는 수직 아키텍처의 예시입니다.


구현되는 소프트웨어는 HumanEval(M. Chen et al, 2021)에 나와 있는 코딩을 위한 벤치마크 데이터세트를 사용합니다. HumanEval은 Python 함수 시그니처와 함수사양을 설명하는 docstring을 프롬프트하여 함수구현을 생성하는 작업입니다. 아래 그림은 이 시나리오의 상태 그래프를 보여줍니다. Leader노드가 Programmer, TestWriter, Evaluator노드에 지시를 내리고 지시를 받은 각 노드가 처리됩니다.


Evaluator노드는 Tool노드를 호출하여 코드와 테스트를 평가합니다. 평가결과를 받으면 Leader노드가 Programmer, TestWriter에 수정을 지시하고 Evaluator에서 다시 평가하는 사이클을 반복합니다. 리더가 개발완료로 판단하면 종료노드로 전환합니다.


llm-langchain84.png 소프트웨어 개발 워크플로우


멀티에이전트에 의한 소프트웨어 개발 구현(src/langgraph/coding.py)

llm-langchain84-1.png
llm-langchain84-2.png
llm-langchain84-3.png


▣ 모듈 가져오기

우선 필요한 모듈을 가져옵니다.

llm-langchain84-4.png

LangGraph(랭그래프)나 LangChain(랭체인)외에 subprocess모듈이나 jsonschema모듈등을 추가하고 있습니다. subprocess모듚ㄹ은 외부프로세스를 실행하기 위해 jsonxschema모듈을 사용하여 JSON데이터의 유효성검사에 사용합니다. ToolNode는 LangGraph의 사전정의된 노드 중 하나이며 도구를 호출하는 노드입니다. 이전에는 tool_node를 직접 정의했지만 여기에서는 동등한 기능을 가진 TooNode를 사용합니다.


▣ 도구 정의

코드와 테스트를 평가하는 도구를 정의합니다.

llm-langchain84-5.png

여기서는 @tool데코레이터를 사용하여 evalute함수를 도구로 정의합니다. evaluate함수는 인수로 코드와 테스트를 문자열로 받아 각 파일을 내보내고 pytest를 실행합니다. pytest의 실행결과인 표준출력과 표준오류를 반환합니다. pytest는 Python 단위테스트 프레임워크입니다.


▣ 상태정의

다음 AgentState형을 정의하여 에이전트 상태를 관리합니다.

llm-langchain84-6.png

messages필드는 에이전트간의 메시지 교환을 저장하는데 사용됩니다. Annotated를 사용하여 opertator.add를 지정하여 업데이트시 메시지를 추가하도록 지정합니다. 다음 필드는 다음 작업을 수행할 에이전트를 지정하는데 사용됩니다. task필드는 개발작업의 내용을 저장하는데 사용됩니다.


▣ 에이전트 정의

각 에이전트에 대한 프롬프트 템플릿과 에이전트를 생성하는 함수를 정의합니다. 우선 리더의 출력을 정의하는 LeaderResponse클래스를 정의합니다.

llm-langchain84-7.png

reasoning 필드는 리더의 의사결정 이유를 나타냅니다. 다음 필드는 다음 작업을 수행할 에이전트를 지정합니다. 'Finish'가 지정되면 개발완료를 나타냅니다. instructions필드는 다음 에이전트에 대한 지시내용을 나타냅니다. 이와 같이 리더의 출력을 구조화해 두는 것으로 리더의 지시를 프로그램으로 용이하게 취급할 수 있습니다. 그런 다음 각 에이전트의 역할을 정의합니다.

llm-langchain84-8.png

MEMBERS는 각 에이전트의 이름과 역할을 정의한 사전형입니다. 이 정보는 프롬프트 템플릿을 생성하는데 사용합니다. 그런 다음 각 에이전트에 대한 프롬프트 템플릿을 생성하는 create_agent함수를 정의합니다.

llm-langchain84-9.png

create_agent함수는 LLM과 에이전트명을 인수로 사용하여 해당 에이전트에 대한 프롬프트 템플릿을 생성합니다. SystemMessagePromptTemplate, HumanMessagePromptTemplate, MessagesPlaceholder를 결합하여 프롬프트 템플릿을 정의합니다. SystemMessagePromptTemplate은 에이전트역할, 개발목표, 코드 및 테스트를 저장할 위치등을 지정합니다. HumanMessagePromptTemplate은 개발작업 내용을 지정합니다. MessagesPlaceholder는 대화기록을 지정합니다. 마지막으로 LLM과 프롬프트 템플릿을 결합한 체인을 에이전트로 반환합니다. 그런 다음 에이전트를 생성합니다.

llm-langchain84-10.png

여기서는 LLM으로 gpt-4o를 사용합니다. 또한, leader_agent가 사용하는 LLM은 LeaderResponse클래스를 사용하여 구조화된 출력을 수행하도록 설정합니다. 또한, evaluator_agent가 사용하는 LLM은 evalute도구를 사용할 수 있도록 설정합니다.


▣ 노드함수 정의

각 에이전트의 노드함수를 정의합니다. leader_node함수는 리더노드함수입니다.

llm-langchain84-11.png

leader_node함수는 상태를 받고, leader_agent를 호출하여 다음 표시를 생성합니다. 생성된 표시를 HumanMessage로 반환한 다음 작업을 수행할 에이전트를 다음 필드로 설정합니다. member_node 함수는 리더 이외의 에이전트 노드 함수입니다.

llm-langchain84-12.png

member_node함수는 for문을 사용하여 에이전트 출력이 활성화될 때까지 10번을 상한으로 처리를 반복합니다. LLM은 올바른 도구호출을 수행한다는 보장을 제공하지 않습니다. 잘못된 인수가 전달되면 제대로 도구를 호출할 수 없습니다. 이러한 경우를 고려해, 도구 호출에 대해서 밸리데이션을 실시하고 있습니다. 에이전트가 잘못된 도구를 호출하거나 도구 인수의 유효성검사에 실패하면 오류 메시지를 출력하고 에이전트를 다시 호출합니다. member_node함수는 유효한 출력이 얻어지면 그 결과를 AIMessage로 반환합니다.

다음 코드의 programmer_node, tester_node, evaluator_node는 member_node함수를 부분적으로 적용하여 생성됩니다.

llm-langchain84-13.png

▣ 조건검사 함수 정의

router는 워크플로우의 다음단계를 결정하는 조건검사 함수입니다. 평가자가 도구를 사용할지 여부를 결정합니다.

llm-langchain84-14.png

router함수는 상태를 받고 마지막 메시지가 도구호출을 포함하면 'call_tool'을 반환하고, 그렇지 않으면 'continue'를 반환합니다.


▣ 워크플로우 정의

마지막으로 StateGraph를 사용하여 워크플로우를 정의합니다.

llm-langchain84-15.png

워크플로우 정의에서는 각 노드를 add_node메소드로 추가하고 노드간의 전이를 add_edge메소드와 add_conditional_edges메소드로 정의합니다. add_conditional_edges메소드를 사용하여 조건에 따라 전환대상을 전환할 수 있습니다. 예를 들어, Evaluator노드는 router함수 결과에 따라 Leader노드 또는 Tool노드로 전환합니다. 리더노드에서는 다음 필드의 값에 따라 Programmer, TestWriter, Evaluator노드 중 하나로 전환됩니다. 'Finish'가 지정되면 종료노드로 전환합니다. 마지막 set_entry_point메소드에서 Leade노드를 진입점으로 설정합니다.


▣ 워크플로우 실행

워크플로우를 실행하려면 compile메소드로 워크플로우를 컴파일하고 invoke메소드에서 초기상태를 전달하고 실행합니다.

llm-langchain84-16.png

여기서는 HumanEval.jsonl이라는 JSON파일에서 첫번째 항목을 읽고 프롬프트를 작업으로 설정합니다. 초기상태로 빈 메시지 목록과 작업을 전달하여 워크플로우를 실행하고 최종 상태를 출력합니다.

이 프로그램을 실행하면 평가자가 마지막으로 검증한 코드와 테스트가 각각 product.py, test_product.py에 저장됩니다. 또한 팀대화는 다음과 같습니다.

llm-langchain84-17.png

여기에서는 리더가 프로그래머에게 코드 구현을 요구해, 테스트라이터에게 테스트케이스의 작성을 요구합니다. 그런 다음 평가자는 도구를 사용하여 코드와 테스트를 평가하고 결과를 리더에게 보고합니다. 이 경우, 첫번째 테스트에서 성공했지만 실패한 경우 테스트를 통과할 때까지 팀에서 수정을 반복할 수 있습니다.

이 프로그램은 마지막으로 함께 메시지를 출력하기 떄문에 출력을 얻는데 약간의 시간이 걸립니다. 또한 에이전트 동작이 안정적이지 않을 수 있습니다.


정상적인 실행결과를 얻을 수 없는 경우 결제 상황에 주의하고 프로그램을 다시 실행해 보십시오. 디렉토리에 올바른 구현 product.py, test_product.py가 저장되어 있으면 정상입니다. 또한, 프로그램 중의 프롬프트를 한국어로 쓰고 있으므로, 에이전트도 한국어로 출력될 것이라고 기대합니다. 그러나, 상담원간의 대화가 영어로 될 수도 있습니다. 그런 경우, '당신은 한국인입니다'라고 하는 역할 또는 페르소나 '반드시 한국어를 사용해 주세요'라는 제약을 프롬프트로 지정하는 것으로 한국어 출력이 쉬워집니다.



©2024-2025 GAEBAL AI, Hand-crafted & made with Damon Jaewoo Kim.

GAEBAL AI 개발사: https://gaebalai.com

AI 강의 및 개발, 컨설팅 문의: https://talk.naver.com/ct/w5umt5


keyword
이전 22화21. 에이전트와 멀티에이전트 아키텍처