如何评测 Text to SQL Agent
在本指南中,你将学习如何用 Ragas 系统地评测并改进一个 text-to-SQL 系统。
你将完成:
- 搭建用于评测的基线 text-to-SQL 系统
- 学习如何创建评测指标
- 为你的 SQL agent 构建可复用的评测流水线
- 基于错误分析实施改进
设置你的环境
我们创建了一个你可以安装并运行的简单模块,这样你可以专注于理解评测过程,而不是创建应用。
uv pip install "ragas-examples[text2sql]"
快速测试 agent
测试 text-to-SQL agent,看它如何把自然语言转换成 SQL:
import os
import asyncio
from openai import AsyncOpenAI
from ragas_examples.text2sql.text2sql_agent import Text2SQLAgent
# Set your OpenAI API key
os.environ["OPENAI_API_KEY"] = "your-api-key-here"
# Create agent
openai_client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
agent = Text2SQLAgent(client=openai_client, model_name="gpt-5-mini")
# Test with a sample query
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
在运行 agent 或数据库工具之前,从 Hugging Face 下载 gated 的 BookSQL 数据集:
huggingface-cli login
uv run python -m ragas_examples.text2sql.data_utils --download-data
如果看到认证错误,请先访问数据集页面并接受条款:BookSQL on Hugging Face
完整代码
你可以在这里查看 agent 和评测流水线的完整代码。
准备你的数据集
我们准备了一个平衡的样本数据集,包含 99 个例子(easy、medium 和 hard 查询各 33 个),来自 BookSQL 数据集。你可以立即开始评测,或按照下一节创建自己的数据集。
下载并检查样本数据集:
# Download the sample CSV from GitHub
curl -o booksql_sample.csv https://raw.githubusercontent.com/vibrantlabsai/ragas/main/examples/ragas_examples/text2sql/datasets/booksql_sample.csv
# View the first few rows to understand the structure
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。
检查数据集结构:
# Check the database schema
sqlite3 BookSQL-files/BookSQL/accounting.sqlite ".schema" | head -20
期望的 schema 输出:
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,
数据集包含:
- Database:带有会计数据(invoices、clients 等)的 SQLite 文件
- Questions:英文自然语言查询
- SQL:对应的 SQL 查询
- Difficulty levels: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
这会创建 datasets/booksql_sample.csv,包含跨难度级别的 99 个平衡例子。
BookSQL 以 CC BY-NC-SA(仅非商业)发布。详见下方细节与引用。
📋 许可与引用细节
许可与使用
BookSQL 数据集以 CC BY-NC-SA 4.0 许可发布。你只能将其用于非商业研究。不允许商业使用。
- Dataset:Hugging Face 上的
Exploration-Lab/BookSQL· GitHub repository - Paper: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。
设置你的 text-to-SQL 系统
创建你的 prompt
提取数据库 schema:
uv run python -m ragas_examples.text2sql.db_utils --schema
📋 期望的 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)
编写 prompt 内容:
我们的 prompt 遵循这个模板结构:
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.
定义评测指标
对于 text-to-SQL 系统,我们需要评测结果准确性的指标。我们将把 execution accuracy 作为主指标,验证生成的 SQL 是否返回正确数据。
Execution Accuracy Metric:使用 datacompy 比较期望 SQL 与预测 SQL 查询的实际结果。这验证两个查询是否返回相同数据,这是正确性的最终检验。
评测系统将结果分类为:
"correct":查询成功并与期望结果匹配"incorrect":查询未成功,或成功但返回了错误结果
设置指标函数
用 Ragas discrete metrics 创建你的评测指标。
# 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):
"""Compare execution results of predicted vs expected SQL using datacompy."""
try:
# Execute expected 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}"
)
# If predicted SQL fails, it's incorrect
if not predicted_success:
return MetricResult(
value="incorrect",
reason=f"Predicted SQL failed to execute: {predicted_result}"
)
# Both queries succeeded - compare DataFrames using datacompy
if isinstance(expected_result, pd.DataFrame) and isinstance(predicted_result, pd.DataFrame):
# Handle empty DataFrames
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"
)
# Use datacompy to compare DataFrames with index-based comparison
comparison = datacompy.Compare(
expected_result.reset_index(drop=True),
predicted_result.reset_index(drop=True),
on_index=True, # Compare row-by-row by index position
abs_tol=1e-10, # Very small tolerance for floating point comparison
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)}"
)
实验函数
实验函数 编排完整评测流水线——运行 text-to-SQL agent 并为每个查询计算指标:
# 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],
):
"""Experiment function for text-to-SQL evaluation."""
# Create text-to-SQL agent
openai_client = AsyncOpenAI(api_key=os.environ["OPENAI_API_KEY"])
agent = Text2SQLAgent(
client=openai_client,
model_name=model,
prompt_file=prompt_file
)
# Generate SQL from natural language query
result = await agent.query(row["Query"])
# Execute predicted 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)}"
# Score the response using execution accuracy
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):
"""Load the text-to-SQL dataset from CSV file."""
dataset_path = Path(__file__).parent / "datasets" / "booksql_sample.csv"
# Read CSV
df = pd.read_csv(dataset_path)
# Limit dataset size if requested
if limit is not None and limit > 0:
df = df.head(limit)
# Create 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():
"""Run text-to-SQL evaluation with direct code approach."""
# Load dataset
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# Run the experiment
results = await text2sql_experiment.arun(
dataset,
name="gpt-5-mini-prompt-v1",
model="gpt-5-mini",
prompt_file=None,
)
# Report results
print(f"✅ gpt-5-mini-prompt-v1: {len(results)} cases evaluated")
# Calculate and display accuracy
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%}")
# Run the evaluation
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:自定义 prompt 文件(默认:None,使用内置 prompt)limit:样本数量(默认:None 表示全部样本,指定数字以限制)name:用于结果跟踪的自定义实验名称
初始表现分析
评测会生成全面的 CSV 结果以供分析。每一行包含:
query:自然语言输入expected_sql:Ground truth SQLpredicted_sql:生成的 SQLlevel:难度(easy/medium/hard)execution_accuracy:correct/incorrect(结果比较)accuracy_reason:详细比较结果
分析错误与失败模式
运行评测后,你可以分析失败模式,了解 Text2SQL agent 在何处出错。下面是基线评测结果中的一些示例行,让你完整了解评测数据长什么样:
| 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 |
从这些例子中得到的关键观察:
Execution Accuracy: 0% - 没有一个查询返回与期望相同的结果
常见失败模式:
- 使用错误的表(
customersvsmaster_txn_table) - 缺少去重(
count(distinct transaction_id)vsCOUNT(*)) - 过滤逻辑不完整(缺少
OR vendor = 'Name'条件) - 错误的列名(
Balancevsopen_balance)
这表明虽然 agent 生成了合法 SQL,但它在理解业务逻辑和数据库 schema 关系方面需要显著改进。
错误分析
要系统地分析失败,请手动审阅并标注结果 CSV 中的每一行,对你观察到的错误类型进行分类。你可以用这个 prompt 让 AI 帮助你分类:
📋 错误分析分类 Prompt
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
复制这个 prompt,并用你喜欢的 LLM 分析来自结果 CSV 的单个失败。
为方便起见,我们把它做成了一个脚本,你可以用 GPT 5 自动分类错误:uv run python -m ragas_examples.text2sql.analyze_errors --input experiments/your_results.csv
审阅过程
- 检查汇总中的高频错误码
- 手动检查 每种主要错误类型的 5-10 个例子
- 核验 AI 的分类 是否符合你的理解
- 检查“期望”SQL 对你的 schema 是否实际正确
- 寻找 AI 可能错过的 模式
- 根据影响和频率确定修复优先级
只有在手动验证之后,才应使用这些洞察来改进 prompts、few-shot 例子或 agent 架构。
样本错误分析结果
下面是标注结果中的一些示例行,展示第一轮改进后仍然存在的错误类型:
| 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 - 输出 schema 不匹配:选择了太多列或错误的列名
- 缺少 joins:没有与
chart_of_accountsjoin 以按账户类型过滤
这些模式为下一轮 prompt 改进提供信息,聚焦完整的过滤逻辑和正确的财务查询处理。
用通用规则决定要在 prompt 中改什么,而不是按行修复。避免添加针对个案的例子;更偏好 grounded 在 schema 上的护栏,这样你就不会对数据过拟合。
迭代地重复这个循环:
- 运行 → 标注 → 审阅 → 决定通用护栏 → 更新
prompt_vX.txt→ 重新运行 → 比较 → 重复。 - 保持护栏简洁且 grounded 在 schema 上,使改进能泛化而不过拟合。
- 为你的 prompts 做版本(
prompt_v2.txt、prompt_v3.txt、prompt_v4.txt),并为每个版本维护一份简短 changelog。 - 当 execution accuracy 在连续两次迭代中趋于平稳,或达到你的业务阈值时停止。
改进你的系统
创建并使用新的 prompt 版本
我们保持基线 prompt 不变,并创建一个新版本用于迭代。
创建 prompt_v2.txt,加入简洁、可复用的护栏。让它们足够通用以便广泛适用,同时 grounded 在所提供的 schema 中。下面是我们添加到 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 保存为 prompt_v2.txt。
用新 prompt 重新运行评测
import asyncio
from ragas_examples.text2sql.evals import text2sql_experiment, load_dataset
async def run_v2_evaluation():
"""Run evaluation with prompt v2."""
# Load dataset
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# Run experiment
results = await text2sql_experiment.arun(
dataset,
name="gpt-5-mini-prompt-v2",
model="gpt-5-mini",
prompt_file="prompt_v2.txt",
)
# Report results
print(f"✅ gpt-5-mini-prompt-v2: {len(results)} cases evaluated")
# Calculate accuracy
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%
我们看到 execution accuracy 从 2.02% 提升到 60.61%,这要归功于 prompt_v2。
审阅 experiments/ 中的新结果 CSV,并继续循环。
继续迭代:创建 prompt v3
即使 prompt_v2.txt 有重大改进,60% 的准确率仍有提升空间。对失败的更深入分析揭示了若干反复出现的模式:
- 对财务概念的误解:模型持续默认聚合
Amount列,而不是正确的Credit(收入)或Debit(支出)列。它也常常未能与chart_of_accountsJOIN以按账户类型(例如 'Income')过滤。 - 添加不必要的变换:模型经常用不被要求的
DISTINCT子句或额外过滤器(如Transaction_TYPE = 'invoice')把查询复杂化,这会改变结果。 - 错误的列选择:对于 "show all transactions" 查询,它经常使用
SELECT *而不是期望的SELECT DISTINCT Transaction_ID,导致 schema 不匹配。它也会为聚合生成错误的列名(例如max(transaction_date)而不是transaction_date)。 - 过滤不完整:它经常漏掉
OR条件(例如,检查与某人交易时同时检查Customers和Vendor),或完全过滤了错误的列。
基于这一更深入的分析,创建 prompt_v3.txt,加入更具体、grounded 在 schema 上的指南,以解决这些反复出现的问题:
对 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():
"""Run evaluation with prompt v3."""
# Load dataset
dataset = load_dataset()
print(f"Dataset loaded with {len(dataset)} samples")
# Run experiment
results = await text2sql_experiment.arun(
dataset,
name="gpt-5-mini-prompt-v3",
model="gpt-5-mini",
prompt_file="prompt_v3.txt",
)
# Report results
print(f"✅ gpt-5-mini-prompt-v3: {len(results)} cases evaluated")
# Calculate accuracy
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()
我们看到 execution accuracy 从 60.61% 提升到 70.71%,这要归功于 prompt_v3。
继续迭代的关键原则
用 prompt_v3.txt 达到的 70% 准确率展示了系统迭代的力量。你可以继续这个过程,把准确率推得更高。
继续迭代的关键原则:
- 每次迭代应从最新结果中针对 3-5 个高频错误模式
- 保持新规则 通用且 grounded 在 schema 上,以避免过拟合
- 当准确率在连续 2-3 次迭代中趋于平稳时停止
- 如果你用 prompt 改进碰到了平台期,可以尝试用更好的模型做实验,或把任何 sql 错误返回给 LLM 去修复,从而做成真正的 agentic 流程。
比较结果
运行所有 prompt 版本后,我们可以比较最终结果。
| 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% 跃升 58 个百分点到 60.61%
- v2 → v3:通过增强的财务查询指南、更好的过滤逻辑和列选择规则,再提升 10 个百分点,从 60.61% 到 70.71%
- 这些改进针对通过错误分析识别出的具体失败模式:财务概念、不必要的变换,以及不完整的过滤
结论
本指南向你展示了如何为 text-to-SQL 系统构建系统化的评测过程。
关键收获:
- 设置 execution accuracy 指标来比较实际查询结果
- 遵循迭代过程:评测 → 分析错误 → 改进 → 重复
评测框架为你提供了一种可靠的方式来测量和改进系统,Ragas 会自动处理编排和结果汇总。