AGI围城
首页
基础知识
工程实践
所见所思
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

AGI围城

是的,这个世界还是很有趣的。
首页
基础知识
工程实践
所见所思
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 提示词工程

  • 大模型API调用

  • OpenAI工具

  • 嵌入向量

  • 检索增强生成(RAG)

  • LlamaIndex

  • LangChain

    • 1. 基本概念
    • 2. 模型 I/O 封装
    • 3. 数据连接封装
    • 4. 历史对话管理
    • 5. Chain 与 LCEL
      • 5.1 Pipeline 式调用 PromptTemplate, LLM 和 OutputParser
      • 5.2 流式输出
      • 5.3 简单实现一个 RAG
      • 5.4 LCEL 实现大模型动态切换
      • 5.5 存储与管理对话历史
      • 5.6 LCEL 更多功能
    • 6. LangServe 与 LangChain.js
  • Agent

  • Workflow

  • Transformer

  • 微调

  • MCP

  • A2A

  • 基础知识
  • LangChain
xiao_sl
2025-03-09
目录

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

# 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

# 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

# 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

# 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

# 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)
#LangChain#RAG
上次更新: 2025/12/19, 15:17:48
4. 历史对话管理
6. LangServe 与 LangChain.js

← 4. 历史对话管理 6. LangServe 与 LangChain.js→

最近更新
01
我是如何发现临时邮箱的?一个真实的故事
06-12
02
4.核心实现
05-26
03
3.A2A开发实践
05-22
更多文章>
Theme by Vdoing | Copyright © 2019-2025 AGI围城 | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式