뱅크샐러드의 LLM 코드 안전화 — DSL로 Vibe Coding을 프로덕션에 쓰는 법

8 min read
AILLMVibe CodingDSL뱅크샐러드
뱅크샐러드의 LLM 코드 안전화 — DSL로 Vibe Coding을 프로덕션에 쓰는 법

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 표현식은 이렇게 생겼다:

샐러드게임 미션 조건 DSL 예시
(:> 그룹소비브랜드목록
  (하나라도포함 카페브랜드목록)
  (== false))

"그룹 소비 브랜드 목록 중 카페 브랜드가 하나라도 포함되면 미션 달성 안 됨"이라는 비즈니스 로직을 한국어 토큰으로 표현한 것이다. 문법이 제한되어 있으니 LLM이 악성 코드를 끼워넣을 여지가 없다.

[💡 잠깐! 이 용어는?] DSL(Domain Specific Language): 특정 도메인 문제만 풀 수 있도록 연산자와 문법을 제한한 미니 언어. SQL이 데이터 조회에 특화된 것처럼, 샐러드게임 DSL은 미션 조건 표현에만 특화됐다.

LLM에게 DSL을 가르치는 법

DSL을 정의했다고 끝이 아니다. LLM이 이 언어를 제대로 쓰도록 프롬프트를 설계해야 한다.

뱅크샐러드의 접근은 두 가지다.

① 스펙 + 사례를 프롬프트에 주입

프롬프트 구성 — DSL 명세서 + 사용 예시
[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이 의도한 범위 안에서만 표현하도록 제약하는 구조 설계"**다.


참고:

관심 있을 만한 포스트

Context Engineering — 에이전트 품질을 결정하는 진짜 레버

프롬프트 엔지니어링을 넘어선 컨텍스트 엔지니어링의 4가지 구성요소와 실전 패턴을 정리한다.

AIContext Engineering

GenUI vs. Vibe Coding — AI가 UI를 결정할 때와 내가 결정할 때

AI가 인터페이스를 생성하는 두 접근법의 핵심 차이와 각각이 적합한 맥락을 분석한다.

GenUIVibe Coding

에이전틱 워크플로우의 멘탈 프레임워크 — AI에게 일을 맡기는 사고 체계

AI 에이전트에게 작업을 위임할 때 필요한 5단계 사고 모델을 정리한다.

AIAgentic Workflow

Cursor Rules 47종 모음 — 16개 프레임워크용 AI 코딩 규칙

React, Next.js, Django 등 주요 프레임워크에 맞춘 Cursor 룰 파일의 구조와 선택 기준을 정리한다.

CursorAI

Factory Model — 코딩 에이전트가 바꾼 소프트웨어 엔지니어링의 구조

Addy Osmani가 제안한 '공장 모델'로 AI 코딩 시대의 엔지니어 역할 변화를 짚는다.

AICoding Agent

배민 다국어 서비스 — 5년 백로그를 LLM으로 19일에 끝낸 이야기

5년간 미루던 다국어 번역 파이프라인을 Claude Haiku에서 Amazon Nova로 전환하며 19일 만에 완성한 우아한형제들의 기술 스택과 구현 구조.

LLM다국어

하네스 엔지니어링 — 팀을 위한 AI 개발 환경을 설계하는 방법

프롬프트를 잘 쓰는 게 아니라 AI가 일하는 환경을 설계하는 것. 우아한형제들이 Rules와 Skills로 팀 맞춤형 AI 워크플로를 구축한 사례.

AIClaude Code

VS Code 1.115 — 에이전트 앱 프리뷰와 터미널 도구 확장

병렬 에이전트 세션 관리를 위한 VS Code Agents App과 백그라운드 터미널 자동화 기능이 추가된 1.115 릴리즈를 살펴본다.

VS Code에이전트