天天看点

基于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目录

继续阅读