3. 使用Chain构建可组合流水线

使用chains构建模块化的流水线

从chain开始

Chains是LangChain的核心,他们是按照特定顺序执行的组件链。

LLMChain是其中最简单的。他的工作原理是接受用户的输入,传递给链中的第一个元素--promptTemplate,将输入格式化到特定的prompt;格式化后的prompt紧接着会传递给chain的下一个(也是最后一个)元素,一个LLM(大语言模型)。

我们从引入我们在下面的例子中使用到的library:

import inspect
import re

from getpass import getpass
from langchain import OpenAI, PromptTemplate
from langchain.chains import LLMChain, LLMMathChain, TransformChain, SequentialChain
from langchain.callbacks import get_openai_callback

为了运行这个notebook,我们需要使用OpenAI的LLM。在这里我们需要设置在整个notbook中使用的LLM,需要输入你的openai的api密钥

OPENAI_API_KEY = getpass()
llm = OpenAI(
    temperature=0, 
    openai_api_key=OPENAI_API_KEY
    )

在这个function中我们将使用一个额外的工具,该工具会告诉我们在每次请求中使用了多少tokens。这是一种越来越重要的好习惯,随着我们使用更加复杂的工具可能会多次调用Api(例如agent)。严谨的控制我们使用了多少tokens对于避免超出预期的支持是非常重要的。

def count_tokens(chain, query):
    with get_openai_callback() as cb:
        result = chain.run(query)
        print(f'Spent a total of {cb.total_tokens} tokens')

    return result

什么是chain?

定义:chains是一个LangChain的一个基础模块

官方定义如下:

chain是由原始元素或者其他chain组成的链. 原始元素可以是prompt、llm、util或者其他的chain

链基本上是一个由特定的原始元素组合的、可以处理输入的pipleline。直观上,他可以被视为一个“步骤”,对输入执行一组特定的操作并返回结果。他们可以是基于prompt的LLM处理,也可以是处理text的python函数。

Chains可以分为三种类型:Utility chains,Generic chains和Combine Documents Chains。在该部分,我们将聚焦在前面两种,因为第三种比较特殊(将会在接下来的课程中详细介绍)。

  1. Utility Chains:通常被用于从llm提取特定答案,具有比较固定的目的,并且即刻可用。
  2. Generic Chains:用来构建其他chain的模块,通常不会单独使用。

接下来看看这两种chain会提供什么!

Utility Chains

我们从一个比较简单的Utility chain来开始。LLMMathChain可以让LLM拥有数学能力。让我们来看看他是怎么工作的!

提示:使用verbose=True来看看chain都有哪些不同的步骤

llm_math = LLMMathChain(llm=llm, verbose=True)

count_tokens(llm_math, "What is 13 raised to the .3432 power?")

> Entering new LLMMathChain chain...
What is 13 raised to the .3432 power?
```python
import math
print(math.pow(13, .3432))
```

Answer: 2.4116004626599237

> Finished chain.
Spent a total of 272 tokens
'Answer: 2.4116004626599237\n'

让我们看看这里面发生了什么。chain接收了一个自然语言描述的问题,并且将它发送给llm。llm返回一段python代码,chain编译了这段代码以给出答案。这引发了一些问题,llm是怎么知道我们期望得到的是一段python代码?

enter prompts

我们作为输入发送给chain的问题并不是llm接收到的唯一输入。输入被插入到一个context,给如何解释我们的输入提供了明确的指令。这个被称为prompt。让我们看看这个chain的prompt是什么。

print(llm_math.prompt.template)
You are GPT-3, and you can't do math.

You can do basic math, and your memorization abilities are impressive, but you can't do any complex calculations that a human could not do in their head. You also have an annoying tendency to just make up highly specific, but wrong, answers.

So we hooked you up to a Python 3 kernel, and now you can execute code. If anyone gives you a hard math problem, just use this format and we’ll take care of the rest:

Question: ${{Question with hard calculation.}}
```python
${{Code that prints what you need to know}}
```
```output
${{Output of your code}}
```
Answer: ${{Answer}}

Otherwise, use this simpler format:

Question: ${{Question without hard calculation}}
Answer: ${{Answer}}

Begin.

Question: What is 37593 * 67?

```python
print(37593 * 67)
```
```output
2518731
```
Answer: 2518731

Question: {question}

让我们来看看上面的输出。我们实际上是在告诉LLM,对于复杂的数学问题,LLM不应该尝试自己计算,而是应该打印出一段python代码来计算这个数学问题。如果我们只是发送查询而没有任何上下文,LLM可能会尝试(并失败)自己进行计算。这个是可以测试的,让我们试试看!

# we set the prompt to only have the question we ask
prompt = PromptTemplate(input_variables=['question'], template='{question}')
llm_chain = LLMChain(prompt=prompt, llm=llm)

# we ask the llm for the answer with no context

count_tokens(llm_chain, "What is 13 raised to the .3432 power?")
Spent a total of 17 tokens
'\n\n2.907'

错误的答案!这里蕴含着prompt的力量,以及到目前为止我们最重要的见解之一:

见解:通过智能的应用prompt,我们可以强制llm避免常见的陷阱,通过明确和有目的的变成使其以特定的方式运行。

Another interesting point about this chain is that it not only runs an input through the llm but it later compiles Python code. Let's see exactly how this works.

这个Chain另外一个有意思的点是,它不仅可以通过llm来运行input,还会编译Python代码。让我们看看这到底是如何运行的。

print(inspect.getsource(llm_math._call))
    def _call(self, inputs: Dict[str, str]) -> Dict[str, str]:
        llm_executor = LLMChain(prompt=self.prompt, llm=self.llm)
        python_executor = PythonREPL()
        self.callback_manager.on_text(inputs[self.input_key], verbose=self.verbose)
        t = llm_executor.predict(question=inputs[self.input_key], stop=["```output"])
        self.callback_manager.on_text(t, color="green", verbose=self.verbose)
        t = t.strip()
        if t.startswith("```python"):
            code = t[9:-4]
            output = python_executor.run(code)
            self.callback_manager.on_text("\nAnswer: ", verbose=self.verbose)
            self.callback_manager.on_text(output, color="yellow", verbose=self.verbose)
            answer = "Answer: " + output
        elif t.startswith("Answer:"):
            answer = t
        else:
            raise ValueError(f"unknown format from LLM: {t}")
        return {self.output_key: answer}

So we can see here that if the llm returns Python code we will compile it with a Python REPL* simulator. We now have the full picture of the chain: either the llm returns an answer (for simple math problems) or it returns Python code which we compile for an exact answer to harder problems. Smart!

我们从这里可以发现,如果llm返回Python代码,我们将使用Python Repl * simulator来编译。现在我们已经对整个chain有了完整的了解:llm要么返回答案(对于简单的数据问题),要么返回编译之后能得到准确答案的python code(对于比较难的问题)。很智能!

The list continues to grow as langchain becomes more and more flexible and powerful so we encourage you to check it out and tinker with the example notebooks that you might find interesting.

Utility chain通常遵循相同的基本结构:有一个prompt,约束llm从给定的query返回特定类型的response。我们可以要求llm来来执行SQL查询、API调用,以及甚至实时创建一个BASH命令

随着LangChain越来越灵活和强大,这个列表不断增长。我们鼓励大家来查看并尝试一些你可能觉得有趣的示例notebooks。

*A Python REPL (Read-Eval-Print Loop) :是一个交互式的shelll,用来一行一行执行python代码。

Generic Chains

LangChain仅仅有三种Generic Chain,我们将在一个case中体现这三种的使用。

假设我们有处理脏输入的经验。具体来说,llms是通过token数量来收费的,我们并不乐意为了额外的输入来付费;而且这也不是很整洁。

首先,我们将构建一个自定义转换函数来清理文本的空格。接着,我们将使用这个function构建一个chain,在其中输入我们的文本,并期望输出一个干净的文本。

def transform_func(inputs: dict) -> dict:
    text = inputs["text"]
    
    # replace multiple new lines and multiple spaces with a single one
    text = re.sub(r'(\r\n|\r|\n){2,}', r'\n', text)
    text = re.sub(r'[ \t]+', ' ', text)

    return {"output_text": text}

重要的是,当我们初始化chain时,我们并没有将LLM作为参数。可以想象,没有LLM会使chain的能力比之前的例子弱很多。然而,正如我们接下来即将看到的,将这个chain和其他chain结合会给出非常理想的结果。

clean_extra_spaces_chain = TransformChain(input_variables=["text"], output_variables=["output_text"], transform=transform_func)
clean_extra_spaces_chain.run('A random text  with   some irregular spacing.\n\n\n     Another one   here as well.')
'A random text with some irregular spacing.\n Another one here as well.'

很棒!现在事情变得有趣起来了。

假设我们想要使用chain来清理输入,然后以特定的风格改写输入,比如一个诗人、或者一个警察。正如我们现在所知,“TransformChain”不适用大语言模型,所以这种风格处理必须在其他地方完成。这就是我们的“LLMChain”发挥作用的地方。我们以及经了解这个chain,也知道通过巧妙的prompt可以做出很棒的事情,所以让我们试试看!

首先,我们将构建prompt模版:

template = """Paraphrase this text:

{output_text}

In the style of a {style}.

Paraphrase: """
prompt = PromptTemplate(input_variables=["style", "output_text"], template=template)

接着,初始化chain

style_paraphrase_chain = LLMChain(llm=llm, prompt=prompt, output_key='final_output')

注意:template中的输入被称为output_text;因为我们想要将TransformChain的输出传递给LLMChain

最后,我们需要将这些在一个聚合chain中联合起来。为此,我们需要使用SequentialChain,这个就是我们的第三个Generic Chain

sequential_chain = SequentialChain(chains=[clean_extra_spaces_chain, style_paraphrase_chain], input_variables=['text', 'style'], output_variables=['final_output'])

我们的输入是LangChain文档,描述chain但是里面有一些无用的空格。

input_text = """
Chains allow us to combine multiple 


components together to create a single, coherent application. 

For example, we can create a chain that takes user input,       format it with a PromptTemplate, 

and then passes the formatted response to an LLM. We can build more complex chains by combining     multiple chains together, or by 


combining chains with other components.
"""

所有工作已就绪。创造奇迹的时刻到了!

count_tokens(sequential_chain, {'text': input_text, 'style': 'a 90s rapper'})
Spent a total of 163 tokens
"\nChains let us link up multiple pieces to make one dope app. Like, we can take user input, style it up with a PromptTemplate, then pass it to an LLM. We can get even more creative by combining multiple chains or mixin' chains with other components."

LangChain-hub说明:

LangChain-hub是LangChain的姐妹库,在这里chain、agent,以及prompt都是被序列化好的。

from langchain.chains import load_chain

从LangChain Hub加载与从已经加载的repository中查找chain、使用相对路径使用load_chain一样简单。我们还有load_prompt和initialize_agent,稍后在做详细的介绍。让我们看看如何围绕LLMMathChain进行这些操作:

llm_math_chain = load_chain('lc://chains/llm-math/chain.json')

如何设置配置参数?可以在load之后进行简单的复写:

llm_math_chain.verbose
True
llm_math_chain.verbose = False
llm_math_chain.verbose
False

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值