원문: Building LangGraph: Designing an Agent Runtime from first principles

2025년 9월 4일

작성자: Nuno Campos

요약: 우리는 약 2년 전에 LangGraph를 저수준 에이전트 프레임워크로 출시했으며, LinkedIn, Uber, Klarna와 같은 기업들이 이미 이를 사용하여 프로덕션 환경에 배포 가능한 에이전트를 구축하는 것을 목격했습니다. LangGraph는 매우 인기 있는 LangChain 프레임워크의 피드백을 기반으로 구축되었으며, 프로덕션 환경을 위한 에이전트 프레임워크가 어떻게 작동해야 하는지를 재고했습니다. 우리는 AI 에이전트를 위한 올바른 추상화를 찾고자 했으며, 그것은 추상화가 거의 없거나 전혀 없는 것이라고 결론 내렸습니다. 대신, 우리는 제어와 내구성에 집중했습니다. 이 글은 안정적인 에이전트 구축에 대해 배운 내용을 바탕으로 LangGraph를 설계한 우리의 설계 원칙과 접근 방식을 공유합니다.

우리는 2년 전에 LangChain의 매우 인기 있는 체인과 에이전트를 재부팅하는 것으로 LangGraph를 시작했습니다. 처음부터 새롭게 시작하는 것이 원래의 langchain 오픈소스 라이브러리 출시 이후 받은 모든 피드백(수많은 GitHub 이슈, 토론, Discord, Slack, Twitter 게시물에서)을 해결하는 데 가장 많은 자유를 줄 것이라고 판단했습니다. 우리가 듣게 된 주요 의견은 langchain이 시작하기는 쉽지만 커스터마이징과 확장이 어렵다는 것이었습니다.

이번에는 LangGraph를 프로덕션 환경에서 에이전트를 실행하는 데 사용할 수 있도록 만드는 것이 최우선 목표였습니다. 절충안을 마련해야 할 때, 우리는 사람들이 시작하기 쉬운 정도보다 프로덕션 준비성을 우선시했습니다.

이 글에서는 LangGraph의 범위를 설정하고 설계한 과정을 공유하겠습니다.

  • 첫째: 전통적인 소프트웨어와 비교하여 에이전트를 구축하는 것이 어떻게 다른지 다룹니다.
  • 둘째: 이러한 차이점을 어떻게 필수 기능으로 전환했는지 논의합니다.
  • 마지막으로: 이러한 요구사항에 맞게 프레임워크를 어떻게 설계하고 테스트했는지 보여줍니다.

그 결과는 에이전트의 규모와 처리량 모두에서 확장 가능한 저수준의 프로덕션 준비 완료된 에이전트 프레임워크입니다.

1. 에이전트에는 무엇이 필요한가?

우리가 먼저 던진 두 가지 질문은 “실제로 LangGraph를 구축해야 하는가?”와 “왜 기존 프레임워크를 사용하여 에이전트를 프로덕션에 배포할 수 없는가?”였습니다. 이러한 질문에 답하기 위해 에이전트가 이전 소프트웨어와 다른 점(또는 유사한 점)을 정의해야 했습니다. 우리 스스로 많은 에이전트를 구축하고 Uber, LinkedIn, Klarna, Elastic과 같은 팀들과 협업하면서, 우리는 이를 3가지 핵심 차이점으로 압축했습니다.

  • 더 많은 지연 시간은 사용자의 참여를 유지하기 어렵게 만듭니다
  • 실패한 장시간 작업을 재시도하는 것은 비용이 많이 들고 시간이 오래 걸립니다
  • AI의 비결정적 특성은 체크포인트, 승인, 테스트를 필요로 합니다

지연 시간 관리

LLM 기반 에이전트의 첫 번째 정의적 특성이자 과제는 지연 시간입니다. 우리는 과거에 백엔드 엔드포인트의 지연 시간을 밀리초 단위로 측정했습니다. 이제는 에이전트 실행 시간을 초, 분 또는 곧 시간 단위로 측정해야 합니다.

이는 LLM 자체가 느리고 테스트 시간 컴퓨팅으로 인해 더 느려지고 있기 때문입니다. 또한 루프 에이전트와 이전 출력을 수정하기 위한 LLM 프롬프트 체이닝을 통해 원하는 결과를 얻기 위해 여러 번의 LLM 호출이 필요하기 때문이기도 합니다. 그리고 일반적으로 LLM 호출 전후에 비LLM 단계를 추가해야 합니다. 예를 들어, 컨텍스트에 데이터베이스 행을 가져오거나 LLM 호출의 정확성을 확인하기 위한 가드레일과 검증기를 만들어야 할 수 있습니다.

이 모든 지연 시간은 새로운 것을 구축할 수 있게 해주지만, 여전히 최종 사용자의 참여를 유지해야 합니다. 따라서 우리는 에이전트를 구축할 때 유용할 두 가지 기능을 식별했습니다:

  • 병렬화. 에이전트에 여러 단계가 있을 때, 다음 단계가 이전 단계의 출력을 필요로 하지 않는 경우, 병렬로 실행할 수 있습니다. 하지만 프로덕션에서 안정적으로 이를 수행하려면 병렬 단계 간의 데이터 경합을 피해야 합니다.
  • 스트리밍. 더 나쁜 결과를 생성하지 않으면서 에이전트의 실제 지연 시간을 더 이상 줄일 수 없을 때는 체감 지연 시간으로 전환합니다. 여기서 핵심적인 해결책은 에이전트가 실행되는 동안 사용자에게 유용한 정보를 보여주는 것인데, 이는 진행 표시줄부터 에이전트가 수행한 주요 작업, 실시간으로 최종 사용자에게 토큰별로 LLM 메시지를 스트리밍하는 것까지 다양합니다.

안정성 관리

LLM 에이전트의 느림은 다른 의미도 가지고 있었습니다. 우리 모두가 너무나 잘 알고 있듯이, 모든 소프트웨어는 필연적으로 버그가 발생합니다. 장시간 실행되는 에이전트는 더 자주 실패하는데, 이는 실행 시간이 길수록 문제가 발생할 기회가 더 많기 때문입니다.

전통적인 소프트웨어에서 버그가 발생하면 일반적으로 재시도하기를 원합니다. AI 에이전트의 경우는 어떨까요? 그것이 최선의 접근 방식이 아닐 수 있습니다. 에이전트가 10분 중 9분째에 실패한다면, 처음으로 돌아가는 것은 상당히 시간이 많이 걸리고 비용도 많이 듭니다.

따라서 우리는 목록에 두 가지 기능을 더 추가해야 한다는 것을 알았습니다:

  • 작업 큐. 큐는 에이전트의 실행을 트리거한 요청으로부터 분리함으로써 하나의 일반적인 실패 원인을 제거합니다. 필요할 때 에이전트를 안정적이고 공정하게 재시도할 수 있는 기본 요소를 제공합니다.
  • 체크포인팅. 이는 중간 단계에서 계산 상태의 스냅샷을 저장하여 실패 시 재시도하는 비용을 훨씬 저렴하게 만듭니다.

비결정적 LLM 관리

다음으로, LLM의 비결정적 특성은 두 가지 추가 과제를 만듭니다. 전통적인 소프트웨어를 작성할 때는 적어도 코드가 무엇을 해야 하는지, 의도한 대로 구축했다면 어떤 결과가 나와야 하는지 알고 있습니다. 생성형 AI는 분명히 이를 바꿉니다.

LLM의 경우, 입력과 출력 모두 개방형입니다. ChatGPT를 사용할 때를 상상해보면, 하루 전에 사용한 것과 동일한 프롬프트가 다른 결과를 생성하거나, 여러 가지 다른 방식으로 질문을 하고 유사한 결과를 얻는 것이 얼마나 쉬운지 알 수 있습니다.

이것이 LLM 에이전트가 이전의 다른 소프트웨어에 비해 매우 강력한 이유의 큰 부분이지만, 프로덕션으로 가져갈 때 과제를 도입하기도 합니다.

개발하는 동안 수행하는 테스트는 사용자가 에이전트를 사용할 놀라운 방식의 많은 부분을 거의 확실하게 놓칠 것입니다. 사용자가 에이전트와 상호 작용하는 모든 방식이나 LLM이 어떻게 응답할지 계획할 수 없습니다. 따라서 프로덕션으로 갈 때 두 가지 기능이 매우 유용해집니다:

  • Human-in-the-loop. 그때까지 수행된 작업을 다시 수행할 필요 없이 언제든지 에이전트를 중단하고 재개할 수 있는 도구는 AI 에이전트를 위한 많은 필수 UX 패턴을 가능하게 합니다. 예를 들어, 작업을 승인하거나 거부하고, 다음 작업을 편집하고, 명확한 질문을 하거나, 심지어 시간 여행을 통해 이전 단계에서 다시 수행할 수 있습니다.
  • 추적. 규모에 맞게 구축하려면 개발자는 에이전트 루프의 세부 사항에서 무슨 일이 일어나고 있는지 명확하게 볼 수 있어야 합니다. 에이전트의 입력, 궤적, 출력을 볼 수 있어야 합니다. 그렇지 않으면 사용자가 무엇을 요청하는지, 에이전트가 어떻게 처리하는지, 사용자가 결과에 만족하는지 알 수 없습니다.

개발자가 에이전트를 구축하는 데 필요한 것

이것이 우리가 에이전트를 프로덕션으로 가져갈 때 대부분의 개발자가 필요로 하는 6가지 기능의 후보 목록을 만든 방법입니다.

  • 병렬화 – 실제 지연 시간을 절약하기 위해
  • 스트리밍 – 체감 지연 시간을 절약하기 위해
  • 작업 큐 – 재시도 횟수를 줄이기 위해
  • 체크포인팅 – 각 재시도의 비용을 줄이기 위해
  • Human-in-the-loop - 사용자와 협업하기 위해
  • 추적 - 사용자가 어떻게 사용하는지 배우기 위해

구축하려는 에이전트가 이러한 기능의 대부분을 필요로 하지 않는다면(예: 도구가 없고 단일 프롬프트만 있는 매우 짧은 에이전트인 경우), LangGraph나 다른 프레임워크가 필요하지 않을 수 있습니다.

이러한 각 기능을 위해 구축하는 것을 고려하면서, 우리는 또한 개발자들이 이러한 모든 기능을 제공하되 최종 사용자에게 LLM 앱을 눈에 띄게 느리게 만드는 대가를 치르는 프레임워크를 채택하지 않을 것임을 깨달았습니다. 특히 챗봇으로 배포되는 에이전트의 경우 더욱 그렇습니다. 그래서 낮은 지연 시간이 우리의 최종적인 포괄적 요구사항이 되었습니다.

다음으로, 우리가 이러한 기능을 LangGraph에 어떻게 구축했는지 다루겠습니다.

2. 왜 LangGraph를 구축해야 했는가?

우리의 실존적 질문으로 돌아가서, 새로운 것을 구축해야 하는가, 아니면 LLM 이전에 구축된 기존 오픈소스 프레임워크 중 하나를 채택해야 하는가? 기능 후보 목록을 갖추고 나니 그 결정을 내리기가 상당히 쉬워졌습니다.

왜 새로운 프레임워크가 필요했는가?

기존 프레임워크는 대부분 두 가지 범주로 나뉘었습니다:

DAG 프레임워크 (Apache Airflow 등으로 대중화됨)

이것들은 이름만으로도 제외해야 했습니다. LLM 에이전트는 실제로 루핑의 이점을 받기 때문입니다. 즉, LLM 에이전트의 계산 그래프는 순환적이므로 DAG 알고리즘으로 처리할 수 없습니다.

내구성 있는 실행 엔진 (Temporal 등으로 대중화됨)

이러한 옵션은 더 가까웠지만, 결국 LLM 에이전트 이전에 설계되었기 때문에 특정 기능, 즉 스트리밍이 부족했습니다. 또한 이러한 엔진은 단계 사이에 지연 시간을 도입했는데, 이는 챗봇 개발자에게 눈에 띄었을 것입니다. 마지막으로, 설계상 히스토리에 더 많은 단계가 있을수록 성능이 저하되는데, 이는 LLM 에이전트가 더 길고 복잡해짐에 따라 나쁜 선택처럼 들렸습니다.

결국 우리의 답은, 예, LLM은 이전의 프로덕션 인프라가 새로운 시대에 적합해지기 위해 새로운 아이디어를 주입받아야 할 만큼 충분히 다르다는 것이었습니다. 그래서 우리는 LangGraph를 구축하기 시작했습니다.

3. 우리의 설계 철학

우리는 두 가지 주요 원칙을 가지고 LangGraph를 설계했습니다.

  • 우리는 AI의 미래가 어떨지 모릅니다. 미래에 대해 적게 가정할수록 좋습니다. 지금부터 1년, 2년, 3년 후에 LLM으로 구축하는 것이 어떨지 아무도 정말 모르기 때문에, 프레임워크의 설계에 가정을 적게 반영할수록 미래에 더 관련성이 있을 것입니다. 우리가 반영하고 싶었던 유일한 가정은 위에서 이야기한 깨달음, 즉 LLM은 느리고, 불안정하며, 개방형이라는 것이었습니다.
  • 코드를 작성하는 것처럼 느껴져야 합니다. 프레임워크의 공개 API는 일반적인 프레임워크 없는 코드를 작성하는 것과 최대한 가까워야 합니다. 개발자의 코드에 부과하는 모든 요구사항은 정말 가치 있는 기능을 활성화하는 것으로 정당화되어야 합니다. 그렇지 않으면 프레임워크를 완전히 건너뛰고자 하는 유혹이 너무 강합니다. 모든 코드 프레임워크의 가장 큰 경쟁자는 항상 프레임워크가 없는 것입니다.

이러한 원칙은 그 이후로 우리와 함께해온 몇 가지 핵심 설계 결정에 영향을 미쳤습니다.

  • 첫째, 라이브러리의 런타임은 개발자 SDK와 독립적입니다. SDK는 개발자가 에이전트를 구축할 때 사용하는 공개 인터페이스(클래스, 함수, 메서드, 상수 등)입니다. 현재 우리는 두 가지를 제공합니다 – StateGraph명령형/함수형 API. 런타임(우리가 PregelLoop이라고 부르는)은 앞서 나열한 각 기능을 구현하고, 각 에이전트 호출에 대한 계산 그래프를 계획하고, 이를 실행합니다. 이 설계는 개발자 API와 런타임을 독립적으로 발전시킬 수 있게 해줍니다. 예를 들어, SDK 측면에서는 명령형 SDK를 도입하고, 공유 상태가 없는 Graph 인터페이스라는 우리가 제공한 첫 번째 SDK를 폐기할 수 있었습니다. 런타임 측면에서는 지난 2년 동안 공개 API에 영향을 주지 않고 많은 성능 개선을 구현할 수 있었고, 런타임에 대한 더 급진적인 변경 실험을 가능하게 했습니다 – 분산 실행을 다룰 때 이에 대해 더 자세히 설명하겠습니다.
  • 둘째, 우리는 6가지 기능 각각을 빌딩 블록으로 제공하고, 개발자가 언제든지 에이전트에서 사용할 것을 자유롭게 선택할 수 있도록 하고 싶었습니다. 예를 들어, human-in-the-loop 시나리오를 위한 중단/재개 기능은 이를 사용하기 전까지는 방해가 되지 않습니다(노드 중 하나에 interrupt() 함수 호출을 추가하는 것만큼 쉽습니다). 그래서 LangGraph는 개발자들이 올해의 고수준 추상화에 모든 것을 걸도록 설득하려는 다른 프레임워크들의 바다에서 독특하게 저수준 프레임워크로 자리 잡았습니다. 우리는 이러한 것들이 왔다가 가는 것을 보았고, LangGraph는 여전히 관련성을 유지하고 있어서 지금까지의 접근 방식에 만족합니다.

4. LangGraph 런타임

이 모든 것을 염두에 두고, LangGraph가 우리가 원했던 6가지 기능(병렬화, 스트리밍, 체크포인팅, human-in-the-loop, 추적 및 작업 큐)을 각각 어떻게 구현하는지 살펴보겠습니다.

개별 단계를 가진 구조화된 에이전트

우리가 내린 다른 모든 아키텍처 결정을 알려주는 하나의 아이디어가 있다면, 그것은 구조화된 에이전트라는 아이디어입니다. 컴퓨터 프로그램에 더 많은 구조를 추가하는 오랜 전통이 있으며, 어느 정도의 유연성을 새로운 기능들로 교환합니다. 옛날에는 if 문과 while 루프와 같은 기본 구조도 새로운 것이었습니다. 에이전트도 하나의 큰 while 루프가 있는 단일 함수로 직접 작성할 수 있습니다. 하지만 그렇게 하면 체크포인팅이나 human-in-the-loop와 같은 기능을 구현할 수 있는 능력을 잃게 됩니다. (참고: 제너레이터와 같은 일부 종류의 서브루틴의 실행을 중단하는 것이 기술적으로 가능할 수 있지만, 해당 실행 상태는 다른 시간에 다른 머신에서 재개할 수 있는 이식 가능한 형식으로 저장할 수 없습니다.)

실행 알고리즘

에이전트를 여러 개의 개별 단계로 구조화하기로 선택하면, 실행을 구성하기 위한 알고리즘을 선택해야 합니다. “알고리즘이 없는” 것처럼 느껴지는 순진한 알고리즘이더라도, 이것이 LangGraph가 출시 전에 시작한 곳입니다. “알고리즘이 없는” 것을 사용하는 문제는, 더 간단해 보일 수 있지만, 진행하면서 만들어가게 되고 예상치 못한 결과를 얻게 된다는 것입니다. (예를 들어, LangGraph의 초기 버전은 동시 노드에서 비결정적 동작을 겪었습니다). 루프가 필요하다는 점을 고려하면 일반적인 DAG 알고리즘(위상 정렬 등)은 그림에서 벗어납니다. 우리는 BSP/Pregel 알고리즘을 기반으로 구축하기로 결정했는데, 이는 루프(순환)를 완전히 지원하면서 결정적 동시성을 제공하기 때문입니다.

우리의 실행 알고리즘은 다음과 같이 작동합니다:

  • 채널은 데이터(모든 Python/JS 데이터 타입)를 포함하며, 이름과 현재 버전(단조 증가하는 문자열)을 가집니다
  • 노드는 실행할 함수이며, 하나 이상의 채널을 구독하고, 변경될 때마다 실행됩니다
  • 하나 이상의 채널이 입력에 매핑됩니다. 즉, 에이전트에 대한 시작 입력이 해당 채널에 작성되므로 구독하는 모든 노드를 트리거합니다
  • 하나 이상의 채널이 출력에 매핑됩니다. 즉, 실행이 중단될 때 에이전트의 반환 값은 해당 채널의 값입니다

실행은 루프로 진행되며, 각 반복은

  • 현재 채널 버전과 각 구독자가 마지막으로 본 버전을 비교하여 실행할 1개 이상의 노드를 선택합니다
  • 채널 값의 독립적인 복사본으로 해당 노드를 병렬로 실행합니다(즉, 상태이므로 실행 중에 서로 영향을 주지 않습니다)
  • 노드는 실행 중에 상태의 로컬 복사본을 수정합니다
  • 모든 노드가 완료되면 상태의 각 복사본에서 업데이트가 결정적 순서로(이것이 데이터 경합이 없음을 보장하는 것입니다) 각 채널에 적용되고 채널 버전이 증가합니다

실행 루프는 더 이상 실행할 노드가 없을 때(즉, 채널을 구독과 비교한 후 모든 노드가 구독한 채널의 가장 최신 버전을 본 것을 발견) 또는 반복 단계가 부족할 때(개발자가 설정할 수 있는 상수) 중단됩니다.

프레임워크 기능 검증

이것이 우리가 구현하고자 했던 6가지 기능에 어떻게 매핑되는지 살펴보겠습니다.

  • 병렬화. 이 알고리즘은 데이터 경합 없이 안전한 병렬화를 위해 설계되었으며, 노드 간의 의존성이 허용할 때마다 자동으로 병렬 실행을 선택하고, 격리된 상태 복사본으로 병렬 노드를 실행하며, 어느 노드가 먼저 시작했는지 또는 먼저 완료했는지에 의존하지 않는 순서로 노드의 업데이트를 적용합니다(실행 간에 변경될 수 있기 때문입니다). 이는 각 노드의 실행 순서와 지연 시간이 에이전트의 최종 출력에 영향을 미치지 않도록 보장합니다. LLM이 비결정적이라는 점을 고려할 때, 우리는 이것이 중요한 속성이라고 느꼈습니다. 출력의 가변성이 에이전트 프레임워크의 잘못이 아니도록 보장하여 문제를 훨씬 쉽게 디버그할 수 있도록 합니다.
  • 스트리밍. 구조화된 실행 모델(즉, 계산이 개별 단계 및/또는 노드로 분할되는 경우)은 전체적으로 중간 출력과 업데이트를 방출할 수 있는 훨씬 더 많은 기회를 제공합니다. 우리의 실행 엔진은 사용자 정의 개발자 코드가 필요 없이 노드가 실행되는 동안과 단계 경계에서 스트리밍 출력을 수집합니다. 이를 통해 LangGraph에서 6가지 고유한 스트림 모드(values, updates, messages, tasks, checkpoints, custom)를 제공할 수 있었습니다. 스트리밍 챗봇은 messages 스트림 모드를 사용할 수 있고, 더 오래 실행되는 에이전트는 updates 모드를 사용할 수 있습니다.
  • 체크포인팅. 다시 말하지만, 구조화된 실행이 이를 가능하게 합니다. 우리는 모든 머신에서 재개할 수 있는 체크포인트를 저장하고자 했으며, 저장된 후 임의의 시간이 지난 후에도 재개할 수 있도록 했습니다. 즉, 특정 머신에서 프로세스를 실행 상태로 유지하거나 메모리에 라이브 데이터를 유지하는 것에 의존하지 않는 체크포인트입니다. 이를 가능하게 하기 위해 우리는 직렬화된 채널 값(기본적으로 MsgPack으로 직렬화되며 선택적으로 암호화됨), 버전 문자열, 각 노드가 가장 최근에 본 채널 버전의 기록을 기록합니다.
  • Human-in-the-loop. 내결함성을 가능하게 하는 동일한 체크포인팅을 에이전트의 “예상된 중단”을 강화하는 데에도 사용할 수 있습니다. 즉, 에이전트가 계속하기 전에 사용자나 개발자의 입력을 요청하기 위해 스스로 중단할 수 있는 기능을 제공합니다. 일반적으로 이 기능은 입력이 도착하기를 기다리는 동안 에이전트를 실행 상태로 유지하는 방식으로 구현되지만, 안타깝게도 시간이나 볼륨 측면에서 확장되지 않습니다. 많은 에이전트가 동시에 중단되거나, 응답하기 전에 며칠(또는 몇 달!)을 기다리고 싶다면, 실제 중단(동일한 지점에서 다시 재개하기 위한 체크포인팅으로 강화됨)이 유일한 방법입니다.
  • 추적. 구조화된 실행을 사용하는 또 다른 좋은 속성은 실행 중과 사후에 에이전트의 진행 상황을 검사할 수 있는 매우 명확한 단계를 얻는다는 것입니다. 우리는 이전에 최초의 LLM 관찰 가능성 플랫폼으로 LangSmith를 구축했으므로 당연히 LangGraph는 기본적으로 이와 통합됩니다. 오늘날 우리는 에이전트가 실행되는 동안 디버그할 수 있는 LangGraph Studio도 보유하고 있으며, LangGraph는 더 넓은 호환성을 위해 OTEL 추적도 방출할 수 있습니다.
  • 작업 큐. 이는 LangGraph와 같은 Python 라이브러리의 범위를 벗어났으므로, 이 필요를 해결하기 위해 LangGraph Platform을 만들게 되었습니다.

전체적으로, 이 아키텍처는 에이전트에 필요한 6가지 핵심 기능을 제공합니다. 동시에 구조화된 접근 방식과 이를 탐색할 수 있는 도구 덕분에 에이전트를 만들고 디버깅하는 속도가 빨라집니다. 마지막으로, 에이전트의 크기와 프로덕션에서 필요한 처리량으로 확장되는 우수한 성능 프로필로 이를 수행합니다 – 다음 섹션에서 이에 대해 더 자세히 설명하겠습니다.

5. 성능 특성

앞서 언급했듯이, 개발자는 안정성을 원하지만 지연 시간을 희생하면서까지 원하지는 않습니다. 따라서 우리의 접근 방식이 이러한 절충안에 대해 어떻게 작동하는지 살펴봐야 합니다. LangGraph는 구축하는 에이전트의 모든 크기 측정값으로 매우 우아하게 확장됩니다. 이는 에이전트가 더 많은 단계, 더 많은 중단, 더 큰 상태 등으로 점점 더 길어지는 미래를 위한 훌륭한 위치입니다.

LangGraph 에이전트의 실행은 크기를 제어하는 주요 변수에 의해 어떻게 영향을 받을까요?

먼저, 가장 일반적인 LangGraph 개발자 SDK인 StateGraph의 주요 크기 변수를 나열해 보겠습니다:

  • 노드 수(개별 단계, 일반적으로 함수)
  • 엣지 수(또는 노드 간의 연결, 고정적이거나 조건부일 수 있음)
  • 채널 수(또는 상태 객체의 키)
  • 활성 노드 수(주어진 단계에서 병렬로 실행될)
  • 호출 히스토리 길이(현재 호출의 이전 단계)
  • 스레드 수(서로 다른 입력과 컨텍스트에 대한 독립적인 호출)

이제 LangGraph 에이전트의 호출에서 주요 순간을 나열하고, 각각이 각 변수와 어떻게 확장되는지 살펴보겠습니다:

  • 호출 시작 또는 재개: 해당 스레드에 대한 가장 최근 체크포인트를 스토리지에서 전송하고 역직렬화하는 것으로 구성됩니다
  • 다음 호출 단계 계획: 다음에 실행할 노드를 결정하고 입력을 준비합니다
  • 단계에 대한 활성 노드 실행: 각 노드에 대한 코드를 실행하여 채널과 엣지에 대한 쓰기를 생성합니다
  • 호출 단계 완료: 각 채널에 업데이트를 적용하고(채널 리듀서 실행 및 채널 버전 증가) 최신 체크포인트를 저장합니다(직렬화 및 스토리지로 전송)

계획 작업이 다음에 실행할 노드를 반환하지 않으면 실행이 단순히 중단되므로 ‘호출 완료’ 작업은 없습니다.

요약하면, 각 작업이 에이전트 크기와 어떻게 확장되는지는 다음과 같습니다:

메트릭 / 작업호출 시작단계 계획단계 실행단계 완료
노드 수O(n)O(1)O(1)O(n)
엣지 수O(1)O(1)O(n)O(1)
채널 수O(n)O(n)O(n)O(n)
활성 노드O(1)O(n)O(n)O(n)
히스토리 길이O(1)O(1)O(1)O(1)
스레드 수O(1)O(1)O(1)O(1)

이제 이 중 일부를 더 자세히 살펴보겠습니다. 먼저, 호출 시작:

  • 노드 수에 선형적으로 확장: 각 노드에 대해 들어오는 엣지의 현재 상태를 보유하는 하나의 숨겨진 제어 채널이 있습니다
  • 엣지 수에 상수: 각 대상 노드에 대한 모든 엣지의 상태가 단일 제어 채널로 축소됩니다
  • 채널 수에 선형적으로 확장: 각 채널에 대해 현재 값의 직렬화된 표현이 있습니다
  • 활성 노드 수에 상수: 이 변수와 관련이 없습니다
  • 히스토리 길이에 상수: 최신 체크포인트만 가져오고 이전 단계를 재생할 필요가 없습니다
  • 스레드 수에 상수: 스레드는 완전히 독립적이며 각 호출은 단일 스레드만 접촉합니다

둘째, 다음 단계 계획:

  • 노드 수에 상수: 이전 단계를 완료할 때 업데이트된 채널 목록을 저장하므로 다음 단계를 계획할 때 모든 노드를 반복할 필요가 없습니다
  • 엣지 수에 상수: 모든 엣지가 노드당 단일 트리거 채널로 축소됩니다
  • 채널 수에 선형적으로 확장: 각 노드에 대한 입력을 조합할 때 현재 설정된 채널을 확인하기 위해 채널을 반복합니다
  • 활성 노드 수에 선형적으로 확장: 이 단계에서 실행할 각 노드에 대해 호출에 사용할 입력과 구성을 조합합니다
  • 히스토리 길이에 상수: 모든 이전 쓰기를 집계하는 최신 체크포인트만 다룹니다
  • 스레드 수에 상수: 스레드는 완전히 독립적이며 각 호출은 단일 스레드만 접촉합니다

셋째, 단계 실행:

  • 노드 수에 상수: 단계에서 활성 상태인 노드만 해당 단계의 실행에 영향을 미칩니다
  • 엣지 수에 선형적으로 확장: 이 단계에서 활성 상태인 노드의 엣지, 각 활성 노드는 각 나가는 엣지에 게시합니다
  • 채널 수에 선형적으로 확장: 각 활성 노드에 대해 노드가 값에 대한 업데이트를 반환했는지 확인합니다(딕셔너리 반환 값을 사용할 때 채널 수에 상수가 되도록 최적화하고 반환 값의 키만 반복합니다)
  • 활성 노드 수에 선형적으로 확장: 각 활성 노드는 동시에 실행됩니다
  • 히스토리 길이에 상수: 이 시점에 히스토리를 다루지 않습니다
  • 스레드 수에 상수: 스레드는 완전히 독립적이며 각 호출은 단일 스레드만 접촉합니다

마지막으로, 단계 완료:

  • 노드 수에 선형적으로 확장: 각 노드에 대해 들어오는 엣지의 현재 상태를 보유하는 하나의 숨겨진 제어 채널이 있습니다
  • 엣지 수에 상수: 각 대상 노드에 대한 모든 엣지의 상태가 단일 제어 채널로 축소됩니다
  • 채널 수에 선형적으로 확장: 각 채널은 활성 노드의 쓰기로 업데이트되고 버전이 증가합니다
  • 활성 노드 수에 선형적으로 확장: 각 활성 노드에서 쓰기를 수집합니다
  • 히스토리 길이에 상수: 모든 이전 쓰기를 집계하는 최신 체크포인트만 다룹니다
  • 스레드 수에 상수: 스레드는 완전히 독립적이며 각 호출은 단일 스레드만 접촉합니다

이러한 성능 특성은 라이브러리에 대한 설계 선택과 지난 2년 동안 수행한 수많은 성능 최적화의 결과입니다.

시작하기

요약하자면, 우리는 LLM으로 구축하는 것이 무엇이 다른지, 에이전트를 프로덕션에서 실행하기 위해 무엇이 필요한지에 대해 깊이 생각했습니다. 이러한 아이디어는 우리가 LangGraph를 구축하고 반복하도록 이끌었습니다. LangGraph는 제어와 내구성에 초점을 맞추므로 에이전트가 의도한 대로 작동할 가능성이 가장 높습니다.

LangGraph에 대해 더 알아보고 자신의 프로젝트를 위해 테스트해보고 싶다면 문서로 이동하여 시작하십시오.