3년 된 핀테크 코드베이스에서 AI의 환각(Hallucination) 현상을 해결한 방법
요약
기존 코드베이스에서 AI 코딩 도구가 발생하는 환각 현상을 해결하기 위해 컨텍스트를 강화하는 방법을 다룹니다. ADR(Architectural Decision Records)을 활용하여 AI가 프로젝트의 구조와 규칙을 명확히 이해하도록 유도하는 워크플로우를 제안합니다.
핵심 포인트
- AI는 제공된 컨텍스트의 범위 내에서만 정확하게 작동함
- 기존 데이터베이스 스키마를 무시하는 환각 현상 발생 가능성
- ADR(아키텍처 결정 기록)을 통해 AI에게 명시적인 가이드 제공
- AI가 좋은 결정을 내릴 수 있도록 구조화된 컨텍스트 구축 필요
AI 코딩 도구들은 별도의 설정 없이 바로 사용하기에는 신뢰할 수 없습니다. 실제 프로덕션 프로젝트에서 이를 사용해 본 사람이라면 누구나 이 사실을 알고 있습니다. 새로운 프로젝트(Greenfield code)에서는 훌륭하게 작동하지만, 히스토리가 쌓인 코드베이스에서는 무너집니다.
저는 이 사실을 고통스럽게 배웠습니다.
문제점
저희는 거의 3년 동안 운영되어 온 핀테크 프로젝트를 가지고 있습니다. 복잡한 관계형 데이터베이스(Relational database)를 기반으로 하는 FastAPI 백엔드 위에 두 개의 React 프론트엔드(관리자 패널 및 클라이언트용 앱)가 구축되어 있습니다. 수많은 테이블, 무거운 조인(Join), 그리고 주의 깊게 다뤄야 하는 금융 및 개인 사용자 데이터가 존재합니다.
새로운 기능을 추가하는 것은 간단해야 합니다. 대부분의 작업은 새로운 프론트엔드 페이지, 새로운 API 경로, 외부 금융 데이터 제공업체와의 새로운 통합처럼 명확합니다. 하지만 작업 시간이 예상보다 오래 걸렸습니다. 데이터베이스 구조를 완전히 이해하고 있는 사람이 단 한 명뿐이었기 때문입니다. 모든 새로운 쿼리(Query)와 모든 새로운 테이블 조인에는 반드시 그 사람이 개입해야 했습니다.
저희는 더 빠르게 움직이기 위해 워크플로우에 AI를 도입하기로 결정했습니다. 바로 그때 흥미로운 일이 발생했습니다.
AI가 우리의 Users 테이블을 완전히 무시해 버렸습니다
저는 AI에게 contacts 테이블을 만들어 달라고 요청했습니다. AI는 요청대로 수행했습니다. 그리고 이름(first name), 성(last name), 이메일, 전화번호 컬럼을 포함하여 테이블을 생성했습니다. 이 모든 필드는 이미 우리의 users 테이블에 존재하는 것들이었습니다. AI는 두 테이블을 외래 키(Foreign key)로 연결하는 대신, 단순히 모든 것을 새 테이블에 쏟아부어 데이터를 중복 생성했습니다. 저는 직접 들어가서 스키마(Schema)를 수동으로 수정해야 했습니다.
이것은 AI가 멍청해서 발생한 일이 아니었습니다. AI는 우리의 users 테이블이 존재하는지 전혀 알지 못했습니다. 불완전한 정보로 합리적인 결정을 내린 것이며, 이것이 바로 스키마에 반영되기 전까지는 포착하기 어려운 실수 유형입니다.
이 사건은 제가 문제에 대해 생각하는 방식을 바꾸어 놓았습니다. "어떻게 하면 AI가 더 나은 코드를 쓰게 만들 수 있을까?"라고 묻는 대신, 저는 다음과 같이 질문하기 시작했습니다: "AI가 좋은 결정을 내리기 위해 무엇을 알아야 하는가?"
해결책: AI가 읽기 쉬운 코드베이스 만들기
저는 Matt Pocock의 구조화된 AI 워크플로우 (structured AI workflows) 연구에서 영감을 얻어 이를 우리 팀에 맞게 조정했습니다. 핵심 아이디어는 간단합니다. AI는 당신이 제공하는 컨텍스트 (context)만큼만 똑똑합니다. 그래서 우리는 컨텍스트를 명시적이고 권위 있게 만들었습니다.
우리가 구축한 내용은 다음과 같습니다:
1. 아키텍처 결정 기록 (Architectural Decision Records, ADRs)
우리는 ADR 파일들이 담긴 docs/adrs/ 디렉토리를 생성했습니다. 이는 단순히 무엇을 결정했는지가 아니라, 왜 특정 아키텍처 결정을 내렸는지를 기록하는 문서입니다. 각 ADR은 AI가 직면할 수 있는 구체적인 질문에 답합니다:
- 새로운 테이블을 어떻게 생성하는가?
- 테이블들을 어떻게 서로 연결하는가?
- 새로운 API 라우트 (API route)를 어떻게 구조화하는가?
- 코드베이스 내에서 각 유형의 파일은 어디에 위치하는가?
contacts 테이블 실수는 ADR-001이 되었습니다: 새로운 테이블을 생성할 때, 먼저 기존 테이블을 확인하십시오. 관련 필드가 이미 존재한다면 외래 키 (foreign key)를 사용하십시오. 사용자 데이터를 절대 중복 생성하지 마십시오. 기존 테이블에 속할 수 있는 새로운 컬럼을 만들기 전에는 항상 문의하십시오.
이제 AI가 새로운 테이블 작업을 마주하면, 그 규칙을 가장 먼저 읽습니다.
2. 컨텍스트 파일 및 용어 사전 (Glossary)
우리 코드베이스에는 우리에게 특정한 의미를 갖는 용어들이 있었고, 일반적인 코드로 학습된 AI가 오해할 수 있는 단어들이 있었습니다. 우리는 context.md를 작성하여 우리 코드베이스에서 각 용어가 구체적으로 무엇을 의미하는지, 서로 다른 용어들이 어떻게 연관되는지, 그리고 발음이 비슷하지만 우리 시스템에서는 실제로 다른 개념들이 무엇인지 설명했습니다.
또한 프로젝트가 무엇인지, 어떤 기능을 수행하는지, 그리고 각 요소들이 어떻게 연결되는지를 보여주는 상위 수준의 지도인 plot.md를 작성했습니다.
두 파일 모두 상단에 하나의 규칙을 명시하고 있습니다: docs 디렉토리가 권위 있는 기준이다. 이 규칙들은 제안 사항이 아니다. 순서대로 따르라. 단계를 건너뛰지 마라.
3. 예외 없는 모든 API에 대한 테스트 케이스 (Test Cases)
이제 모든 새로운 API 라우트는 테스트 케이스와 함께 배포됩니다. 선택 사항이 아닙니다. "시간이 날 때" 하는 것도 아닙니다.
이것은 코드 품질뿐만 아니라, 긴 세션 동안 AI의 신뢰성을 유지하는 데 있어 제가 예상했던 것보다 훨씬 더 중요한 역할을 한다는 것이 밝혀졌습니다.
한 번은 이런 일이 있었습니다. AI가 공유 유틸리티 함수(shared utility function)에 작은 변경을 가했습니다. 단독으로 보기에는 무해해 보이는 종류의 변경이었습니다. 하지만 그 함수는 12곳에서 사용되고 있었고, 그 변경으로 인해 8곳에서 오류가 발생했습니다. 테스트 스위트(test suite)가 이를 즉시 잡아냈습니다. AI는 실패를 확인하고, 이를 공유 함수로 역추적했습니다. 그리고 단순히 되돌리기(revert)를 하는 대신, 기존의 동작과 새로운 요구사항을 모두 처리할 수 있는 함수의 새로운 버전을 생성했습니다. 제가 손을 대지도 않았는데 AI가 스스로 자신의 실수를 바로잡은 것입니다.
만약 그런 테스트들이 없었다면, 그 버그는 그대로 배포되었을 것입니다.
팀에게 일어난 변화
이 사례를 팀원들에게 보여주었을 때, 몇몇 사람들은 회의적이었습니다. 이전에도 AI 도구들이 그들을 실망시킨 적이 있었기 때문입니다. 그들은 환각(hallucination) 현상, 깨진 코드, 그리고 확신에 찬 오답들을 목격해 왔습니다.
세션이 끝난 후, 세 명의 팀원이 자신의 프로젝트도 같은 방식으로 설정해 달라고 요청했습니다.
이 변화는 AI를 더 신뢰하게 된 것에 관한 것이 아니었습니다. 새로운 개발자에게 온보딩(onboarding), 문서화(documentation), 그리고 코드 리뷰(code review)가 필요한 것과 마찬가지로, AI가 신뢰성을 갖기 위해서는 구조(structure)가 필요하다는 점을 이해하게 된 것이었습니다. 신입 사원이 당신의 코드베이스를 모른다고 해서 그를 비난하지는 않습니다. 대신 문서화할 뿐입니다.
우리는 AI에게도 똑같이 했습니다.
직접 시도해보고 싶다면: 설정 방법
docs/
context.md # 이 프로젝트가 무엇인지, 용어의 의미는 무엇인지, 구성 요소들이 어떻게 연결되는지
plot.md # 코드베이스의 상위 수준 지도(high-level map)
...
중요한 몇 가지 사항은 다음과 같습니다:
- ADR(Architecture Decision Records)을 구체적으로 작성하세요. "좋은 데이터베이스 관행을 따를 것"보다는 "새로운 테이블을 만들기 전에 기존 테이블을 확인할 것"이 훨씬 낫습니다.
- 문서를 권위 있게 만드세요. AI에게 이러한 규칙이 항상 최우선이라는 점을 명시하세요.
- AI가 실수를 할 때마다 새로운 ADR을 추가하세요. 실패를 규칙으로 전환하십시오.
- 모든 새로운 API에 대해 테스트 케이스를 작성하세요. 모든 커밋(commit) 전에 테스트를 실행하세요. AI가 무언가를 망가뜨렸을 때, 해당 케이스에 대한 테스트를 추가하여 다시는 조용히 오류가 발생하지 않도록 하세요.
마지막으로 한 가지
이 시스템이 AI를 완벽하게 만드는 것은 아닙니다. 이 시스템은 AI를 예측 가능하게(predictable) 만듭니다. 이 둘 사이에는 분명한 차이가 있습니다.
AI는 여전히 실수를 합니다. 하지만 이제 그 실수들은 더 작고, 포착하기 쉬우며, 실수가 발생했을 때 우리는 그것을 다음 ADR (Architecture Decision Record)에 인코딩(encode)할 수 있는 무언가로 활용할 수 있습니다.
우리의 목표는 AI가 모든 것을 완벽하게 수행하는 코드베이스를 만드는 것이 아니었습니다. 우리의 목표는 AI가 일관되게 동작하며, 그 덕분에 팀이 더 빠르게 움직일 수 있는 코드베이스를 만드는 것이었습니다.
그것이 바로 우리가 구축한 것입니다.
저는 Python, Go, Node.js 기반의 백엔드 시스템을 다루는 풀스택 개발자이자 테크 리드(tech lead)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기