Juncture -- LLM 에이전트 애플리케이션 구축을 위한 LangGraph의 Rust 구현
Juncture -- A Rust implementation of LangGraph for building LLM agent applications
핵심 요약
LangGraph의 핵심 모델을 Rust로 이식하여 컴파일 타임 안전성과 멀티코어 병렬 처리를 제공하는 Juncture 프로젝트 소개.
- Rust 이식 — LangGraph의 StateGraph와 Pregel 엔진을 Rust로 재구현함
- 타입 안전성 — proc-macro를 사용하여 컴파일 타임에 타입 안전한 상태 관리 제공
- 성능 최적화 — tokio 기반의 병렬 실행과 Arc 기반의 복사 방지 기술 적용
- 폭넓은 호환성 — WASM 지원 및 Langfuse 호환 텔레메트리 기능 내장
여러분 안녕하세요! 커뮤니티와 공유하고 싶은 LangGraph의 Rust 포팅 버전인 Juncture를 작업해 왔습니다.
이게 뭔가요?
Juncture는 LangGraph의 핵심 프로그래밍 모델인 StateGraph와 Pregel 실행 엔진을 Rust로 재구현한 것입니다. API 디자인은 LangGraph Python과 유사하게 유지하여 기존 사용자가 지식을 바로 활용할 수 있도록 했습니다.
목표는 바퀴를 다시 발명하는 것이 아니라, LangGraph의 검증된 에이전트 오케스트레이션 모델을 컴파일 타임 안전성과 진정한 멀티코어 병렬 처리 기능을 갖춘 Rust 생태계로 가져오는 것입니다.
주요 설계 결정
#[derive(State)]를 통한 타입 지정 상태
Python의 동적 채널 매핑 대신, Juncture는 proc-macro를 사용하여 컴파일 타임에 필드별 리듀서가 포함된 타입 안전한 State/Update 구조체 쌍을 생성합니다:
#[derive(State, Clone, Debug, Serialize, Deserialize)]
struct MyState { #[reducer(replace)] count: i32, #[reducer(append)] history: Vec<String> , }
리듀서에는 replace, append, ephemeral, last_write_wins, custom이 포함됩니다.
Pregel 실행 엔진
실행 엔진은 tokio::spawn + JoinSet을 사용하여 진정한 병렬 노드 실행을 수행하며, 비용이 많이 드는 상태 복제를 피하기 위해 CowState(Arc 기반의 copy-on-write)를 사용합니다. 세마포어 기반의 제한된 동시성으로 리소스 사용량을 제어합니다.
기능 패리티 집중
Juncture는 새로운 추상화보다는 LangGraph Python과의 의미적 동등성을 목표로 합니다. 이미 구현된 기능은 다음과 같습니다:
- add_node, add_edge, add_conditional_edges를 포함한 StateGraph 빌더
- 노드 반환 라우팅을 위한 Command (goto, resume, 부모 그래프 탐색)
- 병렬 서브그래프를 위한 동적 fan-out용 Send
- Human-in-the-Loop 워크플로우를 위한 interrupt! 매크로
- 9가지 스트리밍 모드 (Values, Updates, Messages, Custom, Debug, Tools, Checkpoints, Tasks, Multi)
- 체크포인트 지속성 (Memory 등)
#[derive(State, Clone, Debug, Serialize, Deserialize)]
struct MyState { #[reducer(replace)] count: i32, #[reducer(append)] history: Vec<String> , }
async fn increment(state: &MyState) -> Result<MyStateUpdate, Box<dyn Error>> { Ok(MyStateUpdate { count: Some(state.count + 1), history: Some(vec![format!("count -> {}", state.count + 1)]), }) }
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { let mut graph = StateGraph::<MyState>::new(); graph.add_node("increment", increment); graph.add_edge(START, "increment"); graph.add_edge("increment", END);
let compiled = graph.compile()?;
let result = compiled
.invoke(MyState { count: 0, history: vec![] }, &RunnableConfig::default())
.await?;
println!("Result: {:?}", result);
Ok(())
}

