AI가 존재하지 않는 컬럼을 계속해서 만들어내는 이유 (그리고 이를 방지하는 방법)
요약
AI가 SQL 생성 시 존재하지 않는 컬럼을 만들어내는 '스키마 환각' 현상의 원인을 분석하고, 이를 방지하기 위한 실질적인 해결책을 제시합니다. 모델의 지능 문제보다는 컨텍스트 제공의 중요성을 강조하며, 실제 DDL을 제공하는 구체적인 방법을 설명합니다.
핵심 포인트
- AI의 SQL 오류는 통계적 토큰 예측에 따른 '스키마 환각' 때문임
- 환각된 컬럼은 구문적으로는 유효하나 의미론적으로 불일치함
- 해결책은 더 똑똑한 모델이 아닌 더 나은 컨텍스트 제공에 있음
- 실제 데이터베이스의 DDL을 프롬프트에 포함하여 스키마 근거를 제공해야 함
AI 어시스턴트에게 "지난달 고객별 총 매출(total revenue by customer last month)"을 요청하면, 깔끔해 보이는 쿼리를 건네줍니다. 하지만 이를 실행하면 Postgres에서 다음과 같은 오류를 던집니다:
ERROR: column "c.total_revenue" does not exist
LINE 3: SUM(c.total_revenue) AS revenue
쿼리는 확신에 차 있었습니다. 구문(syntax)상으로도 완벽했습니다. 하지만 동시에 당신의 스키마(schema)에 존재한 적도 없는 컬럼을 참조하고 있었습니다. AI가 생성한 SQL에서 발생하는 가장 흔한 실패 모드인 **스키마 환각 (schema hallucination)**의 세계에 오신 것을 환영합니다.
LLM을 사용하여 쿼리를 작성하는 데 시간을 할애해 보았다면, 여러분도 이 문제를 겪어보았을 것입니다. 모델이 total_revenue라는 컬럼을 만들어내거나, 당신에게 없는 accounts 테이블과 조인(join)을 시도하거나, 당신의 orders 테이블의 컬럼명이 state임에도 불구하고 status라는 컬럼이 있다고 가정해 버립니다. 모든 것이 올바르게 보이기 때문에 더욱 좌절감을 줍니다. 이 글에서는 왜 이런 일이 내부적으로 발생하는지 설명하고, ChatGPT에 스키마를 붙여넣든 혹은 여러분의 제품에 Text-to-SQL 기능을 구축하든 간에 이를 방지할 수 있는 구체적인 플레이북(playbook)을 제공하겠습니다.
AI가 테이블과 컬럼을 환각하는 이유
대규모 언어 모델(Large language models)은 당신의 데이터베이스를 "알고" 있는 것이 아닙니다. 이들은 산문을 생성하는 것과 동일한 방식으로 SQL을 생성합니다. 즉, 훈련 과정에서 본 패턴을 바탕으로 통계적으로 가장 가능성이 높은 다음 토큰(token)을 예측하는 것입니다. 당신이 매출을 요청하면, 모델은 total_revenue 컬럼이 포함된 수천 개의 튜토리얼을 보았기 때문에, 실제 당신의 스키마에서는 invoices 테이블의 amount_cents로 저장되어 있음에도 불구하고 확신을 가지고 그 이름을 가져다 쓰는 것입니다.
각각 해결 방법이 다르기 때문에 명시할 가치가 있는 몇 가지 뚜렷한 근본 원인들이 있습니다:
| 원인 | 현상 |
|---|---|
| 스키마 근거 부족 (No schema grounding) | 모델이 실제 테이블을 본 적이 없어서 일반적인 이름을 추측함 |
| ... |
중요한 통찰: 환각(hallucination)을 일으킨 컬럼은 실제 스키마와 비교했을 때 구문적으로는 유효하지만 의미론적으로는 일치하지 않는(syntactically valid but semantically misaligned) 상태입니다. 모델이 고장 난 것이 아닙니다. 모델은 정보가 부족한 상황에서 설계된 대로 정확히 동작하고 있는 것입니다. 즉, 해결책은 더 똑똑한 모델을 찾는 것이 아니라, 거의 항상 더 나은 컨텍스트(context)를 제공하는 것에 있습니다.
해결책 #1: 모델에 실제 스키마를 근거로 제공하기 (Ground the model in your real schema)
환각의 가장 큰 원인은 모델이 전달받지 못한 이름을 추측하기 때문입니다. 그러니 모델에게 직접 알려주세요. "고객별 매출을 위한 쿼리를 작성해줘"라고 요청하는 대신, 실제 DDL을 제공하세요:
-- 질문하기 전에 이것을 붙여넣으세요
CREATE TABLE customers (
id BIGINT PRIMARY KEY,
...
해당 컨텍스트가 제공되면, 모델은 이제 다음과 같이 생성합니다:
SELECT
c.company_name,
SUM(i.amount_cents) / 100.0 AS revenue_dollars
...
추측의 여지를 없앴기 때문에 모델이 실제 컬럼 이름인 amount_cents와 issued_at을 사용했다는 점에 주목하세요. 만약 이를 앱으로 구축하고 있다면, 프로그래밍 방식으로 스키마를 가져올 수 있습니다:
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public'
...
이 출력값을 구조화된 컨텍스트(structured context)로 프롬프트에 입력하면 환각 발생률이 극적으로 떨어집니다.
해결책 #2: 모호한 컬럼에 주석 및 메타데이터 추가하기
컬럼 이름은 종종 암호 같을 때가 있으며, 모델은 state가 미국의 '주(state)'를 의미하는지 '주문 상태(order status)'를 의미하는지 구분할 수 없습니다. 이에 주석을 달아주세요. 제공하는 DDL에 포함된 주석은 모델이 읽을 수 있는 문서 역할을 겸하게 됩니다:
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
state TEXT, -- 주문 생명주기: 'cart','placed','shipped','delivered','refunded'
...
-- order total in cents (센트 단위의 주문 총액) 주석 하나만으로도 모델이 100으로 나누는 것을 잊어버리는 전형적인 버그를 방지할 수 있습니다. "활성 (active)" 고객의 기준이 무엇인지, "MRR (월간 반복 매출)"이 어떻게 계산되는지 등 모호한 비즈니스 용어에 대해서는 정의를 명확히 기술하십시오. 모델은 귀하의 비즈니스 로직을 추론할 수 없으며, 오직 제공된 정보만을 바탕으로 작동할 수 있습니다.
해결책 #3: 원시 테이블 대신 시맨틱 레이어 (semantic layer) 사용하기
프로덕션 환경의 Text-to-SQL 기능을 구현할 때, LLM에 원시 테이블 (raw tables)을 그대로 노출하는 것은 문제를 자초하는 일입니다. 더 나은 패턴은 **시맨틱 레이어 (semantic layer)**를 사용하는 것입니다. 이는 지저분한 물리적 테이블을 깔끔한 비즈니스 개념으로 매핑하는 큐레이션된 뷰 (views) 또는 정의된 메트릭 (metrics)의 집합입니다. 모델은 원시 스키마 (raw schema)를 탐색하는 대신, 이 안전하고 이름이 잘 지정된 레이어에 사용자의 의도를 매핑합니다.
CREATE VIEW revenue_by_customer AS
SELECT
c.id AS customer_id,
...
이제 "지난달 Acme의 매출은?"과 같은 질문은 revenue_by_customer에 대한 매우 정확한 쿼리로 매핑됩니다. 테이블 수는 줄어들고, 이름은 더 깔끔해지며, 조인 (join)을 환각 (hallucinate)할 여지도 없습니다. 보너스로, 이 뷰는 비즈니스 규칙(예: paid 상태인 인보이스만 계산)을 강제하므로 AI가 실수로 무효화된(voided) 인보이스를 포함하는 일을 방지할 수 있습니다.
해결책 #4: 모델에게 예시 제공하기 (Few-shot prompting)
귀하의 스키마에서 추출한 몇 가지 올바른 "질문-SQL" 쌍을 보여주면 모델의 정확도가 훨씬 높아집니다. 이를 **퓨샷 프롬프팅 (few-shot prompting)**이라고 하며, DAIL-SQL과 같은 접근 방식에 대한 연구에 따르면 큐레이션된 예시가 정확도를 유의미하게 향상시킨다는 것을 보여줍니다. 프롬프트에 두세 개의 대표적인 쌍을 포함하십시오:
Q: 어제 출고된 주문은 몇 건인가요?
A: SELECT COUNT(*) FROM orders
WHERE state = 'shipped'
...
이러한 예시들은 단 한 번의 파인튜닝 (fine-tuning) 과정 없이도 모델에게 귀하의 명명 규칙 (naming conventions), 선호하는 날짜 처리 방식, 그리고 어떤 뷰를 우선적으로 사용해야 하는지를 가르쳐 줍니다.
해결책 #5: 실행 전 검증하기
완벽한 그라운딩 (Grounding)이 있더라도, 생성된 SQL을 신뢰할 수 없는 입력값으로 취급하십시오. 가장 비용이 적게 드는 안전망은 실행 전에 실제 스키마 (Schema)를 기준으로 컬럼 및 테이블 참조를 검증하는 것입니다. Postgres에서는 EXPLAIN을 사용하여 데이터에 영향을 주지 않고 드라이 런 (Dry-run)을 수행할 수 있습니다:
EXPLAIN SELECT c.company_name, SUM(i.total_revenue) -- 환각된 컬럼 (hallucinated column)
FROM invoices i JOIN customers c ON c.id = i.customer_id
GROUP BY c.company_name;
...
EXPLAIN이 실패한다면, 아무것도 실행하지 않고도 환각 (Hallucination)을 잡아낸 것입니다. 에러 메시지를 모델에 다시 전달하여 스스로 수정하게 하십시오. 이 '검증 후 재시도 (Validate-and-retry)' 루프는 실제 환경에서 가장 효과적인 완화 방법 중 하나입니다. 생성된 쿼리는 항상 **읽기 전용 역할 (Read-only role)**로 실행하여, 환각된 DELETE나 DROP 명령이 결코 피해를 입히지 않도록 하십시오.
일반적인 주의 사항 (Common gotchas)
| 주의 사항 | 방지 방법 |
|---|---|
| 프롬프트 내의 오래된 스키마 | 매 실행 시 스키마 컨텍스트를 재생성하십시오. 한 번 설정한 뒤 하드코딩하지 마십시오. |
| ... | |
가장 까다로운 실패는 에러가 발생하는 경우가 아닙니다. 그런 경우는 해결하기 쉽습니다. 정말 위험한 것은 쿼리가 깔끔하게 실행되지만, 조용히 잘못된 테이블을 사용하거나 WHERE 필터를 누락하여, 대시보드에 그럴듯해 보이지만 틀린 숫자를 반환하는 경우입니다. 이것이 검증과 결과 샘플 확인 (Spot-checking)이 그라운딩만큼 중요한 이유입니다. |
핵심 요약 (Key takeaways)
스키마 환각은 모델이 실제 데이터베이스가 아닌 학습 데이터에 대해 패턴 매칭 (Pattern-matching)을 수행하기 때문에 발생합니다. 해결책은 더 큰 모델이 아니라 컨텍스트 (Context)입니다. 모든 프롬프트를 실제 DDL에 기반하여 그라운딩하고, 모호한 컬럼에는 주석을 달며, 원시 테이블 대신 뷰 (View)로 구성된 깨끗한 시맨틱 레이어 (Semantic layer)를 노출하십시오. 또한 모델에 몇 가지 올바른 예시를 보여주고, 생성된 SQL을 읽기 전용 사용자로 실행하기 전에 항상 스키마와 대조하여 검증하십시오. 이 다섯 가지를 실천한다면 "컬럼이 존재하지 않습니다 (column does not exist)"라는 메시지는 여러분의 삶에서 거의 사라질 것입니다.
여러분은 Text-to-SQL (텍스트-SQL 변환) 기능을 제품에 직접 구축하셨나요, 아니면 여전히 채팅창에 스키마 (schema)를 복사하여 붙여넣고 계신가요? 여러분을 가장 힘들게 했던 문제는 무엇이었나요 — 환각 컬럼 (hallucinated columns), 잘못된 조인 (wrong joins), 아니면 조용히 틀린 답을 내놓는 문제 (silent-wrong-answer problem)였나요? 여러분의 경험담을 댓글로 들려주세요. 그리고 만약 효과적인 그라운딩 (grounding)이나 검증 (validation) 트릭을 발견하셨다면, 꼭 듣고 싶습니다.
출처: Reducing Hallucinations in Text-to-SQL (Wren AI), Deep Dive into Text-to-SQL Hallucinations (DataFocus), Improving Text-to-SQL Accuracy with Schema-Aware Reasoning (Towards AI), Before Generation, Align it! (arXiv)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기