Ragas Ragas
stable · 中文译文
中文译文 · 原文:https://docs.ragas.io/en/stable/howtos/applications/text2sql/ · 许可证 Apache-2.0

如何评测 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 许可发布。你只能将其用于非商业研究。不允许商业使用。

如果你在研究中使用 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 SQL
  • predicted_sql:生成的 SQL
  • level:难度(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% - 没有一个查询返回与期望相同的结果

常见失败模式:

  • 使用错误的表(customers vs master_txn_table)
  • 缺少去重(count(distinct transaction_id) vs COUNT(*))
  • 过滤逻辑不完整(缺少 OR vendor = 'Name' 条件)
  • 错误的列名(Balance vs open_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

审阅过程

  1. 检查汇总中的高频错误码
  2. 手动检查 每种主要错误类型的 5-10 个例子
  3. 核验 AI 的分类 是否符合你的理解
  4. 检查“期望”SQL 对你的 schema 是否实际正确
  5. 寻找 AI 可能错过的 模式
  6. 根据影响和频率确定修复优先级

只有在手动验证之后,才应使用这些洞察来改进 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_accounts join 以按账户类型过滤

这些模式为下一轮 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% 的准确率仍有提升空间。对失败的更深入分析揭示了若干反复出现的模式:

  1. 对财务概念的误解:模型持续默认聚合 Amount 列,而不是正确的 Credit(收入)或 Debit(支出)列。它也常常未能与 chart_of_accounts JOIN 以按账户类型(例如 'Income')过滤。
  2. 添加不必要的变换:模型经常用不被要求的 DISTINCT 子句或额外过滤器(如 Transaction_TYPE = 'invoice')把查询复杂化,这会改变结果。
  3. 错误的列选择:对于 "show all transactions" 查询,它经常使用 SELECT * 而不是期望的 SELECT DISTINCT Transaction_ID,导致 schema 不匹配。它也会为聚合生成错误的列名(例如 max(transaction_date) 而不是 transaction_date)。
  4. 过滤不完整:它经常漏掉 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 会自动处理编排和结果汇总。