먼저 답하면
그래프 엔지니어링은 AI 에이전트의 실행 흐름을 노드(작업 단위), 엣지(이동 규칙), 공유 상태(주고받는 데이터) 세 가지로 명시해서 설계하는 방식입니다. 핵심 변화는 딱 하나입니다. “다음에 뭘 할지”를 매 턴 모델에게 묻는 대신, 개발자가 미리 코드로 고정합니다.
효과는 속도가 아니라 통제입니다. 흐름을 코드로 적어두면 병렬 실행과 중단 후 재개가 가능해지고, 무엇보다 반드시 거쳐야 하는 검증 단계를 건너뛸 수 없게 됩니다. 반대로 노드가 서너 개뿐이고 분기가 없는 작업이라면 프레임워크를 얹는 순간 코드만 늘고 얻는 건 없습니다.
저는 이 개념을 듣고 제 블로그 발행 파이프라인을 열어봤습니다. 문서에는 “게이트 통과 없이 발행 불가”라고 적혀 있었는데, 실제 코드에서는 발행 경로 7개 중 3개가 검증을 우회하고 있었습니다. 이 글은 그 점검 과정과 판단 기준을 정리한 기록입니다.
또 새 유행어인 줄 알았습니다
솔직히 처음엔 시큰둥했습니다. 프롬프트 엔지니어링, 컨텍스트 엔지니어링, 루프 엔지니어링 다음에 그래프 엔지니어링이라니, 반년마다 이름만 바꿔 다는 것처럼 보였거든요. 개념 설명도 “노드와 엣지로 만든다”는 수준이라 새로울 게 없어 보였습니다.
생각이 바뀐 건 제 파이프라인을 실제로 그려보고 나서입니다. 종이에 노드와 화살표를 옮겨 적는 순간, 문서로 읽을 땐 안 보이던 질문들이 튀어나왔습니다. 게이트에서 반려하면 초안의 어떤 필드를 남기고 어떤 걸 버리는지, 실패 횟수는 누가 세는지, 그리고 발행 노드로 들어오는 화살표가 정말 하나뿐인지.
세 번째 질문에서 걸렸습니다. 세어보니 화살표가 일곱 개였고, 검증이 걸린 건 한 개였습니다.
루프와 그래프는 뭐가 다른가
루프 방식은 모델이 매 턴 판단합니다. 도구를 부르고, 결과를 보고, 다음에 뭘 할지 스스로 정하고, 다시 돕니다. 유연하다는 게 장점인데 이 유연함이 그대로 단점이 됩니다. 판단이 90% 정확해도 백 번 돌리면 반드시 몇 번은 엉뚱한 데로 샙니다.
그래프 방식은 그 판단을 미리 못박습니다. “게이트 결과가 PASS면 발행으로, FAIL이면 작성으로, 두 번 연속 실패면 사람에게”를 평범한 조건문으로 적어둡니다. 모델은 판정 재료만 만들고 목적지는 코드가 정합니다.
| 루프 | 그래프 | |
|---|---|---|
| 다음 단계 결정 | 모델이 매 턴 판단 | 코드에 고정 |
| 병렬 실행 | 사실상 어려움 | 의존성 없으면 동시 실행 |
| 중간 실패 | 대개 처음부터 다시 | 실패한 노드부터 재개 |
| 필수 단계 강제 | 프롬프트로 부탁 | 구조적으로 통과 불가 |
| 설계 비용 | 낮음 | 높음 |
여기서 오해하기 쉬운 지점이 있습니다. 그래프가 비결정성을 없애주는 게 아닙니다. 모델을 부르는 노드는 여전히 확률적입니다. 달라지는 건 그 확률적인 부분이 어디에 몇 개나 있는지 내가 안다는 것입니다. 루프는 전체가 확률적이라 어디서 샜는지 찾을 수가 없습니다.
부품은 노드, 엣지, 상태 셋이다
노드는 실제로 일하는 단위입니다. 상태를 받아서 바뀐 부분만 돌려주는 함수라고 보면 됩니다. 중요한 건 노드가 전부 AI일 필요가 없다는 점입니다. “본문이 2,800자 넘었나”를 확인하는 데 언어 모델을 부를 이유는 없는데, 루프로 굴리다 보면 이런 것까지 전부 모델이 하고 있는 경우가 많습니다.
엣지는 노드 사이의 화살표입니다. 항상 같은 순서로 가는 고정 엣지와, 조건에 따라 갈라지는 조건부 엣지 두 종류가 있습니다. 조건부 엣지의 판정을 파이썬으로 쓰면 결정론적이고 모델에게 맡기면 확률적인데, 이 선택이 사실상 그래프 엔지니어링의 전부라고 해도 됩니다.
상태는 노드들이 주고받는 데이터입니다. 초보자가 제일 많이 데는 곳이 여기입니다. 상태를 “지금까지의 대화 전체”로 잡아버리면 이름만 그래프지 결국 루프와 똑같아집니다. 검증 노드가 작성 노드의 변명까지 다 읽게 되니까요.
그래서 상태는 스키마를 좁게 잡아야 합니다. 여러 노드가 동시에 같은 필드를 건드릴 수 있다면 병합 규칙, 그러니까 리듀서를 정해야 합니다. LangGraph에서는 Annotated[list, operator.add] 같은 표기로 “덮어쓰지 말고 이어붙여라”를 지정하는데, 이걸 안 정하면 병렬 노드 중 늦게 끝난 쪽이 조용히 이깁니다.
내 파이프라인에 대봤더니 구멍이 셋 나왔습니다
제 블로그 발행 흐름은 작성 → 게이트 → 발행 3단이고, 게이트에서 떨어지면 작성으로 반려되고 두 번 연속 떨어지면 사람에게 올라가게 되어 있습니다. 문서에는 그렇게 적혀 있었습니다. 코드를 열어보니 얘기가 달랐습니다.
첫째, 검증 함수가 드라이런에서만 호출되고 있었습니다. 분량과 필수 섹션을 확인하는 ensure_publishable_markdown이 미리보기 경로에만 걸려 있고, 실제 발행하는 세 함수는 그걸 지나치지 않았습니다. 미리보기는 엄격한데 진짜 발행은 무방비인 상태로 몇 달을 돌린 셈입니다.
둘째, 실패 횟수를 아무도 세지 않았습니다. “두 번 연속 실패하면 에스컬레이션”이 문서에만 있었고 코드에는 카운터가 없었습니다. 모델이 기억해주길 기대하는 구조였는데, 세션이 끊기면 그 기억도 같이 사라집니다.
셋째, 채널마다 발행 스크립트가 달랐습니다. 워드프레스 채널 둘과 블로거 채널 하나가 서로 다른 파일을 쓰는데, 한쪽에만 검증을 걸어놓고 전부 막았다고 착각하고 있었습니다. 이건 게이트를 만든 당일에 제가 직접 저지른 실수입니다.
라우팅을 코드로 내리면 달라지는 것
고친 방식은 단순합니다. 모델은 네 개 검토 항목의 통과 여부와 치명적 오류 개수만 보고하고, 판정과 목적지 계산은 입출력이 없는 순수 함수 두 개가 맡습니다.
def decide(verdict, fail_count):
if verdict == "PASS":
return "ship"
if fail_count >= 2:
return "escalate"
return "write"
이렇게 하면 실패 횟수가 파일에 남아서 세션이 끊겨도 유지되고, 재작성 시도가 두 번에서 강제로 멈춥니다. 재작성 한 번이 가장 비싼 모델 호출이라, 잘 풀리는 날은 비용 차이가 없고 안 풀리는 날의 최악값만 잘려나갑니다. 이게 결정론적 라우팅이 실제로 파는 물건입니다.
하나 더 붙인 게 판정 시점의 본문 해시를 같이 저장하는 것입니다. 게이트를 통과한 뒤에 본문을 슬쩍 고치면 해시가 달라져서 발행이 막힙니다. 검증한 글과 나가는 글이 같다는 걸 사람의 성실함이 아니라 코드가 보증하게 됩니다.
언제 과한가
모든 워크플로를 그래프로 옮기는 건 명백한 과잉입니다. 판단 기준을 정리하면 이렇습니다.
| 상황 | 판단 |
|---|---|
| 노드 3개 이하, 분기 없음 | 함수 3개 순차 호출로 충분 |
| 필요한 게 재시도뿐 | 재시도 데코레이터 하나면 끝 |
| 사람이 매번 지켜보는 작업 | 재개 기능이 쓸모없음 |
| 병렬로 돌릴 작업이 있음 | 그래프 값어치 있음 |
| 중간에 끊겼다 이어야 함 | 그래프 값어치 있음 |
| 반드시 거쳐야 할 승인 단계가 있음 | 그래프 값어치 있음 |
제 경우 병렬이나 재개는 급하지 않았습니다. 발행 속도가 문제였던 적이 없거든요. 값어치가 있었던 건 세 번째, 건너뛰면 안 되는 단계를 진짜로 못 건너뛰게 만드는 것이었습니다. 그래서 프레임워크를 통째로 도입하지 않고 조건부 엣지 하나만 코드로 내렸습니다.
옮기는 순서는 3층으로 나뉩니다
1층은 그리기입니다. 설치할 것도 없고 30분이면 됩니다. 지금 문장으로 흩어져 있는 흐름을 노드, 엣지, 상태 세 칸짜리 표로 옮겨 적기만 해도 앞에서 말한 종류의 질문들이 튀어나옵니다. 제가 구멍 셋을 찾은 것도 코드를 읽기 전 이 단계에서였습니다.
2층은 상태 계약을 고정하는 것입니다. 노드끼리 주고받는 필드를 파일 하나로 못박고, 각 에이전트에게 “너는 이 필드만 읽고 이 필드만 쓴다”를 명시합니다. 라이브러리 없이도 되고, 투자 대비 회수는 여기가 제일 좋습니다.
3층이 런타임입니다. LangGraph 같은 걸 깔고 체크포인터와 조건부 엣지를 제대로 쓰는 단계인데, 대부분의 개인 작업은 여기까지 안 가도 됩니다. 저도 3층은 통째로 도입하지 않고 필요한 조각만 가져왔습니다.
실수하기 쉬운 지점
가장 흔한 실수는 노드를 전부 모델로 채우는 것입니다. 그래프로 옮기는 이득의 절반은 “여기는 AI 안 써도 되네”를 발견하는 데서 나옵니다. 분량 확인, 금지어 검사, 비중 계산 같은 건 전부 함수여야 하는데 루프로 굴리면 자연스럽게 모델이 하고 있습니다.
두 번째는 문서와 코드가 어긋나는 걸 방치하는 것입니다. 규칙 문서에 “반드시 거친다”고 쓰는 것과 코드가 다른 경로를 막는 것은 신뢰도가 다릅니다. 문서는 지켜달라는 부탁이고 코드는 강제입니다. 제가 몇 달간 안전하다고 믿었던 근거가 부탁이었습니다.
세 번째는 입구가 여러 개인 걸 모르는 것입니다. 검증을 한 군데 걸어놓고 다 막았다고 생각하기 쉬운데, 같은 일을 하는 함수가 넷이면 화살표도 넷입니다. 저는 이걸 고치면서 발행 경로 전체가 함수 하나를 지나가게 묶고, 그 함수 주석에 “새 경로도 반드시 여기를 거칠 것”이라고 적어뒀습니다.
FAQ
Q. LangGraph를 꼭 써야 하나요? 아닙니다. 그래프 엔지니어링은 사고방식이고 LangGraph는 그걸 구현한 도구 중 하나입니다. 조건부 엣지 하나를 파이썬 함수로 내리는 것만으로도 핵심 이득의 상당 부분을 가져올 수 있습니다. 체크포인터 기반 재개나 병렬 팬아웃이 실제로 필요해질 때 도입해도 늦지 않습니다.
Q. 루프 엔지니어링은 이제 안 쓰는 건가요? 대체 관계가 아니라 포함 관계에 가깝습니다. 그래프 안의 노드 하나가 내부적으로 루프를 도는 경우가 흔합니다. 달라지는 건 그 루프가 언제 끝나고 어디로 가는지를 바깥에서 코드가 정한다는 점입니다.
Q. 소규모 개인 프로젝트에도 필요한가요? 대부분은 필요 없습니다. 다만 “이 단계는 절대 건너뛰면 안 된다”는 규칙이 하나라도 있다면, 그 규칙만큼은 프롬프트가 아니라 코드로 옮겨두는 걸 권합니다. 제 경우 그 규칙 하나 때문에 전체를 점검하게 됐습니다.
Q. 상태 스키마는 어떻게 시작하면 되나요? 지금 노드들이 실제로 주고받는 값을 나열하고, 그중 누가 쓰고 누가 읽는지 표시하는 것부터 시작하면 됩니다. 읽는 쪽이 필요 없는 필드가 보이면 그게 잘라낼 후보입니다. 병렬 노드가 같은 필드를 건드리면 그때 리듀서를 정하면 됩니다.
참고 자료
- 코드팩토리, 「또다른 트렌드 Graph Engineering 알려드림」 (2026) — https://youtu.be/SBLDc4R1d_E
- LangGraph 공식 문서, LangGraph overview — https://docs.langchain.com/oss/python/langgraph/overview
- LangGraph 공식 문서, LangGraph components 개념 — https://langchain-ai.github.io/langgraph/concepts/langgraph_components/
본문의 파이프라인 점검 내용은 2026년 8월 8일 제 볼트에서 직접 확인한 결과이며, 관련 변경은 커밋 93d39b4와 e1e1fde에 남겨두었습니다.