大模型上层工具链调研 - langchain, llama-index

langchain 基于 0.0.109 版本

一个基于LLMs的应用程序开发框架, 通过可组合性来使用LLM构建应用程序. 其重点在于"可组合性"

大型语言模型 (LLM) 正在作为一种变革性技术出现,使开发人员能够构建他们以前无法构建的应用程序。但是单独使用这些 LLM 通常不足以创建真正强大的应用程序,当可以将它们与其他计算或知识来源相结合时,就有真的价值了。LangChain 旨在协助开发这些类型的应用程序.

其主要包含六个部分:

  • LLMs和prompt, 对所有大模型的通用交互接口, 以及prompt管理,优化等等
  • chains, 一系列的调用(LLMs或者其他, 如网络, 操作系统), chains提供了标准的接口和设置来组合这些调用.
  • data augmented generation 基于特定数据的内容生成, 一种特殊的chain, 提供了一种能力: 先从外部的源获取信息, 然后喂给LLMs
  • agents, 代理, 非常重要的一环, 关于对LLMs做何种action, 如何做
  • memory 标准的接口, 在chains/call之间保存状态
  • Evaluation 提供了一些prompts/chains来利用模型来评估自身
  • prompt templates

    ChatGPT 提供了通过 prompts 来进行提示的方法,也就是在发起请求时,可以带上一段信息。由于是一个聊天接口,因此可以包含多个聊天的内容。其中包括来自系统的提示:例如用来描述一下 ChatGPT 现在是什么角色,应该具有什么样的语言风格等。另外还可以包含一些用户的历史聊天记录,就像背景知识一类的,都可以作为用户的输入带进去,这样可以使得 ChatGPT 在本次聊天中具有了领域知识与上下文信息。通过这种 prompts 的方式,可以对 ChatGPT 有一定的定制能力,并使用其大模型的自然语言能力来组织回答信息。

    prompt template是简化和用户的交互, 用户提出核心问题, template 渲染问题, 增加上下文信息, 历史聊天信息, 背景知识等等.

    chains

    简单应用可能对LLM进行一次调用即可, 而复杂应用往往需要串联LLMs(相互连接或者和其他的专家系统). langchain为此提供了一套标准的接口和通用的实现.

    chain还细分为两类: 通用任务chain 和 专有工具chain ,前者更多用来组织任务顺序, 后者则专注在对某个工具的调用上.

  • LLMChain: 简单接受template和用户输入, 调用LLM, 返回输出
  • SequentialChain: 组合chains, 发起一系列且有顺序的调用请求
    SimpleSequentialChain(chains=[chain, chain_two], verbose=True)
  • BashChain: 使用LLMs 以及 调用bash命令
  • LLMRequestsChain: 发起调用, 之后将结果输送给后来的chain.
  • .........
  • 自定义chain:
  • 继承Chain
  • 实现input_keys和output_keys (前者接收先前chains的输出, 后者给到后来chains作为输入)
  • 补全调用实现_call ( _call是chain运行会调用的方法)
  • chain可以很简单地理解为 过程的抽象, chains的可组合性, 就是过程的组合.

    agents

    use LLM to determine which actions to take and in what order. An action can either be using a tool and observing its output, or returning to the user.

    搭配上能理解用户输入的大模型, agents接受大模型输出的指令, 可以打造强大的个人助理程序.

    concepts

    Tool : A function that performs a specific duty. This can be things like: Google Search, Database lookup, Python REPL, other chains. The interface for a tool is currently a function that is expected to have a string as an input, with a string as an output.
    工具, 可以执行某种特定任务. 当下, 可以理解为一个 string => string的函数. 内置的工具:
    https://langchain.readthedocs.io/en/latest/modules/agents/tools.html
    可以: 发起调用, http请求, 搜索内容, 执行代码(python), etc.

    LLM : The language model powering the agent.

    Agent : The agent to use. This should be a string that references a support agent class. Because this notebook focuses on the simplest, highest level API, this only covers using the standard supported agents. If you want to implement a custom agent, see the documentation for custom agents (coming soon).
    用来调用工具的代理, 如果标准库里没有合适的工具代理, 可以自定义.
    内置代理类型:

  • zero-shot-react-description 根据工具的描述, 和请求的string 来决定使用哪个工具
  • react-docstore 使用react框架, 和docstore交互, 使用 Search Lookup 工具, 前者用来搜, 后者寻找term, 举例: Wipipedia工具
  • self-ask-with-search 此代理只使用一个工具: Intermediate Answer, 它会为问题寻找事实答案(指的非gpt生成的答案, 而是在网络中,文本中已存在的), 如 Google search API 工具
  • conversational-react-description 为会话设置而设计的代理, 它的prompt会被设计的具有会话性, 且还是会使用 ReAct框架来决定使用来个工具, 并且将过往的会话交互存入内存.
  • https://langchain.readthedocs.io/en/latest/modules/agents/agents.html

    与向量数据库交互

    希望应用程序, 在使用LLM的过程中, 和向量数据库交互(其实, 不用langchain库, 自己编写很简单的代码就可以做到) 只是langchain库在多数向量库之上, 包装了一层统一的交互接口.
    https://langchain.readthedocs.io/en/latest/modules/agents/examples/agent_vectorstore.html

    如下代码所示 , 两个tool被创建, 组合成工具包, 两个工具, 分别是"SOU的qa"和 "Ruff的qa", 他们会分别阅读SOU和Ruff网站的内容, 并且调用LLM的向量接口, 生成向量, 存于本地数据库. 然后使用特定种类的agent作为某种路由, 可以按工具描述使用他们.

    tools = [
        Tool(
            name = "State of Union QA System",
            func=state_of_union.run,
            description="useful for when you need to answer questions about the most recent state of the union address. Input should be a fully formed question."
        Tool(
            name = "Ruff QA System",
            func=ruff.run,
            description="useful for when you need to answer questions about ruff (a python linter). Input should be a fully formed question."
    agent = initialize_agent(tools, llm, agent="zero-shot-react-description", verbose=True)
    agent.run("What did biden say about ketanji brown jackson is the state of the union address?")
    
    先搜再问 - data agumented generation

    对于某些大模型没有的信息或者知识, 如果要补充这些信息, 需要重新训练模型, 成本比较高, 但是可以通过prompt的方式给到它, langchain为了方便上层开发应用, 设计了self-ask-with-search代理, 即先到某个知识库去获取相关的信息和知识, 然后利用LLM来理解这些知识和信息,然后回答用户的问题.

    memory

    chain和agent是无状态的, LLM模型也是, 所以需要prompt来进行历史提示.
    Chains and Agents are stateless, meaning that they treat each incoming query independently (as are the underlying LLMs and chat models).
    memory模块提供了两种能力:

  • 模块化管理和操作先前的对话的工具
  • 将这些工具集成到chain里的设施
  • https://langchain.readthedocs.io/en/latest/modules/memory/types/buffer.html
    从示例中可以看出, langchain将对话保存在模块化的memory中, 并且在predict的时候, 自动带上历史会话, 作为prompt.

    关于agent和chains的差异,尤其是utils chain
    agent是关于对LLM做何种action, 怎么做action, 在于挑选工具. chain重点是对输入做处理 , 并且组织各个过程的顺序.

    data augemented generation

    LLMs是基于大量非结构化的语料训练的, 所以对通用的nlp任务有很好的的效果, 然而很多时候, 我们需要的不是在利用通用数据来生成结果, 而是基于特定的数据来产生结果.

  • Summarization of a specific piece of text (a website, a private document, etc.)
  • Question answering over a specific piece of text (a website, a private document, etc.)
  • Question answering over multiple pieces of text (multiple websites, multiple private documents, etc.)
  • Using the results of some external call to an API (results from a SQL query, etc.)
    即, 不希望模型仅仅通过其训练的数据来生成文本, 而要把某些外部的数据也结合进去, 所以步骤可以拆解为两步:
  • fetching: 取回
  • user provided
  • document retrival
  • api querying
  • augmenting: 传递给LLM
  • fetching

    在fetching过程中, 比较重要的是取回的文档不能太大, 因为目前的模型都对输入的长度有限制, 所以 langchain提供了各种工具来split文档成一片一片, 并且控制每片中的内容重叠(保持上下文). 另外一个是, 不能取回太多的文档, 最好只选取和问题相关的文档, 即相关度要高, 有比较多的方式来做这件事, 当下比较流行的是:
    在语义搜索和Q&A系统中, 将语料的doc切分成chunk, 将chunk向量化, 然后存储, 之后用户的输入到来后, 首先进行向量化的相关性搜索, 将搜索的结果以及用户数据一起作为prompt作为输入喂给LLM, 这是目前常见的做法, 但业界也在考虑转为大模型设计的数据结构和索引技术, 详见 LlamaIndex

    几个开源项目:

  • https://github.com/arc53/docsgpt
  • https://github.com/GanymedeNil/document.ai
    都是用的 data augement generation 来做的.

    LlamaIndex -> 专注在数据层

    对于私有数据, 受限于LLM的无状态, 训练代价大, token个数限制, 甚至fine-tune的接口不开放等等, 对于私有数据上的内容生成, 现在常见的做法是embedding文档, 先搜后问, 也即上文提到的langchain的一个核心模块: data augementing generation. 与此同时, LlamaIndex在考虑为LLM设计的数据结构和索引.

    现在想要增强LLM在私有数据上的表现能力, 有两种方式: fine-tune 和 in-context learning (即将上下文放入输入的prompt中) .
    为了更好地(高效,便宜)进行data augmentation, 想要解决: 数据摄取和索引. LlamaIndex提供的工具:

  • data connectors : 连接到数据源, 和不同的数据格式
  • 利用LLM来给结构化,非结构化数据做索引. 简化了in-context learning过程中繁琐的细节: prompt长度, 文本拆分,etc
  • 用户优化的索引query 接口 和 模型调用接口的结合
  • 一个权衡成本和性能的全面的工具集
  • 不同于langchain的全面开发, 均衡发展策略, llama-index更像是专注在data augmentation generation, 围绕它来开发周边对应的能力. 即丰富的数据源和数据格式的读取器, 统一的index表示, 花式的index读取, 写入.

    smart agents -> 专注在agents

    https://www.fixie.ai/

    Build natural language agents that connect to your data, talk to APIs, and solve complex problems.

    和Llama-index类似, 不过专注在agents, 准备构建一个agents market, 在大模型之上构建一个软件系统, 可以和其它系统交互, 完成复杂任务.

    目前资料较少

    GPT Index vs LangChain 的区别:

    根本上是因为 大语言模型支持的context有限,比如ChatGPT的Davinci model只有 4096 tokens,对应中文,可能只有2000

    “A big limitation of LLMs is context size (e.g. Davinci's limit is 4096 tokens. Large, but not infinite).”

  • 如果单纯的跟GPT模型对接,那直接用 GPT 的 Davinci 模型直接对话就行;ChatGPT 只是 GPT 其中一个 chat-model 而已

  • 如果需要借助GPT语言模型+私有数据,那就需要突破 4096 tokens 的限制,也就是 feed "knowledge" to LLMs,所以需要用上 GPT Index

  • 与此同时,在用 ChatGPT 时,单次输入的 prompt 也有限制,所以 GPT Index 可以简化这个 feed 喂数据的过程。

  • 如果 GPT 直接就满足要求,可以用 GPT Index,就够了。
  • 那为什么还有用上 LangChain 呢?就是因为 LLMs 不止 OpenAI 的 GPT 模型,LangChain 可以对接多种模型,即 Lang

    而 Chain 的意思就是把多个 LLM 调用,而且是调用序列,标准化,集成等工作,最后串起来

  • LangChain 还做了很多框架层面的工作:Prompt、Loader、Chain、Agent、Memory
  • 比如 Loader 部分,它也推出了跟 https://llamahub.ai 类似的 https://github.com/hwchase17/langchain-hub,用来集成外部数据。区别就在于 GPT Index 能用的,LangChain 都能用,LangChain 的 Loader 能加载其他语言模型,是 GPT 的超集。

    再比如 Memory 部分,就是用来持久化 内存 状态,所以能实现 ChatGPT 聊天机器人这样的功能,记住以前的交互非常重要,无论是短期的还是长期的。

    Agent 部分就更有趣了,可以根据用户输入,再来决定调用这些工具中的哪一个,比如 LangChain 的 GPT+WolframAlpha 示例,甚至还可以根据输入去调用 WolframAlpha,解答你的数学提问,弥补GPT 数学弱智的问题。

    当然,它也可以去做 Google 搜索、数据库查找 等操作,通过需要跟 Document Loader 结合起来用,你可以找到类似 https://llamahub.ai/l/file-pdf 等不少例子。