1. LCEL 能为你做什么?为什么它现在才变得重要?
1. LCEL 能为你做什么?为什么现在特别重要?
在构建 AI 应用的过程中,你可能已经发现:随着逻辑复杂度上升,LangChain 的代码会变得越来越冗长、难维护。组件嵌套、数据流转不清晰,调试也愈发困难。这正是 LCEL(LangChain Expression Language) 诞生的背景。
它是一种声明式语言,让你用更简洁、直观的方式组织 LangChain 组件,构建高可读性、高复用性的处理链,尤其适合应对日益复杂的 LLM 应用开发需求。
简单来说:LangChain 越复杂,越需要 LCEL。
LangChain 命令式 vs LCEL
LangChain命令式:
from langchain_core.prompts.chat import ( ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate)from langchain_community.chat_models import ChatOllama# 系统提示词模版template = "你是一个翻译专家,你擅长将 {input_language} 翻译成 {output_language}"system_message_prompt = SystemMessagePromptTemplate.from_template(template)# 用户提示词模版human_template = "{text}"human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)# 构建对话的完整提示词模版chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])# 格式化输入,生成完整对话的消息message = chat_prompt.format_messages(input_language="English", output_language="中文", text="I like swimming")# 使用llama3模型,执行对话并生成内容llm = ChatOllama(model="llama3.2")response = llm.invoke(message)print(response.content)
使用LCEL:
from langchain_core.prompts.chat import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplatefrom langchain_community.chat_models import ChatOllamafrom langchain_core.output_parsers import StrOutputParser# 创建提示模板system_template = "你是一个翻译专家,你擅长将 {input_language} 翻译成 {output_language}"system_message_prompt = SystemMessagePromptTemplate.from_template(system_template)human_message_prompt = HumanMessagePromptTemplate.from_template("{text}")chat_prompt = ChatPromptTemplate.from_messages([system_message_prompt, human_message_prompt])# 创建模型和输出解析器llm = ChatOllama(model="llama3.2")output_parser = StrOutputParser()# 创建LCEL链chain = chat_prompt | llm | output_parser# 使用链result = chain.invoke({ "input_language": "English", "output_language": "中文", "text": "I like swimming"})print(result)
LCEL使用简单的|
操作符连接各个组件,将不同的组件链接在一起,将一个组件的输出作为下一个组件的输入。在这个链条中,用户输入被传递给提示模板,然后提示模板的输出被传递给模型,然后模型的输出被传递给输出解析器。
为什么现在需要LCEL?
- 代码简洁性:使用
|
运算符连接组件,消除了大量中间变量和嵌套调用可维护性:数据流向清晰明了,使代码更易理解和修改组件复用:可以轻松提取、重组和重用链的各个部分一致性接口:统一的调用方式(invoke
、stream
等),无需担心不同组件的接口差异2. LCEL 的设计哲学与编程范式
在深入LCEL细节之前,我们需要理解其背后的核心设计理念,这将帮助你以正确的思维模式来设计和开发LCEL应用。
一切皆 Runnable:统一抽象的力量
LCEL的基础是Runnable
接口,所有组件(从提示模板到语言模型再到输出解析器)都实现了这个接口,具备相同的基本能力:
invoke() # 同步调用ainvoke() # 异步调用batch() # 批量处理abatch() # 异步批量处理stream() # 流式处理astream() # 异步流式处理
这种统一抽象极大简化了组件间的集成,因为每个组件都以相同的方式"说话"。
声明式 vs 命令式:控制流转为数据流
- LCEL的核心是将命令式编程(关注"如何做")转变为声明式编程(关注"做什么")。这种转变让我们能够专注于定义数据流向,而不是手动管理每一步的执行细节。
3. LCEL 的核心能力与特性
现在让我们深入了解LCEL提供的实际能力,并通过具体示例看看如何利用这些特性构建高效的AI应用。
可组合性与可重用性
LCEL的核心优势在于其极强的组合能力,让你可以像搭积木一样构建复杂流程:
# 基本组件prompt = ChatPromptTemplate.from_template("写一篇关于{topic}的总结,50字以内")llm = ChatOllama(model="llama3.2")output_parser = StrOutputParser()# 组合成链content_chain = prompt | llm | output_parser# 构建复杂链advanced_chain = ( RunnablePassthrough.assign( content=lambda x: content_chain.invoke({"topic": x["topic"]})) | ChatPromptTemplate.from_template("将这段内容的关键字提取出来:{content}") | llm | output_parser)# 显式调用链result = advanced_chain.invoke({"topic": "人工智能"})print(result)
这种设计让你可以构建可重用的链组件
并发任务
# 并行执行多个检索器from langchain_core.prompts import ChatPromptTemplatefrom langchain_core.runnables import RunnableParallelfrom langchain_community.chat_models import ChatOllama# 基础组件prompt = ChatPromptTemplate.from_template("给我讲一个关于 {topic} 的笑话")llm = ChatOllama(model="llama3.2")# 绑定变量后再组装chain_1 = prompt.partial(topic="程序员") | llmchain_2 = prompt.partial(topic="人工智能") | llm# 并发执行parallel_jokes = RunnableParallel( joke1 = chain_1, joke2 = chain_2,)# 调用results = parallel_jokes.invoke({})# 输出结果print("程序员笑话:", results["joke1"].content)print("AI 笑话:", results["joke2"].content)
流式输出
from langchain_core.prompts import ChatPromptTemplateprompt = ChatPromptTemplate.from_template("给我讲一个关于 {topic} 的笑话")llm = ChatOllama(model="llama3.2")chain = prompt | llm# 流式输出for chunk in chain.stream({"topic": "冰淇淋"}): print(chunk.content, end="", flush=True)
异步调用
import asynciofrom langchain_core.prompts import ChatPromptTemplate# 定义 prompt 和模型prompt = ChatPromptTemplate.from_template("给我讲一个关于 {topic} 的笑话")llm = ChatOllama(model="llama3.2")chain = prompt | llmawait chain.ainvoke({"topic": "雪花"})
错误处理与回退机制
在LLM应用程序中,可能会出现许多故障点,无论是LLM API的问题、模型输出不佳、其他集成的问题等等。回退可以帮助您优雅地处理和隔离这些问题。
from langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import PromptTemplatefrom langchain_core.runnables import RunnableLambdafrom langchain_ollama import ChatOllama# 定义提示模板和解析器prompt = PromptTemplate.from_template("请将以下文本翻译为英文:{text}")llm = ChatOllama(model="llama3.2")parser = StrOutputParser()chain = prompt | llm | parser# 定义备用链,返回默认响应fallback_chain = RunnableLambda(lambda x: "无法翻译输入文本。")# 创建带有回退机制的链chain_with_fallback = chain.with_fallbacks([fallback_chain])# 执行链result = chain_with_fallback.invoke({"text": "你好"})print(result)
这段代码正常情况输出:Hello
,如果我故意让模型调用出错,比如修改为
chain_with_fallback.invoke({"text66": "你好"})
输出结果就会是:无法翻译输入文本。
4. LCEL 应用场景示例
4.1 多轮对话代理:融合记忆机制与工具调用能力
在实际应用中,我们常常希望构建一个不仅能自然对话、还能调用外部功能(如天气查询、数据库检索等)且具备上下文记忆的智能体。通过 LCEL(LangChain Expression Language)构建这样的 多链组合 Agent,可以轻松实现:
- 语言模型(LLM) :作为核心推理引擎,负责自然语言理解和响应生成。工具(Tools) :将外部函数封装成可调用的能力组件,供模型根据上下文自动使用。记忆(Memory) :保留多轮对话历史,实现跨轮次的上下文理解与指代解析。推理过程(Memory) :可以选择展示推理过程
下面的示例中,我们构建了一个具备 “记忆 + 工具调用 + 对话” 三种能力的对话代理。用户可以先提问天气,再在后续轮次中基于上下文继续提问,比如“那我该穿什么?”——代理会通过记忆理解,结合第一个问题的回答来答复第2个问题。
from langchain.agents import initialize_agent, Tool, AgentTypefrom langchain.memory import ConversationBufferMemoryfrom langchain_ollama import ChatOllamadef fake_weather(city: str) -> str: """获取指定城市的天气情况""" return f"{city} 今天多云转晴,最高气温 22℃,最低气温 10℃,北风微风,空气良。"# 定义工具, Agent 根据描述调用weather_tool = Tool( name="天气查询", func=fake_weather, description="获取指定城市的天气情况,输入应该是城市名称,例如:北京")tools = [weather_tool]# 初始化模型llm = ChatOllama(model="llama3.2", temperature=0)# 创建记忆,便于多轮对话理解上下文。memory = ConversationBufferMemory( memory_key="chat_history", return_messages=True)# 创建代理,具备工具调用能力的智能体(Agent)agent = initialize_agent( tools=tools, llm=llm, agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION, #对话型 Agent 类型,结合工具调用和语言模型推理,适合对话交互。 memory=memory, verbose=True # 可选,会打印 Agent 推理过程。)# 测试对话user_inputs = [ "帮我查一下北京的天气", "那明天我去那边穿什么合适?"]for i, user_input in enumerate(user_inputs, 1): print(f"\n[轮次 {i}]") print(f"用户: {user_input}") try: response = agent.run(user_input) print(f"助手: {response}") except Exception as e: print(f"出现错误: {e}")
输出
5. 总结与下一步建议
LCEL 的价值总结
- 简化复杂性:通过声明式语法处理复杂的AI组件交互提高可维护性:清晰展示数据流,使代码更易理解增强复用性:轻松组合和重用链的各个部分生态系统支持:与监控、调试和部署工具无缝集成
延伸学习路径
下一步我们继续带大家快速掌握:
- LangGraph:基于LCEL构建更复杂的状态管理和循环控制流LangServe:将LCEL链部署为可扩展的API服务LangSmith:深入跟踪和调试链的执行情况AutoGraph:自动化工作流构建和优化