本文 对《LangChain》一文中的 Chain 与 LCEL 部分的示例进行详细的展示。
先回顾下 在LangChain框架中,Chain(链) 和 LCEL(LangChain Expression Language) 是两个密切相关但本质不同的概念。
Chain(链): 是LangChain中处理流程的抽象概念,指将多个组件(模型、工具、逻辑)串联成一个可执行的任务序列。
LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的“提示+LLM”链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。
在本文中 LCEL 产生的对象,被叫做 runnable 或 chain,经常两种叫法混用。本质就是一个自定义调用流程。
1、Pipeline 式调用 PromptTemplate, LLM 和 OutputParser
- from langchain_openai import ChatOpenAI
- 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
复制代码- # 输出结构
- 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.dict(),
- indent = 4,
- ensure_ascii=False
- )
- )
复制代码 输出结果如下:
流式输出,就是那种一个字一个字蹦的输出,大家可以用下面代码体验一下玩一玩- prompt = PromptTemplate.from_template("讲个关于{topic}的笑话")
- runnable = (
- {"topic": RunnablePassthrough()} | prompt | llm | StrOutputParser()
- )
- # 流式输出
- for s in runnable.stream("李华"):
- print(s, end="", flush=True)
复制代码 2、用LECL实现RAG
我们通过分割一个 pdf 文件形成向量库,示例如下:- from langchain_openai import OpenAIEmbeddings
- from langchain_text_splitters import RecursiveCharacterTextSplitter
- from langchain_community.vectorstores import FAISS
- from langchain_openai import ChatOpenAI
- from langchain.chains import RetrievalQA
- from langchain_community.document_loaders import PyMuPDFLoader
- # 加载文档
- 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})
复制代码- 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("Llama 2有多少参数")
复制代码 输出结果如下:
3、LECL 的意义是什么
在 LangChain 框架中,LCEL(LangChain Expression Language)的意义主要体现在增强模块间的解耦能力、提升流程编排的灵活性,并为复杂的 LLM(大语言模型)应用构建提供标准化的逻辑表达工具。以下是具体分析:
一、LangChain 中的模块解耦需求
LangChain 的核心目标是通过组合不同的组件(如 LLMs、Prompt 模板、工具调用、内存管理等)构建复杂的应用流程。例如:
- 组件类型:LLM 调用、工具(如计算器、数据库查询)、内存(对话历史存储)、Prompt 模板、链(Chain,如 SequentialChain、RouterChain)等。
- 解耦痛点:
传统方式下,组件间的交互逻辑(如条件分支、数据转换、循环控制)可能硬编码在链的代码中,导致:
- 链的逻辑难以复用或动态调整;
- 组件更换(如从 OpenAI 切换到 Anthropic)时,需修改链的控制逻辑;
- 复杂流程(如多轮工具调用、动态路由)的编排缺乏标准化表达。
二、LCEL 的核心意义:作为模块间的 “逻辑中介”
LCEL 是 LangChain 设计的一套声明式表达式语言,用于描述组件间的数据流动和逻辑控制。其核心价值体现在以下方面:
1. 分离逻辑控制与组件实现
- 传统硬编码问题:
例如,在链中通过 Python 代码实现条件判断:- if result.score > 0.8:
- return llm.predict(prompt.format(data=result))
- else:
- return tool.query(data=result)
复制代码 此时,条件逻辑与链的代码强耦合,难以复用或动态修改。
- LCEL 的解耦作用:
将条件逻辑抽象为 LCEL 表达式,存储在配置中:- condition: "{{ score > 0.8 }}"
- then:
- action: llm.predict
- params:
- prompt: "用户输入:{{ input }}"
- else:
- action: tool.query
- params:
- data: "{{ input }}"
复制代码
- 链的代码仅负责解析 LCEL 表达式,根据结果调用组件;
- 逻辑变更时只需修改表达式,无需调整链的代码,实现 “逻辑外置”。
2. 标准化组件交互协议
LCEL 定义了一套统一的语法规则(如变量引用、运算符、函数调用),作为不同组件间的 “通信语言”:
- 数据传递:通过 {{ variable }} 引用上游组件的输出(如 LLM 的返回结果、工具的查询数据)。
- 逻辑操作:支持算术运算(+、-)、条件判断(if-else)、集合操作(map、filter)等,避免不同组件自定义逻辑语法。
- 函数扩展:可注册自定义函数(如 format_date()、calculate_score()),供表达式调用,实现跨组件的逻辑复用。
示例:
通过 LCEL 表达式编排多步流程:- from langchain.lcel import LCELChain
- expression = """
- let score = tool.calculate_score(input.data);
- if (score > 0.6) {
- return llm.generate(prompt: "高分结果:{{ score }}");
- } else {
- return tool.analyze(input.data);
- }
- """
- chain = LCELChain.from_expression(expression, components={
- "tool": MyCustomTool(),
- "llm": OpenAI(temperature=0.1)
- })
复制代码
- 组件(tool、llm)仅需按约定输出数据字段,无需关心流程逻辑;
- 链通过 LCEL 表达式动态调度组件,解耦组件与流程控制。
3. 支持动态流程编排与热更新
在 LangChain 中,复杂应用(如智能客服、数据分析助手)常需根据实时数据调整流程。LCEL 的声明式特性使其适合动态场景:
- 场景 1:AB 测试
通过后端配置不同的 LCEL 表达式,控制 A/B 组用户的流程分支(如优先调用工具 A 或工具 B),无需重启服务。
- 场景 2:低代码平台
用户通过可视化界面配置 LCEL 表达式(如拖拽生成 {{ order.amount > 1000 ? "大客户" : "普通客户" }}),快速生成定制化链,降低开发门槛。
- 场景 3:实时策略调整
在金融风控场景中,通过动态修改 LCEL 表达式(如调整风险评分阈值 score > 0.7 → score > 0.75),实时更新风控流程,而无需修改代码。
4. 提升组件复用性与可测试性
- 组件复用:
标准化的 LCEL 接口使组件可插拔。例如,替换 LLM 提供商时,只需修改组件实例(如从 OpenAI 改为 Cohere),LCEL 表达式无需变更。
- 独立测试:
可单独测试 LCEL 表达式的逻辑正确性,如:- from langchain.lcel import evaluate
- # 测试条件表达式
- result = evaluate("{{ score > 0.8 ? 'pass' : 'fail' }}", context={"score": 0.9})
- assert result == "pass"
复制代码 无需启动完整的链或依赖外部服务,提升测试效率。
5. 适配多模态与复杂工具调用
LangChain 常需集成多种工具(如 SQL 查询、API 调用、文件解析),LCEL 可简化多模态数据的处理逻辑:
- 示例:处理结构化数据
- expression = """
- let data = sql_tool.query("SELECT * FROM orders WHERE amount > {{ threshold }}");
- let summary = llm.summarize(text: json.stringify(data.rows));
- return summary;
- """
复制代码
- 通过 LCEL 链式调用 SQL 工具和 LLM,解耦数据查询与摘要生成逻辑;
- 表达式清晰描述数据流动路径,避免代码层面的嵌套回调。
三、LCEL 与 LangChain 架构的协同
LCEL 的设计与 LangChain 的模块化架构深度协同,目标是构建 **“表达式驱动的智能应用”**:
- 底层组件层:提供标准化接口(如 call()、parse()),供 LCEL 表达式调用;
- LCEL 层:作为逻辑编排层,通过表达式描述组件交互规则;
- 应用层:通过配置文件、API 动态注入 LCEL 表达式,快速组装不同功能的链。
这种分层设计使 LangChain 既能保持代码的简洁性,又能通过 LCEL 灵活应对复杂业务需求,尤其适合需要频繁调整逻辑的生成式 AI 场景(如智能文档处理、对话系统流程控制等)。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |