LCEL:打造可观测、可扩展、可部署的 LangChain 应用

LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的"提示+LLM"链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。 LCEL 的一些亮点包括:

  1. 流支持:使用 LCEL 构建 Chain 时,你可以获得最佳的首个令牌时间(即从输出开始到首批输出生成的时间)。对于某些 Chain,这意味着可以直接从 LLM 流式传输令牌到流输出解析器,从而以与 LLM 提供商输出原始令牌相同的速率获得解析后的、增量的输出。
  2. 异步支持:任何使用 LCEL 构建的链条都可以通过同步 API(例如,在 Jupyter 笔记本中进行原型设计时)和异步 API(例如,在 LangServe 服务器中)调用。这使得相同的代码可用于原型设计和生产环境,具有出色的性能,并能够在同一服务器中处理多个并发请求。
  3. 优化的并行执行:当你的 LCEL 链条有可以并行执行的步骤时(例如,从多个检索器中获取文档),我们会自动执行,无论是在同步还是异步接口中,以实现最小的延迟。
  4. 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是使链在规模上更可靠的绝佳方式。目前我们正在添加重试/回退的流媒体支持,因此你可以在不增加任何延迟成本的情况下获得增加的可靠性。
  5. 访问中间结果:对于更复杂的链条,访问在最终输出产生之前的中间步骤的结果通常非常有用。这可以用于让最终用户知道正在发生一些事情,甚至仅用于调试链条。你可以流式传输中间结果,并且在每个 LangServe 服务器上都可用。
  6. 输入和输出模式:输入和输出模式为每个 LCEL 链提供了从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于输入和输出的验证,是 LangServe 的一个组成部分。
  7. 无缝 LangSmith 跟踪集成:随着链条变得越来越复杂,理解每一步发生了什么变得越来越重要。通过 LCEL,所有步骤都自动记录到 LangSmith,以实现最大的可观察性和可调试性。
  8. 无缝 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

如果你觉得本文有帮助,欢迎点赞👍、在看👀、转发📤,也欢迎留言💬分享你的经验!

相关推荐
数据小馒头16 小时前
MySQL并发与锁:从“防止超卖”到排查“死锁”
后端
agicall.com16 小时前
信创电话助手自动录音功能说明
人工智能·语音识别·自动录音·座机录音·固话录音
用户2986985301416 小时前
C#: 在Word文档中添加或移除可编辑区域
后端·c#
初次攀爬者16 小时前
RAG核心升级|多LLM模型动态切换方案
人工智能·后端·ai编程
这儿有一堆花16 小时前
Python 虚拟环境的配置与管理指南
开发语言·python
yunni816 小时前
知识库 × AI写作:打通公文写作的“最后一公里”
大数据·人工智能
Baihai_IDP16 小时前
Andrej Karpathy:2025 年 LLM 领域的六项范式转变
人工智能·面试·llm
EntyIU16 小时前
自己实现mybatisplus的批量插入
java·后端
踩着两条虫16 小时前
VTJ.PRO「AI + 低代码」应用开发平台的后端模块系统
前端·人工智能·低代码