LangChain实战5 | 豆包MarsCode AI刷题

48 阅读17分钟

如果要开发更加复杂的应用程序,就需要通过“Chain"来连接LangChain的各个组件和功能。将多个组件互相链接,组合成一个链的想法简单但很强大。简化了复杂应用程序的实现,并使之更加容易模块化,能够创建出单一的、连贯的应用程序,从而使调试、维护和改进应用程序变得容易。

链的使用:

  • 首先LangChain通过设计好的接口,实现一个具体的链的功能。
  • 实现链的具体功能之后,可以通过将多个链组合,或者将链和其他组件组合来构建更复杂的链。

链可以被视为LangChian中的一种基本功能单元。

image-20241124142150810

LLMChain

LLMChain是最简单的链,围绕着语言模型推理功能又添加了一些功能,整合了PromptTemplate、语言模型和Output Parser。相当于把Model I/O放在一个链中整体操作。它使用提示模板格式化输入,将格式化字符串传递给LLM,并返回LLM输出。

不使用链的代码:

#----第一步 创建提示
# 导入LangChain中的提示模板
from langchain import PromptTemplate
# 原始字符串模板
template = "{flower}的花语是?"
# 创建LangChain模板
prompt_temp = PromptTemplate.from_template(template) 
# 根据模板创建提示
prompt = prompt_temp.format(flower='玫瑰')
# 打印提示的内容
print(prompt)
​
#----第二步 创建并调用模型 
# 导入LangChain中的OpenAI模型接口
from langchain import OpenAI
# 创建模型实例
model = OpenAI(temperature=0)
# 传入提示,调用模型,返回结果
result = model(prompt)
print(result)

使用链:

# 导入所需的库
from langchain import PromptTemplate, OpenAI, LLMChain
# 原始字符串模板
template = "{flower}的花语是?"
# 创建模型实例
llm = OpenAI(temperature=0)
# 创建LLMChain
llm_chain = LLMChain(
    llm=llm,
    prompt=PromptTemplate.from_template(template))
# 调用LLMChain,返回结果
result = llm_chain("玫瑰")
print(result)

简而言之,LLMChain就是将不使用链的代码进行了封装,从而使得能够直接给出变量就能返回相应的结果。

链的调用方式

  1. 直接调用

    当上面所述的方式就是直接调用的链对象。当像函数一样调用一个对象时,实际上会调用该对象内部实现的__call__方法。

    如果提示模板包含多个变量,可以使用字典一次性输入。

    prompt = PromptTemplate(
        input_variables=["flower", "season"],
        template="{flower}在{season}的花语是?",
    )
    llm_chain = LLMChain(llm=llm, prompt=prompt)
    print(llm_chain({
        'flower': "玫瑰",
        'season': "夏季" }))
    
  2. run方法

    run方法也等价于直接调用__call__函数。

    llm_chain("玫瑰")
    

    等价于

    llm_chain.run("玫瑰")
    
  3. predict方法

    predict方法类似于run,只是输入键被指定为关键字参数而不是Python字典。

    result = llm_chain.predict(flower = "玫瑰")
    
  4. apply方法

    apply方法允许我们针对输入列表运行链,一次处理多个输入。

    # apply允许您针对输入列表运行链
    input_list = [
        {"flower": "玫瑰",'season': "夏季"},
        {"flower": "百合",'season': "春季"},
        {"flower": "郁金香",'season': "秋季"}
    ]
    result = llm_chain.apply(input_list)
    print(result)
    
    '''[{'text': '\n\n玫瑰在夏季的花语是“恋爱”、“热情”和“浪漫”。'}, 
    {'text': '\n\n百合在春季的花语是“爱情”和“友谊”。'},
     {'text': '\n\n郁金香在秋季的花语表达的是“热情”、“思念”、“爱恋”、“回忆”和“持久的爱”。'}]'''
  5. generate方法

    generate方法类似于apply,只不过返回一个LLMResult对象,而不是字符串。LLMResult通常包含模型生成文本过程中的一些信息,例如令牌数量、模型名称等。

    result = llm_chain.generate(input_list)
    
    generations=[[Generation(text='\n\n玫瑰在夏季的花语是“热情”、“爱情”和“幸福”。', 
    generation_info={'finish_reason': 'stop', 'logprobs': None})], 
    [Generation(text='\n\n春季的花语是爱情、幸福、美满、坚贞不渝。', 
    generation_info={'finish_reason': 'stop', 'logprobs': None})], 
    [Generation(text='\n\n秋季的花语是“思念”。银色的百合象征着“真爱”,而淡紫色的郁金香则象征着“思念”,因为它们在秋天里绽放的时候,犹如在思念着夏天的温暖。', 
    generation_info={'finish_reason': 'stop', 'logprobs': None})]] 
    llm_output={'token_usage': {'completion_tokens': 243, 'total_tokens': 301, 'prompt_tokens': 58}, 'model_name': 'gpt-3.5-turbo-instruct'} 
    run=[RunInfo(run_id=UUID('13058cca-881d-4b76-b0cf-0f9c831af6c4')), 
    RunInfo(run_id=UUID('7f38e33e-bab5-4d03-b77c-f50cd195affb')), 
    RunInfo(run_id=UUID('7a1e45fd-77ee-4133-aab0-431147186db8'))]
    

Sequential Chain

通过Sequential Chain可以将几个LLMChain串联起来,形成一个顺序链。

通过一个示例给出方法介绍:

  1. 假设大模型是一个植物学家,给出某种特定鲜花的知识和介绍。
  2. 假设大模型是一个鲜花评论者,参考植物学家的文字输出,给出评论。
  3. 假设大模型是系统的运营经理,参考1、2,写一篇鲜花文案。

导入所有的库

# 设置OpenAI API密钥
import os
os.environ["OPENAI_API_KEY"] = '你的OpenAI API Key'from langchain.llms import OpenAI
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.chains import SequentialChain

添加第一个LLMChain,生成鲜花的知识性说明

# 这是第一个LLMChain,用于生成鲜花的介绍,输入为花的名称和种类
llm = OpenAI(temperature=.7)
template = """
你是一个植物学家。给定花的名称和类型,你需要为这种花写一个200字左右的介绍。
​
花名: {name}
颜色: {color}
植物学家: 这是关于上述花的介绍:"""
prompt_template = PromptTemplate(input_variables=["name", "color"], template=template)
introduction_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="introduction")
​

添加第二个LLMChain,生成鲜花的评论

# 这是第二个LLMChain,用于根据鲜花的介绍写出鲜花的评论
llm = OpenAI(temperature=.7)
template = """
你是一位鲜花评论家。给定一种花的介绍,你需要为这种花写一篇200字左右的评论。
​
鲜花介绍:
{introduction}
花评人对上述花的评论:"""
prompt_template = PromptTemplate(input_variables=["introduction"], template=template)
review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="review")
​

添加第三个LLMChain,根据鲜花的介绍和评论写出一篇自媒体的文案。

# 这是第三个LLMChain,用于根据鲜花的介绍和评论写出一篇自媒体的文案
template = """
你是一家花店的社交媒体经理。给定一种花的介绍和评论,你需要为这种花写一篇社交媒体的帖子,300字左右。
​
鲜花介绍:
{introduction}
花评人对上述花的评论:
{review}
​
社交媒体帖子:
"""
prompt_template = PromptTemplate(input_variables=["introduction", "review"], template=template)
social_post_chain = LLMChain(llm=llm, prompt=prompt_template, output_key="social_post_text")
​

添加SequentialChain,把前面三个链串起来。

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

对于所有链的解释:

当运行 overall_chain 时,它会按照以下步骤执行:

  1. introduction_chain 接收输入变量 namecolor,并生成一个输出,该输出包含根据花的名称和颜色生成的鲜花介绍。这个输出被存储在 output_key="introduction" 指定的键下。
  2. review_chain 接收 introduction_chain 的输出(即鲜花介绍)作为输入,并生成一个输出,该输出包含根据鲜花介绍生成的鲜花评论。这个输出被存储在 output_key="review" 指定的键下。
  3. social_post_chain 接收 review_chain 的输出(即鲜花评论)作为输入,并生成一个输出,该输出包含根据鲜花介绍和评论生成的社交媒体帖子。这个输出被存储在 output_key="social_post_text" 指定的键下。

最终,overall_chain 的输出是一个包含所有三个链的输出的字典,其中每个输出都可以通过其相应的 output_key 访问。

最终输出:

> Entering new  chain...
​
> Finished chain.
{'name': '玫瑰', 'color': '黑色', 
'introduction': '\n\n黑色玫瑰,这是一种对传统玫瑰花的独特颠覆,它的出现挑战了我们对玫瑰颜色的固有认知。它的花瓣如煤炭般黑亮,反射出独特的微光,而花蕊则是金黄色的,宛如夜空中的一颗星,强烈的颜色对比营造出一种前所未有的视觉效果。在植物学中,黑色玫瑰的出现无疑提供了一种新的研究方向,对于我们理解花朵色彩形成的机制有着重要的科学价值。', 
'review': '\n\n黑色玫瑰,这不仅仅是一种花朵,更是一种完全颠覆传统的艺术表现形式。黑色的花瓣仿佛在诉说一种不可言喻的悲伤与神秘,而黄色的蕊瓣犹如漆黑夜空中的一抹亮色,给人带来无尽的想象。它将悲伤与欢乐,神秘与明亮完美地结合在一起,这是一种全新的视觉享受,也是一种对生活理解的深度表达。', 
'social_post_text': '\n欢迎来到我们的自媒体平台,今天,我们要向您展示的是我们的全新产品——黑色玫瑰。这不仅仅是一种花,这是一种对传统观念的挑战,一种视觉艺术的革新,更是一种生活态度的象征。
这种别样的玫瑰花,其黑色花瓣宛如漆黑夜空中闪烁的繁星,富有神秘的深度感,给人一种前所未有的视觉冲击力。这种黑色,它不是冷酷、不是绝望,而是充满着独特的魅力和力量。而位于黑色花瓣之中的金黄色花蕊,则犹如星星中的灵魂,默默闪烁,给人带来无尽的遐想,充满活力与生机。
黑色玫瑰的存在,不仅挑战了我们对于玫瑰传统颜色的认知,它更是一种生动的生命象征,象征着那些坚韧、独特、勇敢面对生活的人们。黑色的花瓣中透露出一种坚韧的力量,而金黄的花蕊则是生活中的希望,二者的结合恰好象征了生活中的喜怒哀乐,体现了人生的百态。'}

Router Chain

从下面的任务中引入Router Chain

假设智能客服通常会接触到两大类问题:

  1. 鲜花养护
  2. 鲜花装饰

需求是:接到第一类问题,给Bot A提示,接到第二类问题,给Bot B 提示。

image-20241124154020504

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

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

具体步骤如下:

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

具体实现:

  1. 构建提示信息模板

    # 构建两个场景的模板
    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,
        }]
    ​
    
  2. 初始化语言模型

    # 初始化语言模型
    from langchain.llms import OpenAI
    import os
    os.environ["OPENAI_API_KEY"] = '你的OpenAI Key'
    llm = OpenAI()
    
  3. 构建目标链

    # 构建目标链
    from langchain.chains.llm import LLMChain
    from langchain.prompts import PromptTemplate
    chain_map = {}
    #使用循环,构建出两个目标链,分别处理不同的问题。
    for info in prompt_infos:
        prompt = PromptTemplate(template=info['template'], 
                                input_variables=["input"])
        print("目标提示:\n",prompt)
        chain = LLMChain(llm=llm, prompt=prompt,verbose=True)
        chain_map[info["key"]] = chain
    

    目标链提示是这样:

    目标提示:
    input_variables=['input'] 
    output_parser=None partial_variables={} 
    template='你是一个经验丰富的园丁,擅长解答关于养花育花的问题。\n                        下面是需要你来回答的问题:\n                        
    {input}' template_format='f-string' 
    validate_template=True
    ​
    目标提示:
    input_variables=['input'] 
    output_parser=None partial_variables={} 
    template='你是一位网红插花大师,擅长解答关于鲜花装饰的问题。\n                        下面是需要你来回答的问题:\n                        
    {input}' template_format='f-string' 
    validate_template=True
    
  4. 构建路由链

    # 构建路由链
    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 >>
    ​
    路由提示:
    input_variables=['input'] output_parser=RouterOutputParser(default_destination='DEFAULT', next_inputs_type=<class 'str'>, next_inputs_inner_key='input') 
    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.\n\n
    << FORMATTING >>\n
    Return a markdown code snippet with a JSON object formatted to look like:\n```json\n{{\n "destination": string \ name of the prompt to use or "DEFAULT"\n    "next_inputs": string \ a potentially modified version of the original input\n}}\n```\n\n
    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.\n
    REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed.\n\n<< CANDIDATE PROMPTS >>\n
    flower_care: 适合回答关于鲜花护理的问题\n
    flower_decoration: 适合回答关于鲜花装饰的问题\n\n
    << INPUT >>\n{input}\n\n<< OUTPUT >>\n' 
    template_format='f-string' 
    validate_template=True
    
  5. 构建默认链

    # 构建默认链
    from langchain.chains import ConversationChain
    default_chain = ConversationChain(llm=llm, 
                                      output_key="text",
                                      verbose=True)
    
  6. 构建多提示链

    # 构建多提示链
    from langchain.chains.router import MultiPromptChain
    chain = MultiPromptChain(
        router_chain=router_chain,
        destination_chains=chain_map,
        default_chain=default_chain,
        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.

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

格式(<>)

指导模型如何格式化输出,使其以特定方式返回结果。

Return a markdown code snippet with a JSON object formatted to look like:

表示模型的输出应该是一个Markdown代码片段,其中包含一个特定格式的JSON对象。

下面的代码块显示了期望的JSON结构,其中destination是模型选择的提示名称(或"DEFAULT"),而next_inputs是可能被修订的原始输入。

额外的说明和要求

REMEMBER: "destination" MUST be one of the candidate prompt names specified below OR it can be "DEFAULT"...

这是一个重要的指导,告知模型"destination"字段必须是列出的提示之一或者"DEFAULT"

REMEMBER: "next_inputs" can just be the original input if you don't think any modifications are needed.

这里再次强调,除非模型认为有必要,否则不需要修改原始输入。

候选提示(<>)

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

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

输入/输出

<< INPUT >>\n{input}\n\n<< OUTPUT >>\n:

为模型提供一个格式化的框架,其中将接受一个名为{input}的输入,并在此后的部分输出结果。

下面给出路由提示的解释

路由提示根据路由模板,生成了具体传递给LLM的路由提示信息。

  • 其中input_variables 指定模板接收的输入变量名,这里只有 "input"
  • output_parser 是一个用于解析模型输出的对象,它有一个默认的目的地和一个指向下一输入的键。
  • template 是实际的路由模板,用于给模型提供指示。这就是刚才详细解释的模板内容。
  • template_format 指定模板的格式,这里是 "f-string"
  • validate_template 是一个布尔值,如果为 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。