Skip to content

만들고 나서야 공부를 시작했다 — 내 스택을 역으로 분해하기

공부 순서가 잘못됐다는 걸 최근에 알았다.

나는 8년 동안 Java/Spring으로 백엔드를 했고, 어느 날 갑자기 AI Agent 개발자가 됐다. 체계적으로 배우고 시작한 게 아니라, 요구사항이 떨어졌고, 만들어야 했고, 만들었다. 대화형 진단 서비스를 프로덕션으로 올렸고, GraphRAG를 붙였고, 429 에러랑 싸웠고, 어찌어찌 돌아간다.

그런데 그걸 글로 정리하려니까 이상한 일이 벌어졌다. 내가 만든 걸 설명할 단어가 없었다.

"대화 흐름을 고정된 순서로 관리하는 그 뭐냐... 그거요." 그게 상태기계(FSM)라는 걸, 그리고 그걸 위한 프레임워크가 이미 여러 개 있다는 걸 나중에 알았다. 심지어 내가 직접 만든 "런타임에 모델 바꾸는 기능"은 LiteLLM이라는 이름으로 이미 표준화되어 있었다.

이 글은 그 뒤늦은 수습 과정에 대한 기록이다. 비슷한 처지인 사람이 꽤 있을 것 같아서 정리해둔다.


먼저 만든 사람의 두 가지 부채

선(先)구현 후(後)학습이 나쁜 건 아니다. 오히려 손이 먼저 움직인 덕에 남는 게 있다. 다만 부채가 두 개 생긴다.

첫째, 용어 부채. 내가 만든 건 멀쩡한데 그걸 부르는 이름을 모른다. 대화가 안 통한다. 문서를 검색해도 원하는 게 안 나온다. 남들이 이미 해결해둔 문제를 처음부터 다시 푼다.

둘째, 선택 근거 부채. 이게 더 무섭다. 나는 A를 골랐는데, 왜 B가 아니라 A인지를 모른다. 정확히는 — B의 존재 자체를 몰랐다. 검토해서 탈락시킨 게 아니라 그냥 몰랐던 거다. 이건 "아키텍처 결정"이 아니라 "우연"이다.

두 번째 부채는 특히 남이 물어볼 때 드러난다. 리뷰든 면접이든, 사람들은 "무엇을 만들었나"보다 "왜 그걸 골랐나"를 묻는다. 그리고 나는 그때까지 그 질문을 나 자신에게 해본 적이 없었다.


0단계: 내가 쓴 말을 표준 용어로 번역하기

가장 먼저 한 건 사전 만들기였다. 내가 사내 문서나 회고에 쓴 표현과, 업계에서 실제로 쓰는 용어를 나란히 놓아봤다.

내가 쓰던 말실제 용어
대화 흐름 관리 엔진Finite State Machine / Dialogue Manager
응답 포맷 강제Structured Output / Constrained Decoding
부족한 항목 되묻기Slot Filling / Clarification Question
LLM 실패 시 규칙으로 대체Graceful Degradation / Fallback Chain
키워드 + 의미 검색 섞기Hybrid Search + RRF Fusion
순서 돌려가며 처리하는 큐Fair Queuing / Backpressure
재배포 없이 값 바꾸기Feature Flag / Dynamic Configuration
프롬프트를 DB에 넣어 관리Prompt Registry / Versioning

별거 아닌 것 같은데 효과가 컸다. 왼쪽 열은 검색해도 아무것도 안 나오지만, 오른쪽 열은 검색하면 논문과 라이브러리와 이미 그 문제로 고생한 사람들의 글이 나온다. 반나절 만에 몇 달치 시행착오를 건너뛴 기분이었다.

혹시 나처럼 만들고 나서 공부하는 중이라면, 이 사전 만들기부터 하는 걸 권한다. 어떤 강의보다 투자 대비 효율이 좋았다.


1단계: 내가 안 고른 것들을 공부하기

이게 진짜 핵심이다. 좀 이상하게 들리겠지만 — 내가 쓴 기술보다 안 쓴 기술을 공부하는 게 더 급했다.

내가 쓴 건 이미 손에 익었다. 매일 만진다. 근데 대체제를 모르면 내 선택은 그냥 우연이다. 몇 개만 짚어보면.

상태기계를 직접 만든 것 vs LangGraph

나는 진단 흐름을 결정적 상태기계로 두고, LLM은 문장과 선택지를 생성하는 레이어로만 썼다. 지금도 이 분리는 좋은 결정이었다고 생각한다. 흐름이 결정적이니까 테스트가 되고, 어느 단계에서 뭘 물었는지 로그로 추적되고, LLM이 죽어도 흐름은 산다.

근데 LangGraph를 나중에 보고 좀 아찔했다. 체크포인터로 상태를 영속화하고, Human-in-the-loop 인터럽트가 내장돼 있고, 스트리밍도 붙어 있다. 내가 손으로 짠 것들이 대부분 거기 있었다.

그렇다고 잘못 만든 건 아니다. 당시 흐름이 고정적이었고, 의존성을 늘리고 싶지 않았고, 디버깅할 때 스택이 얕은 게 유리했다. 다 진짜 이유다. 다만 이걸 지금 알게 됐다는 게 문제다. 그때는 이유가 아니라 무지였다.

Semantic Kernel(Azure/.NET 친화적이고 엔터프라이즈 거버넌스 훅이 있다), Temporal(장기 워크플로의 재시도·보상 트랜잭션은 여기가 정석이다), 그리고 그냥 transitions 같은 순수 FSM 라이브러리까지 — 각각 어떤 상황에 맞는지 정도는 알아야 내 선택을 방어할 수 있다.

이건 좀 뼈아팠다. 나는 PostgreSQL을 쓰면서 검색은 Elasticsearch로 붙였다. 그런데 pgvector를 쓰면 확장 하나로 같은 DB 안에서 벡터 검색이 된다. 트랜잭션도 조인도 그대로 쓰면서.

게다가 인프라가 Azure였는데 Azure AI Search는 하이브리드 검색과 시맨틱 랭커, RRF 융합을 기본으로 제공한다. 운영 부담이 훨씬 적다.

물론 Elasticsearch가 나쁜 선택은 아니다. BM25는 여전히 강력하고, 고유명사나 숫자, 코드 같은 건 벡터 검색이 오히려 못 찾는다. 필터링과 집계도 풍부하다. 다만 "왜 pgvector가 아니라 ES인가"라는 질문에 답을 준비하는 것과, 그 질문을 처음 듣는 건 완전히 다른 상황이다.

인메모리 우선순위 큐 vs Celery

동시 접속이 몰릴 때 요청을 평탄화하려고 우선순위 큐를 직접 만들었다. 사용자 클릭을 최우선으로 두고 나머지는 라운드로빈으로 도는 방식.

작동은 잘 했다. 근데 명확한 한계가 있다. 프로세스가 재기동되면 큐가 통째로 날아간다. 그리고 인스턴스를 늘리면 각자 자기 큐를 갖게 되니 우선순위가 무의미해진다. Celery + Redis를 썼으면 둘 다 공짜로 해결됐을 문제다.

이건 방어할 게 아니라 인정할 부분이다. "단일 인스턴스 전제였고, 수평 확장 시점에는 외부 브로커로 빼야 한다"까지 말할 수 있으면 그건 한계 인식이지 실수가 아니다.

부하 테스트 도구를 직접 만든 것 vs k6

가상 사용자를 동시에 쏘는 도구를 직접 짰다. 나중에 k6와 Locust를 알고 나서 "괜히 만들었나" 싶었는데, 이건 좀 생각이 다르다.

내가 측정해야 했던 건 단순 RPS가 아니라 세션 상태를 이어가며 여러 턴을 진행하는 대화 시나리오였다. 이건 범용 도구로 짜기가 은근히 번거롭다. 그래서 이 결정은 지금도 옳았다고 본다.

이런 케이스도 있어야 한다. 전부 "표준 도구가 있었는데 몰랐다"로 끝나면 그건 그것대로 자기비하다.


2단계: 자소서가 아니라 로그가 증명한다

여기서 제일 뼈아픈 걸 깨달았다.

나는 "검색 정확도를 높였다", "처리 속도를 30% 이상 개선했다"고 써놨다. 실제로 좋아졌다. 현업도 만족했다. 그런데 무엇으로 측정했냐고 물으면 할 말이 없다. 눈으로 보고, 몇 건 돌려보고, 좋아졌다고 판단했다.

RAG 평가 도구들이 있다는 걸 그 뒤에 알았다. RAGAS는 Faithfulness(답변이 근거 문서에 충실한가 = 환각 측정), Answer Relevancy, Context Precision/Recall을 뽑아준다. 중요한 건 검색 품질과 생성 품질을 분리해서 본다는 점이다. 나는 이 둘을 뭉뚱그려 "정확도"라고 부르고 있었다.

개인적으로 가장 크게 얻은 교훈

"측정 가능한 지표를 만들고 데이터로 최적점을 찾는다"는 원칙을 나는 부하 테스트에서는 지켰다. 큐 적체량과 429 카운터를 대시보드에 띄우고 실측했으니까. 그런데 똑같은 원칙을 LLM 품질에는 적용하지 않았다. 인프라 지표는 익숙해서 계측할 생각을 했고, 품질 지표는 낯설어서 감으로 갔다. 이 비대칭이 딱 내 부채의 모양이었다.

프롬프트를 고쳤을 때 이전보다 나아졌는지 검증하는 회귀 세트가 없다는 것도 같은 문제다. 골든 데이터셋을 만들어두고 프롬프트 변경마다 돌리는 게 정석인데, 나는 매번 새로 감으로 판단했다. 좋아졌다고 믿었지만 증명은 못 한다.


3단계: 도메인이 요구하는 것

금융·보험 쪽 AI 이야기를 준비하면서 알게 된 건데, 이 도메인은 기술 스택보다 통제 구조를 먼저 본다.

개인정보보호법이 2024년 개정되면서 자동화된 결정에 대한 거부권과 설명 요구권이 들어왔다. AI가 보험금 심사나 인수 판단에 관여하면 이게 바로 걸린다. EU AI Act에서는 신용평가와 보험 인수를 아예 고위험(High-risk)으로 분류한다. 문서화, 인간 감독, 로그 보존이 의무다.

여기서 재밌는 게 있다. 내가 아키텍처 편의를 위해 한 결정 하나가 규제 관점에서 꽤 유리했다는 것.

판단 로직은 결정적 규칙으로 빼고, LLM은 서술만 담당하게 한 구조 말이다. 이러면 "왜 이 결과가 나왔나"에 대해 LLM을 해명할 필요가 없다. 규칙이 근거다. LLM은 그걸 문장으로 옮겼을 뿐이고.

반대로 조심할 게 하나 있는데, LLM의 사고 과정(Chain-of-Thought)을 설명 근거로 제시하면 안 된다는 점이다. 그건 결정의 실제 이유가 아니라 사후 합리화일 수 있다. 이걸 모르고 "AI가 이렇게 추론했습니다"를 심사 근거로 붙이면 나중에 크게 문제가 된다. 나도 최근에 알았다.

전통 ML의 XAI(SHAP, LIME 같은 것)와 LLM의 설명가능성은 다른 문제라는 것도 구분해야 한다. LLM 쪽에서 실질적으로 쓸 수 있는 건 결국 근거 문서를 명시하는 Citation과 Grounding이다. 이 지점에서 GraphRAG가 단순 벡터 검색보다 유리하다 — 근거 문서만이 아니라 문서 사이의 관계까지 함께 반환할 수 있으니까.


그래서 순서는

내가 다시 짠 순서는 이렇다.

  1. 용어 사전 만들기 (반나절) — 내가 쓰는 말 ↔ 표준 용어 매핑
  2. 안 고른 기술 훑기 (며칠) — 대체제의 존재와 트레이드오프. 깊이는 필요 없다
  3. 측정 도구 붙이기 — 이미 만든 시스템에 지금이라도 평가 지표를 얹는다
  4. 도메인 규제 — 내가 일하는(또는 일하고 싶은) 산업이 요구하는 통제 구조
  5. 그 다음에야 새 기술 — 여기까지 하기 전에 새 프레임워크 배우는 건 순서가 아니다

3번이 좀 특이한데, 의도적이다. 이미 돌아가는 시스템에 지표를 붙이는 건 새로 배우는 게 아니라 이미 한 일을 증명 가능하게 만드는 일이다. 투자 대비 회수가 제일 빠르다.


아직 정리 안 된 부분

솔직히 결론이 깔끔하게 안 난다.

먼저 만들고 나중에 배운 게 손해였나? 잘 모르겠다. 순서대로 공부했으면 LangGraph를 썼을 거고, 그러면 상태기계를 직접 짜면서 배운 것들 — 왜 결정성이 중요한지, 어디서 LLM을 격리해야 하는지 — 을 몸으로 알지는 못했을 것 같다. 프레임워크가 대신 해주는 걸 이해하는 것과, 없어서 직접 만들어보는 건 남는 게 다르다.

그렇다고 "직접 만들어보는 게 최고"라고 말하고 싶지도 않다. 인메모리 큐 같은 건 그냥 Celery 쓰는 게 맞았다. 배운 게 있어도 그건 배우지 않아도 됐을 것을 배운 거다.

지금 잠정적으로 내린 결론은, 핵심 도메인 로직은 직접 만들고 주변부는 표준을 쓴다는 정도. 근데 뭐가 핵심이고 뭐가 주변부인지는 결국 대체제를 알아야 판단이 되니까, 돌고 돌아 같은 얘기다. 알아야 안 만들 수 있다.


참고자료

실제로 도움이 된 것만.

삽질 테크 블로그