基于LangChain的LLM应用开发4——链

君子性非异也,善假于物也。

面向对象的世界中,“万事万物皆对象”,对象是最重要的,对象生生死死,不同对象之间发送消息(调用方法),世界才鲜活起来。

在LangChain中,chain(链)是最关键的构建模块,可以被视为 LangChain 中的一种基本功能单元。链将大语言模型与提示词结合在一起。通过链的机制,可以将链模块组合在一起,对文本和其他数据按顺序进行操作。

这种将多个组件相互链接,组合成一个链的想法简单但很强大。它简化了复杂应用程序的实现,并使之更加模块化,能够创建出单一的、连贯的应用程序,从而使调试、维护和改进应用程序变得容易。

本次我们会研究五种链:LLMChain、SimpleSequentialChain、SequentialChain、MultiPromptChain、LLMRouterChain。

同样,先通过.env文件初始化环境变量,记住我们用的是微软Azure的GPT,具体内容参考本专栏的第一篇。

from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file

deployment = "gpt-35-turbo"
model = "gpt-3.5-turbo"

LLMChain

LLMChain是基于大模型构建的最简单、最基本的链类型。它包含提示模板,在后台根据用户输入对其进行格式化,把格式化好的提示传入模型,然后返回LLM的响应,同时解析输出。LLMChain广泛用在整个LangChain 中,在其他链和代理中,经常出现它的身影。

# from langchain.chat_models import ChatOpenAI
from langchain.chat_models import AzureChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.chains import LLMChain

llm = AzureChatOpenAI(temperature=0.9, model_name=model, deployment_name=deployment)
prompt = ChatPromptTemplate.from_template(
    "你是业务咨询顾问,你为生产 {product} 的公司起一个最好的中文名字,只要一个。"
)
chain = LLMChain(llm=llm, prompt=prompt)

product = "大号床单套装"
chain.run(product)

上面是一个最简单的LLMChain应用,但是已经实现了让GPT取名返回的功能。

SimpleSequentialChain

在这里插入图片描述

SimpleSequentialChain是LangChain的顺序组件。

一个应用程序通常不会只调用一次语言模型,当你想要获取一个调用的输出并将其用作另一个调用的输入时顺序链就特别有用。SimpleSequentialChain是顺序链的最简单形式,每个步骤都有一个单一的输入/输出,上一步的输出是下一步的输入。

SimpleSequentialChain的功能类似于Linux下面的管道,如 统计当前目录下所有Java文件的代码行数:find . -name “*.java” -type f -exec cat {} ; | wc -l ,遍历当前目录的java文件,查看文件内容,统计行数,上一个命令的输出就是下一个命令的输入。

from langchain.chains import SimpleSequentialChain

llm = AzureChatOpenAI(temperature=0.9, model_name=model, deployment_name=deployment)

# prompt template 1
first_prompt = ChatPromptTemplate.from_template(
"你是业务咨询顾问,为生产 {product} 的公司起一个最好的名字,只要一个。"
)
# Chain 1
chain_one = LLMChain(llm=llm, prompt=first_prompt)

# prompt template 2
second_prompt = ChatPromptTemplate.from_template(
    "为下面的公司写50个字的描述:{company_name}")
# chain 2
chain_two = LLMChain(llm=llm, prompt=second_prompt)

overall_simple_chain = SimpleSequentialChain(chains=[chain_one, chain_two],
                                             verbose=True
                                            )
overall_simple_chain.run(product)

SimpleSequentialChain的使用很简单,先后定义两个LLMChain之后,变成列表传给SimpleSequentialChain就可以了。因为上一个链的输出就是下一个链的输入:第一条链的输出company_name会传给第二条链,所以不用管中间的参数传递。

注意temperature我们设为了0.9,期待能有更多不确定性的输出,嗯,更有创造力。

看一下输出,还不错:

床纺匠

床纺匠是一家致力于床上用品的研发、生产和销售的公司。专注于打造高品质的床上用品,包括床单、被套、枕头套等。公司拥有独特的设计团队和先进的生产工艺,以确保产品的舒适度和耐用性。床纺匠致力于提供美观、舒适和高品质的床上用品,为人们打造一个优雅而舒适的睡眠环境。

SequentialChain

SequentialChain 是更通用的顺序链形式,允许多个输入/输出。

下面我们用SequentialChain 来实现这样的功能:

  1. 把其他语言的评论Review翻译成英语,结果保存到变量English_Review
  2. 把英语评论总结为一句话保存到变量summary
  3. 检测评论的语言,把检测结果保存在变量language
  4. 针对summary,用language生成响应消息。
翻译
总结
检测
生成
使用
开始
评论为英语
英语评论为一句话
评论语言
响应消息
结束
from langchain.chains import SequentialChain
llm = AzureChatOpenAI(temperature=0.9, model_name=model, deployment_name=deployment)

# prompt template 1: translate to english
first_prompt = ChatPromptTemplate.from_template(
    "Translate the following review to english:"
    "\n\n{review}"
)
# chain 1: input= Review and output= English_Review
chain_one = LLMChain(llm=llm, prompt=first_prompt, 
                     output_key="english_review")

second_prompt = ChatPromptTemplate.from_template(
    "Can you summarize the following review in 1 sentence:"
    "\n\n{english_review}"
)
# chain 2: input= English_Review and output= summary
chain_two = LLMChain(llm=llm, prompt=second_prompt, 
                     output_key="summary"
                    )

# prompt template 3: translate to english
third_prompt = ChatPromptTemplate.from_template(
    "What language is the following review:\n\n{review}"
)
# chain 3: input= Review and output= language
chain_three = LLMChain(llm=llm, prompt=third_prompt,
                       output_key="language"
                      )

# prompt template 4: follow up message
fourth_prompt = ChatPromptTemplate.from_template(
    "Write a follow up response to the following "
    "summary in the specified language:"
    "\n\nSummary: {summary}\n\nLanguage: {language}"
)
# chain 4: input= summary, language and output= followup_message
chain_four = LLMChain(llm=llm, prompt=fourth_prompt,
                      output_key="followup_message"
                     )

# overall_chain: input= Review 
# and output= English_Review,summary, followup_message
overall_chain = SequentialChain(
    chains=[chain_one, chain_two, chain_three, chain_four],
    input_variables=["review"],
    output_variables=["english_review", "summary","followup_message"],
    verbose=True
)
review = "手机很好用,系统使用流畅,拍照清晰,内存大,商品符合描述,主要是活动便宜,能省很多钱,80瓦闪充,非常快速充满电池,非常满意!" #df.Review[5]
overall_chain(review)

注意chain_one的输出会用output_key传出来:english_review,chain_two里会通过提示接收这个输入。这些一定要保持一致,不然运行就会报错。

SequentialChain的使用也比较简单,通过SequentialChain组合不同的chain(基本的LLMChain),指明最开始的输入变量input_variables,中间所有的输出变量output_variables,然后overall_chain(review)就可以输出了。

找了一段电商网站的评论来运行程序,输出如下:

{‘review’: ‘手机很好用,系统使用流畅,拍照清晰,内存大,商品符合描述,主要是活动便宜,能省很多钱,80瓦闪充,非常快速充满电池,非常满意!’,
‘english_review’: ‘The phone is very user-friendly, the system runs smoothly, the camera takes clear photos, and it has a large memory. The product is as described, and the main advantage is that it is affordable, which helps save a lot of money. It also has an 80-watt fast charging feature, which quickly charges the battery. I am very satisfied!’,
‘summary’: “This review highlights the phone’s user-friendly interface, smooth performance, clear camera, ample storage, affordability, and fast-charging capabilities, ultimately expressing high satisfaction with the product.”,
‘followup_message’: ‘回复:\n非常感谢您对我们手机的积极评价!您的评论强调了手机的用户友好界面、流畅的性能、清晰的相机、充足的存储空间、实惠的价格以及快速充电功能,最终您对这款产品表达了高度的满意度。\n\n我们非常重视用户的反馈,对于您的赞赏我们感到非常高兴。我们一直致力于为客户提供优质的产品和良好的使用体验。您的意见将激励我们不断改进和创新,以满足用户的需求。\n\n谢谢您的购买,我们希望我们的手机能一直为您带来愉悦的使用体验。如果您有任何问题或建议,请随时与我们联系。祝您生活愉快!\n\n谢谢!\n\n- 您的手机制造商’}

可以看到“AI客服”的回复也算到位。

LLMRouterChain和MultiPromptChain

路由器链包含条件判断和一系列的目标链。通过调用LLM,路由链能动态判断条件,以确定调用后续哪一个目标链。

RouterChain,也叫路由链,能动态选择用于给定输入的下一个链。根据用户的问题内容,首先使用路由器链router_chain(实质就是调用LLM的推理功能来做决策)确定问题更适合哪个处理模板(从destination_chains中选择),然后将问题发送到该处理模板进行回答。如果问题不适合任何已定义的处理模板,它会被发送到默认链(default_chain)。

通常要用MultiPromptChain来协调router_chain、destination_chains(目标链的词典)、default_chain:

MultiPromptChain(router_chain=router_chain,
                         destination_chains=destination_chains,
                         default_chain=default_chain, verbose=True
                         )

接下来的代码看起来有点复杂,实际上就是做了这些事情:

  1. 构建处理模板:为各个学科领域(物理、数学、历史、计算机科学)分别定义字符串模板。
  2. 构建提示信息:使用一个列表来组织和存储这些处理模板的关键信息,如模板的键、描述和实际的字符串模板。
  3. 初始化语言模型:导入并实例化语言模型。
  4. 构建目标链:根据提示信息中的每个模板构建了对应的 LLMChain,并存储在一个字典中。
  5. 构建路由链:这是决策的核心部分。首先,它根据提示信息构建了一个路由模板,然后使用这个模板创建了一个 LLMRouterChain。
  6. 构建默认链:如果输入不适合任何已定义的处理模板,这个默认链会被触发。
  7. 构建多提示链:使用 MultiPromptChain 将路由链、目标链和默认链组合在一起,形成一个完整的决策系统:router_chain调用llm判断选择采用哪种提示/人设,即在不同链之间路由(根据问题的领域,如果是物理问题则选择物理专家,数学问题则选择数学专家,……,如果不确定是哪个领域的问题,则用通用领域的提示)。MultiPromptChain 为我们提供了一个在多个处理链之间动态路由输入的机制,以得到最相关或最优的输出。

#构建处理模板
physics_template = """You are a very smart physics professor. 
You are great at answering questions about physics in a concise
and easy to understand manner. 
When you don't know the answer to a question you admit that you don't know.

Here is a question:
{input}"""

math_template = """You are a very good mathematician. 
You are great at answering math questions. 
You are so good because you are able to break down 
hard problems into their component parts, 
answer the component parts, and then put them together 
to answer the broader question.

Here is a question:
{input}"""

history_template = """You are a very good historian. 
You have an excellent knowledge of and understanding of people,
events and contexts from a range of historical periods. 
You have the ability to think, reflect, debate, discuss and 
evaluate the past. You have a respect for historical evidence 
and the ability to make use of it to support your explanations 
and judgements.

Here is a question:
{input}"""

computerscience_template = """ You are a successful computer scientist.
You have a passion for creativity, collaboration,
forward-thinking, confidence, strong problem-solving capabilities,
understanding of theories and algorithms, and excellent communication 
skills. You are great at answering coding questions. 
You are so good because you know how to solve a problem by 
describing the solution in imperative steps 
that a machine can easily interpret and you know how to 
choose a solution that has a good balance between 
time complexity and space complexity. 

Here is a question:
{input}"""

# 构建提示信息
prompt_infos = [
    {
    
    
        "name": "physics", 
        "description": "Good for answering questions about physics", 
        "prompt_template": physics_template
    },
    {
    
    
        "name": "math", 
        "description": "Good for answering math questions", 
        "prompt_template": math_template
    },
    {
    
    
        "name": "History", 
        "description": "Good for answering history questions", 
        "prompt_template": history_template
    },
    {
    
    
        "name": "computer science", 
        "description": "Good for answering computer science questions", 
        "prompt_template": computerscience_template
    }
]

# 初始化语言模型
from langchain.chains.router import MultiPromptChain
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.prompts import PromptTemplate
llm = AzureChatOpenAI(temperature=0, model_name=model,
                      deployment_name=deployment)

# 构建目标链
destination_chains = {
    
    }
for p_info in prompt_infos:
    name = p_info["name"]
    prompt_template = p_info["prompt_template"]
    prompt = ChatPromptTemplate.from_template(template=prompt_template)
    chain = LLMChain(llm=llm, prompt=prompt)
    destination_chains[name] = chain

# 构建路由链
destinations = [f"{
      
      p['name']}: {
      
      p['description']}" for p in prompt_infos]
destinations_str = "\n".join(destinations)

# 这个模板可以直接导入:from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
MULTI_PROMPT_ROUTER_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 >>
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 >>
{destinations}

<< INPUT >>
{
    
    {input}}

<< OUTPUT (remember to include the \`\`\`json)>>"""

router\_template = MULTI\_PROMPT\_ROUTER\_TEMPLATE.format(
destinations=destinations\_str
)

# 构建默认链

default\_prompt = ChatPromptTemplate.from\_template("{input}")
default\_chain = LLMChain(llm=llm, prompt=default\_prompt)

# 构建多提示链

# RouterOutputParser将LLM输出解析成字典,根据字典在下游确定使用哪条链

router\_prompt = PromptTemplate(
template=router\_template,
input\_variables=\["input"],
output\_parser=RouterOutputParser(),
)
router\_chain = LLMRouterChain.from\_llm(llm, router\_prompt)

chain = MultiPromptChain(router\_chain=router\_chain,
destination\_chains=destination\_chains,
default\_chain=default\_chain, verbose=True
)

上面的代码,本质还是提示工程(Prompt Engineering),难点在写那一段提示,LangChain只是帮你把提示组织起来了。

用来进行路由决策的router_prompt:

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\nREMEMBER: "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
physics: Good for answering questions about physics\n
math: Good for answering math questions\n
History: Good for answering history questions\n
computer science: Good for answering computer science questions\n\n

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

<< OUTPUT (remember to include the \`\`\`json)>>

所以目前的AI编程,可能还是要花时间在提示工程上面。在AI没有更大进化之前, 提示工程还是见效快的“内功心法”。 不过AI比较好的是,里面是概率世界,人设不会随便就崩了,不会跑出一个扫地僧来回答你的武学难题,也不会有一个体育老师来教你英语(虽然他可能是英语专业毕业)。当然提示也要花心思“调试”,顺利的时候也会有写一般程序的那种“一日看尽长安花”的感觉,但有的时候你也会忍不住抱怨在“对牛弹琴”,恨不得把AI拖出来暴打一顿再杀了祭天(好吧,Cursor/GitHub Copilot虽然有时自说自话,但是态度还是很谦卑的:你说的都对,只是我只能那样改)。

看一下上面代码的输出:

chain.run(“1023+1等于多少?”)

math: {‘input’: ‘1023+1 equals how much?’}

"Thank you for your kind words! I’m happy to help you with your math question.\n\nWhen we add 1023 and 1 together, we simply combine the two numbers. The sum of 1023 and 1 is 1024. Therefore, 1023 + 1 equals 1024.\n\nIf you have any more math questions, feel free to ask!”

自动选了math的人设。

chain.run(“A股为什么熊长牛短?”)

None: {‘input’: ‘A股为什么熊长牛短?’}

'A股市场熊市长牛短的原因有多个方面:\n\n1. 结构性问题:A股市场长期存在着股权融资比例过低,市场参与者多以短期投机为主,导致市场波动性较大。同时,市场监管不健全,信息不对称、内幕交易等问题也影响着市场稳定性。\n\n2. 宏观经济因素:宏观经济周期的波动会影响到A股市场的走势。经济增长放缓、货币政策的紧缩等因素会导致市场信心不足,产生恐慌情绪,引发熊市。相反,经济复苏、宽松的货币政策等因素会推动市场上涨。\n\n3. 投资者心理因素:A股市场中,投资者普遍存在投机心理和短期投资导向,容易受到市场情绪的影响,盲目追涨杀跌。这种投资心态导致了市场的短期波动性增大,市场变得不稳定。\n\n4. 政策因素:政策对市场的影响也是重要原因之一。政府的宏观调控政策、监管政策等都会对市场产生影响,不同政策导向会引发市场行情的变化。\n\n需要注意的是,以上仅是一些可能的原因,实际市场变化受到多种因素的综合影响,不能简单归纳为某一种原因。市场走势的复杂性、不确定性使得预测市场走势变得困难,投资者应该根据自己的投资目标和风险承受能力制定合理的投资策略。’

路由器链找不到合适的人设(None),会路由到默认链,直接当作通用的问题来问语言模型。

展望

旧时王谢堂前燕,飞入寻常百姓家。

是否有一种隐隐约约的感觉:LangChain有点像是一门AI时代的编程语言,有顺序,有分支,可以自由组合各种模块……

大模型和LangChain已经大大简化了人工智能应用的开发,但是LangChain还是不够简洁,差点火候,没有Python那种作为编程语言给人的表达力和简洁流畅的感觉。期待LangChain更好的发展,能真正成为AI时代的编程语言。

参考

  1. 短课程: https://learn.deeplearning.ai/langchain/lesson/4/chains
  2. 文档:https://python.langchain.com/docs/modules/chains/

猜你喜欢

转载自blog.csdn.net/fireshort/article/details/134043525
今日推荐