Functional API를 사용하면 기존 코드를 최소한으로 수정하면서 LangGraph의 핵심 기능인 영속성, 메모리, human-in-the-loop, 스트리밍을 애플리케이션에 추가할 수 있습니다.이 API는 if 문, for 루프, 함수 호출과 같은 표준 언어 프리미티브를 사용하여 분기 및 제어 흐름을 처리하는 기존 코드에 이러한 기능을 통합하도록 설계되었습니다. 코드를 명시적인 파이프라인이나 DAG로 재구성해야 하는 많은 데이터 오케스트레이션 프레임워크와 달리, Functional API는 엄격한 실행 모델을 강제하지 않고도 이러한 기능을 통합할 수 있게 해줍니다.Functional API는 두 가지 핵심 구성 요소를 사용합니다:
entrypoint – entrypoint는 워크플로 로직을 캡슐화하고 장시간 실행되는 작업 및 인터럽트 처리를 포함한 실행 흐름을 관리합니다.
task – API 호출이나 데이터 처리 단계와 같은 개별 작업 단위를 나타내며, entrypoint 내에서 비동기적으로 실행될 수 있습니다. Task는 await하거나 동기적으로 해결할 수 있는 future와 유사한 객체를 반환합니다.
이를 통해 상태 관리 및 스트리밍을 갖춘 워크플로를 구축하기 위한 최소한의 추상화를 제공합니다.
선언적 접근 방식을 선호하는 사용자를 위해 LangGraph의 Graph API는 Graph 패러다임을 사용하여 워크플로를 정의할 수 있게 해줍니다. 두 API는 동일한 기본 런타임을 공유하므로 동일한 애플리케이션에서 함께 사용할 수 있습니다.주요 차이점은 다음과 같습니다:
제어 흐름: Functional API는 그래프 구조에 대해 생각할 필요가 없습니다. 표준 Python 구조를 사용하여 워크플로를 정의할 수 있습니다. 이렇게 하면 일반적으로 작성해야 하는 코드의 양을 줄일 수 있습니다.
단기 메모리: GraphAPI는 State를 선언해야 하며 그래프 상태 업데이트를 관리하기 위해 reducer를 정의해야 할 수 있습니다. @entrypoint와 @task는 상태가 함수 범위로 제한되고 함수 간에 공유되지 않으므로 명시적인 상태 관리가 필요하지 않습니다.
체크포인팅: 두 API 모두 체크포인트를 생성하고 사용합니다. Graph API에서는 모든 superstep 이후에 새 체크포인트가 생성됩니다. Functional API에서는 task가 실행될 때 새 체크포인트를 생성하는 대신 해당 entrypoint와 연결된 기존 체크포인트에 결과가 저장됩니다.
시각화: Graph API를 사용하면 워크플로를 그래프로 쉽게 시각화할 수 있어 디버깅, 워크플로 이해, 다른 사람과의 공유에 유용합니다. Functional API는 런타임에 그래프가 동적으로 생성되므로 시각화를 지원하지 않습니다.
아래에서는 에세이를 작성하고 사람의 검토를 요청하기 위해 인터럽트하는 간단한 애플리케이션을 보여줍니다.
Copy
import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";const writeEssay = task("writeEssay", async (topic: string) => { // 장시간 실행되는 작업의 플레이스홀더입니다. await new Promise((resolve) => setTimeout(resolve, 1000)); return `An essay about topic: ${topic}`;});const workflow = entrypoint( { checkpointer: new MemorySaver(), name: "workflow" }, async (topic: string) => { const essay = await writeEssay(topic); const isApproved = interrupt({ // interrupt에 인자로 제공되는 모든 json 직렬화 가능한 페이로드입니다. // 워크플로에서 데이터를 스트리밍할 때 클라이언트 측에서 Interrupt로 표시됩니다. essay, // 검토를 원하는 에세이입니다. // 필요한 추가 정보를 추가할 수 있습니다. // 예를 들어, 일부 지침과 함께 "action"이라는 키를 도입합니다. action: "Please approve/reject the essay", }); return { essay, // 생성된 에세이 isApproved, // HIL의 응답 }; });
상세 설명
이 워크플로는 “cat” 주제에 대한 에세이를 작성한 다음 사람의 검토를 받기 위해 일시 중지됩니다. 워크플로는 검토가 제공될 때까지 무기한 중단될 수 있습니다.워크플로가 재개되면 처음부터 실행되지만, writeEssay task의 결과가 이미 저장되었기 때문에 task 결과는 다시 계산되지 않고 체크포인트에서 로드됩니다.
Copy
import { v4 as uuidv4 } from "uuid";import { MemorySaver, entrypoint, task, interrupt } from "@langchain/langgraph";const writeEssay = task("writeEssay", async (topic: string) => { // 장시간 실행되는 작업의 플레이스홀더입니다. await new Promise(resolve => setTimeout(resolve, 1000)); return `An essay about topic: ${topic}`;});const workflow = entrypoint( { checkpointer: new MemorySaver(), name: "workflow" }, async (topic: string) => { const essay = await writeEssay(topic); const isApproved = interrupt({ // interrupt에 인자로 제공되는 모든 json 직렬화 가능한 페이로드입니다. // 워크플로에서 데이터를 스트리밍할 때 클라이언트 측에서 Interrupt로 표시됩니다. essay, // 검토를 원하는 에세이입니다. // 필요한 추가 정보를 추가할 수 있습니다. // 예를 들어, 일부 지침과 함께 "action"이라는 키를 도입합니다. action: "Please approve/reject the essay", }); return { essay, // 생성된 에세이 isApproved, // HIL의 응답 }; });const threadId = uuidv4();const config = { configurable: { thread_id: threadId }};for await (const item of workflow.stream("cat", config)) { console.log(item);}
에세이가 작성되어 검토 준비가 완료되었습니다. 검토가 제공되면 워크플로를 재개할 수 있습니다:
Copy
import { Command } from "@langchain/langgraph";// 사용자로부터 검토를 받습니다 (예: UI를 통해)// 이 경우 bool을 사용하지만, json 직렬화 가능한 모든 값이 될 수 있습니다.const humanReview = true;for await (const item of workflow.stream(new Command({ resume: humanReview }), config)) { console.log(item);}
entrypoint는 구성과 함수를 사용하여 entrypoint 함수를 호출하여 정의됩니다.함수는 단일 위치 인자를 받아야 하며, 이는 워크플로 입력으로 사용됩니다. 여러 데이터를 전달해야 하는 경우 첫 번째 인자의 입력 타입으로 객체를 사용하세요.함수로 entrypoint를 생성하면 워크플로 실행을 관리하는 데 도움이 되는 워크플로 인스턴스가 생성됩니다(예: 스트리밍, 재개, 체크포인팅 처리).human-in-the-loop과 같은 기능을 사용하기 위해 영속성을 활성화하려면 종종 체크포인터를 entrypoint 함수에 전달하고자 할 것입니다.
Copy
import { entrypoint } from "@langchain/langgraph";const myWorkflow = entrypoint( { checkpointer, name: "workflow" }, async (someInput: Record<string, any>): Promise<number> => { // API 호출과 같은 장시간 실행되는 작업을 포함할 수 있는 일부 로직이며, // human-in-the-loop을 위해 중단될 수 있습니다. return result; });
직렬화
체크포인팅을 지원하려면 entrypoint의 입력 및 출력이 JSON 직렬화 가능해야 합니다. 자세한 내용은 직렬화 섹션을 참조하세요.
entrypoint가 checkpointer와 함께 정의되면 체크포인트에서 동일한 thread id에 대한 연속적인 호출 사이의 정보를 저장합니다.이를 통해 getPreviousState 함수를 사용하여 이전 호출의 상태에 액세스할 수 있습니다.기본적으로 getPreviousState 함수는 이전 호출의 반환 값을 반환합니다.
이러한 요구 사항은 체크포인팅 및 워크플로 재개를 가능하게 하기 위해 필요합니다. 입력 및 출력이 직렬화 가능하도록 객체, 배열, 문자열, 숫자, 불리언과 같은 프리미티브를 사용하세요.직렬화는 task 결과 및 중간 값과 같은 워크플로 상태를 안정적으로 저장하고 복원할 수 있도록 보장합니다. 이는 human-in-the-loop 상호 작용, 내결함성 및 병렬 실행을 가능하게 하는 데 중요합니다.워크플로가 체크포인터로 구성된 경우 직렬화할 수 없는 입력이나 출력을 제공하면 런타임 오류가 발생합니다.
human-in-the-loop과 같은 기능을 활용하려면 모든 무작위성을 task 내부에 캡슐화해야 합니다. 이렇게 하면 실행이 중지되고(예: human-in-the-loop의 경우) 재개될 때 task 결과가 비결정적이더라도 동일한 _단계 시퀀스_를 따르게 됩니다.LangGraph는 실행되는 대로 task 및 subgraph 결과를 유지하여 이러한 동작을 달성합니다. 잘 설계된 워크플로는 실행을 재개할 때 동일한 _단계 시퀀스_를 따르도록 보장하여 이전에 계산된 결과를 다시 실행하지 않고 올바르게 검색할 수 있게 합니다. 이는 장시간 실행되는 task 또는 비결정적 결과를 가진 task에 특히 유용합니다. 이전에 수행한 작업을 반복하지 않고 본질적으로 동일한 위치에서 재개할 수 있기 때문입니다.워크플로의 다른 실행은 다른 결과를 생성할 수 있지만, 특정 실행을 재개하면 항상 동일한 기록된 단계 시퀀스를 따라야 합니다. 이를 통해 LangGraph는 그래프가 중단되기 전에 실행된 task 및 subgraph 결과를 효율적으로 조회하고 다시 계산하지 않을 수 있습니다.
멱등성은 동일한 작업을 여러 번 실행해도 동일한 결과를 생성하도록 보장합니다. 이는 실패로 인해 단계가 재실행되는 경우 중복 API 호출 및 중복 처리를 방지하는 데 도움이 됩니다. 체크포인팅을 위해 항상 API 호출을 task 함수 내부에 배치하고, 재실행 시 멱등적이 되도록 설계하세요. task가 시작되었지만 성공적으로 완료되지 않으면 재실행이 발생할 수 있습니다. 그런 다음 워크플로가 재개되면 task가 다시 실행됩니다. 중복을 방지하기 위해 멱등성 키를 사용하거나 기존 결과를 확인하세요.
워크플로를 재개할 때 여러 번 실행되지 않도록 부작용(예: 파일 쓰기, 이메일 전송)을 task로 캡슐화하세요.
Incorrect
Correct
이 예제에서는 부작용(파일 쓰기)이 워크플로에 직접 포함되어 있으므로 워크플로를 재개할 때 두 번째로 실행됩니다.
Copy
import { entrypoint, interrupt } from "@langchain/langgraph";import fs from "fs";const myWorkflow = entrypoint( { checkpointer, name: "workflow }, async (inputs: Record<string, any>) => { // 이 코드는 워크플로를 재개할 때 두 번째로 실행됩니다. // 이는 원하는 것이 아닐 가능성이 높습니다. fs.writeFileSync("output.txt", "Side effect executed"); const value = interrupt("question"); return value; });
이는 여러 interrupt 호출이 있는 human-in-the-loop 워크플로를 사용할 때 특히 중요합니다. LangGraph는 각 task/entrypoint에 대한 resume 값의 목록을 유지합니다. interrupt가 발생하면 해당 resume 값과 일치합니다. 이 매칭은 엄격하게 인덱스 기반이므로 resume 값의 순서는 interrupt의 순서와 일치해야 합니다.재개 시 실행 순서가 유지되지 않으면 하나의 interrupt 호출이 잘못된 resume 값과 일치하여 잘못된 결과를 초래할 수 있습니다.자세한 내용은 결정론 섹션을 참조하세요.
Incorrect
Correct
이 예제에서 워크플로는 현재 시간을 사용하여 실행할 task를 결정합니다. 이는 워크플로의 결과가 실행되는 시간에 따라 달라지기 때문에 비결정적입니다.
Copy
import { entrypoint, interrupt } from "@langchain/langgraph";const myWorkflow = entrypoint( { checkpointer, name: "workflow" }, async (inputs: { t0: number }) => { const t1 = Date.now(); const deltaT = t1 - inputs.t0; if (deltaT > 1000) { const result = await slowTask(1); const value = interrupt("question"); return { result, value }; } else { const result = await slowTask(2); const value = interrupt("question"); return { result, value }; } });