5. Chain 与 LCEL
LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的"提示+LLM"链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。
LCEL 通过管道符(|)将不同的组件(如模型、提示模板、数据预处理等)连接起来,但每个组件相互之间是松散耦合的。组件之间的依赖关系被显式地管理和定义,通过管道将输入传递给下一个模块,而不需要在每个模块内部嵌套或依赖于特定的实现方式。使得模块可以独立开发、测试和替换。
LCEL 的一些亮点包括:
- 流支持:使用 LCEL 构建 Chain 时,你可以获得最佳的首个令牌时间(即从输出开始到首批输出生成的时间)。对于某些 Chain,这意味着可以直接从 LLM 流式传输令牌到流输出解析器,从而以与 LLM 提供商输出原始令牌相同的速率获得解析后的、增量的输出。
- 异步支持:任何使用 LCEL 构建的链条都可以通过同步 API(例如,在 Jupyter 笔记本中进行原型设计时)和异步 API(例如,在 LangServe 服务器中)调用。这使得相同的代码可用于原型设计和生产环境,具有出色的性能,并能够在同一服务器中处理多个并发请求。
- 优化的并行执行:当你的 LCEL 链条有可以并行执行的步骤时(例如,从多个检索器中获取文档),我们会自动执行,无论是在同步还是异步接口中,以实现最小的延迟。
- 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是使链在规模上更可靠的绝佳方式。
- 访问中间结果:对于更复杂的链条,访问在最终输出产生之前的中间步骤的结果通常非常有用。这可以用于让最终用户知道正在发生一些事情,甚至仅用于调试链条。
- 输入和输出模式:输入和输出模式为每个 LCEL 链提供了从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于输入和输出的验证,是 LangServe 的一个组成部分。
- 无缝 LangSmith 跟踪集成:随着链条变得越来越复杂,理解每一步发生了什么变得越来越重要。通过 LCEL,所有步骤都自动记录到 LangSmith,以实现最大的可观察性和可调试性。
- 无缝 LangServe 部署集成:任何使用 LCEL 创建的链都可以轻松地使用 LangServe 进行部署。
原文:https://python.langchain.com/docs/expression_language/ (opens new window)
# 5.1 Pipeline 式调用 PromptTemplate, LLM 和 OutputParser
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field, validator
from typing import List, Dict, Optional
from enum import Enum
import json
# 输出结构
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 = ChatOpenAI(model="gpt-4o", temperature=0)
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
)
)
"""
out:
{
"name": null,
"price_lower": null,
"price_upper": 100,
"data_lower": null,
"data_upper": null,
"sort_by": "data",
"ordering": "descend"
}
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 5.2 流式输出
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.runnables import RunnablePassthrough
# Prompt 模板
prompt = PromptTemplate.from_template("讲个关于{topic}的笑话")
# 模型
llm = ChatOpenAI(model="gpt-4o", temperature=0)
runnable = (
{"topic": RunnablePassthrough()} | prompt | llm | StrOutputParser()
)
# 流式输出
for s in runnable.stream("小明"):
print(s, end="", flush=True)
"""
小明有一天上课迟到了,老师问他:"小明,你为什么迟到?"
小明回答:"老师,我在和时间赛跑!"
老师好奇地问:"那你怎么没赢呢?"
小明无奈地说:"因为时间用的是跑步机!"
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 5.3 简单实现一个 RAG
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import ChatOpenAI
from langchain_community.document_loaders import PyMuPDFLoader
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
# 加载文档
loader = PyMuPDFLoader("llama2.pdf")
pages = loader.load_and_split()
# 文档切分
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=100,
length_function=len,
add_start_index=True,
)
texts = text_splitter.create_documents(
[page.page_content for page in pages[:4]]
)
# 灌库
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
db = FAISS.from_documents(texts, embeddings)
# 检索 top-2 结果
retriever = db.as_retriever(search_kwargs={"k": 2})
# Prompt模板
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
# Chain
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0,
max_tokens=1000,
)
rag_chain = (
{"question": RunnablePassthrough(), "context": retriever}
| prompt
| llm
| StrOutputParser()
)
print(rag_chain.invoke("Llama 2有多少参数"))
"""
Llama 2有7B、13B和70B参数的变体。
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 5.4 LCEL 实现大模型动态切换
import dotenv
import os
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.utils import ConfigurableField
from langchain_openai import ChatOpenAI
from langchain_community.chat_models import QianfanChatEndpoint
from langchain.prompts import (
ChatPromptTemplate,
HumanMessagePromptTemplate,
)
dotenv.load_dotenv()
api_key = os.getenv('API_KEY')
secret_key = os.getenv('SECRET_KEY')
# 模型1
ernie_model = QianfanChatEndpoint(
api_key=api_key,
secret_key=secret_key
)
# 模型2
gpt_model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 通过 configurable_alternatives 按指定字段选择模型
model = gpt_model.configurable_alternatives(
ConfigurableField(id="llm"),
default_key="gpt",
ernie=ernie_model,
# claude=claude_model,
)
# Prompt 模板
prompt = ChatPromptTemplate.from_messages(
[
HumanMessagePromptTemplate.from_template("{query}"),
]
)
# LCEL
chain = (
{"query": RunnablePassthrough()}
| prompt
| model
| StrOutputParser()
)
# 运行时指定模型 "gpt" or "ernie"
ret = chain.with_config(configurable={"llm": "gpt"}).invoke("请自我介绍")
print(f"gpt自我介绍:{ret}\n")
ret = chain.with_config(configurable={"llm": "ernie"}).invoke("请自我介绍")
print(f"ernie自我介绍:{ret}")
"""
gpt自我介绍:你好!我是一个人工智能助手,旨在帮助你解答问题、提供信息和支持各种任务。我可以处理多种主题,包括科学、技术、文化、语言学习等。如果你有任何问题或需要帮助的地方,请随时告诉我!
ernie自我介绍:大家好,我叫张明,是一名来自中国的软件工程师...
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 5.5 存储与管理对话历史
from langchain_community.chat_message_histories import SQLChatMessageHistory
def get_session_history(session_id):
# 通过 session_id 区分对话历史,并存储在 sqlite 数据库中
return SQLChatMessageHistory(session_id, connection="sqlite:///memory.db")
from langchain_core.messages import HumanMessage
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain.schema.output_parser import StrOutputParser
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
runnable = model | StrOutputParser()
runnable_with_history = RunnableWithMessageHistory(
runnable, # 指定 runnable
get_session_history, # 指定自定义的历史管理方法
)
context = runnable_with_history.invoke(
[HumanMessage(content="你好,我叫李华")],
config={"configurable": {"session_id": "lh"}},
)
print(context)
context = runnable_with_history.invoke(
[HumanMessage(content="你知道我叫什么名字吗")],
config={"configurable": {"session_id": "lh"}},
)
print(context)
context = runnable_with_history.invoke(
[HumanMessage(content="你知道我叫什么名字")],
config={"configurable": {"session_id": "test"}},
)
print(context)
"""
可以看到SQLChatMessageHistory分session_id分别管理了会话
out:
你好,李华!很高兴认识你。请问有什么我可以帮助你的吗?
是的,你叫李华。有什么我可以帮助你的吗,李华?
抱歉,我无法知道你的名字。如果你愿意,可以告诉我你的名字。
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 5.6 LCEL 更多功能
LCEL 还可以实现:
- 配置运行时变量:https://python.langchain.com/v0.2/docs/how_to/configure/ (opens new window)
- 故障回退:https://python.langchain.com/v0.2/docs/how_to/fallbacks (opens new window)
- 并行调用:https://python.langchain.com/v0.2/docs/how_to/parallel/ (opens new window)
- 逻辑分支:https://python.langchain.com/v0.2/docs/how_to/routing/ (opens new window)
- 动态创建 Chain:https://python.langchain.com/v0.2/docs/how_to/dynamic_chain/ (opens new window)
- 更多例子:https://python.langchain.com/v0.2/docs/how_to/lcel_cheatsheet/ (opens new window)
编辑 (opens new window)
上次更新: 2025/12/19, 15:17:48