LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的"提示+LLM"链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。 LCEL 的一些亮点包括:
- 流支持:使用 LCEL 构建 Chain 时,你可以获得最佳的首个令牌时间(即从输出开始到首批输出生成的时间)。对于某些 Chain,这意味着可以直接从 LLM 流式传输令牌到流输出解析器,从而以与 LLM 提供商输出原始令牌相同的速率获得解析后的、增量的输出。
- 异步支持:任何使用 LCEL 构建的链条都可以通过同步 API(例如,在 Jupyter 笔记本中进行原型设计时)和异步 API(例如,在 LangServe 服务器中)调用。这使得相同的代码可用于原型设计和生产环境,具有出色的性能,并能够在同一服务器中处理多个并发请求。
- 优化的并行执行:当你的 LCEL 链条有可以并行执行的步骤时(例如,从多个检索器中获取文档),我们会自动执行,无论是在同步还是异步接口中,以实现最小的延迟。
- 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是使链在规模上更可靠的绝佳方式。目前我们正在添加重试/回退的流媒体支持,因此你可以在不增加任何延迟成本的情况下获得增加的可靠性。
- 访问中间结果:对于更复杂的链条,访问在最终输出产生之前的中间步骤的结果通常非常有用。这可以用于让最终用户知道正在发生一些事情,甚至仅用于调试链条。你可以流式传输中间结果,并且在每个 LangServe 服务器上都可用。
- 输入和输出模式:输入和输出模式为每个 LCEL 链提供了从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于输入和输出的验证,是 LangServe 的一个组成部分。
- 无缝 LangSmith 跟踪集成:随着链条变得越来越复杂,理解每一步发生了什么变得越来越重要。通过 LCEL,所有步骤都自动记录到 LangSmith,以实现最大的可观察性和可调试性。
- 无缝 LangServe 部署集成:任何使用 LCEL 创建的链都可以轻松地使用 LangServe 进行部署。
1 Pipeline 式调用
Pipeline 式调用 PromptTemplate, LLM 和 OutputParser
python
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field
from typing import List, Dict, Optional
from enum import Enum
import json
from langchain.chat_models import init_chat_model
python
# 输出结构
class SortEnum(str, Enum):
data = 'data'
price = 'price'
class OrderingEnum(str, Enum):
ascend = 'ascend'
descend = 'descend'
class Semantics(BaseModel):
name: Optional[str] = Field(description="流量包名称", default=None)
price_lower: Optional[int] = Field(description="价格下限", default=None)
price_upper: Optional[int] = Field(description="价格上限", default=None)
data_lower: Optional[int] = Field(description="流量下限", default=None)
data_upper: Optional[int] = Field(description="流量上限", default=None)
sort_by: Optional[SortEnum] = Field(description="按价格或流量排序", default=None)
ordering: Optional[OrderingEnum] = Field(
description="升序或降序排列", default=None)
# Prompt 模板
prompt = ChatPromptTemplate.from_messages(
[
("system", "你是一个语义解析器。你的任务是将用户的输入解析成JSON表示。不要回答用户的问题。"),
("human", "{text}"),
]
)
# 模型
llm = init_chat_model("deepseek-chat", model_provider="deepseek")
structured_llm = llm.with_structured_output(Semantics)
# LCEL 表达式
runnable = (
{"text": RunnablePassthrough()} | prompt | structured_llm
)
# 直接运行
ret = runnable.invoke("不超过100元的流量大的套餐有哪些")
print(
json.dumps(
ret.model_dump(),
indent = 4,
ensure_ascii=False
)
)
使用 LCEL 的价值,也就是 LangChain 的核心价值。
2 用 LCEL 实现 RAG
python
import os
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.chains import RetrievalQA
from langchain_community.document_loaders import PyMuPDFLoader
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
# 加载文档
loader = PyMuPDFLoader("./data/deepseek-v3-1-4.pdf")
pages = loader.load_and_split()
# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=200,
length_function=len,
add_start_index=True,
)
texts = text_splitter.create_documents(
[page.page_content for page in pages[:1]]
)
# 灌库
embeddings = DashScopeEmbeddings(
model="text-embedding-v1", dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
)
db = FAISS.from_documents(texts, embeddings)
# 检索 top-5 结果
retriever = db.as_retriever(search_kwargs={"k": 5})
python
docs = retriever.invoke("deepseek v3有多少参数")
for doc in docs:
print(doc.page_content)
print("----")
python
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
# Prompt模板
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# Chain
rag_chain = (
{"question": RunnablePassthrough(), "context": retriever}
| prompt
| llm
| StrOutputParser()
)
rag_chain.invoke("deepseek V3有多少参数")
'DeepSeek-V3 是一个混合专家(Mixture-of-Experts, MoE)语言模型,总参数量为6710亿(671B),其中每个 token 激活**370亿(37B)**参数。'
3 用 LCEL 实现模型切换(工厂模式)
python
from langchain_core.runnables.utils import ConfigurableField
from langchain_community.chat_models import QianfanChatEndpoint
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
)
from langchain.chat_models import init_chat_model
from langchain.schema import HumanMessage
import os
# 模型1
ds_model = init_chat_model("deepseek-chat", model_provider="deepseek")
# 模型2
gpt_model = init_chat_model("gpt-4o-mini", model_provider="openai")
# 通过 configurable_alternatives 按指定字段选择模型
model = gpt_model.configurable_alternatives(
ConfigurableField(id="llm"),
default_key="gpt",
deepseek=ds_model,
# claude=claude_model,
)
# Prompt 模板
prompt = ChatPromptTemplate.from_messages(
[
HumanMessagePromptTemplate.from_template("{query}"),
]
)
# LCEL
chain = (
{"query": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 运行时指定模型 "gpt" or "deepseek"
ret = chain.with_config(configurable={"llm": "deepseek"}).invoke("请自我介绍")
print(ret)
思考:从模块间解依赖角度,LCEL 的意义是什么?
4 配置运行时变量
在实际应用中,我们经常需要在运行时动态配置链的参数,而不是在定义时就固定下来。LCEL 提供了with_config()方法来实现运行时配置。
**场景示例:**根据不同的用户角色,使用不同的 temperature 参数,或者根据业务需求动态调整模型的 max_tokens。
python
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain.chat_models import init_chat_model
# 创建基础链
prompt = ChatPromptTemplate.from_template("请用{tone}的语气回答:{question}")
llm = init_chat_model("qwen-plus", model_provider="dashscope")
chain = prompt | llm | StrOutputParser()
# 方式1:使用 with_config 配置运行时变量
result1 = chain.with_config(
configurable={"tone": "专业"}
).invoke({"question": "什么是人工智能?"})
print(result1)
# 方式2:在 invoke 时传入 config
result2 = chain.invoke(
{"question": "什么是人工智能?"},
config={"configurable": {"tone": "轻松幽默"}}
)
print(result2)
更高级的用法:配置模型参数
python
from langchain_core.runnables import RunnableConfig
# 创建可配置的模型
llm = init_chat_model("qwen-plus", model_provider="dashscope")
# 在运行时配置模型参数
configurable_llm = llm.with_config(
configurable={
"temperature": 0.7,
"max_tokens": 1000
}
)
prompt = ChatPromptTemplate.from_template("回答:{question}")
chain = prompt | configurable_llm | StrOutputParser()
result = chain.invoke({"question": "解释一下机器学习"})
提示:运行时配置的优势在于,同一个链可以在不同场景下复用,只需在调用时传入不同的配置即可,无需创建多个链实例。
5 故障回退
在生产环境中,LLM API 调用可能会因为网络问题、服务限流、模型错误等原因失败。为了提高系统的健壮性,LCEL 提供了with_fallbacks()方法来设置故障回退机制。
**场景示例:**当主模型(如 qwen-plus)调用失败时,自动切换到备用模型(如 qwen-turbo)。
python
from langchain.chat_models import init_chat_model
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableLambda
# 主模型
primary_llm = init_chat_model("qwen-plus", model_provider="dashscope")
# 备用模型(通常选择更便宜或更稳定的模型)
fallback_llm = init_chat_model("qwen-turbo", model_provider="dashscope")
# 创建带故障回退的链
prompt = ChatPromptTemplate.from_template("回答:{question}")
# 方式1:为整个链设置回退
chain_with_fallback = (
prompt
| primary_llm.with_fallbacks([fallback_llm])
| StrOutputParser()
)
result = chain_with_fallback.invoke({"question": "什么是深度学习?"})
print(result)
多级回退策略
python
# 可以设置多个回退,按顺序尝试
backup_llm1 = init_chat_model("qwen-turbo", model_provider="dashscope")
backup_llm2 = init_chat_model("deepseek-chat", model_provider="deepseek")
# 自定义回退处理函数
def custom_fallback(input_data):
return "抱歉,当前服务暂时不可用,请稍后重试。"
robust_chain = (
prompt
| primary_llm.with_fallbacks([
backup_llm1,
backup_llm2,
RunnableLambda(custom_fallback)
])
| StrOutputParser()
)
针对特定异常的回退
python
from langchain_core.runnables import RunnableLambda
def handle_rate_limit_error(input_data):
"""处理限流错误"""
return "当前请求过于频繁,请稍后再试。"
def handle_timeout_error(input_data):
"""处理超时错误"""
return "请求超时,请检查网络连接。"
# 可以根据不同异常类型设置不同的回退策略
chain = prompt | primary_llm.with_fallbacks([
(fallback_llm, lambda e: isinstance(e, Exception))
]) | StrOutputParser()
最佳实践:在生产环境中,建议至少设置一个备用模型,并记录失败日志,以便后续分析和优化。
6 并行调用
当链中有多个可以独立执行的任务时,并行执行可以显著提高效率。LCEL 的RunnableParallel允许同时执行多个子链。
**场景示例:**同时从多个数据源检索信息,或者同时执行多个独立的分析任务。
python
from langchain_core.runnables import RunnableParallel, RunnablePassthrough
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
llm = init_chat_model("qwen-plus", model_provider="dashscope")
# 定义多个独立的处理任务
summarize_prompt = ChatPromptTemplate.from_template("总结以下内容:{text}")
translate_prompt = ChatPromptTemplate.from_template("将以下内容翻译成英文:{text}")
analyze_prompt = ChatPromptTemplate.from_template("分析以下内容的情感倾向:{text}")
# 创建并行执行的链
parallel_chain = RunnableParallel({
"summary": (
{"text": RunnablePassthrough()}
| summarize_prompt
| llm
| StrOutputParser()
),
"translation": (
{"text": RunnablePassthrough()}
| translate_prompt
| llm
| StrOutputParser()
),
"sentiment": (
{"text": RunnablePassthrough()}
| analyze_prompt
| llm
| StrOutputParser()
)
})
# 执行并行任务
text = "今天天气真好,适合出去散步。"
results = parallel_chain.invoke(text)
print(f"总结:{results['summary']}")
print(f"翻译:{results['translation']}")
print(f"情感:{results['sentiment']}")
结合 RAG 的并行检索
python
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
import os
# 假设有多个知识库
embeddings = DashScopeEmbeddings(
model="text-embedding-v1",
dashscope_api_key=os.getenv("DASHSCOPE_API_KEY")
)
# 创建多个检索器(实际应用中可能是不同的知识库)
retriever1 = db1.as_retriever(search_kwargs={"k": 3})
retriever2 = db2.as_retriever(search_kwargs={"k": 3})
retriever3 = db3.as_retriever(search_kwargs={"k": 3})
# 并行检索多个知识库
parallel_retrieval = RunnableParallel({
"tech_docs": retriever1,
"product_docs": retriever2,
"faq_docs": retriever3
})
# 同时从三个知识库检索
docs = parallel_retrieval.invoke("如何使用API?")
# 合并检索结果
all_docs = docs["tech_docs"] + docs["product_docs"] + docs["faq_docs"]
性能提升:并行调用可以将多个串行任务的总耗时从 (O(n)) 降低到 (O(1)),在需要处理多个独立任务时效果显著。
7 逻辑分支
在实际应用中,我们经常需要根据输入或中间结果动态选择执行路径。LCEL 支持通过RunnableLambda和条件判断实现逻辑分支。
**场景示例:**根据用户问题的复杂度,选择不同的处理策略;或者根据检索结果的质量,决定是否需要进一步处理。
python
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
llm_simple = init_chat_model("qwen-turbo", model_provider="dashscope")
llm_complex = init_chat_model("qwen-plus", model_provider="dashscope")
# 定义不同复杂度的处理链
simple_prompt = ChatPromptTemplate.from_template("简单回答:{question}")
complex_prompt = ChatPromptTemplate.from_template(
"详细分析并回答:{question},请提供多个角度的见解。"
)
simple_chain = simple_prompt | llm_simple | StrOutputParser()
complex_chain = complex_prompt | llm_complex | StrOutputParser()
# 判断问题复杂度的函数
def route_by_complexity(input_data):
question = input_data["question"]
# 简单的复杂度判断逻辑(实际应用中可以使用更复杂的判断)
complex_keywords = ["分析", "对比", "详细", "原理", "机制"]
is_complex = any(keyword in question for keyword in complex_keywords)
if is_complex:
return complex_chain.invoke(input_data)
else:
return simple_chain.invoke(input_data)
# 创建路由链
routing_chain = RunnableLambda(route_by_complexity)
# 测试
result1 = routing_chain.invoke({"question": "今天天气怎么样?"})
result2 = routing_chain.invoke({"question": "请详细分析深度学习和机器学习的区别"})
print(f"简单问题:{result1}")
print(f"复杂问题:{result2}")
基于 LLM 的智能路由
python
from langchain_core.output_parsers import StrOutputParser
from pydantic import BaseModel, Field
from enum import Enum
class RouteType(str, Enum):
SIMPLE = "simple"
COMPLEX = "complex"
TECHNICAL = "technical"
class RouteDecision(BaseModel):
route: RouteType = Field(description="路由类型")
# 使用 LLM 判断路由
router_llm = llm_simple.with_structured_output(RouteDecision)
router_prompt = ChatPromptTemplate.from_template(
"判断以下问题应该使用哪种处理方式(simple/complex/technical):{question}"
)
router_chain = router_prompt | router_llm
# 定义不同路由的处理链
chains = {
RouteType.SIMPLE: simple_chain,
RouteType.COMPLEX: complex_chain,
RouteType.TECHNICAL: (
ChatPromptTemplate.from_template("从技术角度详细解答:{question}")
| llm_complex
| StrOutputParser()
)
}
def smart_route(input_data):
# 先判断路由
decision = router_chain.invoke(input_data)
# 根据路由选择链
selected_chain = chains[decision.route]
# 执行选中的链
return selected_chain.invoke(input_data)
smart_routing_chain = RunnableLambda(smart_route)
基于检索结果质量的路由
python
# 如果检索到的文档相关性高,直接使用 RAG;否则使用通用模型
def route_by_retrieval_quality(input_data):
question = input_data["question"]
docs = retriever.invoke(question)
# 简单的相关性判断(实际可以使用更复杂的评分机制)
if docs and len(docs) >= 3:
# 使用 RAG 链
return rag_chain.invoke(question)
else:
# 使用通用模型
return general_chain.invoke({"question": question})
retrieval_routing_chain = RunnableLambda(route_by_retrieval_quality)
设计模式:逻辑分支让链具备了"智能决策"能力,可以根据上下文动态调整处理策略,这是构建复杂 AI 应用的关键技术。
8 动态创建 Chain
在某些场景下,我们需要根据运行时信息动态构建链的结构。LCEL 支持在运行时创建和组合链组件。
**场景示例:**根据用户选择的工具动态构建处理链,或者根据配置动态组合不同的处理步骤。
python
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import StrOutputParser
from typing import List
llm = init_chat_model("qwen-plus", model_provider="dashscope")
# 定义可用的处理步骤
def create_summarize_step():
prompt = ChatPromptTemplate.from_template("总结:{text}")
return prompt | llm | StrOutputParser()
def create_translate_step(target_lang="英文"):
prompt = ChatPromptTemplate.from_template(f"翻译成{target_lang}:{{text}}")
return prompt | llm | StrOutputParser()
def create_analyze_step():
prompt = ChatPromptTemplate.from_template("分析:{text}")
return prompt | llm | StrOutputParser()
# 动态创建链的函数
def create_dynamic_chain(steps: List[str], config: dict = None):
"""
根据步骤列表动态创建链
steps: 步骤列表,如 ["summarize", "translate", "analyze"]
"""
step_functions = {
"summarize": create_summarize_step,
"translate": lambda: create_translate_step(
config.get("target_lang", "英文") if config else "英文"
),
"analyze": create_analyze_step
}
# 动态组合链
chain = RunnablePassthrough()
for step_name in steps:
step_chain = step_functions[step_name]()
# 将前一步的输出作为下一步的输入
chain = chain | step_chain
return chain
# 使用示例
dynamic_chain = create_dynamic_chain(
steps=["summarize", "translate", "analyze"],
config={"target_lang": "英文"}
)
result = dynamic_chain.invoke("今天是一个重要的日子,我们完成了项目的关键里程碑。")
print(result)
基于配置文件的动态链构建
python
import json
# 配置文件示例
chain_config = {
"steps": [
{"type": "summarize", "enabled": True},
{"type": "translate", "enabled": True, "target_lang": "英文"},
{"type": "analyze", "enabled": False}
],
"model": "qwen-plus"
}
def build_chain_from_config(config: dict):
"""根据配置文件构建链"""
llm = init_chat_model(config["model"], model_provider="dashscope")
chain = RunnablePassthrough()
for step_config in config["steps"]:
if not step_config.get("enabled", True):
continue
step_type = step_config["type"]
if step_type == "summarize":
prompt = ChatPromptTemplate.from_template("总结:{text}")
step_chain = prompt | llm | StrOutputParser()
elif step_type == "translate":
target_lang = step_config.get("target_lang", "英文")
prompt = ChatPromptTemplate.from_template(f"翻译成{target_lang}:{{text}}")
step_chain = prompt | llm | StrOutputParser()
elif step_type == "analyze":
prompt = ChatPromptTemplate.from_template("分析:{text}")
step_chain = prompt | llm | StrOutputParser()
else:
continue
chain = chain | step_chain
return chain
# 根据配置构建链
configured_chain = build_chain_from_config(chain_config)
result = configured_chain.invoke("这是一个测试文本")
条件式动态链构建
python
def create_conditional_chain(input_data: dict):
"""根据输入数据动态决定链的结构"""
text = input_data.get("text", "")
task_type = input_data.get("task_type", "general")
llm = init_chat_model("qwen-plus", model_provider="dashscope")
if task_type == "qa":
# 问答任务:检索 + 生成
prompt = ChatPromptTemplate.from_template(
"基于以下上下文回答问题:\n{context}\n\n问题:{question}"
)
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
elif task_type == "summarize":
# 摘要任务
prompt = ChatPromptTemplate.from_template("总结:{text}")
chain = prompt | llm | StrOutputParser()
else:
# 通用任务
prompt = ChatPromptTemplate.from_template("处理:{text}")
chain = prompt | llm | StrOutputParser()
return chain
# 使用
qa_chain = create_conditional_chain({"task_type": "qa"})
summary_chain = create_conditional_chain({"task_type": "summarize"})
灵活性:动态创建链让应用具备了高度的灵活性,可以根据用户需求、业务规则或运行时条件构建最适合的处理流程,这是构建可配置 AI 系统的关键能力。
总结
通过以上章节,我们了解了 LCEL 的核心能力:
- 1:Pipeline 式调用 PromptTemplate、LLM 和 OutputParser
- 2:用 LCEL 实现 RAG
- 3:用 LCEL 实现模型切换(工厂模式)
- 4:运行时配置,让链具备动态参数能力
- 5:故障回退,提高系统健壮性
- 6:并行调用,提升处理效率
- 7:逻辑分支,实现智能路由
- 8:动态创建,构建灵活系统
这些能力组合使用,可以构建出既强大又灵活的 AI 应用系统。
🎉END
如果你觉得本文有帮助,欢迎点赞👍、在看👀、转发📤,也欢迎留言💬分享你的经验!