我们继续写点儿偏工程实践的内容——LangChain的核心模块3——Chain。

1.核心模块3:Chain

在《【chatGPT】学习笔记11-LLM应用-垂直领域知识问答系统(基于ChatGLM2)》中,我们知道LangChain-ChatChat有如下工作流程:

image-20230921092837843

如何实现呢?其实,LangChain抽象了Chain的概念。

1.1.Chain Class

  • 类的继承关系:Chain –> Chain # Examples: LLMChain, MapReduceChain, RouterChain
  • 代码路径/libs/langchain/langchain/chains/base.py,详细源码如下
 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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Chain(Serializable, Runnable[Dict[str, Any], Dict[str, Any]], ABC):
    """Abstract base class for creating structured sequences of calls to components.

    Chains should be used to encode a sequence of calls to components like
    models, document retrievers, other chains, etc., and provide a simple interface
    to this sequence.

    The Chain interface makes it easy to create apps that are:
        - Stateful: add Memory to any Chain to give it state,
          有状态的:给Chain添加Memory,使其具有状态
        - Observable: pass Callbacks to a Chain to execute additional functionality,
            like logging, outside the main sequence of component calls,
          可观察的:向Chain传递Callback来执行额外的功能。
        - Composable: the Chain API is flexible enough that it is easy to combine
            Chains with other components, including other Chains.
          可组合的:Chain API足够灵活,可以轻松地将Chains与其他组件组合在一起,包括组合其他的Chain。

    The main methods exposed by chains are:
        - `__call__`: Chains are callable. The `__call__` method is the primary way to
            execute a Chain. This takes inputs as a dictionary and returns a
            dictionary output.
           执行Chain的主要方式,输入是一个字典,输出也是一个字典。
        - `run`: A convenience method that takes inputs as args/kwargs and returns the
            output as a string or object. This method can only be used for a subset of
            chains and cannot return as rich of an output as `__call__`.
           输入是args/kwargs,输出是字符串or对象。仅用于部分链,没有__call__方法通用。
    """
    
    ............
    
    memory: Optional[BaseMemory] = None
    """Optional memory object. Defaults to None.
    Memory is a class that gets called at the start 
    and at the end of every chain. At the start, memory loads variables and passes
    them along in the chain. At the end, it saves any returned variables.
    There are many different types of memory - please see memory docs 
    for the full catalog."""
    每个链开始和结束时调用。存储Memory
    callbacks: Callbacks = Field(default=None, exclude=True)
    """Optional list of callback handlers (or callback manager). Defaults to None.
    Callback handlers are called throughout the lifecycle of a call to a chain,
    starting with on_chain_start, ending with on_chain_end or on_chain_error.
    Each custom chain can optionally call additional callback methods, see Callback docs
    for full details."""
    callback_manager: Optional[BaseCallbackManager] = Field(default=None, exclude=True)
    """Deprecated, use `callbacks` instead."""
    verbose: bool = Field(default_factory=_get_verbosity)
    """Whether or not run in verbose mode. In verbose mode, some intermediate logs
    will be printed to the console. Defaults to `langchain.verbose` value."""
    开启后,输出更纤细的日志。
    
    ............

我们看一个例子:

  • LLMChain:创建了一个步骤,该步骤可执行prompt提示词。
  • run方法:调用LLMChain的run方法,可以和LLM进行问答。

image-20230921094132633

1.2.顺序链

(1)源码解读

  • SequentialChain:继承于Chain,顺序链,允许将多个Chain链接起来,形成Pipeline(流水线)
  • 代码路径/libs/langchain/langchain/chains/sequential.py,详细源码如下
1
2
3
4
5
6
7
8
class SequentialChain(Chain):
    """Chain where the outputs of one chain feed directly into next."""

    chains: List[Chain]
    input_variables: List[str]
    output_variables: List[str]  #: :meta private:
    return_all: bool = False
    ……………………
  • SimpleSequentialChain:继承于SequentialChain,一种简化版的顺序链,允许将多个Chain链接起来,形成Pipeline(流水线)
  • 代码路径/libs/langchain/langchain/chains/sequential.py,详细源码如下
1
2
3
4
5
6
7
class SimpleSequentialChain(Chain):
    """Simple chain where the outputs of one step feed directly into next."""

    chains: List[Chain]
    strip_outputs: bool = False
    input_key: str = "input"  #: :meta private:
    output_key: str = "output"  #: :meta private:

(2)SimpleSequentialChain的代码示例

我们来看如下的代码,实现了LLM模仿医生对病情进行介绍,并给出治疗方案。

  • 通过SimpleSequentialChain,连接了病情摘要Chain和病情评论Chain。
  • 病情摘要Chain的输出,作为了病情评论Chain的输入

image-20230921095733390

(3)SequentialChain的代码示例

我们来看如下的代码,实现了LLM模仿医生对病情进行介绍,并给出治疗方案。

不同的是,要支持多输入、多输出。

  • 通过SequentialChain,连接了病情摘要Chain和病情评论Chain。
  • 病情摘要Chain的输入有多个,病情摘要Chain的输出作为了病情评论Chain的输入,病情评论Chain的输出有多个

image-20230921100917222

1.3.决策链

(1)源码解读

  • RouterChain:继承于Chain,决策链,允许将多个Chain链接起来,可以实现条件判断的分支Pipeline(分支流水线)
  • 代码路径/libs/langchain/langchain/chains/router/base.py,详细源码如下
 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
class RouterChain(Chain, ABC):
    """Chain that outputs the name of a destination chain and the inputs to it."""

    @property
    def output_keys(self) -> List[str]:
        return ["destination", "next_inputs"]

    def route(self, inputs: Dict[str, Any], callbacks: Callbacks = None) -> Route:
        """
        Route inputs to a destination chain.

        Args:
            inputs: inputs to the chain
            callbacks: callbacks to use for the chain

        Returns:
            a Route object
        """
        result = self(inputs, callbacks=callbacks)
        return Route(result["destination"], result["next_inputs"])

    async def aroute(
        self, inputs: Dict[str, Any], callbacks: Callbacks = None
    ) -> Route:
        result = await self.acall(inputs, callbacks=callbacks)
        return Route(result["destination"], result["next_inputs"])

(2)RouterChain的代码示例

我们来看如下的代码,实现了根据问题内容,选择程序员角色的AI或测试工程师AI出来回答问题。

  • 创建两个决策分支:能回答软件开发问题的程序员、能回答软件测试问题的程序员。

image-20230921102913545

  • 创建根据问题内容进行决策的RouterChain

image-20230921102959963

这个决策链是如何实现的决策功能呢?进一步看一下:

  • destinations_str:描述了根据问题内容,期望选择哪个AI来回答问题。

image-20230921103109653

  • MULTI_PROMPT_ROUTER_TEMPLATE:LangChain提供了决策链的提示词模板。

image-20230921103154608

  • router_template:最终的决策链提示词为:

image-20230921103225927

  • 将决策链和两个回答问题的链进行连接。

image-20230921103257969

  • 测试一下关于软件开发的问题:

image-20230921103327682

  • 测试一下关于软件测试的问题:

image-20230921103359806

2.小结

本文阐述了Chain模块的内部实现:

  • Chain类:抽象了PipeLine流水线
  • 顺序链:SequentialChain,实现了多输入多输出的串行执行的工作流。
  • 决策链:RouterChain,实现了根据问题内容,选择工作流走向的能力。
  • Chain类拥有很多子类,实现不同的业务流程,可以根据实战需要继续阅读源码和实践。

后续文章,我们继续解读LangChain的核心模块,感谢阅读。