Patternsof the Graph.
LangGraph는 그래프로 에이전트의 사고를 설계하는 언어입니다. 이 문서는 2026년 현재 가장 자주 사용되는 스무 가지 패턴을 난이도 순으로 정리한 학습 코덱스 — 각 패턴의 개념, 다이어그램, 최소 실행 코드, 그리고 실제 적용처를 한 화면에 담았습니다.
핵심
가장 단순한 그래프. 노드를 순서대로 연결한다. 분기도 루프도 없다. 결정론적이라 디버깅이 쉽고, RAG 전처리/ETL 파이프라인의 절반은 아직 이 형태다.
동작
StateGraph(State)로 빈 그래프 생성addNode(name, fn)으로 함수를 노드로 등록 — fn 시그니처는state → partial stateaddEdge(from, to)로 방향 엣지 —START가 진입,END가 종료.compile()이 위상 검증·사이클 체크 후 실행 가능 객체 반환invoke(state)또는stream(state)로 실행
API 표
| 이름 | 역할 |
|---|---|
StateGraph(StateAnnotation) | 그래프 빌더 — 상태 스키마 필수 |
START, END | 진입/종료 sentinel 노드 |
addNode(name, fn) | 노드 등록. fn은 state를 받아 부분 dict를 리턴 |
addEdge(from, to) | 무조건 엣지 |
compile(opts) | 실행 가능 그래프로 컴파일. checkpointer/store는 여기 주입 |
흔한 실수
- 노드가 리턴하지 않은 키는 머지되지 않는다 — 변경분만 부분 dict로 리턴할 것
compile()빠뜨리고 빌더 객체에서 바로 invoke 시도addEdge누락으로 그래프가 단절돼 즉시 종료- 상태 키 이름 오타 — TypeScript는
typeof State.State로 잡힌다
이럴 땐 다른 패턴
분기 필요 → Conditional Routing. 반복 필요 → Cycles & Loops. 동적 N병렬 → Send.
핵심
노드 실행 직후 다음 노드를 동적으로 선택한다. 라우팅 함수가 상태를 읽고 다음 노드의 이름(또는 키)을 리턴한다. 분기 패턴의 기본형.
동작
- 라우팅 함수 작성:
(state) → "a" | "b" | "c" addConditionalEdges(source, routeFn, pathMap?)로 등록- 라우터가 리턴한 키로
pathMap을 룩업해 해당 노드로 흐른다 - pathMap 생략 시 리턴 문자열 == 노드 이름으로 직접 매칭
API 표
| 이름 | 역할 |
|---|---|
addConditionalEdges(src, path, pathMap?) | 분기 등록 |
Literal["a", "b"] | 타입 명시 시 그래프 시각화에 자동 반영 |
흔한 실수
- 라우팅 함수에서 상태 변경(부수 효과) — 순수 함수여야 한다
- 리턴 가능한 모든 키를 pathMap에 빠뜨리지 말 것 — 누락 시 KeyError
- 분기 수가 런타임에 결정되는데 conditional edges로 해결하려는 시도 → Send로 전환
관련 패턴
Cycles(자기 자신으로 분기), Reflection(품질 기반 분기), Adaptive RAG(검색 전략 분기).
핵심
같은 상태 키에 여러 노드가 쓸 때 어떻게 머지할지를 선언적으로 정한다. 기본은 last-write-wins(덮어쓰기). Annotation의 reducer 옵션으로 누적/병합/정렬 등을 지정.
동작
- State 정의 시 누적이 필요한 키에 reducer 함수 지정
- reducer 시그니처:
(existing, new) → merged - 병렬 실행 노드들이 같은 키에 쓰면 LangGraph가 자동으로 reducer로 합친다
- 읽기 전용 키에는 reducer 불필요
자주 쓰는 reducer
| reducer | 용도 |
|---|---|
(x, y) => x.concat(y) | 리스트/문자열 누적 |
messagesStateReducer | 메시지 ID 기반 dedup + append |
(x, y) => Math.max(x, y) | 최댓값 |
(x, y) => ({ ...x, ...y }) | 객체 얕은 머지 |
흔한 실수
- 병렬 노드가 같은 list 키에 쓰는데 reducer 안 걸면 한 쪽 결과가 통째로 사라진다
- messages 키에
messagesStateReducer안 쓰면 ID 충돌 / 중복 - reducer가 mutable 객체를 in-place로 수정 — 새 객체를 리턴해야 함
관련 패턴
Send, Map-Reduce — 둘 다 reducer 없으면 동작 자체가 안 된다.
핵심
조건부 엣지가 이전 노드로 돌아갈 수 있을 때 그래프는 루프가 된다. 종료 조건은 상태에 카운터/score를 두고 라우터에서 체크. recursionLimit이 폭주 방지의 마지막 보호막.
동작
- 작업 노드가 상태를 변경 (예:
attempts++) - 라우터가 종료 조건 평가 →
END또는 작업 노드 이름 리턴 - recursionLimit 도달 시
GraphRecursionError - invoke 시
{ recursionLimit: 25 }로 명시
API
addConditionalEdges("work", shouldContinue, { work: "work", [END]: END })- config:
{ recursionLimit: number }(default 25)
흔한 실수
- recursionLimit 도달을 catch 안 하면 호출자에 그대로 throw
- 종료 조건이 영원히 false → 무한 루프 (recursionLimit이 잡아주긴 함)
- attempts 카운터를 reducer 없이 두고 병렬 노드가 += 하면 race condition
관련
Reflection은 본질적으로 cycles + critic node. Plan-and-Execute의 replan loop도 같은 구조.
핵심
파이프라인 맨 앞에서 입력 자격을 심사한다. State 자체를 Pydantic / Zod 스키마로 정의하면 LangGraph가 invoke 시 자동 검증한다.
동작
- State 스키마 정의 (Pydantic BaseModel 또는 Zod schema)
StateGraph(State)가 스키마를 직접 받음- invoke 시점에 입력 검증 — 실패하면 ValidationError throw
- 유연한 처리: 검증 노드를 명시적으로 두고 실패 분기를 만드는 것도 가능
두 가지 스타일
| 방식 | 장단점 |
|---|---|
| 스키마를 State로 직접 | compile-time 안전, 단 throw 처리 필요 |
| 검증 노드 + reject 분기 | graceful degradation, 유연하지만 보일러플레이트 |
흔한 실수
- BaseModel mutable default —
Field(default_factory=list)써야 함 - 검증 실패를 throw로 처리하면 그래프 전체가 죽음 — 사용자 메시지에 노출되지 않게 주의
- Zod schema의
.transform()은 LangGraph가 inferred type을 잘 못 잡는 경우가 있음
핵심
LLM이 생각하고(Reason), 도구를 쓰고(Act), 관찰(Observe)하는 루프. 에이전트의 표준형. createReactAgent 한 줄로 완결되지만 내부는 LLM 노드 + ToolNode + 조건부 엣지 3개의 단순 구조.
동작
- LLM이 메시지 시퀀스를 받아 응답 생성
- 응답에
tool_calls가 있으면 ToolNode로 분기 - ToolNode가 호출 결과를 ToolMessage로 추가하고 LLM으로 리턴
- LLM이 더 이상 도구를 부르지 않을 때 END
API
| 이름 | 역할 |
|---|---|
createReactAgent({ llm, tools, prompt, ... }) | 완성된 에이전트 컴파일 |
tool(fn, { name, description, schema }) | JS 함수를 도구로 wrap |
옵션: checkpointer, store, stateModifier, interruptBefore | HITL/메모리/시스템 프롬프트 동적 주입 |
흔한 실수
- 도구 description이 부실하면 LLM이 못 고른다 — 한 줄 설명에 시간을 써라
- tool에 throw가 있으면 ToolNode가 ToolMessage(error)로 변환해 LLM이 보게 됨 — 의도면 OK, 아니면 try/catch
- recursionLimit 기본 25라 도구 호출이 많은 작업은 명시적으로 늘려야 함
관련
Tool Calling Loop는 ReAct를 손으로 조립한 버전. 커스텀 라우팅이나 추가 노드(가드, 로깅)가 필요할 때 prebuilt에서 빠져나온다.
핵심
createReactAgent의 뚜껑을 연 버전. LLM 노드 + ToolNode + tools_condition의 3요소를 직접 조립해 가드/로깅/커스텀 분기를 자유롭게 끼워 넣는다.
동작
- LLM에
bindTools(tools)로 도구 스키마 주입 - agent 노드:
messages→ LLM → AIMessage 추가 toolsCondition: AIMessage에 tool_calls 있으면 "tools"로, 없으면 END로 라우팅- ToolNode가 도구 실행 후 tool_call_id 매칭 ToolMessage들을 messages에 추가
- 다시 agent로 돌아가 LLM이 응답
API 표
| 이름 | 역할 |
|---|---|
MessagesAnnotation | messages 키 + add_messages reducer 빌트인 |
ToolNode(tools) | tool_calls를 병렬 실행 |
toolsCondition | END or "tools" 라우팅 함수 |
bindTools(tools) | LLM에 도구 스키마 주입 |
흔한 실수
bindTools안 한 모델로 invoke하면 영원히 도구를 안 부른다- messages 키에 reducer 안 걸면 (MessagesAnnotation 안 쓰면) 메시지가 덮어써짐
- 여러 도구 동시 호출(parallel tool calls)을 막고 싶으면
parallel_tool_calls=False
핵심
그래프가 interrupt()를 만나면 그 자리에서 멈춘다. 외부(사람/다른 시스템)가 결정을 입력하고 Command(resume=...)로 재개하면 interrupt 이후부터 이어 달린다. checkpointer가 필수.
동작
- checkpointer 컴파일된 그래프
- 노드 안에서
interrupt(payload)호출 — 그래프가 GraphInterrupt 던지며 멈춤 - checkpointer가 interrupt 직전 상태를 저장
- 외부에서 검토 후
graph.invoke(new Command({ resume: value }), cfg) - interrupt가
value를 리턴하며 노드가 이어 실행
API
| 이름 | 역할 |
|---|---|
interrupt(payload) | 그래프 일시정지, payload는 외부에 전달될 데이터 |
Command({ resume }) | 재개 신호. resume 값이 interrupt의 리턴 값이 됨 |
Command({ goto, update }) | 재개 시 다른 노드로 분기 + 상태 업데이트 |
compile 옵션 interruptBefore/After | 특정 노드 앞/뒤에서 자동 interrupt |
흔한 실수
- checkpointer 없이 interrupt → 작동 안 함
- 같은 노드 안에서 interrupt 두 번 연속 — 두 번째는 첫 resume의 값을 리턴할 수 있음 (재실행 시 cached)
- thread_id를 매번 새로 만들면 재개가 불가능 — 동일 thread_id 유지 필수
고급
HITL 패턴은 단순 승인뿐 아니라 편집 / 분기 선택 / 도구 호출 가로채기까지 확장 가능. Command는 resume 외에 update/goto도 받아 재개 시 임의 변경을 허용한다.
핵심
각 step마다 상태 스냅샷이 저장된다. 과거 시점 조회/재생/분기가 가능 — 그래프 디버거이자 시뮬레이터. 멀티 사용자 세션 격리도 thread_id로 자연스럽게 해결된다.
동작
- compile에 checkpointer 주입
- 매 step 종료 시 자동으로 (thread_id, checkpoint_id) 키로 저장
getStateHistory(cfg)로 시간 역순 이터레이션- 특정 checkpoint_id로 재호출 시 그 지점부터 재생
updateState로 상태를 수동 패치한 새 분기 생성도 가능
저장소 옵션
| backend | 특징 |
|---|---|
| MemorySaver | 인메모리, 테스트용 |
| SqliteSaver | 경량 단일 노드 |
| PostgresSaver | 프로덕션 표준, pgvector 함께 쓰기 좋음 |
| RedisSaver | 저지연, TTL 자연스러움 |
흔한 실수
- PostgresSaver 첫 사용 시
await saver.setup()빠뜨림 → 테이블 없음 에러 - thread_id를 사용자 ID로 설정 → 한 사용자가 여러 대화 못 함. 보통
{userId}-{conversationId}형태 - 체크포인트가 무한 누적 → 주기적 GC 필요 (Postgres는 직접 DELETE)
시간여행 활용
버그 재현, A/B 테스트(같은 지점에서 다른 도구 시도), HITL 편집 후 재실행, "마지막에 한 행동 취소" 같은 UX.
핵심
그래프는 3가지 층위로 스트리밍한다. UI는 보통 updates(노드 단위)와 messages(LLM 토큰 단위)를 동시에 받아 결합한다.
스트림 모드
| mode | 방출 단위 | 용도 |
|---|---|---|
values | 매 step의 전체 상태 | 로깅, 풀 스냅샷 |
updates | 해당 step의 변경분(노드 → 부분 dict) | "OO 노드가 진행 중" UI |
messages | LLM 토큰 (BaseMessage chunk + meta) | 타이핑 효과 |
custom | 노드 안에서 get_stream_writer().write(x)로 임의 이벤트 | 진행률 바, 로그 |
debug | 모든 내부 이벤트 | 디버깅 |
다중 모드
streamMode: ["updates", "messages"]를 주면 튜플 [kind, chunk]로 방출. 클라이언트는 kind로 분기해 SSE에 다른 이벤트 타입으로 전달.
흔한 실수
- messages 모드만 켜면 어느 노드가 토큰을 내뱉는지 모른다 → updates 함께
- tool 호출 시 verbose ToolMessage가 stream에 그대로 흘러 UI 깨짐 → 필터링 필요
- 긴 응답에서 첫 토큰까지 시간이 길면 사용자 체감이 나쁨 → "노드 시작" 이벤트로 placeholder 띄우기
핵심
조건부 엣지가 Send 객체 리스트를 리턴하면 각 Send는 독립된 서브 실행으로 병렬 처리된다. 런타임에 개수가 정해지는 동적 분산.
동작
- fan-out 함수:
(state) => state.items.map(x => new Send("worker", { item: x })) - 각 Send는 워커 노드를 독립 인스턴스로 실행 — 각자 자기만의 입력 dict
- 워커는 결과를
results같은 reducer 키에 push - 모든 워커 종료 후 다음 노드로 진행 (자동 동기화)
주의
| 항목 | 설명 |
|---|---|
| 입력 격리 | 각 Send는 dict로 명시한 것만 받는다 — 부모 상태 자동 상속 X |
| 리듀서 필수 | 결과 키에 reducer 없으면 마지막 워커 결과만 남음 |
| 병렬도 제한 | concurrency 제어는 기본 없음 — LLM rate limit 주의 |
흔한 실수
- 워커 노드를 미리
addNode로 등록 안 함 → "Unknown node" 에러 - Send 리스트가 빈 배열 → 다음 노드로 그냥 흐름 (오류 아님, 주의 필요)
- 각 워커가 부모 state를 통째로 받을 거라 가정
관련
Map-Reduce의 Map 단계가 정확히 이 패턴. LLMCompiler는 Send를 의존성 있는 DAG 스케줄링으로 확장.
핵심
Send로 Map(병렬 처리), 공통 노드에서 Reduce(통합). 다수 문서/후보를 다루는 작업의 기본 구조.
동작
- plan 노드에서 작업 단위 분할
- fan-out:
items.map(it => new Send("map", { item: it })) - map 워커 N개 병렬 실행 → 각자 부분 결과를 reducer 키에 push
- 모든 워커 종료 후 reduce 노드 자동 호출
- reduce가 누적된 결과를 통합해 final 산출
설계 팁
- Map 출력은 reduce가 다루기 쉬운 형태로 — 짧은 요약, 점수, 임베딩
- Reduce가 LLM이라면 입력 길이 폭발 주의 — 계층적 reduce(2-pass) 고려
- Map 단계에서 실패한 워커 처리 정책: 무시 / 재시도 / 전체 실패
흔한 실수
- reduce 노드를 fan-out 분기에 직접 연결 → reduce가 워커마다 호출됨. 워커 → reduce 엣지는 그냥
addEdge("map", "reduce")면 자동 동기화 - reducer를 안 걸어서 결과가 누락
핵심
컴파일된 그래프를 다른 그래프의 노드로 삽입한다. 부모/자식은 다른 상태 스키마를 가질 수 있고, 함수로 매핑한다. LangGraph의 모듈화 단위.
두 가지 통합 방식
| 방식 | 설명 |
|---|---|
| 직접 노드로 등록 | parent.addNode("team", childGraph) — 상태 스키마가 호환될 때만 |
| 래퍼 함수 | 일반 함수 안에서 childGraph.invoke({...}) — 변환 자유 |
동작
- 자식 그래프 정의 + compile
- 부모에서 자식을 노드로 등록 또는 호출
- 스트리밍 시
subgraphs: True로 자식 이벤트도 함께 받기 - 각 자식은 독립된 thread_id namespace를 갖는다 (checkpoint도 따로)
흔한 실수
- 스키마 불일치한데 직접 등록 시도 → 컴파일 에러
- 자식 그래프의 checkpointer를 부모와 다르게 줘서 thread 상태가 흩어짐
- 스트리밍 시 자식 이벤트가 안 보임 →
subgraphs: true옵션 필요
관련
Hierarchical Teams는 서브그래프를 팀 단위로 쌓는 구조. Multi-Agent Supervisor도 종종 각 에이전트를 서브그래프로 캡슐화.
핵심
생성 → 비평 → 재생성의 순환. 비평가 노드가 결과의 약점을 텍스트로 돌려주면 생성 노드가 이를 반영해 다시 만든다. 종료 조건은 점수 임계 또는 반복 횟수.
동작
- generate 노드: 현재 draft + 누적 feedback을 LLM에 주고 새 draft 생성
- critic 노드: draft를 평가, 약점/점수 리턴
- shouldStop 라우터: 점수 ≥ 임계 또는 iter ≥ N → END, 아니면 generate
- 매 iteration 마다 feedback이 누적되거나 최신만 유지
변형
| 변형 | 특징 |
|---|---|
| Self-Refine | 같은 LLM이 비평과 생성을 모두 함 |
| Reflexion | 실패 trace를 메모리에 저장해 다음 시도에 활용 |
| Constitutional AI | 고정된 원칙 리스트로 비평 |
흔한 실수
- 비평이 너무 일반적("좀 더 좋게") → 비평가에 구체적 평가 기준(rubric) 주입
- 매 라운드 같은 비평 반복 → 진동. 비평가에 "이전 비평 + 변화" 보여주기
- 종료 조건이 너무 엄격 → recursionLimit 도달
핵심
먼저 Planner가 단계 리스트를 뽑고 → Executor가 단계별로 실행하고 → Replanner가 남은 단계를 갱신한다. ReAct가 매 턴 모든 컨텍스트를 다시 보는 비효율을 해결.
동작
- plan 노드: 목표를 받아 단계 리스트 생성 (3-7개)
- execute 노드: 첫 단계를 ReAct 에이전트로 실행, 결과를 past에 누적
- replan 노드: 남은 단계가 있는지 확인, 필요시 plan 갱신, 없으면 final 산출
- execute ↔ replan 루프
장점/단점
| 장점 | 단점 |
|---|---|
| 긴 작업에서 토큰 효율 | 플래너의 품질이 전체 성능을 좌우 |
| 의도가 명시적 — 디버깅 쉬움 | plan이 너무 fine-grained면 오버헤드 |
| HITL 자연스러움 (plan 검토) | 중간 결과로 plan을 갱신 못 하면 실패 폭주 |
흔한 실수
- replan을 안 두고 plan만 한 번 — 첫 plan이 틀리면 답 없음
- past를 cumulative하게 다 보내 토큰 폭발 → 요약 노드 끼우기
핵심
Checkpointer가 한 세션 안의 상태라면, Store는 세션을 초월한 기억. 네임스페이스 기반 CRUD + 벡터 인덱싱. "이 사용자가 누구인가"를 기억하는 모든 프로덕션 에이전트의 필수.
데이터 모델
| 구성 | 설명 |
|---|---|
| namespace | 계층적 튜플 (디렉토리). 예: ("memories", userId, "preferences") |
| key | namespace 내 고유 문자열 |
| value | 임의 JSON. 일부 필드를 인덱싱해 벡터 검색 가능 |
API
| 메서드 | 역할 |
|---|---|
store.put(ns, key, value) | upsert |
store.get(ns, key) | 정확 키 조회 |
store.search(ns, { query, filter, limit }) | 벡터 검색 + 메타 필터 |
store.delete(ns, key) | 삭제 |
메모리의 3분류 (CoALA)
- Semantic — 사실/지식. "사용자는 서울에 산다"
- Episodic — 과거 경험. "3일 전 option B를 골랐다"
- Procedural — 방법/규칙. "이 사용자엔 코드 먼저"
쓰기 전략
- Hot path: 응답 중 동기 쓰기. 단순하지만 지연 증가
- Background: 응답 후 별도 워커가 추출/dedupe/저장. 프로덕션 표준
흔한 실수
- 중복 메모리 폭발 → 벡터 유사도 dedupe 또는 Trustcall 패치 방식 도입
- 임베딩 비용 — 동일 텍스트 재임베딩 피하기
- 네임스페이스에 멀티테넌시 키를 빠뜨리고 시작 — 나중에 분리 불가
- PII를 그대로 저장 → GDPR 대응 시 재앙
핵심
Supervisor LLM이 매 턴 팀원 에이전트 중 하나에게 일을 위임한다. 팀원은 서로를 모르고, 오직 슈퍼바이저만이 전체 흐름을 본다. langgraph-supervisor 패키지로 한 줄 컴파일.
동작
- 각 specialist는
createReactAgent로 만든 독립 에이전트 — name 필수 createSupervisor({ agents, llm, prompt })로 묶음- 슈퍼바이저는 매 step 다음 행동 결정: 특정 agent 호출 또는 종료
- 각 agent는 자신의 ToolNode 루프를 돌고 결과를 반환
설계 패턴
| 패턴 | 설명 |
|---|---|
| Pure delegation | 슈퍼바이저는 라우팅만, 응답 생성은 specialist가 |
| Synthesizing supervisor | 슈퍼바이저가 specialist 결과를 모아 최종 응답 작성 |
| Tool-as-agent | specialist를 슈퍼바이저의 "tool"로 노출 — 더 자연스러운 LLM API |
흔한 실수
- 슈퍼바이저 프롬프트가 specialist 능력을 명시 안 함 → 잘못된 위임
- specialist 이름이 모호 (agent1, agent2) → 기능 기반 이름으로
- 슈퍼바이저가 영원히 같은 에이전트만 호출 → loop 가드 / max steps
핵심
슈퍼바이저 위에 또 슈퍼바이저. 팀 안에 팀이 있고 각 팀은 하나의 서브그래프. 대규모 에이전트 조직(10명+)을 관리할 때 단일 supervisor의 라우팅 정확도가 무너지는 걸 막는다.
동작
- 각 팀을 createSupervisor로 독립 컴파일
- top-level supervisor가 팀들을 agent로 받아 컴파일
- top → 팀 supervisor → specialist 의 3-tier 라우팅
- 각 팀은 독자적인 prompt, tools, memory 가질 수 있음
왜 단일 슈퍼바이저로 안 되나
- 15명+ 에이전트 중 선택 — LLM의 라우팅 정확도가 급락
- 각 specialist 설명을 모두 시스템 프롬프트에 넣으면 토큰 폭발
- 역할별 prompt를 다르게 줘야 할 때 한 슈퍼바이저로는 표현력 부족
설계 팁
| 팁 | 이유 |
|---|---|
| 팀 경계 = 도메인 경계 | research, writing, ops 처럼 의미 단위로 |
| 팀당 3-5 에이전트 | 그 이상이면 또 분할 |
| 팀 간 통신은 top을 통해서만 | peer-to-peer가 필요하면 swarm 패턴 검토 |
핵심
슈퍼바이저가 없다. 에이전트 각자가 다음 활성 에이전트를 직접 지명해 핸드오프한다. 활성 에이전트가 상태에 기록되고 그래프가 그 노드로 흐른다. langgraph-swarm.
동작
- 각 에이전트에 다른 에이전트로 넘기는 handoff tool 부착
- handoff tool 호출 시 상태의 active_agent 변경 + Command(goto=...) 리턴
- 그래프가 활성 에이전트 노드로 라우팅
- 새 에이전트는 같은 messages 컨텍스트를 이어받아 작업
Supervisor vs Swarm
| Supervisor | Swarm | |
|---|---|---|
| 중앙 | O — 매 턴 라우팅 | X — 분산 |
| 토큰 | 슈퍼바이저 LLM 매 턴 호출 | 핸드오프 시에만 |
| 제어 | 강함 | 약함, 자율성 높음 |
| 적합 | 구조화된 워크플로 | 고객 지원, 전문가 릴레이 |
흔한 실수
- handoff 조건이 모호해 ping-pong 발생 → 명확한 위임 기준
- default_active_agent 안 지정 → 첫 진입 시 어디로 가야 할지 모름
- 에이전트끼리 서로 모르는 채 잘못된 핸드오프 → 각 에이전트 prompt에 다른 에이전트 능력 요약 포함
핵심
두 개의 최상급 패턴을 하나로. LLMCompiler는 의존성 있는 도구 호출을 DAG로 병렬 스케줄링. Adaptive RAG는 질의 분류 → 검색 전략 → 등급 → 재작성의 자기보정 루프. 2026 프로덕션 에이전트의 최전선.
Adaptive RAG 동작
- route: 질의 유형 분류 (web/vector/kg/none)
- retrieve: 선택된 소스에서 문서 가져오기
- grade: LLM이 각 문서의 관련성 평가
- 관련 문서가 0개면 rewrite → route로 돌아감
- 충분한 문서 확보 시 generate
- (선택) 최종 응답을 다시 grade — hallucination 체크
LLMCompiler 동작
- Planner가 도구 호출 DAG를 한 번에 생성 — 각 호출에 변수와 의존성 명시
- Task Fetching Unit이 의존성이 충족된 task부터 병렬 실행
- Joiner가 결과를 보고 더 호출이 필요한지 또는 종료할지 결정
- 필요시 Planner로 돌아가 새 DAG 생성
왜 ReAct보다 빠른가
- ReAct는 도구 호출 1개씩 sequential — 5개 호출 = 5번 LLM 왕복
- LLMCompiler는 1번 LLM 왕복으로 5개 task DAG 생성 후 병렬 실행
- 의존성 없는 호출(독립 검색 3개)에서 latency 대폭 감소
흔한 실수
- rewrite 루프가 종료 못 함 → 최대 시도 횟수 가드
- grade가 너무 엄격 → 모든 문서 reject 후 무한 rewrite
- LLMCompiler에서 task 의존성 표현이 부정확 → race condition
- Adaptive RAG의 라우터가 분류 정확도 낮음 → 잘못된 소스로 검색
관련
둘 다 다른 모든 패턴의 조합 — Conditional, Cycles, Send, Reflection이 한 그래프에서 동시에 일어난다.