40%에서 100%까지 SQL 생성 정확도: Local AI는 Perfect Prompts가 아니라 Self-Correction이 필요하다
Source: Dev.to
개요
자기‑수정 루프는 로컬 AI에서 “완벽‑첫‑시도” 접근법보다 뛰어납니다. 양자화된 24B 모델을 사용해 노트북에서 완전히 실행되는 소매 분석 코파일럿을 구축하는 것은 프라이버시 측면에서는 좋지만, 신뢰성 측면에서는 악몽입니다. 호스팅된 GPT와 달리, 시니어 엔지니어처럼 지시를 정확히 따르는 모델과는 달리 로컬 모델은 열정적인 인턴과 같습니다: 열심히 하려 하지만 구문을 환각하고, 스키마 세부 정보를 잊어버리며, 코딩을 해야 할 때 대화를 좋아합니다.
기준선
초기 기준선은 형편없었습니다: 생성된 SQL 쿼리 중 **40 %**만 실제로 실행되었습니다. 나머지는 구문 오류, 환각된 컬럼, 혹은 대화형 잡담(“Here is your query …”)에 시달렸습니다.
전환: 오류를 기대하고, 그 후에 복구
오류를 방지하려 하기보다, 오류를 기대하고 자동으로 복구하는 시스템을 구축했습니다.
복구 루프 워크플로우
- 생성 – 모델이 쿼리를 작성합니다.
- 실행 – SQLite에 실행합니다.
- 포착 – 실패하면 오류를 캡처합니다(예:
no such column: 'Price'). - 피드백 – 정확한 오류 메시지를 모델에 다시 전달합니다: “The previous query failed with error X. Fix it.”
이 패턴은 SQL을 넘어 일반화됩니다. 확률적 시스템, 실패할 수 있는 외부 API, 혹은 모호한 사용자 입력을 다룰 때는 항상 우아한 감소 설계를 고려하세요.
실패로부터 만든 학습 데이터
각 (failed_query, error_message, corrected_query) 삼중항은 향후 최적화를 위한 잠재적인 few‑shot 예제가 됩니다. 버그를 고치는 것뿐 아니라, 스스로 개선되는 시스템을 구축하는 것입니다.
복구 루프 구현 (Python)
def sql_execution_node(state: AgentState) -> AgentState:
"""Execute SQL and handle errors gracefully."""
query = state["sql_query"]
try:
cursor.execute(query)
state["sql_results"] = cursor.fetchall()
state["errors"] = []
except sqlite3.OperationalError as e:
# Don't crash—capture and route to repair
state["sql_results"] = []
state["errors"].append(str(e))
state["feedback"] = f"SQL execution failed: {e}. Fix the query."
state["repair_count"] = state.get("repair_count", 0) + 1
return state
def should_repair(state: AgentState) -> str:
"""Conditional edge: repair or continue?"""
if state["errors"] and state["repair_count"] str:
match = re.search(r"(SELECT\s+.*)", text, re.IGNORECASE)
return match.group(1).strip() if match else ""
더 나은 방법: 구조화된 출력(예: JSON)을 강제하여 자유 형식 파싱을 완전히 피하세요.
프롬프트 엔지니어링에서 DSPy 로 전환
프롬프트를 손으로 작성하던 방식을 멈추고 DSPy 로 전환했습니다. DSPy는 프롬프트 최적화를 학습 가능한 문제로 취급합니다.
전통적인 프롬프트 엔지니어링을 하이퍼파라미터를 수동으로 튜닝하는 것에 비유한다면, DSPy는 역전파와 같으며, 정의한 메트릭을 기반으로 그래디언트‑프리 최적화를 통해 자동으로 최적 프롬프트를 탐색합니다.
메트릭 정의
“쿼리가 실행되고 비어 있지 않은 결과를 반환하면 좋은 쿼리이다.”
BootstrapFewShot 옵티마이저를 사용해 DSPy는 여러 후보 SQL 쿼리를 생성하고 실행한 뒤, 메트릭을 통과한 것만 few‑shot 예제로 유지했습니다.
메트릭 개선
| 메트릭 | 기준선 | 최적화 후 | 복구 루프 후 |
|---|---|---|---|
| 유효한 SQL (%) | 40 % | 85 % | 100 % |
| 올바른 포맷 (%) | 30 % | 60 % | 95 % |
| 엔드‑투‑엔드 성공 (%) | 12 % | 51 % | 66 %* |
*100 % 유효한 SQL과 66 % 엔드‑투‑엔드 성공 사이의 차이는 한 구성 요소를 최적화하면 다른 곳에 새로운 병목(오케스트레이션 로직 등)이 생긴다는 것을 보여줍니다(모델 품질 자체는 아님).
실전에서의 복구 패턴
LLM 상태에 feedback 필드를 추가하세요. 실패 시 오류 메시지를 삽입하고 재시도합니다. 이는 LLM 호출을 한 번 더 하는 비용이 들지만, 신뢰성을 한 차례 정도는 크게 향상시킵니다.
“ELECT” 테스트
assert clean_sql_output("SQL: SELECT * FROM orders") == "SELECT * FROM orders"
assert clean_sql_output("SELECT * FROM orders") == "SELECT * FROM orders"
이 어설션이 실패한다면 문자열 메서드를 잘못 사용하고 있는 것입니다.
DSPy 시작 템플릿
- 작업을
(inputs, output, metric)형태로 정의합니다. - 옵티마이저가 직접 효과적인 few‑shot 예제를 찾아내게 하여 수동 작성을 생략합니다.
왜 중요한가 (이 프로젝트를 넘어)
AI 환경은 두 갈래로 갈라지고 있습니다:
| 접근법 | 특징 |
|---|---|
| 클라우드‑우선 (GPT‑5.1, Claude, …) | 강력하지만 비용이 많이 들고 프라이버시 위험이 있음. |
| 엣지‑우선 (로컬 모델) | 저렴하고 프라이버시 보호가 가능하지만 다루기 어려움. |
클라우드 LLM이 금지된 규제 산업(헬스케어, 금융, 정부)에서 로컬 AI를 마스터한 기업이 우위를 점할 것입니다. 병목은 모델 가중치가 아니라 신뢰성 엔지니어링입니다.
7B 모델을 교묘한 오케스트레이션으로 70B 모델처럼 동작하게 만들 수 있다면, 1,000억 달러 규모의 문제를 해결하는 셈입니다. 이는 단순 기술 실험이 아니라 전략적 방어벽입니다.
중요한 스킬
- 시스템 사고 – 실패 모드를 이해하고 설계에 반영하기.
- 최적화 – 프롬프트를 예술이 아닌 학습 가능한 파라미터로 다루기.
- 방어적 엔지니어링 – 확률적 세계에 대비한 설계.
이들을 마스터하면 프롬프트 엔지니어가 아니라 AI‑네이티브 기업의 인프라 엔지니어와 경쟁할 수 있습니다.
프로젝트 저장소: