Text to SQL 에이전트 평가하기
Text to SQL 에이전트 평가하기 (How to evaluate a Text to SQL Agent)
이 가이드에서는 Ragas를 사용해서 텍스트-to-SQL 시스템을 체계적으로 평가하고 개선하는 방법을 배워요. 평가용 베이스라인 텍스트-to-SQL 시스템을 설정하고, 평가 메트릭을 만들고, SQL 에이전트를 위한 재사용 가능한 평가 파이프라인을 구축하며, 오류 분석을 바탕으로 개선을 구현할 수 있어요.
출처: 문서
본문
이 가이드에서는 Ragas를 사용해서 텍스트-to-SQL 시스템을 체계적으로 평가하고 개선하는 방법을 배워요.
달성할 것:
- 평가용 베이스라인 텍스트-to-SQL 시스템 설정
- 평가 메트릭 생성 방법 학습
- SQL 에이전트를 위한 재사용 가능한 평가 파이프라인 구축
- 오류 분석을 바탕으로 개선 구현
환경 설정
애플리케이션 구축 대신 평가 과정 이해에 집중할 수 있도록 간단한 모듈을 설치해서 실행할 수 있게 만들어 두었어요.
uv pip install "ragas-examples[text2sql]"
빠른 에이전트 테스트
텍스트-to-SQL 에이전트가 자연어를 SQL로 변환하는지 테스트해요.
import os
import asyncio
from openai import AsyncOpenAI
from ragas_examples.text2sql.text2sql_agent import Text2SQLAgent
# OpenAI API 키 설정
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# 에이전트 생성
openai_client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
agent = Text2SQLAgent(client=openai_client, model_name="gpt-5-mini")
# 샘플 쿼리로 테스트
test_query = "How much open credit does customer Andrew Bennett?"
result = asyncio.run(agent.query(test_query))
print(f"Natural Query: {result['query']}")
print(f"Generated SQL: {result['sql']}")
출력
Natural Query: How much open credit does customer Andrew Bennett?
Generated SQL: select sum(open_balance) from ( select distinct transaction_id, open_balance from master_txn_table where customers = "Andrew Bennett" )
이것은 자연어 쿼리에서 SQL을 생성해요. 이제 체계적인 평가 프로세스를 구축해볼게요.
BookSQL 다운로드
에이전트나 데이터베이스 유틸리티를 실행하기 전에 Hugging Face에서 gated BookSQL 데이터셋을 다운로드해요.
huggingface-cli login
uv run python -m ragas_examples.text2sql.data_utils --download-data
인증 오류가 보이면 먼저 데이터셋 페이지를 방문해서 약관에 동의해요: Hugging Face의 BookSQL
전체 코드
에이전트와 평가 파이프라인의 전체 코드는 여기에서 볼 수 있어요.
데이터셋 준비
BookSQL 데이터셋에서 easy, medium, hard 쿼리를 각각 33개씩, 총 99개 예제로 구성된 균형 잡힌 샘플 데이터셋을 준비했어요. 바로 평가를 시작하거나 다음 섹션에 따라 자신만의 데이터셋을 만들 수 있어요.
샘플 데이터셋 다운로드 및 검사:
# GitHub에서 샘플 CSV 다운로드
curl -o booksql_sample.csv https://raw.githubusercontent.com/vibrantlabsai/ragas/main/examples/ragas_examples/text2sql/datasets/booksql_sample.csv
# 구조 파악을 위해 처음 몇 행 보기
head -5 booksql_sample.csv
| Query | SQL | Levels | split |
|---|---|---|---|
| What is the balance due from Richard Aguirre? | select sum(open_balance) from ( select distinct transaction_id, open_balance from master_txn_table where customers = "Richard Aguirre" ) | medium | train |
| What is the balance due from Sarah Oconnor? | select sum(open_balance) from ( select distinct transaction_id, open_balance from master_txn_table where customers = "Sarah Oconnor" ) | medium | train |
| What is my average invoice from Jeffrey Moore? | select avg(amount) from (select distinct transaction_id, amount from master_txn_table where customers = "Jeffrey Moore" and transaction_type = 'invoice') | hard | train |
| How much open credit does customer Andrew Bennett? | select sum(open_balance) from ( select distinct transaction_id, open_balance from master_txn_table where customers = "Andrew Bennett" ) | easy | train |
📋 선택: 샘플 데이터셋을 준비한 방법
데이터셋 다운로드 및 검사
이 가이드에서는 BookSQL 데이터셋을 사용할게요. 자신만의 데이터셋이 있다면 이 섹션을 건너뛰어도 돼요.
데이터셋 다운로드:
export HF_TOKEN=your-huggingface-token
uv run python -m ragas_examples.text2sql.data_utils --download-data
참고: BookSQL은 gated 데이터셋이에요. 데이터셋 페이지를 방문해서 약관에 동의하고, 인증 오류가 발생하면 huggingface-cli login을 실행해요.
데이터셋 구조 검사:
# 데이터베이스 스키마 확인
sqlite3 BookSQL-files/BookSQL/accounting.sqlite ".schema" | head -20
기대 스키마 출력:
CREATE TABLE master_txn_table(
id INTEGER ,
businessID INTEGER NOT NULL ,
Transaction_ID INTEGER NOT NULL,
Transaction_DATE DATE NOT NULL,
Transaction_TYPE TEXT NOT NULL,
Amount DOUBLE NOT NULL,
CreatedDATE DATE NOT NULL,
CreatedUSER TEXT NOT NULL,
Account TEXT NOT NULL,
AR_paid TEXT,
AP_paid TEXT,
Due_DATE DATE,
Open_balance DOUBLE,
Customers TEXT,
Vendor TEXT,
Product_Service TEXT,
Quantity INTEGER,
Rate DOUBLE,
Credit DOUBLE,
데이터셋은 다음을 포함해요.
- 데이터베이스: 회계 데이터(인보이스, 고객 등)가 있는 SQLite 파일
- 질문: 영어 자연어 쿼리
- SQL: 해당 SQL 쿼리
- 난이도 수준: Easy, Medium, Hard 카테고리
균형 잡힌 평가 하위 집합 생성:
uv run python -m ragas_examples.text2sql.data_utils --create-sample --samples 33 --validate --require-data
이것은 실제 데이터를 반환하는 검증된 쿼리로 균형 잡힌 CSV를 만든다.
기대 출력:
📖 Loading data from BookSQL-files/BookSQL/train.json...
📊 Loaded 70828 total records
🚂 Found 70828 train records
🔍 Removed 35189 duplicate records (same Query + SQL)
📊 35639 unique records remaining
📈 Difficulty distribution (after deduplication):
• medium: 20576 records
• hard: 11901 records
• easy: 3162 records
✅ Added 33 validated 'easy' records
✅ Added 33 validated 'medium' records
✅ Added 33 validated 'hard' records
💾 Saved 99 records to datasets/booksql_sample.csv
📋 Final distribution:
• medium: 33 records
• hard: 33 records
• easy: 33 records
이것은 난이도 수준 전반에 걸친 99개의 균형 잡힌 예제로 datasets/booksql_sample.csv를 만든다.
BookSQL은 CC BY-NC-SA(비상업용 전용)로 릴리스되었어요. 자세한 내용과 인용은 아래를 참고해요.
📋 라이선스 및 인용 세부사항
라이선스 및 사용
BookSQL 데이터셋은 CC BY-NC-SA 4.0 라이선스로 릴리스되었어요. 비상업적 연구에만 사용할 수 있어요. 상업적 사용은 허용되지 않아요.
- 데이터셋: Hugging Face의
Exploration-Lab/BookSQL· GitHub 저장소 - 논문: ACL Anthology — BookSQL: A Large Scale Text-to-SQL Dataset for Accounting Domain
연구에서 BookSQL을 사용한다면 논문을 인용해주세요.
@inproceedings{kumar-etal-2024-booksql,
title = {BookSQL: A Large Scale Text-to-SQL Dataset for Accounting Domain},
author = {Kumar, Rahul and Raja, Amar and Harsola, Shrutendra and Subrahmaniam, Vignesh and Modi, Ashutosh},
booktitle = {Proceedings of the 2024 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies (Volume 1: Long Papers)},
month = {June},
year = {2024},
address = {Mexico City, Mexico},
publisher = {Association for Computational Linguistics},
}
자신만의 평가 데이터셋을 만드는 방법에 대한 조언은 Datasets - Core Concepts를 참고해요.
텍스트-to-SQL 시스템 설정
프롬프트 만들기
데이터베이스 스키마 추출:
uv run python -m ragas_examples.text2sql.db_utils --schema
📋 기대 스키마 출력
=== Database Schema ===
name type sql
chart_of_accounts table CREATE TABLE chart_of_accounts(
id INTEGER ,
businessID INTEGER NOT NULL,
Account_name TEXT NOT NULL,
Account_type TEXT NOT NULL,
PRIMARY KEY(id,businessID,Account_name)
)
customers table CREATE TABLE customers(
id INTEGER ,
businessID INTEGER NOT NULL,
customer_name TEXT NOT NULL,
customer_full_name TEXT ,
... (continues for all columns)
PRIMARY KEY(id,businessID,Customer_name)
)
... (continues for all 7 tables with complete DDL)
프롬프트 내용 작성:
우리 프롬프트는 다음과 같은 템플릿 구조를 따라요.
You are a SQL query generator for a business accounting database. Convert natural language queries to SQL queries.
DATABASE CONTEXT:
This is an accounting database (accounting.sqlite) containing business transaction and entity data.
TABLES AND THEIR PURPOSE:
- master_txn_table: Main transaction records for all business transactions
- chart_of_accounts: Account names and their types for all businesses
- products_service: Products/services and their types used by businesses
- customers: Customer records with billing/shipping details
- vendors: Vendor records with billing address details
- payment_method: Payment methods used by businesses
- employees: Employee details including name, ID, hire date
DATABASE SCHEMA (DDL):
[Complete DDL statements for all tables]
INSTRUCTIONS:
Convert the user's natural language query into a valid SQL SELECT query. Return only the SQL query, no explanations or formatting.
평가 메트릭 정의
텍스트-to-SQL 시스템에서는 결과의 정확도를 평가하는 메트릭이 필요해요. 생성된 SQL이 올바른 데이터를 반환하는지 검증하는 실행 정확도(execution accuracy)를 기본 메트릭으로 사용할게요.
실행 정확도 메트릭: datacompy를 사용해서 기대 SQL과 예측 SQL의 실제 결과를 비교해요. 두 쿼리가 동일한 데이터를 반환하는지 검증하는데, 이것이 정확도의 궁극적인 테스트예요.
평가 시스템은 결과를 다음과 같이 분류해요.
"correct": 쿼리가 성공하고 기대 결과와 일치"incorrect": 쿼리가 성공하지 못하거나 성공했지만 잘못된 결과를 반환
메트릭 함수 설정
Ragas 이산 메트릭을 사용해서 평가 메트릭을 만들어요.
# File: examples/ragas_examples/text2sql/evals.py
from ragas.metrics.discrete import discrete_metric
from ragas.metrics.result import MetricResult
from ragas_examples.text2sql.db_utils import execute_sql
@discrete_metric(name="execution_accuracy", allowed_values=["correct", "incorrect"])
def execution_accuracy(expected_sql: str, predicted_success: bool, predicted_result):
"""datacompy를 사용해 예측 SQL과 기대 SQL의 실행 결과 비교."""
try:
# 기대 SQL 실행
expected_success, expected_result = execute_sql(expected_sql)
if not expected_success:
return MetricResult(
value="incorrect",
reason=f"Expected SQL failed to execute: {expected_result}"
)
# 예측 SQL이 실패하면 incorrect
if not predicted_success:
return MetricResult(
value="incorrect",
reason=f"Predicted SQL failed to execute: {predicted_result}"
)
# 두 쿼리 모두 성공 - datacompy로 DataFrame 비교
if isinstance(expected_result, pd.DataFrame) and isinstance(predicted_result, pd.DataFrame):
# 빈 DataFrame 처리
if expected_result.empty and predicted_result.empty:
return MetricResult(value="correct", reason="Both queries returned empty results")
if expected_result.empty != predicted_result.empty:
return MetricResult(
value="incorrect",
reason=f"Expected returned {len(expected_result)} rows, predicted returned {len(predicted_result)} rows"
)
# 인덱스 기반 비교로 DataFrame 비교에 datacompy 사용
comparison = datacompy.Compare(
expected_result.reset_index(drop=True),
predicted_result.reset_index(drop=True),
on_index=True, # 인덱스 위치로 행 단위 비교
abs_tol=1e-10, # 부동소수점 비교용 매우 작은 허용 오차
rel_tol=1e-10,
df1_name='expected',
df2_name='predicted'
)
if comparison.matches():
return MetricResult(
value="correct",
reason=f"DataFrames match exactly ({len(expected_result)} rows, {len(expected_result.columns)} columns)"
)
else:
return MetricResult(
value="incorrect",
reason="DataFrames do not match - different data returned"
)
except Exception as e:
return MetricResult(
value="incorrect",
reason=f"Execution accuracy evaluation failed: {str(e)}"
)
실험 함수
실험 함수는 텍스트-to-SQL 에이전트를 실행하고 각 쿼리에 대한 메트릭을 계산하는 완전한 평가 파이프라인을 오케스트레이션해요.
# File: examples/ragas_examples/text2sql/evals.py
from typing import Optional
from openai import AsyncOpenAI
from ragas import experiment
from ragas_examples.text2sql.text2sql_agent import Text2SQLAgent
from ragas_examples.text2sql.db_utils import execute_sql
@experiment()
async def text2sql_experiment(
row,
model: str,
prompt_file: Optional[str],
):
"""텍스트-to-SQL 평가용 실험 함수."""
# 텍스트-to-SQL 에이전트 생성
openai_client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
agent = Text2SQLAgent(
client=openai_client,
model_name=model,
prompt_file=prompt_file
)
# 자연어 쿼리에서 SQL 생성
result = await agent.query(row["Query"])
# 예측 SQL 실행
try:
predicted_success, predicted_result = execute_sql(result["sql"])
except Exception as e:
predicted_success, predicted_result = False, f"SQL execution failed: {str(e)}"
# 실행 정확도로 응답 채점
accuracy_score = await execution_accuracy.ascore(
expected_sql=row["SQL"],
predicted_success=predicted_success,
predicted_result=predicted_result,
)
return {
"query": row["Query"],
"expected_sql": row["SQL"],
"predicted_sql": result["sql"],
"level": row["Levels"],
"execution_accuracy": accuracy_score.value,
"accuracy_reason": accuracy_score.reason,
}
데이터셋 로더
평가 데이터셋을 Ragas Dataset 객체로 로드해서 실험을 실행해요.
# File: examples/ragas_examples/text2sql/evals.py
import pandas as pd
from pathlib import Path
from typing import Optional
from ragas import Dataset
def load_dataset(limit: Optional[int] = None):
"""CSV 파일에서 텍스트-to-SQL 데이터셋 로드."""
dataset_path = Path(__file__).parent / "datasets" / "booksql_sample.csv"
# CSV 읽기
df = pd.read_csv(dataset_path)
# 요청 시 데이터셋 크기 제한
if limit is not None and limit > 0:
df = df.head(limit)
# Ragas Dataset 생성
dataset = Dataset(name="text2sql_booksql", backend="local/csv", root_dir=".")
for _, row in df.iterrows():
dataset.append({
"Query": row["Query"],
"SQL": row["SQL"],
"Levels": row["Levels"],
"split": row["split"],
})
return dataset
데이터셋 로더에는 개발 워크플로우용 limit 파라미터가 포함되어 있어요. 작은 샘플로 시작해 기본 오류를 빠르게 잡고, 그다음 전체 평가로 확장해요.
베이스라인 평가 실행
평가 파이프라인 실행 및 결과 수집
import asyncio
from ragas_examples.text2sql.evals import text2sql_experiment, load_dataset
async def run_evaluation():
"""직접 코드 접근 방식으로 텍스트-to-SQL 평가 실행."""
# 데이터셋 로드
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# 실험 실행
results = await text2sql_experiment.arun(
dataset,
name="gpt-5-mini-prompt-v1",
model="gpt-5-mini",
prompt_file=None,
)
# 결과 보고
print(f"✅ gpt-5-mini-prompt-v1: {len(results)} cases evaluated")
# 정확도 계산 및 표시
accuracy_rate = sum(1 for r in results if r["execution_accuracy"] == "correct") / max(1, len(results))
print(f"gpt-5-mini-prompt-v1 Execution Accuracy: {accuracy_rate:.2%}")
# 평가 실행
await run_evaluation()
📋 출력 (prompt v1)
Loading dataset...
Dataset loaded with 99 samples
Running text-to-SQL evaluation with model: gpt-5-mini
Using prompt file: prompt.txt
Running experiment: 100%|██████████████████████| 99/99 [01:06<00:00, 1.49it/s]
✅ gpt-5-mini-prompt-v1: 99 cases evaluated
gpt-5-mini-prompt-v1 Execution Accuracy: 2.02%
구성 옵션:
model: 사용할 OpenAI 모델 (기본값: "gpt-5-mini")prompt_file: 커스텀 프롬프트 파일 (기본값: 내장 프롬프트용 None)limit: 샘플 수 (기본값: 전체 샘플용 None, 숫자 지정 시 제한)name: 결과 추적용 커스텀 실험 이름
초기 성능 분석
이 평가는 분석용 포괄적인 CSV 결과를 생성해요. 각 행은 다음을 포함해요.
query: 자연어 입력expected_sql: ground truth SQLpredicted_sql: 생성된 SQLlevel: 난이도 (easy/medium/hard)execution_accuracy: correct/incorrect (결과 비교)accuracy_reason: 상세 비교 결과
오류 및 실패 패턴 분석
평가를 실행한 후 실패 패턴을 분석해서 Text2SQL 에이전트가 어디에서 실수를 하는지 이해할 수 있어요. 다음은 베이스라인 평가 결과의 몇 가지 샘플 행으로, 평가 데이터가 어떤 모습인지 완전히 보여줘요.
| Query | Expected SQL | Predicted SQL | Level | Execution Accuracy |
|---|---|---|---|---|
| What is the balance due from Richard Aguirre? | select sum(open_balance) from ( select distinct transaction_id, open_balance from master_txn_table where customers = "Richard Aguirre" ) |
SELECT customer_name, customer_full_name, Balance FROM customers WHERE customer_name = 'Richard Aguirre' OR customer_full_name = 'Richard Aguirre'; |
medium | incorrect |
| How much open credit does customer Natalie Myers? | select sum(open_balance) from ( select distinct transaction_id, open_balance from master_txn_table where customers = "Natalie Myers" ) |
SELECT Balance FROM customers WHERE customer_name = 'Natalie Myers' OR customer_full_name = 'Natalie Myers'; |
easy | incorrect |
| What is my average invoice from Ashley Thompson? | select avg(amount) from (select distinct transaction_id, amount from master_txn_table where customers = "Ashley Thompson" and transaction_type = 'invoice') |
SELECT AVG(Amount) FROM master_txn_table WHERE Customers = 'Ashley Thompson' AND Transaction_TYPE = 'Invoice'; |
hard | incorrect |
| Show number of transactions with Carol Smith | select count(distinct transaction_id) from master_txn_table where customers = 'Carol Smith' or vendor = 'Carol Smith' |
SELECT COUNT(*) AS num_transactions FROM master_txn_table WHERE Customers = 'Carol Smith'; |
medium | incorrect |
이 예제들의 핵심 관찰:
실행 정확도: 0% - 어떤 쿼리도 기대와 동일한 결과를 반환하지 못함
일반적인 실패 패턴:
- 잘못된 테이블 사용 (
customersvsmaster_txn_table) - 중복 제거 누락 (
count(distinct transaction_id)vsCOUNT(*)) - 불완전한 필터링 로직 (
OR vendor = 'Name'조건 누락) - 잘못된 컬럼 이름 (
Balancevsopen_balance)
이것은 에이전트가 유효한 SQL을 생성하지만 비즈니스 로직과 데이터베이스 스키마 관계 이해에 상당한 개선이 필요하다는 것을 보여줘요.
오류 분석
실패를 체계적으로 분석하려면 결과 CSV의 각 행을 수동으로 검토하고 주석을 달아, 관찰한 오류 유형을 분류해요. 다음 프롬프트로 AI를 사용해 분류를 도울 수 있어요.
📋 오류 분석 분류 프롬프트
You are analyzing why a Text2SQL prediction failed. Given the following information, identify the error codes and provide a brief analysis.
Available error codes:
- AGGR_DISTINCT_MISSING: Used COUNT/SUM without DISTINCT or deduplication
- WRONG_FILTER_COLUMN: Filtered on the wrong column
- WRONG_SOURCE_TABLE_OR_COLUMN: Selected metric from the wrong table/column
- EXTRA_TRANSFORMATION_OR_CONDITION: Added ABS(), extra filters that change results
- OUTPUT_COLUMN_ALIAS_MISMATCH: Output column names don't match
- NULL_OR_EMPTY_RESULT: Result is None/empty due to wrong filters or source
- GENERIC_VALUE_MISMATCH: Aggregation computed but numeric value differs for unclear reasons
- OTHER: Fallback
Query: [YOUR_QUERY]
Expected SQL: [EXPECTED_SQL]
Predicted SQL: [PREDICTED_SQL]
Execution Accuracy: [ACCURACY_RESULT]
Accuracy Reason: [ACCURACY_REASON]
Respond with:
- error_codes: array of applicable error codes (1 or more)
- error_analysis: brief 1-3 sentence explanation of what went wrong
이 프롬프트를 복사해서 선호하는 LLM과 함께 사용해 결과 CSV에서 개별 실패를 분석해요.
편의를 위해 GPT 5로 오류를 자동 분류하는 스크립트를 만들어 두었어요: uv run python -m ragas_examples.text2sql.analyze_errors --input experiments/your_results.csv
검토 프로세스
- 요약에서 고빈도 오류 코드 검사
- 각 주요 오류 유형의 5-10개 예제를 수동 검사
- AI 분류가 내 이해와 일치하는지 확인
- 스키마에 대해 "expected" SQL이 실제로 올바른지 확인
- AI가 놓쳰을 수 있는 패턴 찾기
- 영향과 빈도를 기준으로 수정 우선순위 정하기
수동 검증 후에만 이러한 통찰을 사용해서 프롬프트, few-shot 예제, 에이전트 아키텍처를 개선해야 해요.
샘플 오류 분석 결과
다음은 첫 번째 개선 후에도 남는 오류 유형을 보여주는 주석 처리된 결과의 몇 가지 샘플 행이에요.
| Query | Expected SQL | Predicted SQL | Error Analysis | Error Codes |
|---|---|---|---|---|
| How much we received from Fuel? | select sum(credit) from master_txn_table as T1 join chart_of_accounts as T2 on T1.account = T2.account_name where account_type in ('Income', 'Other Income') and instr(account,"Fuel") |
SELECT SUM(Amount) FROM (SELECT DISTINCT Transaction_ID, Amount FROM master_txn_table WHERE Customers = 'Fuel'); |
The prediction summed Amount from master_txn_table instead of credit and omitted the join to chart_of_accounts, using Customers='Fuel' rather than filtering account names containing 'Fuel' and restricting to Income/Other Income. | ["WRONG_SOURCE_TABLE_OR_COLUMN", "WRONG_FILTER_COLUMN", "NULL_OR_EMPTY_RESULT"] |
| What was the most recent invoice for Roberta Shaw? | select transaction_id from master_txn_table where transaction_type = 'invoice' and customers = "Roberta Shaw" order by transaction_date desc limit 1 |
SELECT Transaction_ID, Transaction_DATE, Amount, Open_balance, CreatedDATE, CreatedUSER, Account, Customers, Product_Service FROM master_txn_table WHERE Transaction_TYPE = 'invoice' AND Customers = 'Roberta Shaw' ORDER BY Transaction_DATE DESC LIMIT 1; |
The prediction selected nine columns instead of only Transaction_ID as expected, causing a column/schema mismatch despite returning the same row. | ["OUTPUT_COLUMN_ALIAS_MISMATCH"] |
| What is my average invoice from Jeremy Strong? | select avg(amount) from (select distinct transaction_id, amount from master_txn_table where customers = "Jeremy Strong" and transaction_type = 'invoice') |
SELECT AVG(Amount) FROM (SELECT DISTINCT Transaction_ID, Amount FROM master_txn_table WHERE Transaction_TYPE = 'invoice' AND Vendor = 'Jeremy Strong') AS t; |
The query filters on Vendor = 'Jeremy Strong' instead of the correct customers column, so it likely matched no rows. This leads to AVG(amount) returning NULL. | ["WRONG_FILTER_COLUMN", "NULL_OR_EMPTY_RESULT"] |
결과의 핵심 관찰:
- 오류 패턴:
- OR 조건 누락: 누군가 "와/과" 거래하는 질문은
customers와vendor컬럼을 모두 확인해야 함 - 잘못된 컬럼 선택: 금융 쿼리에
Amount대신credit사용 - 출력 스키마 불일치: 너무 많은 컬럼이나 잘못된 컬럼 이름 선택
- 조인 누락: 계정 유형 필터링에
chart_of_accounts와 조인하지 않음
이러한 패턴은 다음 프롬프트 개선 반복에 정보를 제공해요. 완전한 필터링 로직과 올바른 금융 쿼리 처리에 초점을 맞춰요.
행별 수정이 아닌 일반 규칙을 사용해서 프롬프트에서 무엇을 바꿀지 결정해요. 사례별 예제 추가는 피하고, 스키마에 근거한 가드레일을 선호해서 데이터에 과적합되지 않도록 해요.
이 루프를 반복적으로 수행해요:
- 실행 → 주석 → 검토 → 일반 가드레일 결정 →
prompt_vX.txt업데이트 → 재실행 → 비교 → 반복. - 개선이 과적합 없이 일반화되도록 가드레일을 간결하고 스키마에 근거하게 유지해요.
- 프롬프트 버전 관리(
prompt_v2.txt,prompt_v3.txt,prompt_v4.txt)와 버전별 간략한 변경 로그 유지. - 실행 정확도가 두 번 연속 반복에서 평평해지거나 비즈니스 임계값을 충족하면 중단.
시스템 개선
새 프롬프트 버전 만들고 사용하기
베이스라인 프롬프트는 그대로 두고 반복용 새 버전을 만들어요.
간결하고 재사용 가능한 가드레일을 포함하도록 prompt_v2.txt를 만들어요. 제공된 스키마에 근거하면서 광범위하게 적용될 수 있도록 충분히 일반적으로 유지해요. prompt_v1.txt에서 prompt_v2.txt를 만들기 위해 추가한 섹션의 예:
- Use exact table and column names from the schema; do not invent fields
- Prefer transactional facts from `master_txn_table`; use entity tables for static attributes
- Map parties correctly in filters:
- Customer-focused → filter on `Customers`
- Vendor-focused → filter on `Vendor`
- Disambiguate events via `Transaction_TYPE` (e.g., invoices → `Transaction_TYPE = 'invoice'`)
- Avoid double-counting by deduplicating on `Transaction_ID` for counts and aggregates:
- Counts: `count(distinct Transaction_ID)`
- Aggregates: compute over a deduplicated subquery on `(Transaction_ID, metric_column)`
- For open credit/balance due per customer, aggregate `Open_balance` from `master_txn_table` filtered by `Customers` with deduplication
- Do not add extra transforms or filters (e.g., `abs()`, `< 0`) unless explicitly asked
- Keep a single `SELECT`; avoid aliases for final column names
이 개선된 프롬프트를 prompt_v2.txt로 저장해요.
새 프롬프트로 평가 재실행
import asyncio
from ragas_examples.text2sql.evals import text2sql_experiment, load_dataset
async def run_v2_evaluation():
"""prompt v2로 평가 실행."""
# 데이터셋 로드
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# 실험 실행
results = await text2sql_experiment.arun(
dataset,
name="gpt-5-mini-prompt-v2",
model="gpt-5-mini",
prompt_file="prompt_v2.txt",
)
# 결과 보고
print(f"✅ gpt-5-mini-prompt-v2: {len(results)} cases evaluated")
# 정확도 계산
accuracy_rate = sum(1 for r in results if r["execution_accuracy"] == "correct") / max(1, len(results))
print(f"gpt-5-mini-prompt-v2 Execution Accuracy: {accuracy_rate:.2%}")
await run_v2_evaluation()
📋 출력 (prompt v2)
Loading dataset...
Dataset loaded with 99 samples
Running text-to-SQL evaluation with model: gpt-5-mini
Using prompt file: prompt_v2.txt
Running experiment: 100%|██████████████████████| 99/99 [01:00<00:00, 1.63it/s]
✅ gpt-5-mini-prompt-v2: 99 cases evaluated
gpt-5-mini-prompt-v2 Execution Accuracy: 60.61%
prompt_v2로 실행 정확도가 2.02%에서 60.61%로 개선된 것을 볼 수 있어요.
experiments/의 새 결과 CSV를 검토하고 루프를 다시 계속해요.
반복 계속: prompt v3 만들기
prompt_v2.txt의 큰 개선에도 불구하고 60% 정확도는 여전히 성장 여지가 있어요. 실패를 더 깊이 분석하면 몇 가지 반복되는 패턴이 드러나요.
- 금융 개념 오해: 모델이 올바른
Credit(수입) 또는Debit(지출) 컬럼 대신 일관되게Amount컬럼을 집계하는 기본값을 사용해요. 또한 계정 유형(예: 'Income')으로 필터링하기 위해chart_of_accounts와JOIN하는 것을 자주 실패해요. - 불필요한 변환 추가: 모델이 요청되지 않은
DISTINCT절이나 추가 필터(예:Transaction_TYPE = 'invoice')로 쿼리를 자주 복잡하게 만들어 결과를 바꾸기도 해요. - 잘못된 컬럼 선택: "모든 거래 표시" 쿼리에서 기대하는
SELECT DISTINCT Transaction_ID대신SELECT *를 자주 사용해서 스키마 불일치를 만든다. 집계에도 잘못된 컬럼 이름을 생성해요 (예:transaction_date대신max(transaction_date)). - 불완전한 필터링:
OR조건(예: 누군가와의 거래에서Customers와Vendor를 모두 확인)을 자주 놓치거나 완전히 잘못된 컬럼에서 필터링해요.
이 더 깊은 분석을 바탕으로 이러한 반복 문제를 해결하는 더 구체적이고 스키마에 근거한 지침으로 prompt_v3.txt를 만들어요.
prompt_v3.txt의 핵심 추가 사항:
### CORE QUERY GENERATION GUIDELINES
1. **Use Correct Schema**: Use exact table and column names...
2. **Simplicity First**: Keep the query as simple as possible...
...
### ADVANCED QUERY PATTERNS
5. **Financial Queries (Revenue, Sales, Expenses)**:
- **Metric Selection**:
- For revenue, income, sales, or money **received**: aggregate the `Credit` column.
- For expenses, bills, or money **spent**: aggregate the `Debit` column.
- Use the `Amount` column only when...
- **Categorical Financial Queries**: For questions involving financial categories... you **MUST** `JOIN` `master_txn_table` with `chart_of_accounts`...
6. **Filtering Logic**:
- **Ambiguous Parties**: For questions about transactions "with" or "involving" a person or company, you **MUST** check both `Customers` and `Vendor` columns. E.g., `WHERE Customers = 'Name' OR Vendor = 'Name'`.
- **Avoid Extra Filters**: Do not add implicit filters...
7. **Column Selection and Naming**:
- **Avoid `SELECT *`**: When asked to "show all transactions", return only `DISTINCT Transaction_ID`...
- **"Most Recent" / "Last" Queries**: To get the 'most recent' or 'last' record, use `ORDER BY Transaction_DATE DESC LIMIT 1`. This preserves the original column names... Avoid using `MAX()`...
이 새 규칙들은 일반적이지만 관찰된 실패 패턴을 직접 겨냥하도록 설계됐어요.
prompt_v3.txt로 평가 재실행:
import asyncio
from ragas_examples.text2sql.evals import text2sql_experiment, load_dataset
async def run_v3_evaluation():
"""prompt v3로 평가 실행."""
# 데이터셋 로드
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# 실험 실행
results = await text2sql_experiment.arun(
dataset,
name="gpt-5-mini-prompt-v3",
model="gpt-5-mini",
prompt_file="prompt_v3.txt",
)
# 결과 보고
print(f"✅ gpt-5-mini-prompt-v3: {len(results)} cases evaluated")
# 정확도 계산
accuracy_rate = sum(1 for r in results if r["execution_accuracy"] == "correct") / max(1, len(results))
print(f"gpt-5-mini-prompt-v3 Execution Accuracy: {accuracy_rate:.2%}")
await run_v3_evaluation()
prompt_v3로 실행 정확도가 60.61%에서 70.71%로 개선된 것을 볼 수 있어요.
반복 계속하기 위한 핵심 원칙
prompt_v3.txt로 달성한 70% 정확도는 체계적 반복의 힘을 보여줘요. 이 프로세스를 계속해서 정확도를 더 높일 수 있어요.
반복 계속을 위한 핵심 원칙:
- 각 반복은 최신 결과의 고빈도 오류 패턴 3-5개를 겨냥해야 해요
- 일반적이고 스키마에 근거한 규칙을 유지해서 과적합을 피해요
- 2-3회 연속 반복에서 정확도가 평평해지면 중단해요
- 프롬프트 개선으로 한계에 부딪히면 더 나은 모델로 실험하거나, 어떤 SQL 오류든 LLM에 되돌려 고치게 하는 실제 에이전트 플로우를 만들어볼 수 있어요
결과 비교
모든 프롬프트 버전을 실행한 후 최종 결과를 비교할 수 있어요.
| Prompt | Execution Accuracy | Results CSV |
|---|---|---|
v1 (prompt.txt) |
2.02% | experiments/...-prompt-v1.csv |
v2 (prompt_v2.txt) |
60.61% | experiments/...-prompt-v2.csv |
v3 (prompt_v3.txt) |
70.71% | experiments/...-prompt-v3.csv |
진행 분석:
- v1 → v2: 기본 중복 제거와 비즈니스 로직 지침을 통해 2.02%에서 60.61%로 58퍼센트포인트의 대폭 상승
- v2 → v3: 향상된 금융 쿼리 지침, 더 나은 필터링 로직, 컬럼 선택 규칙으로 60.61%에서 70.71%로 추가 10퍼센트포인트 개선
- 개선 사항은 오류 분석으로 식별된 특정 실패 패턴(금융 개념, 불필요한 변환, 불완전한 필터링)을 겨냥해요
결론
이 가이드에서는 텍스트-to-SQL 시스템을 위한 체계적인 평가 프로세스를 구축하는 방법을 보여줬어요.
핵심 요약:
- 실제 쿼리 결과를 비교하는 실행 정확도 메트릭 설정
- 반복 프로세스 따라가기: 평가 → 오류 분석 → 개선 → 반복
Ragas가 오케스트레이션과 결과 집계를 자동으로 처리하므로, 평가 프레임워크는 시스템을 측정하고 개선하는 신뢰할 수 있는 방법을 제공해요.