langchain Chatchat 學習實踐(四)——實現對Text2Sql的支持
這里記錄一下langchain chatchat項目中的text2sql的實現思路。
1、SQLDatabaseChain鏈
SQLDatabaseChain是langchain框架自帶的數據庫自然語言交互工具,其內部通過sqlalchemy來獲取數據庫的表名和表結構、字段信息,然后將數據庫的信息和用戶的自然語言請求一起發送給大模型進行分析,讓大模型返回sql語句后,執行sql,并返回執行結果。
db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True,top_k=top_k,return_intermediate_steps=return_intermediate_steps) result = db_chain.invoke({"query":query,"table_names_to_use":table_names})
2、SQLDatabaseSequentialChain
SQLDatabaseChain會將數據庫所有的表信息發送給大模型,如果數據庫的表比較多且復雜,構造的prompt會很長,這樣會超過一些大模型的token長度限制,而有些問題其實不需要把所有表發送給大模型,也會造成資源浪費。
SQLDatabaseSequentialChain會先進性一次判斷,根據需求,結合表名,預測會用到哪些表,然后在將相關表和需求轉給SQLDatabaseChain,這樣可以更加精確的實現需求,節約token。
3、表名作用強調
SQLDatabaseSequentialChain有一個比較麻煩的問題,就是其預測過程嚴重依賴表名,因此如果你設計的表名難于被大模型理解用途,就會導致預測失敗,后續的SQLDatabaseChain也必然會導致錯誤的結果。
即使只考慮SQLDatabaseChain,其執行的過程也會將表名構造到prompt中,因而表名如果難于理解,會對執行效果有很大負面影響。
由于langchain框架內部封裝這兩個工具,不能增加prompt模板輸入參數變量,除非修改langchain源碼。但是我們可以通過對query進行補充,明確告知大模型某些表的實際含義:
#如果發現大模型判斷用什么表出現問題,嘗試給langchain提供額外的表說明,輔助大模型更好的判斷應該使用哪些表,尤其是SQLDatabaseSequentialChain模式下,是根據表名做的預測,很容易誤判 #由于langchain固定了輸入參數,所以只能通過query傳遞額外的表說明 if table_comments: TABLE_COMMNET_PROMPT="\n\nI will provide some special notes for a few tables:\n\n" table_comments_str="\n".join([f"{k}:{v}" for k,v in table_comments.items()]) query=query+TABLE_COMMNET_PROMPT+table_comments_str+"\n\n"
4、read-only模式
text2sql固然帶來了全新的人與數據庫交互體驗,但是也帶來一定安全風險,在某些生產環境中,我們只希望提供只讀權限給用戶,禁止寫入。
解決方法有三個:
(1)開發者自己將設置數據庫只讀賬戶,并提供給text2sql(強烈推薦)
(2)因為SQLDatabaseChain本身是用sqlalchemy實現的,因此可以通過添加攔截器,對寫入操作進行攔截:
# 定義一個攔截器函數來檢查SQL語句,以支持read-only,可修改下面的write_operations,以匹配你使用的數據庫寫操作關鍵字 def intercept_sql(conn, cursor, statement, parameters, context, executemany): # List of SQL keywords that indicate a write operation write_operations = ("insert", "update", "delete", "create", "drop", "alter", "truncate", "rename") # Check if the statement starts with any of the write operation keywords if any(statement.strip().lower().startswith(op) for op in write_operations): raise OperationalError("Database is read-only. Write operations are not allowed.", params=None, orig=None)
event.listen(db._engine, "before_cursor_execute", intercept_sql)
(3)但是攔截器這種方法會導致異常拋出,給用戶交互帶來不好的體驗,解決思路就是在read-only模式下,讓大模型先進行預測,預測該請求是否涉及寫操作,如果預測會用到寫操作,那么直接返回相關提示即可,后續流程終止,這樣可以帶來更加友好的體驗:
READ_ONLY_PROMPT_TEMPLATE="""You are a MySQL expert. The database is currently in read-only mode. Given an input question, determine if the related SQL can be executed in read-only mode. If the SQL can be executed normally, return Answer:'SQL can be executed normally'. If the SQL cannot be executed normally, return Answer: 'SQL cannot be executed normally'. Use the following format: Answer: Final answer here Question: {query} """ if read_only: # 在read_only下,先讓大模型判斷只讀模式是否能滿足需求,避免后續執行過程報錯,返回友好提示。 READ_ONLY_PROMPT = PromptTemplate( input_variables=["query"], template=READ_ONLY_PROMPT_TEMPLATE, ) read_only_chain = LLMChain( prompt=READ_ONLY_PROMPT, llm=llm, ) read_only_result = read_only_chain.invoke(query) if "SQL cannot be executed normally" in read_only_result["text"]: return "當前數據庫為只讀狀態,無法滿足您的需求!"

浙公網安備 33010602011771號