AI系统性学习—LangChain入门

文章目录

  • 1、LangChain入门
    • 1.1 简介
    • 1.2 架构
    • 1.3 核心概念
    • 1.2 快速入门
    • 1.3 安装
  • 2、LangChain Prompt Template
    • 2.1 什么是提示词模版
    • 2.1 创建一个提示词模版
    • 2.2 聊天消息提示词模版
    • 2.3 模版追加示例
  • 3、语言模型
    • 3.1 LLM基础模型
    • 3.2 LangChain聊天模型
    • 3.3 自定义模型
    • 3.4 输出解析器
      • 3.4.1 Pydantic解析器

1、LangChain入门

1.1 简介

LangChain是一个开源的Python库,它提供了构建基于大模型的AI应用所需的模块和工具。通过LangChain,开发者轻松地与大语言模型(LLM)集成,完成文本生成、问答、翻译、对话等任务。LangChain降低了AI应用开发的门槛,让任何人都可以基于LLM构建属于自己的创建应用。LangChain特性如下:

  1. LLM和Prompt:LangChain对所有LLM大模型进行了API抽象,统一了大模型访问API,同时提供了Prompt提示模型管理机制。
  2. Chain:这些是对LLM或其他使用程序的调用序列。LangChain为链提供了标准接口,与各种工具集成,为流行应用提供了端到端的链。
  3. 数据增强:LangChain使链能够与外部数据源交互以收集生成步骤的数据。例如,它可以帮助总结长文本或使用特定数据源回答问题。
  4. Agents:Agents让LLM做出有关行动的决定,采取这些行动,检查结果,并继续前进直到工作完成。LangChain提供了代理的标准接口,多种代理可以选择,以及端到端的代理示例。
  5. 评估:很难用传统指标评估生成模型。这就是为什么LangChain提供提示和链来帮助开发者自己使用LLM评估他们的模型。

1.2 架构

在这里插入图片描述
LangChain提供一套Pormpt提示词模版管理工具,负责处理提示词,然后传递给大模型处理,最后处理大模型返回的结果,目前对大模型的封装主要包括LLM和Chat Model两种类型:

  • LLM - 问答模型,模型接受一个文本输入,然后返回一个文本结果。
  • Chat Model - 接收一组对话消息,然后返回对话消息,类似聊天信息一样。

1.3 核心概念

  • Components and Chains
    在LangChain中,Component是模块化构建块,可以组合起来创建强大的应用程序。Chain是组合在一起以完成特定任务的一些列Components(或其他Chains)。例如,一个Chain可能包括一个Prompt模版、一个语言模型和一个输出解析器,他们一起工作以处理用户输入、生成响应并处理输出。
  • Prompt Templates and Values
    Prompt Template负责创建PromptValue,这是最终传递给语言模型的内容。提示词模版有助于将用户输入和其他动态信息转换为适合语言模型的格式。PromptValue是具有方法的类,这些方法可以转换为每个模型类型期望的确切输入类型(如文本或聊天类型)。
  • Example Selectors
    当你想要在Prompts中动态包含示例时,Example Selectors很有用。他们接受用户输入并返回一个示例列表以在提示中使用,使其更强大和特定于上下文。
  • Output Parsers
    Output Parsers 负责将语言模型响应构建为更有用的格式。它们实现了两种主要方法:一种用于提供格式化指令,另一种用于将语言模型的响应解析为结构化格式。这使得在您的应用程序中处理输出数据变得更加容易。
  • Indexes and Retrievers
    Index 是一种组织文档的方式,使语言模型更容易与它们交互。检索器是用于获取相关文档并将它们与语言模型组合的接口。LangChain 提供了用于处理不同类型的索引和检索器的工具和功能,例如矢量数据库和文本拆分器。
  • Chat Message History
    LangChain 主要通过聊天界面与语言模型进行交互。ChatMessageHistory 类负责记住所有以前的聊天交互数据,然后可以将这些交互数据传递回模型、汇总或以其他方式组合。这有助于维护上下文并提高模型对对话的理解。
  • Agents and Toolkits
    Agent 是在 LangChain 中推动决策制定的实体。他们可以访问一套工具,并可以根据用户输入决定调用哪个工具。Tookits 是一组工具,当它们一起使用时,可以完成特定的任务。代理执行器负责使用适当的工具运行代理。

1.2 快速入门

  • 安装
pip install langchain
  • 环境设置
    设用openai提供的模型,注意设置openai的api key:
from langchain.llms import OpenAI

llm = OpenAI(openai_api_key="Replace By Your Key")
  • 构建应用程序
    基于Langchain实现聊天
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI
from langchain.prompts.chat import (
    ChatPromptTemplate,
    SystemMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.chains import LLMChain

# 基于Langchain实现聊天

# 定义模型:思考如果是其他的模型怎么引入?
chat = ChatOpenAI(openai_api_key="Replace By Your Key",temperature=0)

# 定义模版
template = "Your are helpful assistant that translates {input_language} to {output_language}"
system_message_prompt = SystemMessagePromptTemplate.from_template(template)
human_template = "{text}"
human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt,human_message_prompt])

# 创建链条
chain = LLMChain(llm=chat,prompt=chat_prompt)
r = chain.run(input_language="English",output_language="French",text="i love programming!")
print(r)

代理(Agent)

为了处理复杂的工作流程, 我需要执行一序列操作用于增强LLM(大模型)的能力。代理(Agents)简单的理解就是代替模型执行一些操作,大模型作为大脑用于决策,由大模型决定执行什么操作或者操作的顺序。代理可以访问工具,工具指的是封装好的一些操作/处理逻辑等,通过代理运行工具并观察输出, 直到它们得出最终答案。要加载一个代理,您需要选择一个:

  • LLM/聊天模型: 驱动代理的语言模型。
  • 工具: 执行特定任务的函数。这可以是以下事项:谷歌搜索、数据库查找、Python REPL、其他链。
    代理名称: 用于引用代理类。

对于此示例,我们将使用SerpAPI查询搜索引擎。

安装SerpAPI Python包:

from langchain.agents import AgentType,initialize_agent,load_tools
from langchain.llms import OpenAI

# 使用openai大模型
llm = OpenAI(openai_api_key="Replace By Your Key",temperature=0)

# 加载工具类
tools = load_tools(["serpapi","llm-math"],llm=llm)

# 通过工具类初始化代理
agent =  initialize_agent(tools=tools,llm=llm,agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)

# 向AI进行提问
agent.run("What was the high temperature in SF yesterday in Fahrenheit? What is that number raised to the .023 power?")

记忆(Memory)
到目前为止,我们看到的链和代理都是无状态的,但是对于许多应用来说,参考过去的交互非常必要。这在聊天机器人中是十分明显的,您会希望它根据过去的消息来理解新的消息。

目前所有大模型都存在有限的上下文限制,简单的说大模型记不住曾经说过话、做过的事情,LangChain通过memory模块帮助记忆历史信息。

Memory模块为您提供了一种维护应用程序状态的方式。基本的内存接口很简单:它允许您根据最新运行的输入和输出更新状态,并允许您使用存储的状态修改(或上下文化)下一个输入。

有许多内置的memory系统。其中最简单的就是缓冲内存,它只是将最近的几个输入/输出追加到当前输入中 —— 我们将在下面的示例中使用它。

from langchain import OpenAI, ConversationChain

# model_provider = OpenAIModelProvider(model_name="text-davinci-004")
llm = OpenAI(openai_api_key="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",
             temperature=0,)

conversation = ConversationChain(llm=llm,verbose=True)
# 第一运行
# r = conversation.run("你好")
# print(r)

# 第二次运行
r = conversation.run("我刚才说的是什么?")
print(r)

1.3 安装

pip install langchain

2、LangChain Prompt Template

语言模型以文本作为输入 - 这个文本通常被称为提示词(prompt)。在开发过程中,对于提示词通常不能直接硬编码,不利于提示词管理,而是通过提示词模板进行维护,类似开发过程中遇到的短信模板、邮件模板等等。

2.1 什么是提示词模版

提示词模板本质上跟平时大家使用的邮件模板、短信模板没什么区别,就是一个字符串模板,模板可以包含一组模板参数,通过模板参数值可以替换模板对应的参数。

一个提示词模版可以包含下面内容:

  • 发给大语言模型的指令
  • 一组问答示例,以提醒LLM以什么样的风格返回请求
  • 发给语言模型的问题

以下是一个提示词模版例子:

from langchain import PromptTemplate

# 提示词模板,内嵌了一个变量product
template = """\
You are a naming consultant for new companies.
What is a good name for a company that makes {product}?
"""

prompt = PromptTemplate.from_template(template)
# 根据变量渲染模板
prompt.format(product="colorful socks")

2.1 创建一个提示词模版

from langchain import PromptTemplate

# 没有输入变量的示例提示
no_input_prompt = PromptTemplate(input_variables=[], template="告诉我一个笑话。")
no_input_prompt.format()
# -> "告诉我一个笑话。"

# 一个输入变量的示例提示
one_input_prompt = PromptTemplate(input_variables=["adjective"], template="告诉我一个{adjective}的笑话。")
one_input_prompt.format(adjective="好笑的")
# -> "告诉我一个好笑的笑话。"

# 多个输入变量的示例提示
multiple_input_prompt = PromptTemplate(
    input_variables=["adjective", "content"],
    template="告诉我一个{adjective}关于{content}的笑话。"
)
multiple_input_prompt.format(adjective="好笑的", content="鸡")
# -> "告诉我一个好笑的关于鸡的笑话。"

也可以类似前面的模型加载方式:

template = "告诉我一个{adjective}的笑话,和{content}相关的"

# 通过from_template创建提示词模板
prompt_template = PromptTemplate.from_template(template)
# 打印模板参数,from_template会自动根据模板内容推断出具体关联的模板参数,不需要手动定义
prompt_template.input_variables
# -> ['adjective', 'content']
prompt_template.format(adjective="好笑的", content="鸡")
# -> 告诉我一个好笑的笑话,和鸡相关的。

2.2 聊天消息提示词模版

聊天模型(Chat Model)以聊天消息列表作为输入,这个聊天消息列表的消息内容也可以通过提示词模板进行管理。这些聊天消息与原始字符串不同,因为每个消息都与“角色(role)”相关联。

例如,在OpenAI的Chat Completion API中,聊天消息可以与AI、人类(human)或系统(system)角色相关联。Openai的聊天模型,给不同的聊天消息定义了三种角色类型分别是AI、人类(human)或系统(system)角色:

  • AI消息指的是当前消息是AI回答的内容
  • 人类(human)消息指的是你发给AI的内容
  • 系统(system)消息通常是用来给AI身份进行描述。
from langchain.prompts import (
    ChatPromptTemplate,
    PromptTemplate,
    SystemMessagePromptTemplate,
    AIMessagePromptTemplate,
    HumanMessagePromptTemplate,
)
from langchain.schema import (
    AIMessage,
    HumanMessage,
    SystemMessage
)

具体的使用方法就类似上面给出的例子

2.3 模版追加示例

提示词中包含交互样本的作用是为了帮助模型更好地理解用户的意图,从而更好地回答问题或执行任务。小样本提示模板是指使用一组少量的示例来指导模型处理新的输入。这些示例可以用来训练模型,以便模型可以更好地理解和回答类似的问题。

  • 创建数据集:
examples = [
    {
        "question":"谁的寿命更长,*·阿里还是艾伦·图灵?",
        "answer":
"""
这里需要跟进问题吗:是的。
跟进:*·阿里去世时多大?
中间答案:*·阿里去世时74岁。
跟进:艾伦·图灵去世时多大?
中间答案:艾伦·图灵去世时41岁。
所以最终答案是:*·阿里
"""
    },
    {
        "question": "craigslist的创始人是什么时候出生的?",
        "answer":
"""
这里需要跟进问题吗:是的。
跟进:craigslist的创始人是谁?
中间答案:craigslist由Craig Newmark创立。
跟进:Craig Newmark是什么时候出生的?
中间答案:Craig Newmark于1952年12月6日出生。
所以最终答案是:1952年12月6日
"""
    },
    {
        "question": "乔治·华盛顿的祖父母中的母亲是谁?",
        "answer":
"""
这里需要跟进问题吗:是的。
跟进:乔治·华盛顿的母亲是谁?
中间答案:乔治·华盛顿的母亲是Mary Ball Washington。
跟进:Mary Ball Washington的父亲是谁?
中间答案:Mary Ball Washington的父亲是Joseph Ball。
所以最终答案是:Joseph Ball
"""
    },
    {
        "question": "《大白鲨》和《皇家赌场》的导演都来自同一个国家吗?",
        "answer":
"""
这里需要跟进问题吗:是的。
跟进:《大白鲨》的导演是谁?
中间答案:《大白鲨》的导演是Steven Spielberg。
跟进:Steven Spielberg来自哪里?
中间答案:美国。
跟进:《皇家赌场》的导演是谁?
中间答案:《皇家赌场》的导演是Martin Campbell。
跟进:Martin Campbell来自哪里?
中间答案:新西兰。
所以最终答案是:不是
"""
    }
]
  • 通过promptTemplate对象,简单的在提示词模版中插入样例。
example_prompt = PromptTemplate(input_variables=["question", "answer"], template="问题:{question}\\n{answer}")

# 提取examples示例集合的一个示例的内容,用于格式化模板内容
print(example_prompt.format(**examples[0]))
  • 将示例和格式化程序提供给FewShotPromptTemplate
    通过FewShotPromptTemplate对象,批量插入示例内容。
# 接收examples示例数组参数,通过example_prompt提示词模板批量渲染示例内容
# suffix和input_variables参数用于在提示词模板最后追加内容
prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    suffix="问题:{input}",
    input_variables=["input"]
)

print(prompt.format(input="乔治·华盛顿的父亲是谁?"))
  • 使用示例选择器
    这里重用前一部分中的示例集和提示词模板。但是,不会将示例直接提供给FewShotPromptTemplate对象,把全部示例插入到提示词中,而是将它们提供给一个ExampleSelector对象,插入部分示例。
    这里我们使用SemanticSimilarityExampleSelector类。该类根据与输入的相似性选择小样本示例。它使用嵌入模型计算输入和小样本示例之间的相似性,然后使用向量数据库执行相似搜索,获取跟输入相似的示例
example_selector = SemanticSimilarityExampleSelector.from_examples(
    # 可供选择的示例列表
    examples,
    # 用于生成嵌入的嵌入类,该嵌入用于衡量语义相似性
    OpenAIEmbeddings(openai_api_key="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"),
    # 用于存储嵌入和执行相似性搜索的BectorStore类
    Chroma,
    # 生成的示例数
    k = 1
)

question = "乔治·华盛顿的父亲是谁?"
selected_examples = example_selector.select_examples({"question":question})
print(f"最相似的示例:{question}")
for example in selected_examples:
    print("\\n")
    for k, v in example.items():
        print(f"{k}{v}")

3、语言模型

3.1 LLM基础模型

大语言模型(LLMs)是LangChain的核心组件,LangChain本身不提供大语言模型能力,LangChain封装了各种常见的大语言模型,提供一套操作大语言模型的标准接口,方便开发者。目前LangChain封装不少大语言模型,也支持自定义模型,开发者可以自己封装新的模型接口。

目前开源大模型和商业大模型有很多,考虑到成本&模型能力的差异,大家会有不同的选择,同时也可能会经常换模型,但是换模型又不想改动太多业务代码,这个时候LangCain的价值就体现出来了。

  • LLM使用例子
    LangChain封装了很多大模型接口 (例如:OpenAI, Cohere, Hugging Face等等) - LLM 基类就是面向各类模型接口的统一抽象,里面提供了一些基础的接口设计。前面的例子都是基于openai的LLM的使用例子。

3.2 LangChain聊天模型

聊天模型是语言模型的一种变体。虽然聊天模型在底层使用的也是语言模型,但它们所公开的接口有些不同。它们不是通过 “输入文本,输出文本” API 公开接口,而是通过 “聊天消息” 作为输入和输出的接口,聊天模型的整个交互过程类似互相发送聊天消息的过程。

3.3 自定义模型

目前AI模型领域百家争鸣,LangChain官方也没有对接好所有模型,有时候你需要自定义模型,接入LangChain框架。

自定义 LLM 需要实现以下必要的内容:

  • _call方法:它需要接受一个字符串、一些可选的停用词,并返回一个字符串;
    它还可以实现第二个可选的内容:
  • _identifying_params 属性,用于帮助打印此类。应该返回一个字典。

实现一个非常简单的自定义LLM,它只是返回输入的前N个字符

from typing import Any, List, Mapping, Optional

from langchain.callbacks.manager import CallbackManagerForLLMRun
from langchain.llms.base import LLM


# 自定义模型类,实现基础的LLM接口
class CustomLLM(LLM):
    n: int

    @property
    def _llm_type(self) -> str:
                # 模型名称
        return "custom"
    # 在这里实现模型调用
    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.")
        return prompt[: self.n]

    @property
    def _identifying_params(self) -> Mapping[str, Any]:
        """返回模型信息,类似其他java的tostring函数,返回对象信息"""
        return {"n": self.n}


# 实例化模型
llm = CustomLLM(n=10)

# 调用模型
print(llm("This is a foobar thing"))

3.4 输出解析器

LLM语言模型输出内容是文本格式,但是开发AI应用的时候,我们希望能拿到的是格式化的内容,例如结果转成目标对象,数组等,方便程序处理。这就需要LangChain提供的输出解析器(Output parser)格式化模型返回的内容。

输出解析器作用是用于是格式化语言模型返回的结果。一个输出解析器必须实现两种必要的方法:

  • “get_format_instructions”: 返回一个字符串,其中包含要求语言模型应该返回什么格式内容的提示词。
  • “parse”: 将模型返回的内容,解析为目标格式。

3.4.1 Pydantic解析器

from langchain.prompts import PromptTemplate,ChatPromptTemplate,HumanMessagePromptTemplate
from langchain.llms import OpenAI
from langchain.chat_models import ChatOpenAI

from langchain.output_parsers import PydanticOutputParser
from pydantic import BaseModel,Field,validator
from typing import List

# 初始化大模型
model_name = "text-davinci-003"
temperature = 0
model = OpenAI(model_name=model_name,
               temperature=temperature,
               openai_api_key="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")

# 定义模型返回的数据结构
class Joke(BaseModel):
    setup: str = Field(description="这是一个笑话的问题")
    punchline: str = Field(description="回答解决笑话的问题")

    # 您可以使用 Pydantic 轻松添加自定义验证逻辑。
    @validator('setup')
    def question_ends_with_question_mark(self, field):
        if field[-1] != '?':
            raise ValueError("问题不正确!")
        return field


# 初始化解析器
parser = PydanticOutputParser(pydantic_object=Joke)

# 创建提示词模板,里面包含要求模型返回指定格式的提示词指令,返回格式提示词由parser.get_format_instructions(),解析器提供。
prompt = PromptTemplate(
    template="回答用户查询。\\n{format_instructions}\\n{query}\\n",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()}
)

# 提供一个查询参数,渲染提示词模板,获取最终的提示词。
joke_query = "讲个笑话。"
_input = prompt.format_prompt(query=joke_query)

# 调用模型
output = model(_input.to_string())

# 解析器解析后的结果,已经转换为前面定义的Joke对象
# Joke(setup='为什么鸡会穿越马路?', punchline='为了到达对面!')
上一篇:Redis数据迁移


下一篇:基于Java的厦门旅游电子商务预订系统(Vue.js+SpringBoot)-五、核心代码