LangChain: Blog
88

Thủ thuật

Kết hợp LLM và SQL: Xây dựng giải pháp Text-to-SQL đáng tin cậy với LangChain

(giờ Việt Nam)

Tóm tắt AI

Bài viết hướng dẫn cách sử dụng LLM để truy vấn cơ sở dữ liệu SQL bằng ngôn ngữ tự nhiên, tập trung vào các kỹ thuật giảm thiểu ảo giác và tối ưu hóa độ chính xác thông qua khung làm việc LangChain.

Bản dịch AI

LLMs and SQL

Francisco Ingham và Jon Luo là hai thành viên cộng đồng đang dẫn đầu sự thay đổi trong các tích hợp SQL. Chúng tôi rất hào hứng khi được viết bài blog này cùng họ để chia sẻ tất cả các mẹo và thủ thuật mà họ đã đúc kết được trong quá trình thực hiện. Chúng tôi còn vui mừng hơn khi thông báo rằng chúng tôi sẽ tổ chức một buổi hội thảo trực tuyến (webinar) kéo dài một giờ với họ để thảo luận về những bài học này và giải đáp các câu hỏi liên quan khác. Buổi hội thảo sẽ diễn ra vào ngày 22 tháng 3 - hãy đăng ký tại liên kết bên dưới:

Thư viện LangChain có nhiều chuỗi SQL (SQL chains) và thậm chí là một tác nhân SQL (SQL agent) nhằm mục đích giúp việc tương tác với dữ liệu lưu trữ trong SQL trở nên dễ dàng nhất có thể. Dưới đây là một số liên kết liên quan:

Giới thiệu

Hầu hết dữ liệu của doanh nghiệp theo truyền thống đều được lưu trữ trong các cơ sở dữ liệu SQL. Với lượng dữ liệu giá trị được lưu trữ tại đó, các công cụ trí tuệ doanh nghiệp (BI) giúp truy vấn và hiểu dữ liệu trở nên phổ biến hơn. Nhưng nếu bạn có thể tương tác với cơ sở dữ liệu SQL bằng ngôn ngữ tự nhiên thì sao? Với các LLM ngày nay, điều đó là hoàn toàn khả thi. Các LLM có hiểu biết về SQL và có khả năng viết mã SQL khá tốt. Tuy nhiên, có một vài vấn đề khiến đây không phải là một nhiệm vụ đơn giản.

Các vấn đề

Vậy LLM có thể viết SQL - thì còn cần gì nữa?

Thật không may, vẫn còn một vài điều cần thiết.

Vấn đề chính tồn tại là hiện tượng ảo tưởng (hallucination). LLM có thể viết SQL, nhưng chúng thường có xu hướng tự tạo ra các bảng, tự tạo ra các trường và nhìn chung là viết những câu lệnh SQL mà nếu thực thi trên cơ sở dữ liệu của bạn thì sẽ không hợp lệ. Vì vậy, một trong những thách thức lớn mà chúng ta phải đối mặt là làm thế nào để gắn kết (ground) LLM với thực tế để nó tạo ra các câu lệnh SQL hợp lệ.

Ý tưởng chính để khắc phục điều này (chúng tôi sẽ đi sâu vào chi tiết bên dưới) là cung cấp cho LLM kiến thức về những gì thực sự tồn tại trong cơ sở dữ liệu và yêu cầu nó viết truy vấn SQL nhất quán với dữ liệu đó. Tuy nhiên, điều này dẫn đến vấn đề thứ hai - độ dài cửa sổ ngữ cảnh (context window). Các LLM có một cửa sổ ngữ cảnh nhất định, giới hạn lượng văn bản mà chúng có thể xử lý. Điều này rất quan trọng vì các cơ sở dữ liệu SQL thường chứa rất nhiều thông tin. Vì vậy, nếu chúng ta truyền tất cả dữ liệu một cách ngây thơ để gắn kết LLM với thực tế, chúng ta có khả năng sẽ gặp phải vấn đề này.

Vấn đề thứ ba là một vấn đề cơ bản hơn: đôi khi LLM chỉ đơn giản là làm sai. Câu lệnh SQL mà nó viết có thể không chính xác vì bất kỳ lý do gì, hoặc nó có thể đúng nhưng lại trả về kết quả không mong đợi. Vậy chúng ta phải làm gì? Chúng ta có bỏ cuộc không?

Các giải pháp (ở cấp độ cao)

Khi suy nghĩ về cách giải quyết những vấn đề này, việc xem xét cách con người chúng ta xử lý chúng sẽ rất hữu ích. Nếu chúng ta có thể tái tạo các bước mà chúng ta thực hiện để giải quyết những vấn đề đó, chúng ta cũng có thể giúp LLM làm điều tương tự. Vì vậy, hãy suy nghĩ xem một nhà phân tích dữ liệu sẽ làm gì nếu họ được yêu cầu trả lời một câu hỏi về BI.

Khi các nhà phân tích dữ liệu truy vấn cơ sở dữ liệu SQL, có một vài điều họ thường làm để giúp họ thực hiện các truy vấn đúng đắn. Ví dụ, họ thường tạo một truy vấn mẫu trước đó để hiểu dữ liệu trông như thế nào. Họ có thể xem lược đồ (schema) của các bảng, hoặc thậm chí là các hàng cụ thể. Điều này có thể được coi là việc nhà phân tích dữ liệu tìm hiểu dữ liệu trông như thế nào để khi họ viết truy vấn SQL trong tương lai, nó sẽ dựa trên những gì thực sự tồn tại. Các nhà phân tích dữ liệu cũng thường không nhìn vào tất cả dữ liệu (hoặc hàng ngàn hàng) cùng một lúc - họ có thể giới hạn bất kỳ truy vấn khám phá nào ở K hàng đầu tiên, hoặc xem các số liệu thống kê tóm tắt thay thế. Điều này có thể mang lại một số gợi ý về cách vượt qua các giới hạn của cửa sổ ngữ cảnh. Và cuối cùng, nếu một nhà phân tích dữ liệu gặp lỗi, họ không chỉ bỏ cuộc - họ học hỏi từ lỗi đó và viết một truy vấn mới.

Chúng tôi thảo luận về từng giải pháp này trong một phần riêng biệt bên dưới.

Mô tả cơ sở dữ liệu của bạn

Để cung cấp cho LLM đủ thông tin nhằm tạo ra các truy vấn hợp lý cho một cơ sở dữ liệu nhất định, chúng ta cần mô tả cơ sở dữ liệu một cách hiệu quả trong câu lệnh nhắc (prompt). Điều này có thể bao gồm việc mô tả cấu trúc bảng, các ví dụ về dữ liệu trông như thế nào, và thậm chí là các ví dụ về các truy vấn tốt cho cơ sở dữ liệu đó. Các ví dụ dưới đây lấy từ cơ sở dữ liệu Chinook.

Mô tả lược đồ (schema)

Trong các phiên bản cũ của LangChain, chúng tôi chỉ đơn giản cung cấp tên bảng, các cột và kiểu dữ liệu của chúng:

Bảng 'Track' có các cột: TrackId (INTEGER), Name (NVARCHAR(200)), AlbumId (INTEGER), MediaTypeId (INTEGER), GenreId (INTEGER), Composer (NVARCHAR(220)), Milliseconds (INTEGER), Bytes (INTEGER), UnitPrice (NUMERIC(10, 2))

Rajkumar và cộng sự đã thực hiện một nghiên cứu đánh giá hiệu suất Text-to-SQL của OpenAI Codex với nhiều cấu trúc nhắc khác nhau. Họ đạt được hiệu suất tốt nhất khi nhắc Codex bằng các lệnh CREATE TABLE, bao gồm tên cột, kiểu dữ liệu, tham chiếu cột và khóa. Đối với bảng Track, nó trông như sau:

CREATE TABLE "Track" ("TrackId" INTEGER NOT NULL,"Name" NVARCHAR(200) NOT NULL,"AlbumId" INTEGER,"MediaTypeId" INTEGER NOT NULL,"GenreId" INTEGER,"Composer" NVARCHAR(220),"Milliseconds" INTEGER NOT NULL,"Bytes" INTEGER,"UnitPrice" NUMERIC(10, 2) NOT NULL,PRIMARY KEY ("TrackId"),FOREIGN KEY("MediaTypeId") REFERENCES "MediaType" ("MediaTypeId"),FOREIGN KEY("GenreId") REFERENCES "Genre" ("GenreId"),FOREIGN KEY("AlbumId") REFERENCES "Album" ("AlbumId"))

Mô tả dữ liệu

Chúng ta có thể cải thiện hơn nữa khả năng tạo truy vấn tối ưu của LLM bằng cách cung cấp thêm các ví dụ về dữ liệu trông như thế nào. Ví dụ, nếu chúng ta đang tìm kiếm các nhà soạn nhạc trong bảng Track, sẽ rất hữu ích khi biết liệu cột Composer bao gồm tên đầy đủ, tên viết tắt, cả hai, hay thậm chí là các cách biểu diễn khác. Rajkumar và cộng sự nhận thấy rằng việc cung cấp các hàng ví dụ trong câu lệnh SELECT theo sau mô tả CREATE TABLE mang lại sự cải thiện hiệu suất nhất quán. Điều thú vị là họ nhận thấy rằng cung cấp 3 hàng là tối ưu, và việc cung cấp nhiều nội dung cơ sở dữ liệu hơn thậm chí có thể làm giảm hiệu suất.

Chúng tôi đã áp dụng các kết quả thực tiễn tốt nhất từ bài báo của họ làm cài đặt mặc định. Tổng hợp lại, mô tả cơ sở dữ liệu của chúng tôi trong câu lệnh nhắc trông như thế này:

db = SQLDatabase.from_uri( "sqlite:///../../../../notebooks/Chinook.db", include_tables=['Track'], # chỉ bao gồm một bảng để minh họa sample_rows_in_table_info=3)print(db.table_info)

Kết quả đầu ra là:

CREATE TABLE "Track" ("TrackId" INTEGER NOT NULL,"Name" NVARCHAR(200) NOT NULL,"AlbumId" INTEGER,"MediaTypeId" INTEGER NOT NULL,"GenreId" INTEGER,"Composer" NVARCHAR(220),"Milliseconds" INTEGER NOT NULL,"Bytes" INTEGER,"UnitPrice" NUMERIC(10, 2) NOT NULL,PRIMARY KEY ("TrackId"),FOREIGN KEY("MediaTypeId") REFERENCES "MediaType" ("MediaTypeId"),FOREIGN KEY("GenreId") REFERENCES "Genre" ("GenreId"),FOREIGN KEY("AlbumId") REFERENCES "Album" ("AlbumId"))SELECT * FROM 'Track' LIMIT 3;TrackId Name AlbumId MediaTypeId GenreId Composer Milliseconds Bytes UnitPrice1 For Those About To Rock (We Salute You) 1 1 1 Angus Young, Malcolm Young, Brian Johnson 343719 11170334 0.992 Balls to the Wall 2 2 1 None 342562 5510424 0.993 Fast As a Shark 3 2 1 F. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman 230619 3990994 0.99

Sử dụng thông tin bảng tùy chỉnh

Mặc dù LangChain tự động tập hợp lược đồ và mô tả hàng mẫu một cách thuận tiện, nhưng có một vài trường hợp tốt hơn là ghi đè thông tin tự động bằng các mô tả được soạn thủ công. Ví dụ, nếu bạn biết rằng vài hàng đầu tiên của một bảng không chứa thông tin hữu ích, tốt nhất là nên cung cấp thủ công các hàng ví dụ cung cấp cho LLM nhiều thông tin hơn. Ví dụ, trong bảng `Track`, đôi khi nhiều nhà soạn nhạc được phân tách bằng dấu gạch chéo thay vì dấu phẩy. Điều này xuất hiện lần đầu ở hàng 111 của bảng, vượt xa giới hạn 3 hàng của chúng tôi. Chúng ta có thể cung cấp thông tin tùy chỉnh này để các hàng ví dụ chứa thông tin mới này. Đây là một ví dụ về việc thực hiện điều này trong thực tế.

Cũng có thể sử dụng mô tả tùy chỉnh để giới hạn các cột của bảng hiển thị với LLM. Một ví dụ về hai cách sử dụng này áp dụng cho bảng Track có thể trông như sau:

CREATE TABLE "Track" ("TrackId" INTEGER NOT NULL,"Name" NVARCHAR(200) NOT NULL,"Composer" NVARCHAR(220),PRIMARY KEY ("TrackId"),)SELECT * FROM 'Track' LIMIT 4;TrackId Name Composer1 For Those About To Rock (We Salute You) Angus Young, Malcolm Young, Brian Johnson2 Balls to the Wall None3 Fast As a Shark F. Baltes, S. Kaufman, U. Dirkscneider & W. Hoffman4 Money Berry Gordy, Jr./Janie Bradford

Nếu bạn có dữ liệu nhạy cảm mà bạn không muốn gửi đến API, bạn có thể sử dụng tính năng này để cung cấp dữ liệu giả (mock data) thay vì cơ sở dữ liệu thực của bạn.

Giới hạn kích thước đầu ra

Khi chúng ta thực hiện các truy vấn với LLM trong một chuỗi hoặc tác nhân, kết quả từ truy vấn của chúng ta sẽ được sử dụng làm đầu vào cho một LLM khác. Nếu kết quả truy vấn quá lớn, nó sẽ vượt quá kích thước đầu vào của mô hình. Vì vậy, thường là một thực tiễn tốt khi giới hạn kích thước đầu ra của truy vấn một cách hợp lý. Chúng ta có thể thực hiện điều này bằng cách hướng dẫn LLM sử dụng càng ít cột càng tốt và giới hạn số lượng hàng trả về.

Như chúng ta có thể thấy trong ví dụ sau, nếu chúng ta yêu cầu danh sách tổng doanh số theo quốc gia mà không chỉ định số lượng quốc gia, truy vấn sẽ bị giới hạn ở mức 10. Bạn có thể quản lý giới hạn này bằng tham số top_k.

agent_executor.run("Liệt kê tổng doanh số theo quốc gia. Khách hàng của quốc gia nào chi tiêu nhiều nhất?")

>>

…Action Input: SELECT c.Country, SUM(i.Total) AS TotalSales FROM Invoice i INNER JOIN Customer c ON i.CustomerId = c.CustomerId GROUP BY c.Country ORDER BY TotalSales DESC LIMIT 10Observation: [('USA', 523.0600000000003), ('Canada', 303.9599999999999), ('France', 195.09999999999994), ('Brazil', 190.09999999999997), ('Germany', 156.48), ('United Kingdom', 112.85999999999999), ('Czech Republic', 90.24000000000001), ('Portugal', 77.23999999999998), ('India', 75.25999999999999), ('Chile', 46.62)]…

Kiểm tra cú pháp

LangChainSQLLLMData EngineeringText-to-SQL
Đọc bài gốc

Bài viết được AI dịch và tổng hợp tự động từ LangChain: Blog. Liên kết bài gốc ở phía trên. Dữ liệu đồng bộ qua API công khai được ghi nguồn tại AI HOT (canonical) ↗. AIHOT.vn luôn dẫn nguồn đầy đủ — nếu bạn thấy điểm cần chỉnh sửa, hãy gửi ý kiến tại trang phản hồi.