LangChain(三) LCEL

什么是LCEL

LCEL 全称LangChain Expression Language 是LangChain定义的一种声明式语言,其优势在于能够轻松构建不同调用顺序的Chain(由LCEL构建的调用链被称为Chain)

举个例子比如处理结构化输出

python 复制代码
from langchain.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from pydantic import BaseModel, Field, validator
from typing import List, Dict, Optional
from enum import Enum
import json
from langchain.chat_models import init_chat_model

# 输出结构
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("gpt-4o", model_provider="openai")

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
    )
)

输出:

json 复制代码
{
    "name": null,
    "price_lower": null,
    "price_upper": 100,
    "data_lower": null,
    "data_upper": null,
    "sort_by": "data",
    "ordering": "descend"
python 复制代码
runnable = (
    {"text": RunnablePassthrough()} | prompt | structured_llm
)

这一部分就是LCEL表达式,不过看上去还是有点疑惑,dict或prompt或structured_llm?python里的或运算符也不是管道符啊,而且一个逻辑运算怎么就能调invoke方法了?

下面我们来说明一下LCEL的基本构成

LCEL的基本构成

首先我们来看管道符是怎么回事。

管道符

这里的管道符,是由LangChain重载过的操作符,它的作用是表示from A to B,如果你觉得这样的写法容易让人混乱,LangChain也支持其他的写法:

python 复制代码
chain = A.pipe(B)

而这两个写法都等价于

python 复制代码
chain = RunnableSequence([A, B])

RunnableSequence是LCEL中的两个基本组合单元,所谓基本组合单元就是一堆工作单元的组合。最基础的基本组合单元有两个:RunnableSequenceRunnableParallel

RunnableSequence 和 RunnableParallel

RunnableSequence

先来解释下什么是RunableRunable是LCEL的一个基本概念,可以理解为是能够调用,批处理,流处理,转换和组合的工作单元。

RunnableSequence是多个工作单元的组合,可以让我们按顺序组装多个Runable,上一个Runable的输出是下一个Runable的输入,举个例子:

python 复制代码
from langchain_core.runnables import RunnableSequence,RunnableLambda

def func1(num):
    num +=1
    return num

def func2(num):
    num+=1
    return num
    
    
# 函数需要转换为RunnableLambda,不然会触发异常
runnable1 = RunnableLambda(func1) 
runnable2 = RunnableLambda(func2) 
chain = RunnableSequence(runnable1, runnable2)
print(chain.invoke(10))

这种写法就等价于

python 复制代码
chain = runnable1|runnable2

实际上,LCEL表达式会被强制转换为

ini 复制代码
chain = RunnableSequence(runnable1, runnable2)

RunnableParallel

RunnableParallel同样是多个工作单元的组合,不同的是RunnableParallel可以支持同时运行多个对象,并为每个可运行对象提供相同的输入

在LCEL中字典会被强制转换为RunnableParallel,我们来看一个例子:

python 复制代码
import asyncio

from langchain_core.runnables import RunnableLambda

def add_one(x: int) -> int:
    return x + 1

def mul_two(x: int) -> int:
    return x * 2

def mul_three(x: int) -> int:
    return x * 3

runnable_1 = RunnableLambda(add_one)
runnable_2 = RunnableLambda(mul_two)
runnable_3 = RunnableLambda(mul_three)

sequence = runnable_1 | { 
    "mul_two": runnable_2,
    "mul_three": runnable_3,
}

async def main():
    res = sequence.invoke(1)
    print('invoke:',res)
    res = await sequence.ainvoke(1)
    print('ainvoke:',res)
    res = sequence.batch([1, 2, 3])
    print('batch:',res)
    res = await sequence.abatch([1, 2, 3])
    print('abatch:',res)
asyncio.run(main())

上面的

python 复制代码
sequence = runnable_1 | { 
    "mul_two": runnable_2,
    "mul_three": runnable_3,
}

等价于

python 复制代码
sequence =RunnableSequence(runnable_1,RunnableParallel(mul_two=runnable_2,mul_three=runnable_3))

RunnableSequenceRunnableParallel 是两个最基本的组合单元,其他的组合单元都是这两个组合单元的变体

以上就是LCEL的基本介绍,怎么样,是不是有点复杂?LangChain官方也这么觉得,以下是官方文档原话:

虽然我们已经看到用户在生产中运行具有数百个步骤的链,但我们通常建议使用 LCEL 来执行更简单的编排任务。当应用程序需要复杂的状态管理、分支、周期或多个代理时,我们建议用户利用 LangGraph

基于LCEL的RAG

下面我们就简单的构建一个RAG应用,使用LCEL实现一下RAG的核心环节

  1. 加载文档
  2. 切分文档
  3. 灌库
  4. 检索
  5. 返回结果
ini 复制代码
from langchain.chat_models import init_chat_model
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.chains import RetrievalQA
from langchain_community.document_loaders import PyMuPDFLoader
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
import dotenv
dotenv.load_dotenv()

# 加载文档
loader = PyMuPDFLoader("../data/deepseek-v3-1-4.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}
"""
llm = init_chat_model("gpt-4o", model_provider="openai")
prompt = ChatPromptTemplate.from_template(template)

# Chain
rag_chain = (
    {"question": RunnablePassthrough(), "context": retriever}
    | prompt
    | llm
    | StrOutputParser()
)

print(rag_chain.invoke("deepseek v3有多少参数"))

输出:

复制代码
DeepSeek-V3有6710亿(671B)个总参数。
相关推荐
mzlogin1 小时前
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
人工智能
归去_来兮2 小时前
知识图谱技术概述
大数据·人工智能·知识图谱
就是有点傻2 小时前
VM图像处理之图像二值化
图像处理·人工智能·计算机视觉
行云流水剑2 小时前
【学习记录】深入解析 AI 交互中的五大核心概念:Prompt、Agent、MCP、Function Calling 与 Tools
人工智能·学习·交互
love530love2 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
A林玖2 小时前
【机器学习】主成分分析 (PCA)
人工智能·机器学习
Jamence2 小时前
多模态大语言模型arxiv论文略读(108)
论文阅读·人工智能·语言模型·自然语言处理·论文笔记
tongxianchao2 小时前
双空间知识蒸馏用于大语言模型
人工智能·语言模型·自然语言处理
苗老大2 小时前
MMRL: Multi-Modal Representation Learning for Vision-Language Models(多模态表示学习)
人工智能·学习·语言模型
中达瑞和-高光谱·多光谱2 小时前
中达瑞和SHIS高光谱相机在黑色水彩笔墨迹鉴定中的应用
人工智能·数码相机