为什么需要LCEL?
LangChain的早期版本饱受诟病:过度封装、调试困难、灵活性差。2023年底,LangChain推出了 LCEL(LangChain Expression Language),用一种声明式的链式语法重塑了AI应用的构建方式。
LCEL的核心理念是:将AI应用表达为数据流管道,而非命令式的函数调用序列。这种范式转变带来了自动的流式处理、并行执行和异步支持,同时保留了完整的可调试性。
LCEL基础:管道操作符 |
LCEL最标志性的特性是用 | 操作符连接组件:
from langchain_anthropic import ChatAnthropic
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
# 初始化模型
model = ChatAnthropic(model="claude-3-5-sonnet-20241022")
# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个专业的技术文档作者,用中文写作。"),
("human", "请用{format}格式解释:{concept}")
])
# 输出解析器
parser = StrOutputParser()
# 用 | 构建链:prompt → model → parser
chain = prompt | model | parser
# 调用链
result = chain.invoke({
"format": "简洁的5步骤列表",
"concept": "什么是梯度下降"
})
print(result)
这看起来简单,但背后的机制非常强大:
- 类型安全:每个组件声明输入/输出类型,不匹配时提前报错
- 自动流式:所有LCEL链天然支持
.stream()方法 - 异步支持:所有同步方法都有对应的
ainvoke()、astream()版本 - 内置追踪:与LangSmith无缝集成,自动记录每个步骤的输入输出
核心组件深度解析
RunnablePassthrough:数据透传与注入
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
# 场景:在管道中同时传递原始输入和中间结果
retrieval_chain = (
{
"context": retriever, # 检索到的文档
"question": RunnablePassthrough(), # 原始问题直接传递
}
| prompt
| model
| parser
)
# RunnablePassthrough.assign:向数据流中添加新字段(不丢弃原有字段)
enriched_chain = (
RunnablePassthrough.assign(
# 在保留原始输入的基础上,添加检索结果
context=lambda x: retriever.invoke(x["question"]),
timestamp=lambda x: "2026-04-27",
)
| prompt
| model
| parser
)
RunnableLambda:将任意函数集成到管道
from langchain_core.runnables import RunnableLambda
import json
# 将普通函数包装为Runnable
def parse_json_safely(text: str) -> dict:
"""从LLM输出中安全解析JSON"""
import re
match = re.search(r'\{.*\}', text, re.DOTALL)
if match:
return json.loads(match.group())
return {"error": "无法解析JSON", "raw": text}
json_parser = RunnableLambda(parse_json_safely)
# 集成到链中
structured_chain = (
prompt
| model
| StrOutputParser()
| json_parser
)
result = structured_chain.invoke({"query": "列出Python的5个核心特性,返回JSON"})
# result是一个dict,而不是字符串
RunnableParallel:并行执行提升效率
from langchain_core.runnables import RunnableParallel
# 并行执行多个独立任务
parallel_analysis = RunnableParallel(
sentiment=sentiment_chain, # 情感分析链
keywords=keyword_chain, # 关键词提取链
summary=summary_chain, # 文本摘要链
language=language_detect_chain, # 语言识别链
)
# 所有4个链并行执行,总耗时≈最慢的那个,而非4个之和
result = parallel_analysis.invoke({"text": "用户输入的文本..."})
print(result)
# {
# "sentiment": "positive",
# "keywords": ["AI", "LLM", "应用"],
# "summary": "...",
# "language": "zh-CN"
# }
高级模式:条件路由与动态链
基于内容的条件路由
from langchain_core.runnables import RunnableBranch
# 根据用户意图路由到不同的处理链
router = RunnableBranch(
# (条件函数, 对应的链)
(lambda x: "代码" in x["question"] or "programming" in x["question"].lower(),
code_assistant_chain),
(lambda x: "数学" in x["question"] or "计算" in x["question"],
math_assistant_chain),
(lambda x: "翻译" in x["question"],
translation_chain),
# 默认链(不满足任何条件时执行)
general_assistant_chain,
)
# 路由会自动选择正确的链
result = router.invoke({"question": "帮我写一个快速排序的Python代码"})
# 自动路由到 code_assistant_chain
动态Few-Shot提示选择
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_anthropic import AnthropicEmbeddings
from langchain_core.prompts import FewShotChatMessagePromptTemplate
# 准备Example库
examples = [
{"input": "2+2", "output": "4"},
{"input": "SQL注入如何防范", "output": "使用参数化查询..."},
{"input": "解释REST API", "output": "REST是一种架构风格..."},
# ... 更多例子
]
# 基于语义相似度动态选择最相关的例子
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples,
AnthropicEmbeddings(),
InMemoryVectorStore,
k=3, # 选择最相似的3个例子
)
few_shot_prompt = FewShotChatMessagePromptTemplate(
example_selector=example_selector,
example_prompt=ChatPromptTemplate.from_messages([
("human", "{input}"),
("ai", "{output}"),
]),
)
dynamic_chain = (
few_shot_prompt
| ChatPromptTemplate.from_messages([
("system", "你是一个帮助回答技术问题的助手。"),
("placeholder", "{examples}"),
("human", "{question}"),
])
| model
| parser
)
流式输出:实时响应用户
import asyncio
async def stream_response(question: str):
"""异步流式输出,实现打字机效果"""
chain = prompt | model | parser
print("回答:", end="", flush=True)
async for chunk in chain.astream({"question": question}):
print(chunk, end="", flush=True)
# 在实际应用中,这里可以通过WebSocket推送给前端
print() # 换行
# 在FastAPI中集成流式输出
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.get("/chat/stream")
async def chat_stream(question: str):
async def generate():
async for chunk in chain.astream({"question": question}):
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(
generate(),
media_type="text/event-stream"
)
完整RAG应用示例
from langchain_community.vectorstores import Chroma
from langchain_anthropic import AnthropicEmbeddings
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
# 构建知识库
def build_knowledge_base(documents: list[str]) -> Chroma:
"""从文档列表构建向量知识库"""
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
)
docs = [Document(page_content=doc) for doc in documents]
splits = text_splitter.split_documents(docs)
return Chroma.from_documents(
splits,
AnthropicEmbeddings(),
persist_directory="./chroma_db",
)
# 构建RAG链(LCEL风格)
def build_rag_chain(vectorstore: Chroma):
retriever = vectorstore.as_retriever(
search_type="mmr", # Maximum Marginal Relevance,减少冗余
search_kwargs={"k": 5, "fetch_k": 20},
)
prompt = ChatPromptTemplate.from_messages([
("system", """你是一个知识库助手。基于提供的上下文回答问题。
上下文:
{context}
规则:
1. 只根据提供的上下文回答,不要编造信息
2. 如果上下文中没有相关信息,明确说明"根据现有信息无法回答"
3. 引用具体的信息来源时,使用[来源N]的格式"""),
("human", "{question}")
])
def format_docs(docs):
return "\n\n".join([
f"[来源{i+1}] {doc.page_content}"
for i, doc in enumerate(docs)
])
rag_chain = (
{
"context": retriever | RunnableLambda(format_docs),
"question": RunnablePassthrough(),
}
| prompt
| model
| parser
)
return rag_chain
# 使用
vectorstore = build_knowledge_base(["文档1内容...", "文档2内容..."])
rag_chain = build_rag_chain(vectorstore)
answer = rag_chain.invoke("LangChain LCEL有哪些核心优势?")
LCEL调试技巧
# 技巧1:在管道中间打印调试信息
def debug_step(name: str):
def _debug(x):
print(f"\n[DEBUG] {name}:")
print(f" 输入类型: {type(x)}")
if isinstance(x, str):
print(f" 内容预览: {x[:100]}...")
elif isinstance(x, dict):
print(f" 键: {list(x.keys())}")
return x
return RunnableLambda(_debug)
debug_chain = (
prompt
| debug_step("prompt输出")
| model
| debug_step("model输出")
| parser
| debug_step("parser输出")
)
# 技巧2:使用 .with_config 为链添加运行时配置
configurable_chain = chain.with_config({
"run_name": "my_debug_run", # 在LangSmith中显示的名称
"tags": ["debug", "v2"],
"metadata": {"experiment_id": "exp_001"},
})
总结
LCEL将LangChain从"一堆抽象类的堆砌"变成了"优雅的数据流DSL"。其核心价值:
- 可组合性:任何Runnable都可以通过
|自由组合 - 一致性:所有链天然支持
invoke/stream/batch/ainvoke四种调用方式 - 可观测性:天然与LangSmith集成,每一步都可追踪
- 声明式:用"是什么"而非"怎么做"描述AI应用逻辑
掌握LCEL,是在LangChain生态中构建生产级AI应用的必要基础。