0%

使用大型語言模型(LLM)完成Text-to-SQL任務 - 透過Langchain SQL Chain建立資料問答系統

延續上一篇使用大型語言模型(LLM)完成Text-to-SQL任務,在開發大型語言模型的應用程式時,可以採用像LangChain一樣的開源框架。LangChain目的在幫助開發者建立大型語言模型的應用程式,縮短開發時間並更容易的實現各種複雜的大型語言模型應用程式。

準備資料庫

一開始先建立Chinook這個資料庫來當作範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import sqlite3
import requests

# 下載 SQL 腳本
url = "https://raw.githubusercontent.com/lerocha/chinook-database/master/ChinookDatabase/DataSources/Chinook_Sqlite.sql"
response = requests.get(url)

# 儲存 SQL 腳本到本地檔案
with open('Chinook_Sqlite.sql', 'w') as f:
f.write(response.text)

# 連接到 SQLite 資料庫 (會自動建立資料庫檔案)
conn = sqlite3.connect('Chinook.db')
cursor = conn.cursor()

# 執行 SQL 腳本來建立資料庫
with open('Chinook_Sqlite.sql', 'r') as f:
cursor.executescript(f.read())

# 提交變更並關閉連接
conn.commit()
conn.close()

接著使用langchain的SQLDatabase來建立資料庫的查詢引擎

1
2
3
4
5
6
7
from langchain_community.utilities import SQLDatabase
# 準備sqlite db
db = SQLDatabase.from_uri("sqlite:///Chinook.db")

print(db.dialect)
print(db.get_usable_table_names())
db.run("SELECT * FROM Artist LIMIT 10;")
1
2
sqlite
[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham')]

準備大型語言模型服務

再來準備一個大型語言模型服務,這裡使用Gemini當範例

1
2
3
4
5
6
7
8
9
10
import os
import getpass
import google.generativeai as genai
# https://python.langchain.com/docs/integrations/chat/google_generative_ai/

# Configure Gemini
GEMINI_API_KEY = getpass.getpass() # input your gemini api key
genai.configure(api_key=GEMINI_API_KEY)

llm = genai.GenerativeModel('gemini-1.5-flash')

使用LangChain

在LangChain中「Chain」是代表一組有順序執行的步驟或是一系列連接在一起的組件,可以把Chain視為一個工作流程,其中每個步驟都由一個特定的組件負責。每一個組件可能是語言模型、資料來源或是API,這些組件經由連接後可以形成一個完整的應用程式,進而建構更複雜模型推論應用。

使用create_sql_query_chain

準備好資料庫和大型語言模型服務後,透過create_sql_query_chain建立一個SQL查詢的Chain,這個Chain能夠在給定的資料庫下生成SQL查詢。前面使用的SQLDatabase則可以在這裡用來查詢資料庫類型和調用get_table_info來取得欄位資訊。

1
2
3
4
5
from langchain.chains import create_sql_query_chain

chain = create_sql_query_chain(llm, db)
response = chain.invoke({"question": "How many employees are there"})
print(f"response: {response}")

這裡使用到的create_sql_query_chain會把使用者輸入的文字轉成以下的SQL查詢

1
response: SQLQuery: SELECT COUNT(*) FROM Employee

接著可以詠唱一段解析程式把SQL取出來後對資料庫下查詢。

1
2
3
4
5
6
7
8
9
10
11
12
# prompt: write a regular expression to parse all substring start from "SQLQuery: "
import re

def parse_sql(answer):
match = re.search(r"SQLQuery: (.*)", answer)
if match:
return match.group(1)
else:
return None

sql_str = parse_sql(response)
db.run(sql_str)
1
[(8,)]
Chain起生成SQL到下查詢

再來可以把這些動作都Chain起來,步驟會是(1) 將輸入問題透過LLM生成出SQL查詢 (2) 從生成結果解析出SQL (3) 對資料庫下SQL查詢

1
2
3
4
5
6
7
8
from langchain_community.tools.sql_database.tool import QuerySQLDataBaseTool
from langchain_core.runnables import RunnableLambda

execute_query = QuerySQLDataBaseTool(db=db, verbose=True)
write_query = create_sql_query_chain(llm, db)
# 生成SQL查詢 -> 解析出SQL -> 下查詢
chain = write_query | RunnableLambda(lambda x: parse_sql(x)) | execute_query
chain.invoke({"question": "How many employees are there"})
1
[(8,)]
把查詢結果轉換成人話

到這麼已經可以自動化的從輸入問題到生成結果並查詢資料庫,這時如果不想讓Agent只有回覆查詢數字,就可以把資料庫的查詢結果再丟給LLM生成出像人一樣的回答。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from operator import itemgetter

from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough

answer_prompt = PromptTemplate.from_template(
"""Given the following user question, corresponding SQL query, and SQL result, answer the user question.
Question: {question}
SQL Query: {query}
SQL Result: {result}
Answer: """
)

answer = answer_prompt | llm | StrOutputParser()
chain = (
RunnablePassthrough
.assign(query=write_query) # answer_prompt中的query參數 = SQLQuery: SELECT COUNT(*) FROM Employee
.assign(result=itemgetter("query") | RunnableLambda(lambda x: parse_sql(x)) | execute_query # answer_prompt中的result參數 = [(8,)]
)
# 到這裡會把放進answer_prompt裡面的dict準備好
# {'question': 'How many employees are there',
# 'query': 'SQLQuery: SELECT COUNT(*) FROM "Employee"',
# 'result': '[(8,)]'}
| answer
)

chain.invoke({"question": "How many employees are there"}) # answer_prompt中的question = How many employees are there

從answer_prompt可以看到把question、query、result組成prompt後,就可以請LLM生成一個答案。所以完整的步驟會有四個 (1) 將輸入問題透過LLM生成出SQL查詢 (2) 從生成結果解析出SQL (3) 對資料庫下SQL查詢 (4) 將查詢結果透過LLM生成自然語言回答

1
There are 8 employees.

LangChain提供了開發大型語言模型所需要的組件,透過上面的範例可以快速的建立Text-to-SQL核心功能,加速開發大型語言應用程式。

參考資料: Build a Question/Answering system over SQL data