【LangChain】理论及应用实战(3)
文章目录
- 一、Chain 链
- 1.1 LLMChain
- 1.2 SequentialChain 顺序链
- 1.3 RouterChain 路由链
- 1.4 Transformation Chain 转换链
- 二、链的调用方法
- 2.1 langchain-hub开源项目(已过时)
- 2.2 langchain-hub产品(推荐)
- 三、自定义链
- 总结
- 参考资料
本文主要内容参考资料:AI Agent智能体开发,一步步教你搭建agent开发环境(需求分析、技术选型、技术分解)
一、Chain 链
chain(链)是langchain当中一个非常重要的构建模块,应该说langchain框架中无论多复杂的功能基本都是通过一个个简单或复杂的chain来实现的。
chain的最基础功能就是将LLM与提示词结合在一起进而得到想要的结果,另外不同的chain之间也可以进行联动,也就是说一个复杂的链中可能包含很多其他的链,通过这种方式对你提供的数据和文本进行操作等实现一些更复杂的功能。这篇记录一下langchain当中,几种常见的链的用法。
langchain中有四种基础内置链,可适配各种任务:
- LLMChain: 最常用的链,
提示词模板+{LLM/chatModels}+输出格式化器(可选)
,其支持多种调用方式; - SequentialChain :顺序链,顺序执行,将前一个LLM的输出作为下一个LLM的输入;
- RouterChain:路由链,可以根据输入内容自动化路由到对应链。
- TransformationChain: 转换链
1.1 LLMChain
这是最基础、简单、又强大的链,很多复杂的链都由它参与支撑,其基本格式为:提示词模板+{LLM/chatModels}+输出格式化器(可选)
代码示例如下:
from langchain.chains import LLMChain
from langchain_ollama import OllamaLLM
from langchain.prompts import PromptTemplatemodel = OllamaLLM(model="llama3.1:8b")prompt_template = "请帮我的{product}想三个广告词"llm_chain = LLMChain(llm=model,prompt=PromptTemplate.from_template(prompt_template),verbose=True # 是否开启日志
)result = llm_chain("西瓜")
print(result)
输出如下:
{'product': '西瓜', 'text': '1. "西瓜甜而爽口,夏日最佳解暑果!"\n2. "西瓜清新水多,喝了就如沐清泉!"\n3. "西瓜冰镇夏季伴侣,解暑解馋!"'}
1.2 SequentialChain 顺序链
SequentialChain为顺序链。其顺序执行,将前一个LLM的输出作为下一个LLM的输入。SequentialChain 主要包含两类:SimpleSequentialChain 和 SequentialChain。
- SimpleSequentialChain 简单顺序链:上一个chain的输出作为下一个chain的输入,按照固定顺序调用执行。
这里提供了一个简单的实例,LLMChain当中可以设置llm的类型,以及提示词的模版类型。它相当于是将prompt、llm封装成一条链来运行。实现代码如下:
from langchain.chains import LLMChain
from langchain_ollama import OllamaLLM
from langchain.prompts import ChatPromptTemplate
from langchain.chains import SimpleSequentialChainllm_model = OllamaLLM(model="llama3.1:8b")# chain 1
prompt_template_1 = "请帮我的{product}取1个响亮容易记忆的店铺名字"
prompt_1 = ChatPromptTemplate.from_template(prompt_template_1)chain_1 = LLMChain(llm=llm_model,prompt=prompt_1,verbose=True
)prompt_template_2 = "用3个形容词描述下这个店铺的名字: {shop_name}"
prompt_2 = ChatPromptTemplate.from_template(prompt_template_2)chain_2 = LLMChain(llm=llm_model,prompt=prompt_2,verbose=True
)all_chain = SimpleSequentialChain(chains=[chain_1, chain_2],verbose=True # 打开日志
)result = all_chain.run("酸辣粉")
print(result)
输出如下:
> Entering new LLMChain chain...
Prompt after formatting:
Human: 请帮我的酸辣粉取1个响亮容易记忆的店铺名字> Finished chain.
"辣天下"> Entering new LLMChain chain...
Prompt after formatting:
Human: 用3个形容词描述下这个店铺的名字: "辣天下"> Finished chain.
刺激、独特、热情。
- SequentialChain:后续chain可以自由调用前面chain的结果。
代码示例如下:
from langchain.chains import LLMChain
from langchain_ollama import OllamaLLM
from langchain.prompts import ChatPromptTemplate
from langchain.chains import SequentialChainmodel = OllamaLLM(model="llama3.1:8b")# chain 1: 翻译成中文
prompt_1 = ChatPromptTemplate.from_template("将下面的内容翻译为中文:\n\n{content}")
chain_1 = LLMChain(llm=model,prompt=prompt_1,verbose=True,output_key="Chinese_content"
)# chain 2: 对翻译后的中文内容进行总结摘要,其 input_key 为上一个chain的output_key
prompt_2 = ChatPromptTemplate.from_template("用一句话总结下面内容:\n\n{Chinese_content}")
chain_2 = LLMChain(llm=model,prompt=prompt_2,verbose=True,output_key="Chinese_summary"
)# chain 3: 识别语言
prompt_3 = ChatPromptTemplate.from_template("下面的内容是什么语言:\n\n{Chinese_summary}")
chain_3 = LLMChain(llm=model,prompt=prompt_3,verbose=True,output_key="get_lanuage"
)# chain 4: 针对摘要使用指定语言进行评论
prompt_4 = ChatPromptTemplate.from_template("请使用指定的语言对以下内容进行回复:\n\n内容:{Chinese_summary}\n\n语言:{get_lanuage}")
chain_4 = LLMChain(llm=model,prompt=prompt_4,verbose=True,output_key="get_reply"
)# chain_all
chain_all = SequentialChain(chains=[chain_1, chain_2, chain_3, chain_4],verbose=True,input_variables=["content"],output_variables=["Chinese_content", "Chinese_summary", "get_lanuage", "get_reply"]
)content = "YouTube is an American social media and online video sharing platform owned by Google. YouTube was founded on February 14, 2005, by Steve Chen, Chad Hurley, and Jawed Karim, three former employees of PayPal. Headquartered in San Bruno, California, it is the second-most-visited website in the world, after Google Search. In January 2024, YouTube had more than 2.7 billion monthly active users, who collectively watched more than one billion hours of videos every day. As of May 2019, videos were being uploaded to the platform at a rate of more than 500 hours of content per minute, and as of mid-2024, there were approximately 14.8 billion videos in total."
result = chain_all(content)
print(result)
上述代码实现了这样一个任务,对一篇英文或者其他语言的英文报道用中文进行总结评论,并且查询原语言的种类,并将中文评论用原语言返回给用户。这个任务就不是单纯的依次执行了,可以看到,chain_1的输出流向chain_2,chain_3的输入来自用户输入,而chain_4的输入又来自chain_2与chain_3。
输出如下:
> Entering new SequentialChain chain...> Entering new LLMChain chain...
Prompt after formatting:
Human: 将下面的内容翻译为中文:YouTube is an American social media and online video sharing platform owned by Google. YouTube was founded on February 14, 2005, by Steve Chen, Chad Hurley, and Jawed Karim, three former employees of PayPal. Headquartered in San Bruno, California, it is the second-most-visited website in the world, after Google Search. In January 2024, YouTube had more than 2.7 billion monthly active users, who collectively watched more than one billion hours of videos every day. As of May 2019, videos were being uploaded to the platform at a rate of more than 500 hours of content per minute, and as of mid-2024, there were approximately 14.8 billion videos in total.> Finished chain.> Entering new LLMChain chain...
Prompt after formatting:
Human: 用一句话总结下面内容:YouTube是一家位于美国的社交媒体和在线视频共享平台,由Google拥有。 YouTube于2005年2月14日由前PayPal员工陈思成、查德·赫利(Chad Hurley)、贾韦德·卡里姆(Jawed Karim)共同创立。总部位于加州圣布鲁诺,它是世界上第二访问最多的网站,仅次于谷歌搜索。在2024年1月,YouTube有超过2.7亿每月活跃用户,他們在每天观看了超过10亿小时的視頻。截至2019年5月,平台上传速度达到每分钟超过500小时的内容,并截至2024年中期共有约14.8亿个视频。> Finished chain.> Entering new LLMChain chain...
Prompt after formatting:
Human: 下面的内容是什么语言:YouTube是一家社交媒体和在线视频共享平台,由Google拥有,其创始于2005年,现为世界上第二访问最多的网站。> Finished chain.> Entering new LLMChain chain...
Prompt after formatting:
Human: 请使用指定的语言对以下内容进行回复:内容:YouTube是一家社交媒体和在线视频共享平台,由Google拥有,其创始于2005年,现为世界上第二访问最多的网站。语言:这是中文(简体字)。> Finished chain.> Finished chain.
从代码可以看到,一般顺序链实现定向输出的方式是在每个子链的output_key参数中定义了该链的输出变量名,然后在用到这个输出的链的prompt当中引入这个变量名,从而实现了对前面多个子链的输出定向调用。
1.3 RouterChain 路由链
上述两种顺序链本质上还是简单地人为预设好不同链的顺序及输入关系,在一些更复杂的任务中,往往根据输入的内容,按照某种方式自动选择下游链。即可能定义了多条实现不同功能的子链,用户的输入或者上游输出是其中的某一种,让它自动判断选哪一条链来处理。
路由链支持创建一个非确定性链,由LLM来选择下一步。链内的多个prompts模板描述了不同的提示请求,使得可以根据输入内容自动化路由到对应链。
下面给出一个示例,实现根据用户输入的内容,选择对应的chain来回答问题。
(1)构建目标子链
根据学科内容,设置不同的提示词,分别构建对应的目标子链,存放在一个dest_chains字典中,作为准备路由的子链池。另外还设置了一个默认链,即在用户问题不属于任何一个定义的学科时,则使用通用的默认链来回答。
# Step 1. 构建目标链
from langchain.prompts import PromptTemplate
from langchain.chains import ConversationChain, LLMChain
from langchain_ollama import OllamaLLM# 物理链
template_physics = """你是一位非常聪明的物理教授。\n
你擅长以简洁易懂的方式回答物理问题。\n
当你不知道问题答案的时候,你会坦率承认不知道。\n
下面是一个问题:
{input}
"""prompt_physics = PromptTemplate.from_template(template_physics)# 数学链
template_math = """你是一位非常聪明的数学教授。\n
你擅长回答数学问题。\n
你之所以如此优秀,是因为你能够将困难的问题分解,然后将他们组合起来,以回答更广泛的问题。\n
当你不知道问题答案的时候,你会坦率承认不知道。\n
下面是一个问题:
{input}
"""prompt_math = PromptTemplate.from_template(template_math)# 提示词表
prompt_infos = [{"name": "physics","desp": "擅长回答物理问题","prompt_template": template_physics,},{"name": "math","desp": "擅长回答数学问题","prompt_template": template_math,},
]llm_model = OllamaLLM(model="llama3.1:8b")dest_chain = {} # 目标链# 创建物理链和数学链,写到目标链dest_chain中
for p_info in prompt_infos:name = p_info["name"]prompt_template = p_info["prompt_template"]prompt = PromptTemplate(template=prompt_template,input_variables=["input"])chain = LLMChain(llm=llm_model,prompt=prompt)dest_chain[name] = chain# 构建对话链作为默认链
default_chain = ConversationChain(llm=llm_model,output_key="text"
)
(2)构建路由链
这是实现路由的关键一步,实现方式是通过一个LLMRouterChain来判断属于哪个子链。这一步需要定义一个路由提示词模板(如代码中MULTI_PROMPT_ROUTER_TEMPLATE所示)。
提示词的核心就是引导大模型将用户输入归类到我们定义的几个学科中去,并输出成供下游链解读的形式。
from langchain.chains.router.llm_router import LLMRouterChain, RouterOutputParser
from langchain.chains.router.multi_prompt_prompt import MULTI_PROMPT_ROUTER_TEMPLATE
from langchain.chains.router import MultiPromptChain# 将prompt的描述信息写入str字符串中
dest = [f"{p['name']}:{p['desp']}" for p in prompt_infos]
dest_str = "\n".join(dest)
# print(dest_str) # physics:擅长回答物理问题 math:擅长回答数学问题# 定义路由链的template模板
router_template = MULTI_PROMPT_ROUTER_TEMPLATE.format(destinations=dest_str)
router_prompt = PromptTemplate(template=router_template,input_variables=["input"],output_parser=RouterOutputParser()
)
print('router_prompt:', router_prompt)# Step 2. 定义路由链
router_chain = LLMRouterChain.from_llm(llm=llm_model,prompt=router_prompt
)
这里我们来看看
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 (must include ```json at the start of the response) >> << OUTPUT (must end with ```) >> """ ```
(3)构建最终完整的链
有了前面设置的目标链和路由链,最后将他们整合成一个完整的MultiPromptChain
。
all_chain = MultiPromptChain(router_chain=router_chain,destination_chains=dest_chain,default_chain=default_chain,verbose=True
)result_1 = all_chain.run("什么是牛顿第一定律") # 命中 physics chain
print(result_1)result_2 = all_chain.run("高等数学包含哪些内容") # 命中 math chain
print(result_2)result_3 = all_chain.run("给我讲一个笑话") # 命中 default chain
print(result_3)
(4)运行上述代码,最终输出如下:
> Entering new MultiPromptChain chain...
physics: {'input': "What is Newton's First Law"}
> Finished chain.
很简单的问题!牛顿第一定律也称为动静法则或质点守恒法则。它指出:一个物体保持其当前状态,除非有外力干扰它。这意味着,如果没有外力作用于一个物体,它将继续以当前的速度和方向运动,并不会改变它们。举个例子,你在汽车里静止不动,车辆不会自己开始移动;如果你停下脚步,不会自动跑起来。只有当外界给予力量(如发动机加速或受到摩擦力)才会引起变化。> Entering new MultiPromptChain chain...
math: {'input': '高等数学的主要内容包括多元函数、微分论和积分论等'}
> Finished chain.
一个很棒的问题!然而,作为一名数学教授,我必须指出,这个问题实际上并不是一个具体的问题,而是一些基本概念的描述。如果你想问的是“如何理解或应用这些概念”,那我们可以进行更深入的讨论。否则,我将说这是一个比较容易回答的问题,但可能不太有挑战性。> Entering new MultiPromptChain chain...
None: {'input': '我要听一个笑话'}
> Finished chain.
你喜欢听中国式幽默还是欧美风格的?我知道一个关于一只开了门但没有人家的狗的故事,听着好玩吗?
1.4 Transformation Chain 转换链
支持对传递部件的转换,比如将一个超长文本过滤转换为仅包含前三个段落,然后提交给LLM。
示例代码如下:
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain, SimpleSequentialChain, TransformChain
from langchain_ollama import OllamaLLMllm_model = OllamaLLM(model="llama3.1:8b")def transform_func(inputs:dict)->dict:text = inputs["text"]shortened_text = "\n\n".join(text.split("\n\n")[:3]) # 取前3段文本res = {"output_text": shortened_text}return res# 文档转换链
transform_chain = TransformChain(input_variables=["text"],output_variables=["output_text"],transform=transform_func
)template = """对面的文字进行总结:
{output_text}
总结:
"""prompt = PromptTemplate(input_variables=["output_text"],template=template
)llm_chain = LLMChain(llm=llm_model,prompt=prompt
)# 使用顺序链连接起来
sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain],verbose=True
)# 加载文件
with open("./data/file.txt") as f:file_content = f.read()# 查看输出
result = sequential_chain.run(file_content)
print(result)
二、链的调用方法
2.1 langchain-hub开源项目(已过时)
Github开源项目:langchain-hub
langchain-hub中包含许多链,如问答链、数学计算的链。langchain中将链的配置做成json文件,我们来看看一个简单的数学链的chain.json文件内容:
{"memory": null,"verbose": true,"llm": {"model_name": "text-davinci-003","temperature": 0.0,"max_tokens": 256,"top_p": 1,"frequency_penalty": 0,"presence_penalty": 0,"n": 1,"best_of": 1,"request_timeout": null,"logit_bias": {},"_type": "openai"},"prompt": {"input_variables": ["question"],"output_parser": null,"template": "You are GPT-3, and you can't do math.\n\nYou can do basic math, and your memorization abilities are impressive, but you can't do any complex calculations that a human could not do in their head. You also have an annoying tendency to just make up highly specific, but wrong, answers.\n\nSo we hooked you up to a Python 3 kernel, and now you can execute code. If anyone gives you a hard math problem, just use this format and we\u2019ll take care of the rest:\n\nQuestion: ${{Question with hard calculation.}}\n```python\n${{Code that prints what you need to know}}\n```\n```output\n${{Output of your code}}\n```\nAnswer: ${{Answer}}\n\nOtherwise, use this simpler format:\n\nQuestion: ${{Question without hard calculation}}\nAnswer: ${{Answer}}\n\nBegin.\n\nQuestion: What is 37593 * 67?\n\n```python\nprint(37593 * 67)\n```\n```output\n2518731\n```\nAnswer: 2518731\n\nQuestion: {question}\n","template_format": "f-string","_type": "prompt"},"input_key": "question","output_key": "answer","_type": "llm_math_chain"
}
我们可以直接使用load_chain直接加载其他langchain-hub已有的chain,到吗示例如下:
pip install numexpr
from langchain.chains import load_chain
chain = load_chain("lc://chains/hello-world/chain.json") # 注:这种加载方式已经过时 不适用了result = chain.run("今天天气真好")
print(result)
- 这里使用了
lc前缀
的地址,使得我们可以直接访问到langchain hub
库中的链。- 目前这种加载chain的方式已经过时,官方推荐使用 https://smith.langchain.com/hub 中的方式来加载,具体可以参考 2.2 节的内容。
2.2 langchain-hub产品(推荐)
目前官方已经将该开源项目升级为了langchain家族的产品,访问网址:https://smith.langchain.com/hub
从主页右侧的菜单栏可以看到,langchain-hub 开放了各种场景下的prompt的编写及chain的加载方式,我们可以根据自己的需求去选择相应的chain或者是参考他们的写法。
我们来看一个简单的 text-to-sql 的示例,来自官网:https://smith.langchain.com/hub/rlm/text-to-sql
- Readme
The prompt can be use as shown below:from langchain.utilities import SQLDatabase
from langchain.chat_models import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser
from langchain import hub# Initialize database
db = SQLDatabase.from_uri("sqlite:///Chinook.db")
# Pull down prompt
prompt = hub.pull("rlm/text-to-sql")
# Initialize model
model = ChatOpenAI()# Create chain with LangChain Expression Language
inputs = {"table_info": lambda x: db.get_table_info(),"input": lambda x: x["question"],"few_shot_examples": lambda x: "","dialect": lambda x: db.dialect,
}
sql_response = (inputs| prompt| model.bind(stop=["\nSQLResult:"])| StrOutputParser()
)# Call with a given question
sql_response.invoke({"question": "How many customers are there?"})
For more detail, see the Summarization use case doc.
- ChatPromptTemplate
humanGiven an input question, first create a syntactically correct {dialect} query to run, then look at the results of the query and return the answer.Use the following format:Question: "Question here"SQLQuery: "SQL Query to run"SQLResult: "Result of the SQLQuery"Answer: "Final answer here"Only use the following tables:{table_info}.Some examples of SQL queries that corrsespond to questions are:{few_shot_examples}Question: {input}
- Use object in LangChain:基于
pull_prompt
函数
# Create a LANGSMITH_API_KEY in Settings > API Keys
from langsmith import Client
client = Client(api_key=LANGSMITH_API_KEY)
prompt = client.pull_prompt("rlm/text-to-sql", include_model=True)
三、自定义链
当 langchain-hub 中的prompt 及 chain 不满足我们的需求时,我们也可以构建自己的chain。
下面我们看一个具体示例,通过自定义链实现维基百科形式的文章。
(1)自定义链
from typing import List, Dict, Any, Optional
from langchain.callbacks.manager import CallbackManagerForChainRun
from langchain.chains.base import Chain
from langchain.prompts.base import BasePromptTemplate
from langchain.base_language import BaseLanguageModelclass WikiArticleChain(Chain):"""开发一个wiki文章生成器"""prompt: BasePromptTemplatellm: BaseLanguageModelout_key: str="text"@propertydef _chain_type(self) -> str:"""链的类型"""return "wiki_article_chain"@propertydef input_keys(self) -> List[str]:"""将返回prompt所需要的所有键"""return self.prompt.input_variables@propertydef output_keys(self) -> List[str]:"""将始终返回text键"""return [self.out_key]def _call(self,inputs: Dict[str, Any],run_manager: Optional[CallbackManagerForChainRun] = None,) -> Dict[str, Any]:"""复写call方法,运行链的入口函数"""prompt_value = self.prompt.format(**inputs)response = self.llm.generate_prompt([prompt_value],callbacks=run_manager.get_child() if run_manager else None)if run_manager:run_manager.on_text("wiki article is written")return {self.out_key:response.generations[0][0].text}
(2)调用链
from langchain_ollama import OllamaLLM
from langchain.prompts import PromptTemplatellm_model = OllamaLLM(model="llama3.1:8b")chain = WikiArticleChain(prompt=PromptTemplate(template="写一篇关于{topic}的维基百科形式的文章",input_variables=["topic"]),llm=llm_model
)result = chain.invoke({"topic": "机器学习"})
总结
langchain框架通过chain这个基本模块,让我们可以根据需求,对大模型进行灵活组合搭配,也能将大模型一次无法很好执行的大任务拆分成一个个细分的子任务去更精细地实现。包括后续的文档问答、代理等也都会用到链。从对chain的学习也能看出,想要用好大模型,提示词的构建依然是关键。
参考资料
AI Agent智能体开发,一步步教你搭建agent开发环境(需求分析、技术选型、技术分解)