LangChain基础入门[2] 模块拆解

2023-11-24 14:55:23 浏览数 (2)

前言

本章节所涉及的内容是LangChain模块拆解之旅,相比较来说该模块也是重要的一节,在之前的章节中我们已经通过简单的demo初步感受到了大型语言模型模块的魅力,也就是LLM模块。本次章节主要的内容是把大型语言模型进行一个抽象,从而使得与模型的交互变得更加方便。

但是想要与模型进行交互我们就要有一套语言体系,这种语言体系我们称之为与语言模型交互的编程方式。这个语言提示就是我们常听到的prompt,也可以叫做提示。所谓提示其实就是给模型一些限定。比如我们常用的GPT中会在会在对话之前设置它的角色北京,比如:可以设置角色背景是酒吧的店员,当你向它提问需要一份菜单的时候,它就会给你提供一份酒水菜单。如果你设置的角色背景是水果超市的店员,那么它给你提供的便是水果的菜单。

总之:

  • 精确的提示:可以产生理想的结果;
  • 冗余的提示:可以产生稳定的结果;

image.png

Prompt(提示)

如何使用提示模板

提示模板可以包括:

  • 对语言模型的指令(如说我们想让他告诉我们某一个明星的生日)
  • 提供一些简单示例给语言模型从而使模型输出更接近理想结果(可以提供一些简单的事例给语言模型从而使语言模型呢得出更接近理想的结果)
  • 提给语言模型问题
  • ...

需要留意的是提示模板的核心其实还是提示词,至于提示词包括的东西就多了。提示词的使用方法也是日新月异。现在甚至有一个专门的职业叫做提示工程师。他们的薪水也是非常可观的。所以里面可以研究的东西还是不少的而LangChai正好也给我们提供了研究他们的特别好的一个工具,和编程语言里的函数一样,提示模板它并不是只能接受一个参数你可以让它不接受任何的参数,那么这个时候这个模板实际上就退化成了提示词本身,你也可以让它只接受一个参数,你可以让它接受多个参数,

下面我们来看几个提示模板的例子

代码语言:javascript复制
# 首先先加载环境变量
%load_ext dotenv
%dotenv
from langchain import PromptTemplate

# 一个没有参数的提示模板
no_input_prompt = PromptTemplate(input_variables=[], template="给我讲个故事")
no_input_prompt.format()




# 只有一个参数的提示模板
one_input_prompt = PromptTemplate(input_variables=["什么样儿"], template="给我讲个{什么样儿}的故事")
one_input_prompt.format(什么样儿="暴发户")




# 多个参数的提示模板
multiple_input_prompt = PromptTemplate(
    input_variables=["什么样儿", "啥"],
    template="给我讲个关于{啥}的{什么样儿}的故事"
)
multiple_input_prompt.format(什么样儿="讽刺且好笑", 啥="罗刹海市")




# 自定义模板
template = "给我讲个关于{啥}的{什么样儿}的故事"
prompt_template = PromptTemplate.from_template(template)
prompt_template.input_variables

如图:

需要注意的是有时候我们可能没有办法一次性的提供所有的参数,那么这个时候我们可以只提供一部分的参数,然后用这一部分的参数和我们的原始模板结合在一起就得到了一个新的模板,这个新的模板就是只接受了部分参数的提示模板。

例如:

我们可以想象一下:

场景一

我们创建了一个LangChain的应用,这个应用是让用户输入两个东西第一个是一个城市的名字、第二个是一个明星的名字,然后应用的功能就是返回这个城市在明星出生的那一年的平均气温。我知道这个例子可能有点无厘头,但是这就是一个例子。那么接下来你可能会有一个原始的提示模板,就像这样:

它接收两个参数:一个叫城市一个叫年份,但是假设用户只知道了城市明星的名字,我们没有年份怎么办呢?我们可以先把城市给它填进去,城市填进去之后我们不就有一个新的模板了吗。这个模板它就只接受一个参数,就是年份。那么我们可以用某一个其他的比较八卦的一个大语言模型。让它去帮我们找这个明星出生是哪一年,然后把这个年份填进去。这样的话我们就可以得到一个要发给LLM模块的一个最终的完整的提示词了。

场景二

假设我们已经可以根据特定的某种方法获取自己想要的参数,比如:我们在上面原有的基础上加入参数日期,这个参数日期的获取方式可以通过我们已经定义好的一个函数来获取,比如:_get_cur_date

如图:

这样提示模板就变成了:告诉我{城市}在{年份}年{日期}的平均气温。

之前我们可以提供明星的名字,已经可以通过其它的大语言模型获取明星出生的年份,然后再通过已有的函数_get_cur_date()来获取当前的日期。至此,一个完整的提示词模板就形成了。

接下来,我们直接代码实现:

代码语言:javascript复制
from langchain.prompts import PromptTemplate

prompt = PromptTemplate(template="告诉我{城市}在{年份}年{日期}的平均气温", input_variables=["城市", "年份", "日期"])
partial_prompt = prompt.partial(城市="武汉")
print(partial_prompt.format(年份="1998", 日期="12月12日"))







from datetime import datetime

def _get_cur_date():
    now = datetime.now()
    return now.strftime("%m/%d/%Y, %H:%M:%S")

prompt = PromptTemplate(template="告訴我{城市}在{年份}年{日期}的平均氣溫", input_variables=["城市", "年份", "日期"])
partial_prompt = prompt.partial(城市="武漢", 日期=_get_cur_date)
print(partial_prompt.format(年份="1998"))

代码图示:

image.png

提供示例给语言模型从而实现逻辑思维

  • few shot:可以通过在提示词中给出一些简单的实例来规范语言模型的功能,其实这种提示的方式有一个它自己的名字叫做少样本学习,英文是few shot
  • fine-tuning:与few shot的区别是,它会直接去影响模型本身。使用大量的例子去调整模型本身的参数,那么它这样得到的结果是一个调教之后的新模型和老模型就不一样了。新模型实际已经具备了一个新的功能,新的功能就是通过fine-tuning调教得来的。

few shot可以通过少量的样本/案例,让语言模型学会处理某类特定的问题,或者是一某种特定的方式来处理一些问题。我们可以理解为给元模型的一种思维惯性,让它按照我们给定的一种模式或者是套路来完成我们的任务。

代码示例
代码语言:javascript复制
# 样本模板,
from langchain.prompts.few_shot import FewShotPromptTemplate
from langchain.prompts.prompt import PromptTemplate
examples = [
    {
        "question": "你好吗?",
        "answer": "帅哥,我很好"
    },
    {
        "question": "今天周几?",
        "answer": "帅哥,今天周日"
    },
    {
        "question": "天气好吗?",
        "answer": "帅哥,是的,今天天气不错"
    },
]

example_prompt = PromptTemplate(input_variables=["question", "answer"], template="Question:{question}n{answer}")
prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    suffix="Question: {input}",
    input_variables=["input"]
)
print(prompt.format(input="我怎么这么丑?"))
代码语言:javascript复制
# 将上面的样本模板全部喂给我们的大语言模型
from langchain.llms import OpenAI

llm = OpenAI()

llm.predict(prompt.format(input="我怎么这么丑?"))

如图:

样本筛选器筛选文本

当你在使用few shot给的样本特别多的时候,你可以使用样本筛选器来帮助你筛选最合适的样本。它筛选出来的是一个子集,然后这个子集会作为你的最终提示词里面用到的样本。

使用场景:

当你的样本数量特别多,但是并没有必要把所有的样本全部喂给大型语言模型来回答一个相对较小的问题,这样可避免浪费资源,同时这样的样本筛选出来它的调教效果对于当前的问题可能是更好的。由于其它不相关的样本使用筛选器筛选掉的话,同时也可以帮你缩短发送给大型语言模型的提示词的长度。就目前来讲,提示词的长度越长,所花费的钱也就越多。所以使用样本筛选器实际上也是在变相省钱。

源码

langchain-model-2.ipynblangchain-modle.ipynb

大型语言模型的封装-LLM模块

LLM模块是LangChain里面最重要最核心的模块,但同时它也是最简单的模块。它的功能全部概括起来就是和大型语言模型进行交互。

  • LangChain不提供现成的大型语言模型
  • LangChain只针对不同语言模型的标准化接口

LLM模块基本用法

直接调用

功能类似与调用predict(),让LLM模块根据输入续写文本

批量生成
  • generate()
  • 输入:文本的列表
  • 输出:文本的列表
使用样例
代码语言:javascript复制
# 首先先加载环境变量
%load_ext dotenv
%dotenv



from langchain.llms import OpenAI

llm = OpenAI()




# 功能和predict() 一样
llm("给我讲一个笑话")


# 批量调用
generate_res = llm.generate(["给我讲一个笑话", "给我讲一个悲伤的故事"])


generate_res



generate_res.generations[0]



# generate_res.generations[0][0].text
generate_res.generations[1][0].text
示例代码源码

langchain-llm-demo.ipynb

自定义LLM模块

LangChain中的自定义LLM模块主要有以下几种使用场景

  • 用于封装LangChain尚未支持的大型语言模型
  • 也可以用来模拟测试
  • 可以用来定义该LLM模块被调用的时候,如何根据输入的文本来输出
使用样例
代码语言:javascript复制
from typing import Any, List, Mapping, Optional
from langchain.llms.base import LLM
from langchain.callbacks.manager import CallbackManagerForLLMRun


class 冷淡AI(LLM):
    
    @property
    def _llm_type(self) -> str:
        return "冷淡AI"
    
    def _call(
            self,
            prompt: str,
            stop: Optional[List[str]] = None,
            run_manager: Optional[CallbackManagerForLLMRun] = None,
    ) -> str:
        if stop is not None:
            raise ValueError("stop kwargs are not permitted.")
        pd = prompt.find("吗")
        if pd >= 0:
            return prompt[0:pd]   "。"
        return "哦。"



llm = 冷淡AI()
print(llm)



llm("你好吗?")



llm("吃了吗?")




llm("你为什么对我这么冷淡?")

如图:

示例源码

langchain-llm-custom.ipynb

至此,本次入门的教程到此结束了,感谢你的阅读。希望对你的学习有所帮助

往期精彩内容推荐

  • LangChain基础入门 第一课

0 人点赞