뱅크샐러드의 LLM 코드 안전화 — DSL로 Vibe Coding을 프로덕션에 쓰는 법
LLM이 생성한 코드를 프로덕션에서 안전하게 실행하기 위해 뱅크샐러드가 선택한 DSL 기반 전략을 해부한다.
LLM이 코드를 생성해주는 건 좋다. 문제는 그 코드를 어디에 쓰느냐다.
개발자 로컬 환경에서 프로토타입을 뚝딱 만드는 건 괜찮다. 그런데 비개발 직군인 운영자가 LLM 생성 코드를 직접 프로덕션에 반영하는 상황이라면? 뱅크샐러드는 이 문제를 정면으로 맞닥뜨렸고, 해결책으로 DSL(Domain Specific Language) 이라는 중간 계층을 설계했다.
문제: LLM 코드의 예측 불가능성
뱅크샐러드의 샐러드게임은 운영자가 실시간으로 미션 규칙을 수정할 수 있어야 한다. "특정 카페 브랜드에서 결제하면 미션 달성"처럼 비즈니스 로직이 수시로 바뀐다.
여기서 선택지가 갈린다.
| 방식 | 장점 | 단점 |
|---|---|---|
| 순수 LLM 코드 생성 | 자유도 높음 | 환각, 보안 구멍, 검증 불가 |
| 하드코딩된 UI | 안전 | 운영자 자율성 없음 |
| DSL + LLM | 안전 + 자율성 | 설계 비용 선투자 |
핵심 문제는 LLM이 생성한 일반 코드는 무엇을 실행할지 예측하기 어렵다는 점이다. eval()이나 임의 코드 실행과 다를 바 없다.
비유하면 이렇다. 백지에 자유롭게 그림을 그리라고 하면 어린이가 벽지까지 물감을 칠할 수 있다. 반면 색칠공부 책을 주면 선 안에서만 움직인다. DSL은 LLM에게 주는 색칠공부 책이다.
포인트: LLM 코드를 직접 신뢰하는 대신, LLM이 안전한 언어 안에서만 표현하게 제약을 걸면 된다.
해결책: 제한된 DSL 설계
뱅크샐러드가 선택한 방식은 GitLab의 오픈소스 micro-language-framework를 기반으로 한 커스텀 DSL이다. 이 DSL은 map, filter, reduce 같은 제한된 연산만 허용한다.
실제 DSL 표현식은 이렇게 생겼다:
(:> 그룹소비브랜드목록
(하나라도포함 카페브랜드목록)
(== false))"그룹 소비 브랜드 목록 중 카페 브랜드가 하나라도 포함되면 미션 달성 안 됨"이라는 비즈니스 로직을 한국어 토큰으로 표현한 것이다. 문법이 제한되어 있으니 LLM이 악성 코드를 끼워넣을 여지가 없다.
[💡 잠깐! 이 용어는?] DSL(Domain Specific Language): 특정 도메인 문제만 풀 수 있도록 연산자와 문법을 제한한 미니 언어. SQL이 데이터 조회에 특화된 것처럼, 샐러드게임 DSL은 미션 조건 표현에만 특화됐다.
LLM에게 DSL을 가르치는 법
DSL을 정의했다고 끝이 아니다. LLM이 이 언어를 제대로 쓰도록 프롬프트를 설계해야 한다.
뱅크샐러드의 접근은 두 가지다.
① 스펙 + 사례를 프롬프트에 주입
[DSL 명세서]
허용 연산자: 하나라도포함, 모두포함, ==, !=, >, <
데이터 타입: 브랜드목록, 금액, 날짜
...
[사용 예시]
조건: 스타벅스에서 5,000원 이상 결제
DSL: (:and (포함 스타벅스) (:> 결제금액 5000))
[생성할 조건]
...② 이중 검토(생성 → 검증)
첫 번째 LLM 호출로 DSL을 생성하고, 두 번째 LLM 호출로 해당 DSL이 의도한 조건을 올바르게 표현하는지 검증한다. 환각(Hallucination) 방지용이다.
이때 LLM에게 테스트 케이스와 경계 조건까지 함께 생성하도록 지시하면, QA 비용도 줄어든다.
검증 파이프라인
생성된 DSL은 바로 프로덕션에 올라가지 않는다. TestSaladgameDSL API를 통해 실제 입력 데이터로 즉시 테스트한다. 파이프라인 구조는 이렇다.
운영자 입력
↓
LLM 1차 생성 (DSL 작성)
↓
LLM 2차 검증 (의도 일치 확인)
↓
TestSaladgameDSL API 자동 테스트
↓
QA 엔지니어 최종 수동 확인
↓
프로덕션 반영
마지막 수동 확인 단계를 남겨둔 이유는 명확하다. 자동화가 잡지 못하는 비즈니스 컨텍스트는 결국 사람이 판단해야 한다.
실제 적용 시 체크포인트
DSL 설계 시 꼭 고려해야 할 것들을 정리하면 이렇다.
| 체크 항목 | 확인 내용 |
|---|---|
| 연산자 범위 | 필요한 연산만 허용 (eval, 파일 I/O 금지) |
| 데이터 타입 | 허용할 타입 명시 (문자열·숫자·목록만 등) |
| 재귀 깊이 | 무한 루프 방지를 위한 깊이 제한 |
| 실행 시간 | 타임아웃 설정 (10초 이내 권장) |
| 메모리 한계 | DSL 실행 샌드박스의 메모리 제한 |
확장 고려 사항
이 접근법의 한계도 솔직하게 보면 된다.
- DSL 설계 비용: 초기에 언어 스펙을 잘 설계해야 한다. 나중에 연산자를 추가하면 기존 DSL과의 호환성을 관리해야 한다.
- 한글 토큰 지원: 오픈소스 프레임워크가 한글 토큰을 지원하지 않아 직접 기여(Contribution)가 필요했다. 한국어 DSL을 설계한다면 미리 확인할 것.
- LLM 모델 종속성: 모델이 바뀌면 DSL 생성 품질이 달라질 수 있다. 이중 검토 레이어가 이를 완충해준다.
정리
- LLM 코드를 직접 신뢰하지 말고, 표현 범위를 제한한 DSL을 중간 계층으로 설계한다
- DSL 스펙과 사용 예시를 프롬프트에 포함해 일관성 있는 생성을 유도한다
- 생성 → 검증의 이중 LLM 호출로 환각 리스크를 줄인다
- 자동 테스트 API + 수동 QA의 이중 안전망을 유지한다
LLM 시대 엔지니어의 핵심 과제는 "LLM이 무엇이든 할 수 있게 허용"이 아니라, **"LLM이 의도한 범위 안에서만 표현하도록 제약하는 구조 설계"**다.
참고:
- 뱅크샐러드 기술 블로그: https://blog.banksalad.com/tech/banksalad-vibe-coding/
같은 카테고리 · AI
비슷한 주제의 최신 글
태그가 겹치는 글
공통 태그가 많을수록 위에 보인다