Graphs
LangGraph의 핵심은 에이전트 워크플로우를 그래프로 모델링하는 것입니다. 세 가지 주요 구성 요소를 사용하여 에이전트의 동작을 정의합니다:-
State: 애플리케이션의 현재 스냅샷을 나타내는 공유 데이터 구조입니다. 모든 데이터 타입이 될 수 있지만, 일반적으로 공유 state 스키마를 사용하여 정의됩니다. -
Nodes: 에이전트의 로직을 인코딩하는 함수입니다. 현재 state를 입력으로 받아 일부 계산이나 부수 효과를 수행하고, 업데이트된 state를 반환합니다. -
Edges: 현재 state를 기반으로 다음에 실행할Node를 결정하는 함수입니다. 조건부 분기 또는 고정 전환이 될 수 있습니다.
Nodes와 Edges를 구성하여 시간이 지남에 따라 state를 발전시키는 복잡하고 반복적인 워크플로우를 만들 수 있습니다. 그러나 진정한 힘은 LangGraph가 해당 state를 관리하는 방식에서 나옵니다. 강조하자면: Nodes와 Edges는 함수에 불과합니다 - LLM을 포함하거나 단순히 일반적인 코드를 포함할 수 있습니다.
간단히 말해: nodes는 작업을 수행하고, edges는 다음에 무엇을 할지 알려줍니다.
LangGraph의 기본 그래프 알고리즘은 message passing을 사용하여 일반적인 프로그램을 정의합니다. Node가 작업을 완료하면 하나 이상의 edge를 따라 다른 node(들)로 메시지를 보냅니다. 이러한 수신 node들은 함수를 실행하고, 결과 메시지를 다음 node 집합으로 전달하며, 프로세스가 계속됩니다. Google의 Pregel 시스템에서 영감을 받아, 프로그램은 개별적인 “super-steps”로 진행됩니다.
super-step은 그래프 node에 대한 단일 반복으로 간주될 수 있습니다. 병렬로 실행되는 node들은 동일한 super-step의 일부이며, 순차적으로 실행되는 node들은 별도의 super-step에 속합니다. 그래프 실행이 시작될 때 모든 node는 inactive 상태에서 시작합니다. node는 들어오는 edge(또는 “channels”) 중 하나에서 새 메시지(state)를 받으면 active 상태가 됩니다. 활성 node는 함수를 실행하고 업데이트로 응답합니다. 각 super-step이 끝날 때 들어오는 메시지가 없는 node들은 자신을 inactive로 표시하여 halt에 투표합니다. 모든 node가 inactive 상태이고 전송 중인 메시지가 없을 때 그래프 실행이 종료됩니다.
StateGraph
StateGraph 클래스는 사용할 주요 그래프 클래스입니다. 이것은 사용자 정의 State 객체로 매개변수화됩니다.
그래프 컴파일하기
그래프를 빌드하려면 먼저 state를 정의하고, nodes와 edges를 추가한 다음 컴파일합니다. 그래프를 컴파일한다는 것은 정확히 무엇이며 왜 필요할까요? 컴파일은 매우 간단한 단계입니다. 그래프 구조에 대한 몇 가지 기본 검사(고립된 node가 없는지 등)를 제공합니다. 또한 checkpointers 및 breakpoints와 같은 런타임 인수를 지정할 수 있는 곳이기도 합니다..compile 메서드를 호출하여 그래프를 컴파일합니다:
State
그래프를 정의할 때 가장 먼저 하는 일은 그래프의State를 정의하는 것입니다. State는 그래프의 스키마와 state에 업데이트를 적용하는 방법을 지정하는 reducer 함수로 구성됩니다. State의 스키마는 그래프의 모든 Nodes와 Edges에 대한 입력 스키마가 되며, TypedDict 또는 Pydantic 모델이 될 수 있습니다. 모든 Nodes는 State에 대한 업데이트를 내보내며, 이는 지정된 reducer 함수를 사용하여 적용됩니다.
Schema
그래프의 스키마를 지정하는 주요 문서화된 방법은TypedDict를 사용하는 것입니다. state에 기본값을 제공하려면 dataclass를 사용하세요. 재귀적 데이터 검증을 원하는 경우 Pydantic BaseModel을 그래프 state로 사용하는 것도 지원합니다(단, pydantic은 TypedDict 또는 dataclass보다 성능이 낮습니다).
기본적으로 그래프는 동일한 입력 및 출력 스키마를 갖습니다. 이를 변경하려면 명시적인 입력 및 출력 스키마를 직접 지정할 수도 있습니다. 이는 많은 키가 있고 일부는 명시적으로 입력용이고 다른 일부는 출력용일 때 유용합니다. 사용 방법은 여기 가이드를 참조하세요.
다중 스키마
일반적으로 모든 그래프 node는 단일 스키마와 통신합니다. 이는 동일한 state 채널에서 읽고 쓴다는 것을 의미합니다. 그러나 이에 대한 더 많은 제어가 필요한 경우가 있습니다:- 내부 node는 그래프의 입력/출력에 필요하지 않은 정보를 전달할 수 있습니다.
- 그래프에 대해 다른 입력/출력 스키마를 사용하고 싶을 수도 있습니다. 예를 들어 출력에는 단일 관련 출력 키만 포함될 수 있습니다.
PrivateState를 정의할 수 있습니다.
그래프에 대한 명시적인 입력 및 출력 스키마를 정의하는 것도 가능합니다. 이러한 경우 그래프 작업과 관련된 모든 키를 포함하는 “내부” 스키마를 정의합니다. 그러나 그래프의 입력 및 출력을 제한하기 위해 “내부” 스키마의 하위 집합인 input 및 output 스키마도 정의합니다. 자세한 내용은 이 가이드를 참조하세요.
예제를 살펴보겠습니다:
-
node_1에 입력 스키마로state: InputState를 전달합니다. 그러나OverallState의 채널인foo에 씁니다. 입력 스키마에 포함되지 않은 state 채널에 어떻게 쓸 수 있을까요? 이는 node가 그래프 state의 모든 state 채널에 쓸 수 있기 때문입니다. 그래프 state는 초기화 시 정의된 state 채널의 합집합이며, 여기에는OverallState와 필터InputState및OutputState가 포함됩니다. -
StateGraph(OverallState,input_schema=InputState,output_schema=OutputState)로 그래프를 초기화합니다. 그렇다면node_2에서PrivateState에 어떻게 쓸 수 있을까요?StateGraph초기화에 전달되지 않았는데 그래프가 이 스키마에 어떻게 액세스할 수 있을까요? state 스키마 정의가 존재하는 한 node는 추가 state 채널을 선언할 수도 있기 때문에 이렇게 할 수 있습니다. 이 경우PrivateState스키마가 정의되어 있으므로 그래프에bar를 새 state 채널로 추가하고 여기에 쓸 수 있습니다.
Reducers
Reducer는 node의 업데이트가State에 어떻게 적용되는지 이해하는 데 핵심입니다. State의 각 키는 자체적인 독립적인 reducer 함수를 가집니다. reducer 함수가 명시적으로 지정되지 않으면 해당 키에 대한 모든 업데이트가 이를 덮어쓴다고 가정합니다. 기본 reducer 타입부터 시작하여 몇 가지 다른 타입의 reducer가 있습니다:
기본 Reducer
다음 두 예제는 기본 reducer를 사용하는 방법을 보여줍니다: 예제 A:{"foo": 1, "bar": ["hi"]}. 그런 다음 첫 번째 Node가 {"foo": 2}를 반환한다고 가정해 봅시다. 이것은 state에 대한 업데이트로 처리됩니다. Node가 전체 State 스키마를 반환할 필요가 없다는 점에 유의하세요 - 업데이트만 반환하면 됩니다. 이 업데이트를 적용한 후 State는 {"foo": 2, "bar": ["hi"]}가 됩니다. 두 번째 node가 {"bar": ["bye"]}를 반환하면 State는 {"foo": 2, "bar": ["bye"]}가 됩니다.
예제 B:
Annotated 타입을 사용하여 두 번째 키(bar)에 대한 reducer 함수(operator.add)를 지정했습니다. 첫 번째 키는 변경되지 않은 상태로 유지됩니다. 그래프에 대한 입력이 {"foo": 1, "bar": ["hi"]}라고 가정해 봅시다. 그런 다음 첫 번째 Node가 {"foo": 2}를 반환한다고 가정해 봅시다. 이것은 state에 대한 업데이트로 처리됩니다. Node가 전체 State 스키마를 반환할 필요가 없다는 점에 유의하세요 - 업데이트만 반환하면 됩니다. 이 업데이트를 적용한 후 State는 {"foo": 2, "bar": ["hi"]}가 됩니다. 두 번째 node가 {"bar": ["bye"]}를 반환하면 State는 {"foo": 2, "bar": ["hi", "bye"]}가 됩니다. 여기서 bar 키는 두 리스트를 함께 추가하여 업데이트됩니다.
Graph State에서 Messages 작업하기
왜 messages를 사용하나요?
대부분의 최신 LLM 제공업체는 메시지 목록을 입력으로 받는 chat model 인터페이스를 가지고 있습니다. 특히 LangChain의ChatModel은 Message 객체 목록을 입력으로 받습니다. 이러한 메시지는 HumanMessage(사용자 입력) 또는 AIMessage(LLM 응답)와 같은 다양한 형태로 제공됩니다. message 객체가 무엇인지에 대한 자세한 내용은 이 개념 가이드를 참조하세요.
그래프에서 Messages 사용하기
많은 경우 그래프 state에 이전 대화 기록을 메시지 목록으로 저장하는 것이 유용합니다. 이를 위해Message 객체 목록을 저장하는 키(채널)를 그래프 state에 추가하고 reducer 함수로 주석을 달 수 있습니다(아래 예제의 messages 키 참조). reducer 함수는 각 state 업데이트(예: node가 업데이트를 보낼 때)와 함께 state의 Message 객체 목록을 업데이트하는 방법을 그래프에 알려주는 데 필수적입니다. reducer를 지정하지 않으면 모든 state 업데이트가 가장 최근에 제공된 값으로 메시지 목록을 덮어씁니다. 기존 목록에 메시지를 단순히 추가하려면 operator.add를 reducer로 사용할 수 있습니다.
그러나 그래프 state에서 메시지를 수동으로 업데이트하고 싶을 수도 있습니다(예: human-in-the-loop). operator.add를 사용하면 그래프에 보내는 수동 state 업데이트가 기존 메시지를 업데이트하는 대신 기존 메시지 목록에 추가됩니다. 이를 방지하려면 메시지 ID를 추적하고 업데이트된 경우 기존 메시지를 덮어쓸 수 있는 reducer가 필요합니다. 이를 달성하기 위해 사전 빌드된 add_messages 함수를 사용할 수 있습니다. 완전히 새로운 메시지의 경우 기존 목록에 단순히 추가하지만 기존 메시지에 대한 업데이트도 올바르게 처리합니다.
직렬화
메시지 ID를 추적하는 것 외에도add_messages 함수는 messages 채널에서 state 업데이트를 받을 때마다 메시지를 LangChain Message 객체로 역직렬화하려고 시도합니다. LangChain 직렬화/역직렬화에 대한 자세한 내용은 여기를 참조하세요. 이를 통해 다음 형식으로 그래프 입력/state 업데이트를 보낼 수 있습니다:
add_messages를 사용할 때 state 업데이트는 항상 LangChain Messages로 역직렬화되므로 state["messages"][-1].content와 같이 점 표기법을 사용하여 메시지 속성에 액세스해야 합니다. 다음은 add_messages를 reducer 함수로 사용하는 그래프의 예입니다.
MessagesState
state에 메시지 목록을 갖는 것이 매우 일반적이므로 메시지를 쉽게 사용할 수 있도록 하는MessagesState라는 사전 빌드된 state가 있습니다. MessagesState는 AnyMessage 객체 목록인 단일 messages 키로 정의되며 add_messages reducer를 사용합니다. 일반적으로 메시지 외에 추적할 state가 더 많으므로 다음과 같이 이 state를 서브클래스화하고 더 많은 필드를 추가하는 것을 볼 수 있습니다:
Nodes
LangGraph에서 node는 다음 인수를 받는 Python 함수(동기 또는 비동기)입니다:state: 그래프의 stateconfig:thread_id와 같은 구성 정보 및tags와 같은 추적 정보를 포함하는RunnableConfig객체runtime: runtimecontext및store및stream_writer와 같은 기타 정보를 포함하는Runtime객체
NetworkX와 유사하게 add_node 메서드를 사용하여 그래프에 이러한 node를 추가합니다:
START Node
START Node는 사용자 입력을 그래프로 보내는 node를 나타내는 특수 node입니다. 이 node를 참조하는 주요 목적은 어떤 node를 먼저 호출해야 하는지 결정하는 것입니다.
END Node
END Node는 터미널 node를 나타내는 특수 node입니다. 이 node는 완료된 후 작업이 없는 edge를 나타낼 때 참조됩니다.
Node 캐싱
LangGraph는 node에 대한 입력을 기반으로 tasks/nodes의 캐싱을 지원합니다. 캐싱을 사용하려면:- 그래프를 컴파일할 때(또는 entrypoint를 지정할 때) cache를 지정합니다
- node에 대한 cache policy를 지정합니다. 각 cache policy는 다음을 지원합니다:
key_func는 node에 대한 입력을 기반으로 cache 키를 생성하는 데 사용되며, 기본적으로 pickle을 사용한 입력의hash입니다.ttl, cache의 수명(초)입니다. 지정하지 않으면 cache가 만료되지 않습니다.
- 첫 번째 실행은 실행하는 데 2초가 걸립니다(모의 비용이 많이 드는 계산으로 인해).
- 두 번째 실행은 cache를 활용하여 빠르게 반환됩니다.
Edges
Edge는 로직이 라우팅되는 방식과 그래프가 중지를 결정하는 방식을 정의합니다. 이것은 에이전트가 작동하는 방식과 다른 node들이 서로 통신하는 방식의 큰 부분입니다. 몇 가지 주요 edge 타입이 있습니다:- Normal Edges: 한 node에서 다음 node로 직접 이동합니다.
- Conditional Edges: 다음에 이동할 node(들)를 결정하기 위해 함수를 호출합니다.
- Entry Point: 사용자 입력이 도착할 때 먼저 호출할 node입니다.
- Conditional Entry Point: 사용자 입력이 도착할 때 먼저 호출할 node(들)를 결정하기 위해 함수를 호출합니다.
Normal Edges
node A에서 node B로 항상 이동하려면add_edge 메서드를 직접 사용할 수 있습니다.
Conditional Edges
하나 이상의 edge로 선택적으로 라우팅하거나(또는 선택적으로 종료하려면)add_conditional_edges 메서드를 사용할 수 있습니다. 이 메서드는 node의 이름과 해당 node가 실행된 후 호출할 “routing function”을 받습니다:
routing_function은 그래프의 현재 state를 받아 값을 반환합니다.
기본적으로 routing_function의 반환 값은 다음에 state를 보낼 node(또는 node 목록)의 이름으로 사용됩니다. 해당 node들은 모두 다음 superstep의 일부로 병렬로 실행됩니다.
선택적으로 routing_function의 출력을 다음 node의 이름에 매핑하는 dictionary를 제공할 수 있습니다.
state 업데이트와 라우팅을 단일 함수에서 결합하려면 conditional edges 대신
Command를 사용하세요.Entry Point
entry point는 그래프가 시작될 때 실행되는 첫 번째 node(들)입니다. 가상START node에서 실행할 첫 번째 node로 add_edge 메서드를 사용하여 그래프에 진입할 위치를 지정할 수 있습니다.
Conditional Entry Point
conditional entry point를 사용하면 사용자 정의 로직에 따라 다른 node에서 시작할 수 있습니다. 가상START node에서 add_conditional_edges를 사용하여 이를 수행할 수 있습니다.
routing_function의 출력을 다음 node의 이름에 매핑하는 dictionary를 제공할 수 있습니다.
Send
기본적으로 Nodes와 Edges는 미리 정의되며 동일한 공유 state에서 작동합니다. 그러나 정확한 edge가 미리 알려지지 않았거나 동시에 다른 버전의 State가 존재하기를 원하는 경우가 있을 수 있습니다. 이에 대한 일반적인 예는 map-reduce 디자인 패턴입니다. 이 디자인 패턴에서 첫 번째 node는 객체 목록을 생성할 수 있으며, 해당 객체 모두에 다른 node를 적용하고 싶을 수 있습니다. 객체의 수는 미리 알 수 없을 수 있으며(즉, edge의 수를 알 수 없음) 다운스트림 Node에 대한 입력 State는 달라야 합니다(생성된 각 객체에 대해 하나씩).
이 디자인 패턴을 지원하기 위해 LangGraph는 conditional edges에서 Send 객체를 반환하는 것을 지원합니다. Send는 두 개의 인수를 받습니다: 첫 번째는 node의 이름이고 두 번째는 해당 node에 전달할 state입니다.
Command
제어 흐름(edges)과 state 업데이트(nodes)를 결합하는 것이 유용할 수 있습니다. 예를 들어, 동일한 node에서 state 업데이트를 수행하고 다음에 이동할 node를 결정하고 싶을 수 있습니다. LangGraph는 node 함수에서 Command 객체를 반환하여 이를 수행하는 방법을 제공합니다:
Command를 사용하면 동적 제어 흐름 동작(conditional edges와 동일)도 달성할 수 있습니다:
node 함수에서
Command를 반환할 때 node가 라우팅하는 node 이름 목록과 함께 반환 타입 주석을 추가해야 합니다. 예: Command[Literal["my_other_node"]]. 이는 그래프 렌더링에 필요하며 LangGraph에 my_node가 my_other_node로 이동할 수 있음을 알려줍니다.Command를 사용하는 방법에 대한 엔드투엔드 예제는 이 how-to 가이드를 확인하세요.
언제 conditional edges 대신 Command를 사용해야 하나요?
- 그래프 state를 업데이트하고 다른 node로 라우팅해야 할 때
Command를 사용하세요. 예를 들어, 다른 에이전트로 라우팅하고 해당 에이전트에 일부 정보를 전달하는 것이 중요한 multi-agent handoffs를 구현할 때입니다. - state를 업데이트하지 않고 조건부로 node 간에 라우팅하려면 conditional edges를 사용하세요.
부모 그래프의 node로 이동하기
subgraphs를 사용하는 경우 subgraph 내의 node에서 다른 subgraph(즉, 부모 그래프의 다른 node)로 이동하고 싶을 수 있습니다. 이를 위해Command에서 graph=Command.PARENT를 지정할 수 있습니다:
이는 multi-agent handoffs를 구현할 때 특히 유용합니다.
자세한 내용은 이 가이드를 확인하세요.
tools 내부에서 사용하기
일반적인 사용 사례는 tool 내부에서 그래프 state를 업데이트하는 것입니다. 예를 들어, 고객 지원 애플리케이션에서 대화 시작 시 계정 번호 또는 ID를 기반으로 고객 정보를 조회하고 싶을 수 있습니다. 자세한 내용은 이 가이드를 참조하세요.Human-in-the-loop
Command는 human-in-the-loop 워크플로우의 중요한 부분입니다: interrupt()를 사용하여 사용자 입력을 수집할 때 Command는 Command(resume="User input")를 통해 입력을 제공하고 실행을 재개하는 데 사용됩니다. 자세한 내용은 이 개념 가이드를 확인하세요.
Graph Migrations
LangGraph는 state를 추적하기 위해 checkpointer를 사용하는 경우에도 그래프 정의(nodes, edges 및 state)의 마이그레이션을 쉽게 처리할 수 있습니다.- 그래프의 끝에 있는 스레드(즉, 중단되지 않음)의 경우 그래프의 전체 토폴로지를 변경할 수 있습니다(즉, 모든 node와 edge를 제거, 추가, 이름 변경 등).
- 현재 중단된 스레드의 경우 node 이름 변경/제거를 제외한 모든 토폴로지 변경을 지원합니다(해당 스레드가 더 이상 존재하지 않는 node에 진입하려고 할 수 있으므로) — 이것이 차단 요소인 경우 연락하시면 솔루션의 우선순위를 정할 수 있습니다.
- state 수정의 경우 키 추가 및 제거에 대한 완전한 하위 및 상위 호환성이 있습니다.
- 이름이 변경된 state 키는 기존 스레드에서 저장된 state를 잃습니다.
- 호환되지 않는 방식으로 타입이 변경된 state 키는 현재 변경 전의 state가 있는 스레드에서 문제를 일으킬 수 있습니다 — 이것이 차단 요소인 경우 연락하시면 솔루션의 우선순위를 정할 수 있습니다.
Runtime Context
그래프를 생성할 때 node에 전달되는 runtime context에 대한context_schema를 지정할 수 있습니다. 이는 그래프 state의 일부가 아닌 정보를 node에 전달하는 데 유용합니다. 예를 들어, model 이름이나 데이터베이스 연결과 같은 종속성을 전달하고 싶을 수 있습니다.
invoke 메서드의 context 매개변수를 사용하여 이 context를 그래프에 전달할 수 있습니다.
Recursion Limit
recursion limit은 단일 실행 중에 그래프가 실행할 수 있는 최대 super-steps 수를 설정합니다. 제한에 도달하면 LangGraph는GraphRecursionError를 발생시킵니다. 기본적으로 이 값은 25 steps로 설정됩니다. recursion limit은 런타임에 모든 그래프에서 설정할 수 있으며 config dictionary를 통해 invoke/stream에 전달됩니다. 중요한 점은 recursion_limit은 독립적인 config 키이며 다른 모든 사용자 정의 구성처럼 configurable 키 내부에 전달되어서는 안 된다는 것입니다. 아래 예제를 참조하세요:
Visualization
그래프를 시각화할 수 있는 것은 종종 좋으며, 특히 그래프가 더 복잡해질수록 그렇습니다. LangGraph는 그래프를 시각화하는 여러 가지 내장 방법을 제공합니다. 자세한 내용은 이 how-to 가이드를 참조하세요.Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.