LangChain学习笔记 | 豆包MarsCode AI刷题

285 阅读8分钟

LangChain 链

如果想开发更复杂的应用程序,就需要通过 Chain 来连接 LangChain 的各个组件。

比如, LLMChain 能够接受用户输入,使用 PromptTemplate 进行格式化,然后将格式化的响应传递给 LLM,相当于把整个 Model I/O 的流程封装到链里。实现了链的具体功能之后,能够将多个链连接在一起,构建更复杂的链

LLMChain

不调用链的话,如果想让大模型告诉我某种花的花语,需要创建模板,创建提示,创建并调用模型,返回结果

如果使用链的话,能够将提示模板的构建和模型的调用封装在一起,代码更加简洁

 # 原始字符串模板
 template = "{flower}的花语是?"
 # 创建模型实例
 llm = ChatOpenAI(temperature=0, model=os.environ.get("LLM_MODELEND"))
 # 创建LLMChain
 llm_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(template))
 # 调用LLMChain,返回结果
 result = llm_chain("玫瑰")
 print(result)

但是运行课程中给出的代码的时候,会提示 LLMChain 已经被弃用了

 The class `LLMChain` was deprecated in LangChain 0.1.17 and will be removed in 1.0. Use :meth:`~RunnableSequence, e.g., `prompt | llm`` instead.
   llm_chain = LLMChain(llm=llm, prompt=PromptTemplate.from_template(template))

也就是说,代码最好是换成下面这样

 import os
 from dotenv import load_dotenv
 load_dotenv()
 ​
 # 导入所需的库
 from langchain_core.prompts import PromptTemplate
 from langchain_core.output_parsers import StrOutputParser
 from langchain_openai import ChatOpenAI
 ​
 # 原始字符串模板
 template = "{flower}的花语是?"
 prompt = PromptTemplate.from_template(template)
 # 创建模型实例
 llm = ChatOpenAI(temperature=0, model=os.environ.get("LLM_MODELEND"))
 ​
 # 使用管道操作符 '|' 创建一个 RunnableSequence
 chain = prompt | llm | StrOutputParser()
 ​
 result = chain.invoke("玫瑰")
 print(result)

SequentialChain

可以使用 Sequential Chain 把几个 LLMChain 串起来,形成一个顺序链。但是 LLMChain 已经弃用了,使用管道操作符的话我暂时找到能够适应 Sequential Chain的方法,所以只能使用 LLMChain 了

我们的目标是这样的:

  • 第一步,我们假设大模型是一个植物学家,让他给出某种特定鲜花的知识和介绍。
  • 第二步,我们假设大模型是一个鲜花评论者,让他参考上面植物学家的文字输出,对鲜花进行评论。
  • 第三步,我们假设大模型是易速鲜花的社交媒体运营经理,让他参考上面植物学家和鲜花评论者的文字输出,来写一篇鲜花运营文案。

我们需要创建三个 LLMChain 来分别实现这三个部分,最后使用Sequential Chain连接起来

 # 这是总的链,我们按顺序运行这三个链
 overall_chain = SequentialChain(
     chains=[introduction_chain, review_chain, social_post_chain],
     input_variables=["name", "color"],
     output_variables=["introduction","review","social_post_text"],
     verbose=True)
 ​
 # 运行链,并打印结果
 result = overall_chain({"name":"玫瑰", "color": "黑色"})
 print(result)

verbose参数的意思是会打印一些链执行的额外日志

RouterChain

RouterChain,也叫路由链,能动态选择用于给定输入的下一个链。可以根据用户的问题内容,首先使用路由链确定问题更适合哪个处理模板,然后将问题发送到该处理模板进行回答。如果问题不适合任何已定义的处理模板,它会被发送到默认链。

任务

假设聊天机器人通常会接到两类问题,鲜花养护、鲜花装饰。

需求是,如果接到的是第一类问题,需要给机器人A提示,反之给出B提示

框架

在这个例子中,会使用 LLMRouterChain 和 MultiPromptChain 组合实现路由功能。该 MultiPromptChain 会调用 LLMRouterChain 选择与给定问题最相关的提示,然后使用该提示回答问题。

步骤如下:

  1. 构建处理模板:为鲜花护理和鲜花装饰分别定义两个字符串模板。
  2. 提示信息:使用一个列表来组织和存储这两个处理模板的关键信息,如模板的键、描述和实际内容。
  3. 初始化语言模型:导入并实例化语言模型。
  4. 构建目标链:根据提示信息中的每个模板构建了对应的LLMChain,并存储在一个字典中。
  5. 构建LLM路由链:这是决策的核心部分。首先,它根据提示信息构建了一个路由模板,然后使用这个模板创建了一个LLMRouterChain。
  6. 构建默认链:如果输入不适合任何已定义的处理模板,这个默认链会被触发。
  7. 构建多提示链:使用MultiPromptChain将LLM路由链、目标链和默认链组合在一起,形成一个完整的决策系统。
实现

前面4步此处直接给出部分代码

 # 构建两个场景的模板
 flower_care_template = """
 你是一个经验丰富的园丁,擅长解答关于养花育花的问题。
 下面是需要你来回答的问题:
 {input}
 """
 ​
 flower_deco_template = """
 你是一位网红插花大师,擅长解答关于鲜花装饰的问题。
 下面是需要你来回答的问题:
 {input}
 """
 ​
 # 构建提示信息
 prompt_infos = [
     {
         "key": "flower_care",
         "description": "适合回答关于鲜花护理的问题",
         "template": flower_care_template,
     },
     {
         "key": "flower_decoration",
         "description": "适合回答关于鲜花装饰的问题",
         "template": flower_deco_template,
     },
 ]
 ​
 # 初始化语言模型 略
 ​
 # 构建目标链
 from langchain.chains import LLMChain
 from langchain.prompts import PromptTemplate
 ​
 chain_map = {}
 ​
 for info in prompt_infos:
     prompt = PromptTemplate(template=info["template"], input_variables=["input"])
 ​
     chain = LLMChain(llm=llm, prompt=prompt, verbose=True)
     chain_map[info["key"]] = chain

来到构建LLM路由链

 from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
 from langchain.chains.router.multi_prompt_prompt import (
     MULTI_PROMPT_ROUTER_TEMPLATE as RounterTemplate,
 )
 ​
 destinations = [f"{p['key']}: {p['description']}" for p in prompt_infos]
 router_template = RounterTemplate.format(destinations="\n".join(destinations))
 print("路由模板:\n", router_template)
 ​
 router_prompt = PromptTemplate(
     template=router_template,
     input_variables=["input"],
     output_parser=RouterOutputParser(),
 )
 print("路由提示:\n", router_prompt)
 ​
 router_chain = LLMRouterChain.from_llm(llm, router_prompt, verbose=True)

然后我们就能够得到 路由模板 和 路由提示:

 路由模板:
  Given a raw text input to a language model select the model prompt best suited for the input. You will be given the names of the available prompts and a description of what the prompt is best suited for. You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.
 ​
 << FORMATTING >>
 Return a markdown code snippet with a JSON object formatted to look like:
 ```json
 {{
     "destination": string \ name of the prompt to use or "DEFAULT"
     "next_inputs": string \ a potentially modified version of the original input
 }}
 ```
 ​
 REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT" if the input is not well suited for any of the candidate prompts.
 REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed.
 ​
 << CANDIDATE PROMPTS >>
 flower_care: 适合回答关于鲜花护理的问题
 flower_decoration: 适合回答关于鲜花装饰的问题
 ​
 << INPUT >>
 {input}
 ​
 << OUTPUT (must include ```json at the start of the response) >>
 << OUTPUT (must end with ```) >>
 路由提示:
 input_variables=['input'] 
 input_types={}
 output_parser=RouterOutputParser() 
 partial_variables={} 
 template='上面的路由模板,篇幅原因不再重复'

路由模板的解释

引言

Given a raw text input to a language model select the model prompt best suited for the input.

这是一个简单的引导语句,告诉模型你将给它一个输入,它需要根据这个输入选择最适合的模型提示。

You will be given the names of the available prompts and a description of what the prompt is best suited for.

这里进一步提醒模型,它将获得各种模型提示的名称和描述。

You may also revise the original input if you think that revising it will ultimately lead to a better response from the language model.

这是一个可选的步骤,告诉模型它可以更改原始输入以获得更好的响应

格式说明(FORMATTING)

  1. 首先指导模型要格式化其输出,返回一个JSON格式的结果,并且给出了示例
  2. 给出了一些额外的说明和要求,也就是两个 REMEMBER 。提醒模型 destination 字段的值必须是下面列出的提示之一或是 DEFAULT 。并且再次强调,除非模型认为有必要,否则原始输入不需要修改。

候选提示(CANDIDATE PROMPTS)

列出了两个示例模型提示及其描述:

  • “flower_care: 适合回答关于鲜花护理的问题”,适合处理与花卉护理相关的问题。
  • “flower_decoration: 适合回答关于鲜花装饰的问题”,适合处理与花卉装饰相关的问题。

如果路由链没有找到合适的链的话,需要转入默认链进行处理,因此还需要准备一个默认链

 from langchain.chains import ConversationChain
 ​
 default_chain = ConversationChain(llm=llm, output_key="text", verbose=True)

最后,构建多提示链 MultiPromptChain 把前几个链整合在一起实现路由功能。里面主要有三个关键参数:

  • router_chain(类型RouterChain):这是用于决定目标链和其输入的链。当给定某个输入时,这个 router_chain 决定哪一个 destination_chain 应该被选中,以及传给它的具体输入是什么。
  • destination_chains(类型Mapping[str, LLMChain]):这是一个映射,将名称映射到可以将输入路由到的候选链。例如,你可能有多种处理文本输入的方法(或“链”),每种方法针对特定类型的问题。destination_chains 可以是这样一个字典:{'weather': weather_chain, 'news': news_chain}。在这里,weather_chain可能专门处理与天气相关的问题,而news_chain处理与新闻相关的问题。
  • default_chain(类型LLMChain):当 router_chain 无法将输入映射到 destination_chains 中的任何一个链时,LLMChain 将使用此默认链。这是一个备选方案,确保即使路由器不能决定正确的链,也总有一个链可以处理输入。

它的工作流程如下:

  1. 输入首先传递给 router_chain
  2. router_chain 根据某些标准或逻辑决定应该使用哪一个 destination_chain
  3. 输入随后被路由到选定的 destination_chain ,该链进行处理并返回结果。
  4. 如果 router_chain 不能决定正确的 destination_chain ,则输入会被传递给 default_chain