本系列文章是基于langchainv-v0.3版本框架的学习实战笔记。
什么是LCEL
LCEL(Langchain Expression Language):LangChain表达式语言,是langchain官方推出的一种新的语法,用于表征Runnable对象之间的上下游关系,从而构建大模型服务的处理链。LCEL作为一种流程编排解决方案,优化了处理链的并行、异步和流式处理,同时可对外提供的统一的调用接口,无缝支持LangSmith追踪和LangServe部署。
快速开始
在LCEL中,任何两个 Runnable 对象都可以“链接”在一起形成一个新的 Runnable 对象,后一个对象以前一个对象的输出作为输入。这种“链接”可以通过管道操作符|
或者 .pipe() 方法来构建。
接下来我们基于LCEL表达式构建一个规划旅行方案的大模型应用,首先我们先创建聊天模型以及提示模板。
import os
os.environ['OPENAI_API_KEY'] = "XXXXXX"
os.environ["OPENAI_API_BASE"] = "XXXXXX"
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from operator import itemgetter
from langchain_core.runnables import RunnableParallel
model = ChatOpenAI(
model="gpt-4o",
temperature=0.0
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你的工作是根据用户输入的城市和旅行天数给出对应的旅行规划建议,请使用以下回答模板\n 穿衣指数:...\n规划建议:...|||",
),
("human", "城市:{city},旅行天数:{day}"),
]
)
(1)、基于操作符构建
travel_assistant = (
{"city": itemgetter("city"), "day": itemgetter("travel_day")}
| prompt
| model.bind(stop="|||")
| StrOutputParser()
)
travel_assistant.invoke({"city":"北京","travel_day":"3"})
(2)、基于方法构建
travel_assistant = (
RunnableParallel({"city": itemgetter("city"), "day": itemgetter("travel_day")})
.pipe(prompt)
.pipe(model.bind(stop="|||"))
.pipe(StrOutputParser())
)
travel_assistant.invoke({"city": "北京", "travel_day": "3"})
上述两种构建方法是等效的。通过执行代码可以获得如下结果:
穿衣指数: 北京的天气在不同季节会有很大差异。春秋季节建议穿轻便的夹克或毛衣,夏季则需要轻薄的衣物,冬季则需要厚重的外套和保暖衣物。请根据具体的旅行日期查询天气预报,选择合适的衣物。
规划建议:
1. 第一天:
- 天安门广场和故宫:早上参观天安门广场,然后进入故宫,感受中国古代皇宫的宏伟。
- 王府井大街:下午可以前往王府井大街购物和品尝北京小吃。
2. 第二天:
- 长城:早上出发前往八达岭长城,体验“不到长城非好汉”的壮丽景观。
- 颐和园:下午返回市区,参观颐和园,欣赏皇家园林的美景。
3. 第三天:
- 天坛:早上参观天坛,了解古代皇帝祭天的场所。
- 南锣鼓巷:下午可以前往南锣鼓巷,感受老北京的胡同文化,品尝地道的北京小吃。
希望你在北京的旅行愉快!
使用LCEL构建大模型服务处理链非常的高效且灵活,在构建处理链的过程中,允许向Runnable对象添加调用参数,包括聊天模型参数以及绑定工具等。工具定义及调用方法可以参考上一篇文章【langchain-v0.3实战笔记】3、10分钟教你打造自己的智能体工具
travel_assistant = (
{"city": itemgetter("city"), "day": itemgetter("travel_day")}
| prompt
| model.bind(stop="|||") //绑定参数
| StrOutputParser()
)
此外langchain还允许将自定义函数,包装成Runnable对象构建处理链。
构建自定义Runnable对象
langchain支持将任意函数包装成Runnable对象,包装后的函数被称为RunnableLambdas
。这为我们提供了更加灵活的格式化输出和自定义功能组件的能力。具体的我们可以通过RunnableLambda
构造函数或者@chain
装饰器将自定义函数构建为Runnable对象。被封装后的函数可以直接使用.invoke()
调用,也可以通过LCEL表达式与其他Runnable对象进行组装。例如我们可以把搜索工具等自定义功能封装成可执行对象嵌入到处理链中,从而实现更复杂的功能。
注意: langchain仅支持将输入为单值的函数封装为Runnable对象,如果需要多值输入,必须使用 dict 类型。
(1)、使用构造函数封装函数
import requests
from langchain_core.runnables import RunnableLambda
def weather_search(dict):
query = dict["city"]+"天气情况"
top_k = dict["top_k"]
url = f"https://www.googleapis.com/customsearch/v1?key={YOU_KEY}&cx={YOU_CX}&q={query}".format(
query=query)
response = requests.get(url)
# 只保留有用信息
result = []
for item in response.json()["items"]:
if len(result)>=top_k:
break
result.append({"Title": item['title'], "Snippet": item['snippet'], "Link": item['link']})
return result
search_chain = RunnableLambda(weather_search)
search_chain.invoke({"city": "北京", "top_k": 2})
(2)、使用@chain
装饰器封装函数
import requests
from langchain_core.runnables import chain
@chain
def weather_search(dict):
query = dict["city"]+"天气情况"
top_k = dict["top_k"]
url = f"https://www.googleapis.com/customsearch/v1?key={YOU_KEY}&cx={YOU_CX}&q={query}".format(
query=query)
response = requests.get(url)
# 只保留有用信息
result = []
for item in response.json()["items"]:
if len(result)>=top_k:
break
result.append({"Title": item['title'], "Snippet": item['snippet'], "Link": item['link']})
return result
weather_search.invoke({"city": "北京", "top_k": 2})
在构建好可以获取站外数据的Runnable对象之后,我们就可以将它链接到对话模型中,从而得到一个可以回答准确天气的处理链。
构建自定义处理链
from langchain_core.prompts import ChatPromptTemplate
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你的工作是根据用户提供的上线文信息总结指定城市的天气情况",
),
("human", "content:{content},city:{city}"),
]
)
weather_assistant = (
{"content": {"city": itemgetter("city"), "top_k": itemgetter("top_k")} | weather_search ,
"city": itemgetter("city")}
| prompt
| model
| StrOutputParser()
)
weather_assistant.invoke({"city":"北京","top_k":2})
我们通过LCEL表达式将自定义的天气检索模块链接到对话模型中,从而实现将天气检索模块输出结果作为背景知识输入给下游的对话模型,从而获得更准确的天气情况。具体输出结果如下:
北京的天气预报显示,今明两天全国大部分地区将以晴朗干燥的天气为主。未来一段时间,北京也将受到冷空气的影响,南方地区昼夜温差加大,多地气温较常年同期明显偏冷。
参数透传
上述 dict 的参数输入方式,并不符合用户聊天习惯,我们也可以通过RunnablePassthrough
实现参数的透传,允许用户直接输入问题。具体如下:
from langchain_core.runnables import RunnablePassthrough
weather_assistant = (
{"content": {"city": RunnablePassthrough(), "top_k": lambda x: 2} | weather_search,
"city": RunnablePassthrough()
}
| prompt
| model
| StrOutputParser()
)
weather_assistant.invoke("北京")
注意: top_k 的赋值必须通过
lambda x: 2
将入参构造成匿名函数,不允许直接传递常量2
;此外lambda
也可以实现参数的提取,itemgetter("city")
等价于itemgetter("city")
更近一步的,langchain还允许我们将多个处理链进行组合,构建功能更加复杂的应用。例如我们可以将天气助手处理链和第一节构造的旅行助手处理链进行二次链接,从而获得一个基于真实天气情况的旅行助手。
【实战】基于天气信息的旅行助手
在第一小节的实战中,旅行规划助手由于无法得知真实的天气情况,对于穿衣指数的建议过于宽泛,不具备实际的参考价值,本小节我们将基于前文所学知识构建一个基于真实天气进行规划的旅行助手,完整代码如下:
import os
os.environ['OPENAI_API_KEY'] = "XXXXXX"
os.environ["OPENAI_API_BASE"] = "XXXXXX"
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from operator import itemgetter
from langchain_core.runnables import chain
import requests
model = ChatOpenAI(
model="gpt-4o",
temperature=0.0
)
# 天气情况chain
@chain
def weather_search(dict):
query = dict["city"] + "天气怎么样"
top_k = dict["top_k"]
url = f"https://www.googleapis.com/customsearch/v1?key={YOU_KEY}&cx={YOU_CX}&q={query}".format(
query=query)
response = requests.get(url)
# 只保留有用信息
result = []
for item in response.json()["items"]:
if len(result) >= top_k:
break
result.append({"Title": item['title'], "Snippet": item['snippet'], "Link": item['link']})
return result
# 天气助手
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你的工作是根据用户提供的上线文信息总结指定城市的天气情况",
),
("human", "content:{content},city:{city}"),
]
)
weather_assistant = (
{"content": {"city": itemgetter("city"), "top_k": itemgetter("top_k")} | weather_search,
"city": itemgetter("city")}
| prompt
| model
| StrOutputParser()
)
# 旅行助手
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"你的工作是根据用户输入的城市和旅行天数和该城市的天气情况给出对应的旅行规划建议,请使用以下回答模板\n 穿衣指数:...\n规划建议:...|||",
),
("human", "天气情况:{content}, 城市:{city},旅行天数:{day}"),
]
)
travel_assistant = (
{"content": {"city": itemgetter("city"), "top_k": itemgetter("top_k")} | weather_assistant,
"city": itemgetter("city"),
"day": itemgetter("travel_day")}
| prompt
| model.bind(stop="|||")
| StrOutputParser()
)
travel_assistant.invoke({"city": "北京", "travel_day": "3", "top_k": 2})
运行结果:
穿衣指数: 由于北京未来几天将受到冷空气的影响,建议穿着保暖的衣物,如厚外套、毛衣、围巾和手套等。同时,昼夜温差较大,建议携带可以灵活增减的衣物。
规划建议:
1. 第一天:可以选择参观室内景点,如故宫博物院、国家博物馆等,既能避寒又能欣赏丰富的文化遗产。
2. 第二天:天气晴朗干燥,适合户外活动。可以前往天坛公园、颐和园等地,享受北京的秋日美景。
3. 第三天:可以安排一些轻松的活动,如逛逛南锣鼓巷、798艺术区等,体验北京的现代文化和艺术氛围。
注意事项:由于气温较常年同期明显偏冷,出行时务必注意保暖,尤其是在早晚温差较大的时候。同时,保持适当的饮水量,防止干燥天气引起的不适。
与第一小节的例子对比可以看到,由于输入了当前城市的天气情况,穿衣指数输出部分的建议更具实际参考价值。
系列文章
【langchain实战笔记】1、构建简单LLM应用
【langchain实战笔记】2、LLM服务限速参数
【langchain实战笔记】3、构建自定义多工具智能体