Xây dựng Text-to-SQL Engine sẵn sàng cho môi trường Production với Vanna.ai và Python

AI tutorial - IT technology blog
AI tutorial - IT technology blog

Vấn đề với Ad-Hoc Query và Generic LLM

Hầu hết các engineering team đều gặp phải cùng một trở ngại. Product manager, support lead và ban điều hành liên tục cần số liệu nhanh cho các buổi họp sync hàng tuần.

Chỉ sau vài tháng, các senior engineer đã phải dành từ 4 đến 6 tiếng mỗi tuần chỉ để viết những câu query SQL dùng một lần thay vì tập trung phát triển các tính năng cốt lõi. Việc cấp quyền truy cập trực tiếp các database client như DBeaver cho đồng nghiệp phi kỹ thuật hiếm khi mang lại hiệu quả. Khi không có kiến thức chuyên sâu về SQL, người dùng rất khó join các bảng chính xác, chưa kể việc cấp quyền truy cập trực tiếp vào các production replica tiềm ẩn rủi ro lớn về tính ổn định và tuân thủ dữ liệu.

Ngược lại, việc kết nối trực tiếp các foundation model có sẵn vào database lại phát sinh một loạt phiền toái khác. Đưa cho GPT-4o một prompt kiểu như “Bạn là chuyên gia Postgres, hãy trả lời câu hỏi này của người dùng bằng SQL”, và những lỗ hổng sẽ lộ ra ngay lập tức.

Các base model hoàn toàn không nắm được ngữ nghĩa kinh doanh (business semantics) đặc thù của doanh nghiệp bạn. Chúng thường bịa ra các tên cột không tồn tại, nhầm lẫn các cờ soft-delete kế thừa từ hệ thống cũ, và sinh ra các câu query đúng cú pháp nhưng tính toán sai hoàn toàn chỉ số. Việc nhồi nhét toàn bộ schema gồm 80 bảng vào context window của prompt cũng làm lãng phí hàng chục nghìn token cho mỗi lượt gọi, đồng thời khiến model bị nhiễu bởi các định nghĩa bảng không liên quan.

Khái niệm cốt lõi: Cách Vanna.ai làm cầu nối giữa ngôn ngữ tự nhiên và SQL

Vanna.ai giải quyết vấn đề này bằng cách tiếp cận Text-to-SQL như một bài toán truy xuất thông tin (information retrieval) thay vì phỏng đoán tạo sinh thuần túy. Thay vì gửi toàn bộ catalog dữ liệu trong mỗi API call, Vanna tách biệt domain knowledge của bạn thành các indexed vector embedding. Khi nhận được câu hỏi, hệ thống chỉ kéo về các đoạn schema và sample query thực sự liên quan trước khi tạo prompt hoàn chỉnh gửi tới model.

Ba tầng ngữ cảnh (Context Layers)

Để sinh ra các câu query chuẩn xác, model cần tài liệu tham chiếu đặc thù theo từng domain. Vanna tổ chức dữ liệu training của bạn qua ba tầng riêng biệt:

  • DDL (Data Definition Language): Các câu lệnh CREATE TABLE chính xác với đầy đủ primary key, foreign key, giá trị mặc định và ràng buộc cột.
  • Documentation: Thuật ngữ nghiệp vụ bằng ngôn ngữ tự nhiên và các quy tắc đặc thù của domain (ví dụ: “Active MRR chỉ tính các tài khoản có status = ‘active’ và billing_plan != ‘trial'”).
  • Golden Query Pairs: Các cặp câu hỏi và câu lệnh SQL chuẩn đã được con người kiểm duyệt cẩn thận. Những ví dụ này đóng vai trò như các minh họa few-shot, giúp model bám sát dialect và cách join bảng ưa thích của team bạn.

Kiến trúc dạng module

Vanna giữ các thành phần tách rời nhau, cho phép bạn lắp ghép tech stack production linh hoạt như các khối lego:

  1. Vector Store: ChromaDB, FAISS, Pinecone, Qdrant hoặc PGVector để lưu trữ và truy vấn ngữ cảnh ngữ nghĩa (semantic context).
  2. Language Model: Các hosted endpoint như OpenAI (GPT-4o), Anthropic Claude và Google Gemini, hoặc các model tự host chạy qua Ollama và vLLM.
  3. Database Driver: Native connector cho PostgreSQL, MySQL, Snowflake, BigQuery, SQLite và ClickHouse.

Thực hành: Từng bước xây dựng pipeline Text-to-SQL tùy chỉnh

Bạn có thể dựng xong một bản prototype chạy local hoàn chỉnh chỉ trong khoảng 15 phút. Dưới đây là kiến trúc tôi thường triển khai khi xây dựng trợ lý truy vấn nội bộ cho các data team.

Bước 1: Thiết lập môi trường

Cài đặt package cốt lõi cùng với vector storage local và database driver:

pip install "vanna[chromadb,openai]" psycopg2-binary

Bước 2: Khởi tạo Vanna Client

Vanna sử dụng Python mixin để kết hợp vector database bạn chọn với một LLM provider. Thiết lập này tạo một instance ChromaDB local bền vững trên ổ đĩa và kết nối trực tiếp đến OpenAI API:

import os
from vanna.openai.openai_chat import OpenAI_Chat
from vanna.chromadb.chromadb_vector import ChromaDB_VectorStore

class CustomVanna(ChromaDB_VectorStore, OpenAI_Chat):
    def __init__(self, config=None):
        ChromaDB_VectorStore.__init__(self, config=config)
        OpenAI_Chat.__init__(self, config=config)

vn = CustomVanna(config={
    'api_key': os.environ.get("OPENAI_API_KEY"),
    'model': 'gpt-4o',
    'path': './vanna_chroma_db'  # Thư mục lưu trữ bền vững cho embedding cục bộ
})

Bước 3: Huấn luyện Pipeline với Schema và ngữ cảnh

Độ chính xác ngoài thực tế phụ thuộc phần lớn vào bước này. Chỉ index DDL thuần túy thường là chưa đủ. Bổ sung thêm business rule và các câu query đã được kiểm định sẽ giúp tăng độ chính xác khi sinh query từ khoảng 60% lên trên 90% đối với các relational schema phức tạp.

# 1. Huấn luyện bằng các câu lệnh DDL
vn.train(ddl="""
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    is_active BOOLEAN DEFAULT TRUE
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INT REFERENCES users(id),
    amount NUMERIC(10, 2) NOT NULL,
    status VARCHAR(50) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
""")

# 2. Huấn luyện bằng business rule và thuật ngữ nghiệp vụ
vn.train(documentation="""
'Completed revenue' chỉ tính các đơn hàng có status = 'paid'. 
Các đơn hàng có status 'pending' hoặc 'refunded' luôn phải bị loại trừ khỏi phép tính tổng doanh thu.
""")

# 3. Huấn luyện bằng các cặp câu hỏi - SQL đã kiểm duyệt
vn.train(
    question="Ai là top 5 khách hàng theo tổng chi tiêu?",
    sql="""
    SELECT u.id, u.email, SUM(o.amount) AS total_spent
    FROM users u
    JOIN orders o ON u.id = o.user_id
    WHERE o.status = 'paid'
    GROUP BY u.id, u.email
    ORDER BY total_spent DESC
    LIMIT 5;
    """
)

Bước 4: Kết nối đến Database và chạy truy vấn

Khi ngữ cảnh đã được index, hãy kết nối Vanna với database instance của bạn để chuyển đổi ngôn ngữ tự nhiên thành pandas DataFrame được thực thi hoàn chỉnh:

vn.connect_to_postgres(
    host='localhost',
    dbname='analytics_db',
    user='readonly_user',
    password=os.environ.get("DB_PASSWORD"),
    port=5432
)

# Đặt câu hỏi bằng ngôn ngữ tự nhiên
question = "Doanh thu tháng trước từ người dùng đang hoạt động là bao nhiêu?"
sql = vn.generate_sql(question=question)
print("SQL được sinh:\n", sql)

# Thực thi và xem trước DataFrame kết quả
df = vn.run_sql(sql=sql)
print("Kết quả truy vấn:\n", df.head())

Bước 5: Gia cố bảo mật trong môi trường Production

Việc kết nối LLM trực tiếp vào thực thi database đòi hỏi thiết kế mang tính phòng thủ chặt chẽ. Hãy thiết lập các guardrail sau trước khi triển khai agent lên kênh Slack nội bộ hoặc web UI:

  • Dedicated Read-Only User: Tạo một database role riêng biệt chỉ được cấp duy nhất quyền SELECT. Thu hồi tường minh toàn bộ quyền ghi và DDL trên tất cả production schema.
  • Xác thực truy vấn bằng AST: Đưa các câu query được tạo qua một AST parser như sqlglot hoặc sqlparse. Chặn thực thi ngay lập tức nếu syntax tree phát hiện các từ khóa thay đổi dữ liệu như DROP, TRUNCATE, UPDATE, hoặc ALTER.
  • Giới hạn Timeout nghiêm ngặt: Thiết lập timeout cứng theo session cho database role (chẳng hạn SET statement_timeout = '3000ms'). Điều này ngăn chặn các tác vụ scan bảng không index hoặc phép Cartesian join vô tình làm suy giảm hiệu năng cơ sở dữ liệu.
  • Giới hạn số dòng trả về: Thiết lập trần giới hạn số dòng trả về cụ thể (ví dụ: LIMIT 1000) để tránh sự cố tràn bộ nhớ (OOM) khi người dùng đặt những câu hỏi quá mở.

Kết luận: Hiện thực hóa Self-Service Data đáng tin cậy

Hệ thống Text-to-SQL thành hay bại phụ thuộc hoàn toàn vào ngữ cảnh bạn cung cấp cho nó. Bằng cách kết hợp truy xuất vector với các định nghĩa nghiệp vụ tường minh, Vanna loại bỏ triệt để hiện tượng hallucination vốn thường làm hỏng các LLM wrapper cơ bản. Hãy bắt đầu từ quy mô nhỏ: index từ ba đến bốn bảng có lượng truy vấn cao, ghi chép tài liệu về các công thức tính toán quan trọng, và bổ sung các query đã kiểm duyệt mỗi khi đồng nghiệp yêu cầu báo cáo tùy chỉnh. Bạn vừa trao cho các bên liên quan câu trả lời tức thì, vừa giúp engineering team tiết kiệm hàng giờ viết báo cáo tẻ nhạt mỗi tuần.

Share: