天天看點

基于LLM的SQL應用程式開發實戰(一)

作者:矽谷ChatGPT和LLM中心

SQL on LLMs應用程式初始化

本節主要從案例代碼的角度切入,探索ChatGPT以及大模型,尤其是從生産環境的視角,來思考具體的最佳實踐。本節主要跟大家談的是,在LangChain這樣一個架構下,我們使用GPT-3.5或者GPT-4大模型,同時使用第三方工具,例如MySQL或者SQLite等等。如果做一些基于記憶體或者輕量級的開發,SQLite是一個非常友善的工具,從整體的角度講,你可以把它當做檔案,然後它内部的資料又是結構化的,可以采用SQL的方式進行操作。許多年前,當我們做安卓應用程式的時候,絕大多數應用程式都使用SQLite。是以,這些工具都非常實用。如果你要開發大型模型應用程式,SQLite仍然是一個很好的工具。當然,如果你要使用MySQL等工具也沒有問題。這些都是基礎操作,在這裡就不再詳細闡述了。

Gavin大咖微信:NLP_Matrix_Space

使用SQL是從工具層面。從模型層面,我們使用GPT-3.5或者GPT-4,另外一個很重要的點是鍊(Chain)層面, 我們回到LangChain的官方文檔,看到的是最新的Python版本,如果你喜歡TypeScript,也是非常棒的,作者去年到今年有很多項目,大多數項目都使用了TypeScript,因為它前端後端都是統一的,同時LangChain本身也支援TypeScript。現在我們主要讨論的是Python。實際上,如果你很有程式設計經驗,程式設計語言對你來說沒有太大差別,隻是面向對象封裝以及解耦合會有所不同,但邏輯都是一樣的。

讓我們再次看一下鍊(Chain),實際上,我們已經多次讨論了鍊的作用。你可以将鍊視為一個容器,類似于Docker或者其他雲計算概念中的容器,它提供了一個封閉的空間,裡面有你需要的一切。如果你需要外部的資源,你是通過容器進行操作。我們可以認為鍊是一個專為某些特定用例設計的容器,你可以自由組合,因為鍊具有嵌套性,也可以自定義開發相關内容。官方文檔中也非常明确地說明了這些内容。單獨地使用大語言模型,對于簡單的應用程式來說是很好的,但更複雜的應用程式需要連結大語言模型,LangChain為“鍊式”應用程式提供了連結口。我們将鍊定義為對元件的一系列調用,這些調用可以包括其他鍊。

對于一些比較複雜的應用程式,LLMChain的處理可能涉及多個語言模型的互相作用,這時就會非常有用。舉個例子,你可以讓GPT-4和GPT-3.5互相互動,進行同行評審(Peer Review),或者與其他模型互相作用。顯然,隻要你管理模型得當,它往往可以提高性能。在我們這裡主要是SQL,還有其他的元件,在這種場景下,鍊的封裝是核心,是以它被稱為鍊的應用程式,是一個比較高層次的接口,可以有不同的實作。

大家看這個官網提供的代碼示例:

  1. class Chain(BaseModel, ABC):
  2. """所有鍊應實作的基本接口."""
  3. memory: BaseMemory
  4. callbacks: Callbacks
  5. def __call__(
  6. self,
  7. inputs: Any,
  8. return_only_outputs: bool = False,
  9. callbacks: Callbacks = None,
  10. ) -> Dict[str, Any]:
  11. ...

Chain類繼承至BaseModel類,同時繼承至ABC類,Chain提供一些接口,你必須做具體的實作,例如:__call__方法,call方法的前面和後面都有雙下劃線, __call__是在什麼時候被調用的?Chain類執行個體化之後,執行個體化本身會調它的構造函數,然後你調用這個執行個體的的時候,__call__方法會被調用。

我們在開發大模型應用的時候,經常需要建構對象,并調用這些對象。這是Python非常基礎的内容,大家應該都了解的。之是以強調這一點,是想确認大家對這個概念的了解。舉個例子,以下代碼對SQLDatabaseChain進行了執行個體化:Gavin大咖微信:NLP_Matrix_Space

  1. db = SQLDatabase.from_uri("sqlite:///./data/ncv.db")
  2. llm = OpenAI(temperature=0)
  3. db_chain = SQLDatabaseChain(
  4. llm=llm,
  5. database=db,
  6. verbose=True, # Show its work
  7. return_direct=False, # Return the results without sending back to the LLM
  8. )

這裡有一個db_chain對象,它是SQLDatabaseChain類的一個執行個體。那麼,我們如何調用它呢?可以直接給db_chain對象傳遞參數:

  1. db_chain("How many voters are there?")

這個時候,我們調用的是定義的__call__方法。

  1. class Chain(BaseModel, ABC):
  2. ...
  3. def __call__(
  4. self,
  5. inputs: Any,
  6. return_only_outputs: bool = False,
  7. callbacks: Callbacks = None,
  8. ) -> Dict[str, Any]:
  9. ...

讀者可能會提一個問題:程式設計小白是否适合使用LangChain?我們的回答是,隻要你有程式設計的基礎,就可以學習LangChain。這裡所說的程式設計基礎包括對各種類型的循環的了解,以及對狀态的管理和操縱,此外,掌握基本的面向對象程式設計也是必要的。如果你已經掌握了這些知識,學習LangChain就不會有問題。

我們繼續讨論這個非常重要的__call__方法。從源代碼的角度,來讓大家熟悉它,讓我們再次看看它的重要性。我們看一下base.py。

  1. class Chain(Serializable, ABC):
  2. ...
  3. def __call__(
  4. self,
  5. inputs: Union[Dict[str, Any], Any],
  6. return_only_outputs: bool = False,
  7. callbacks: Callbacks = None,
  8. *,
  9. tags: Optional[List[str]] = None,
  10. metadata: Optional[Dict[str, Any]] = None,
  11. include_run_info: bool = False,
  12. ) -> Dict[str, Any]:
  13. """ 執行鍊
  14. ...
  15. """
  16. inputs = self.prep_inputs(inputs)
  17. callback_manager = CallbackManager.configure(
  18. callbacks,
  19. self.callbacks,
  20. self.verbose,
  21. tags,
  22. self.tags,
  23. metadata,
  24. self.metadata,
  25. )
  26. new_arg_supported = inspect.signature(self._call).parameters.get("run_manager")
  27. run_manager = callback_manager.on_chain_start(
  28. dumpd(self),
  29. inputs,
  30. )
  31. try:
  32. outputs = (
  33. self._call(inputs, run_manager=run_manager)
  34. if new_arg_supported
  35. else self._call(inputs)
  36. )
  37. except (KeyboardInterrupt, Exception) as e:
  38. run_manager.on_chain_error(e)
  39. raise e
  40. run_manager.on_chain_end(outputs)
  41. final_outputs: Dict[str, Any] = self.prep_outputs(
  42. inputs, outputs, return_only_outputs
  43. )
  44. if include_run_info:
  45. final_outputs[RUN_KEY] = RunInfo(run_id=run_manager.run_id)
  46. return final_outputs
  47. ...
  48. @abstractmethod
  49. def _call(
  50. self,
  51. inputs: Dict[str, Any],
  52. run_manager: Optional[CallbackManagerForChainRun] = None,
  53. ) -> Dict[str, Any]:
  54. """ 執行鍊.

以上代碼第3行,是__call__方法,注意,這裡有兩個下劃線。通常情況下,編譯器或其他工具會自動生成該方法。在Chain中,我們也有類似的方法,每當調動對象的時候,都會運作Chain,它的注釋也說得很明白,是執行鍊。當我們運作LangChain時,通常會使用語言模型。例如,可能會使用GPT-4,它會根據輸入提示詞、角色定義、上下文定義、具體的指令、以及使用者的目标來生成輸出。此外,可能還會有一些限制或限制。我們在前面的項目代碼中對這些内容進行了詳細的解釋

以上代碼第28行,執行callback_manager.on_chain_start方法,它負責管理生命周期方法。你也可以自己定義這個方法。由于它是面向對象的,當運作時,它會調用具體子類的實作。這是面向對象的基本概念。Gavin大咖微信:NLP_Matrix_Space

以上代碼第33行至36行,我們看一下output,它調用的是self._call方法。請注意,在__call__方法中,開頭和結尾都有兩個下劃線。在這裡,self._call方法在名稱前加一個下劃線,以實作一些具體的業務邏輯,這是一個内部實作。實際上,在Python程式設計中,這是一種基本的操作。因為它是抽象方法,這邊沒有具體實作,具體實作要到Chain類的子類去檢視。Chain類也提供了async異步實作的方式。

我們再次回到LangChain的官網文檔,雖然文檔中講解了很多内容,但其核心實際上與我們之前談到的内容相同,例如,文檔中講解的PromptTemplate,我們已經多次提到過。

  1. from langchain.llms import OpenAI
  2. from langchain.prompts import PromptTemplate
  3. llm = OpenAI(temperature=0.9)
  4. prompt = PromptTemplate(
  5. input_variables=["product"],
  6. template="What is a good name for a company that makes {product}?",
  7. )

接下來是LLMChain,傳入一個Prompt參數,它是一個PromptTemplate的執行個體。當LLMChain中運作時,你可以直接調用它的run方法來執行操作。

  1. from langchain.chains import LLMChain
  2. chain = LLMChain(llm=llm, prompt=prompt)
  3. # 隻運作指定輸入變量的鍊
  4. print(chain.run("colorful socks"))

在LangChain SQL示例中,我們直接建構了SQLDatabaseChain。

Gavin大咖微信:NLP_Matrix_Space

  1. db = SQLDatabase.from_uri("sqlite:///./data/ncv.db")
  2. llm = OpenAI(temperature=0)
  3. db_chain = SQLDatabaseChain(
  4. llm=llm,
  5. database=db,
  6. verbose=True, # 展示
  7. return_direct=False, # 傳回結果而不發送回LLM
  8. )

然後,我們将目标對象傳給db_chain,即“How many voters are there?”(“有多少選民?”),這是使用者目标。

  1. db_chain("How many voters are there?")

這個時候,日志顯示“Entering new SQLDatabaseChain chain”(進入到SQLDatabaseChain鍊”):

> Entering new SQLDatabaseChain chain...

How many voters are there?

SQLQuery:SELECT COUNT(*) FROM ncvoter29;

SQLResult: [(126876,)]

Answer:There are 126876 voters.

> Finished chain.

在這個例子中,我們輸入一些指令,然後産生一個SQLQuery。SQLQuery是由誰産生的呢?當然是由我們的語言模型産生的。當Chain接收到使用者的輸入資訊時,它會調用我們的GPT-4或GPT-3.5來分析這些資訊,生成SQL語句。然後,Chain獲得了SQL語句後,會執行它。

如圖16-1所示,我們來看一下具體的源碼實作。LangChain的chains目錄裡面,有很多鍊的實作,例如:操作bash的鍊,或者進行數學計算的鍊等。

基于LLM的SQL應用程式開發實戰(一)

圖16- 1 LangChain的chains目錄

如圖16-2所示,chains目錄裡面有一個sql_database目錄,是資料庫的相關内容。

基于LLM的SQL應用程式開發實戰(一)

圖16- 2 chains裡面的sql_database目錄

繼續閱讀