Langchain学习笔记(十一):Chain构建与组合技巧

发布于:2025-06-08 ⋅ 阅读:(22) ⋅ 点赞:(0)

:本文是Langchain框架的学习笔记;不是教程!不是教程!内容可能有所疏漏,欢迎交流指正。后续将持续更新学习笔记,分享我的学习心得和实践经验。

前言

在LangChain的发展过程中,API设计经历了重要的演进。从0.1.17版本开始,传统的Chain类(如LLMChain、SequentialChain等)被标记为已弃用,官方推荐使用更现代的LCEL(LangChain Expression Language)和管道符(|)语法。本文将详细对比新旧两种方式,帮助大家理解这一重要变化


1. Chain的基本概念与版本变化

1.1 什么是Chain?

Chain(链)是LangChain中用于连接多个组件的核心抽象。想象一下工厂的流水线,每个工作站负责一个特定的任务,产品从一个工作站传递到下一个工作站,最终完成整个生产过程。Chain就是这样的概念:

  • 输入处理:接收用户的原始输入

  • 中间处理:通过多个步骤对数据进行转换和处理

  • 输出生成:产生最终的结果

Chain的核心价值在于:

  1. 模块化设计:将复杂的AI任务分解为多个简单的步骤

  2. 可重用性:每个组件都可以在不同的场景中重复使用

  3. 可维护性:便于调试、测试和优化单个环节

  4. 灵活组合:可以根据需求灵活组合不同的处理步骤


1.2 版本演进的背景

为什么要从传统Chain迁移到LCEL?

传统的Chain类虽然功能强大,但存在一些局限性:

  • 性能瓶颈:无法充分利用并行处理能力

  • 语法复杂:需要创建多个类实例,代码冗长

  • 功能限制:不支持流式处理和高级组合模式

  • 维护困难:复杂的继承关系增加了维护成本

LCEL(LangChain Expression Language)的优势:

  • 直观语法:使用管道符(|)连接组件,类似Unix管道

  • 高性能:原生支持并行、批处理和流式处理

  • 类型安全:更好的类型提示和错误检查

  • 易于调试:清晰的数据流向,便于问题定位


1.3 新旧版本对比表

功能 旧版本(已弃用) 新版本(推荐) 说明
基础链 LLMChain prompt | llm 最基础的提示词+模型组合
顺序链 SequentialChain chain1 | chain2 | chain3 按顺序执行多个处理步骤
执行方法 chain.run() chain.invoke() 单次执行链的方法
批量执行 chain.apply() chain.batch() 批量处理多个输入
异步执行 chain.arun() chain.ainvoke() 异步执行,提高并发性能
流式执行 不支持 chain.stream() 实时流式输出,改善用户体验



2. LLMChain的新旧版本对比

2.1 LLMChain的作用和意义

LLMChain是LangChain中最基础、最常用的链类型。它的作用是将提示词模板(Prompt Template)与大语言模型(LLM)连接起来,形成一个完整的处理单元。

LLMChain的核心功能

  1. 模板化输入:使用提示词模板,支持变量替换

  2. 模型调用:自动调用指定的大语言模型

  3. 输出处理:可选的输出解析和格式化

  4. 错误处理:统一的错误处理机制

使用场景:

  • 文本生成(如写作助手、内容创作)

  • 问答系统(基于提示词的简单问答)

  • 文本分析(情感分析、关键词提取等)

  • 格式转换(如JSON生成、代码生成)


2.2 基础用法对比

旧版本写法(已弃用)

from langchain_ollama import ChatOllama
from langchain.chains import LLMChain  # 已弃用
from langchain.prompts import PromptTemplate

llm = ChatOllama(
    base_url=OLLAMA_BASE_URL,
    model=MODEL_NAME,
    temperature=0.1  # 降低温度以获得更确定性的回答
)

prompt_template = PromptTemplate(
    input_variables=["topic"],
    template="请为我写一篇关于{topic}的简短介绍,大约100字左右。"
)

# 构建LLMChain(已弃用)
llm_chain = LLMChain(
    llm=llm,
    prompt=prompt_template,
    verbose=True
)

# 使用链(已弃用的方法)
result = llm_chain.run(topic="人工智能")  # 弃用警告
print(result)

新版本写法(推荐)

from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser

OLLAMA_BASE_URL = "http://192.168.0.118:11434"
MODEL_NAME = "deepseek-r1:1.5b"

llm = ChatOllama(
    base_url=OLLAMA_BASE_URL,
    model=MODEL_NAME,
    temperature=0.1  # 降低温度以获得更确定性的回答
)

prompt_template = PromptTemplate(
    input_variables=["topic"],
    template="请为我写一篇关于{topic}的简短介绍,大约100字左右。"
)

# 使用管道符构建链(推荐)
chain = prompt_template | llm | StrOutputParser()

# 使用invoke方法执行(推荐)
result = chain.invoke({"topic": "人工智能"})
print(result)

# 新版本支持的其他执行方式
# 批量执行
results = chain.batch([
    {"topic": "人工智能"},
    {"topic": "机器学习"}
])
print(results)

新版本的优势解析:

  • 管道符语法:prompt_template | llm | StrOutputParser() 清晰地表达了数据流向

  • 输出解析器:StrOutputParser() 确保输出为字符串格式

  • 批量处理:batch() 方法可以高效处理多个输入

  • 类型安全:更好的类型提示,减少运行时错误


2.3 带输出解析器的高级用法

输出解析器的作用

输出解析器(Output Parser)是LangChain中用于处理模型输出的重要组件。它的主要作用包括:

  1. 格式化输出:将模型的原始输出转换为结构化数据

  2. 类型转换:确保输出符合预期的数据类型

  3. 验证检查:验证输出是否符合预定义的格式要求

  4. 错误处理:处理格式不正确的输出

新版本写法

from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain.output_parsers import CommaSeparatedListOutputParser

OLLAMA_BASE_URL = "http://192.168.0.118:11434"
MODEL_NAME = "deepseek-r1:1.5b"

llm = ChatOllama(
    base_url=OLLAMA_BASE_URL,
    model=MODEL_NAME,
    temperature=0.1  # 降低温度以获得更确定性的回答
)

# 新版本
output_parser = CommaSeparatedListOutputParser()

list_prompt = PromptTemplate(
    template="列出5个关于{topic}的关键词,用逗号分隔。\n{format_instructions}",
    input_variables=["topic"],
    partial_variables={"format_instructions": output_parser.get_format_instructions()}
)

# 使用管道符连接解析器
chain = list_prompt | llm | output_parser

keywords = chain.invoke({"topic": "机器学习"})
print(f"关键词列表: {keywords}")
print(f"类型: {type(keywords)}")

3. SequentialChain

3.1 SequentialChain的概念和作用

SequentialChain(顺序链)是用于按顺序执行多个处理步骤的链类型。它的核心思想是将复杂的任务分解为多个简单的子任务,然后按照特定的顺序依次执行。

SequentialChain的特点:

  1. 步骤化处理:将复杂任务分解为多个简单步骤

  2. 数据传递:前一个步骤的输出作为后一个步骤的输入

  3. 模块化设计:每个步骤都是独立的,便于测试和维护

  4. 灵活组合:可以根据需要调整步骤的顺序和组合

适用场景:

  • 内容创作流程:大纲生成 → 内容写作 → 格式优化

  • 数据分析流程:数据收集 → 数据清洗 → 分析报告

  • 产品开发流程:需求分析 → 方案设计 → 实施计划

  • 客户服务流程:问题识别 → 解决方案 → 回复生成


3.2 简单顺序链

简单顺序链的特点:

  • 每个步骤只有一个输入和一个输出

  • 前一步的输出直接作为后一步的输入

  • 适合线性处理流程

新版本写法

from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser

OLLAMA_BASE_URL = "http://192.168.0.118:11434"
MODEL_NAME = "deepseek-r1:1.5b"

llm = ChatOllama(
    base_url=OLLAMA_BASE_URL,
    model=MODEL_NAME,
    temperature=0.1  # 降低温度以获得更确定性的回答
)

# 新版本
outline_prompt = PromptTemplate(
    input_variables=["topic"],
    template="为主题'{topic}'创建一个详细的文章大纲,包含3-5个主要部分。"
)

article_prompt = PromptTemplate(
    input_variables=["outline"],
    template="根据以下大纲写一篇简短的文章:\n{outline}"
)

# 使用管道符构建顺序链
writing_chain = (
        outline_prompt
        | llm
        | StrOutputParser()
        | (lambda outline: {"outline": outline})
        | article_prompt
        | llm
        | StrOutputParser()
)

final_article = writing_chain.invoke({"topic": "可持续发展"})
print(final_article)

3.2 复杂顺序链

复杂顺序链的特点:

  • 支持多个输入和输出变量

  • 可以在处理过程中保留和传递多个数据

  • 支持并行处理某些步骤

  • 适合复杂的业务逻辑

新版写法

from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough, RunnableParallel

OLLAMA_BASE_URL = "http://192.168.0.118:11434"
MODEL_NAME = "deepseek-r1:1.5b"

llm = ChatOllama(
    base_url=OLLAMA_BASE_URL,
    model=MODEL_NAME,
    temperature=0.1  # 降低温度以获得更确定性的回答
)

# 新版本
analysis_prompt = PromptTemplate(
    input_variables=["product_name", "product_description"],
    template="产品:{product_name}\n描述:{product_description}\n请分析产品特点:"
)

slogan_prompt = PromptTemplate(
    input_variables=["product_name", "product_analysis"],
    template="产品:{product_name}\n分析:{product_analysis}\n请创建3个营销标语:"
)

# 使用RunnableParallel和管道符构建复杂链
product_marketing_chain = (
        RunnablePassthrough.assign(product_analysis=analysis_prompt | llm | StrOutputParser()) | RunnableParallel(
    product_analysis=lambda x: x["product_analysis"], marketing_slogans=slogan_prompt | llm | StrOutputParser()
)
)

result = product_marketing_chain.invoke({
    "product_name": "智能语音助手",
    "product_description": "基于AI技术的智能语音助手"
})

print("产品分析:", result["product_analysis"])
print("营销标语:", result["marketing_slogans"])

核心组件解析:

  1. RunnablePassthrough.assign()

    • 作用:在保留原有输入的基础上,添加新的字段
    • 这里添加了 product_analysis 字段,包含产品分析结果
  2. RunnableParallel()

    • 作用:并行执行多个任务,提高处理效率
    • 同时输出产品分析和营销标语两个结果

处理流程:

  1. 输入处理:接收产品名称和描述
  2. 产品分析:生成产品特点分析,并保留原始输入
  3. 并行输出:同时生成最终的分析结果和营销标语



4. RouterChain

4.1 RouterChain的概念和作用

RouterChain(路由链)是一种智能分发机制,它可以根据输入内容的特征,自动选择最合适的处理路径。就像交通路口的智能信号灯,根据不同方向的车流量来调整信号,RouterChain根据不同类型的输入来选择最佳的处理方式。

RouterChain的核心价值:

  1. 智能分类:自动识别输入内容的类型或意图
  2. 专业处理:为不同类型的问题提供专门的处理逻辑
  3. 提高效率:避免用通用方法处理所有问题
  4. 提升质量:专业化处理通常能获得更好的结果

典型应用场景:

  • 智能客服系统:根据问题类型路由到不同的处理模块

  • 内容分类处理:技术问题、商业问题、创意问题分别处理

  • 多语言支持:根据语言类型选择对应的处理器

  • 难度分级:简单问题快速回答,复杂问题深度分析


4.2 基础路由链

路由逻辑的设计思路:

  1. 关键词匹配:通过关键词识别问题类型
  2. 专业模板:为每种类型设计专门的提示词模板
  3. 默认处理:为无法分类的问题提供通用处理方式

新版写法

from langchain_core.prompts import PromptTemplate
from langchain_ollama import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnableBranch

OLLAMA_BASE_URL = "http://192.168.0.118:11434"
MODEL_NAME = "deepseek-r1:1.5b"

llm = ChatOllama(
    base_url=OLLAMA_BASE_URL,
    model=MODEL_NAME,
    temperature=0.1  # 降低温度以获得更确定性的回答
)

# 新版本
tech_prompt = PromptTemplate(
    template="你是技术专家。请回答:{input}",
    input_variables=["input"]
)

business_prompt = PromptTemplate(
    template="你是商业顾问。请回答:{input}",
    input_variables=["input"]
)

default_prompt = PromptTemplate(
    template="请回答:{input}",
    input_variables=["input"]
)


# 使用RunnableBranch实现路由逻辑
def route_question(x):
    question = x["input"].lower()
    if any(keyword in question for keyword in ["代码", "编程", "技术", "开发"]):
        return "tech"
    elif any(keyword in question for keyword in ["商业", "市场", "销售", "管理"]):
        return "business"
    else:
        return "default"


router_chain = RunnableBranch(
    (lambda x: route_question(x) == "tech", tech_prompt | llm | StrOutputParser()),
    (lambda x: route_question(x) == "business", business_prompt | llm | StrOutputParser()),
    default_prompt | llm | StrOutputParser()
)

result = router_chain.invoke({"input": "如何优化Python代码性能?"})
print(result)

核心组件详解:

  1. route_question() 函数:

    • 作用:分析输入问题,判断问题类型
    • 逻辑:通过关键词匹配来识别问题领域
    • 返回:问题类型标识(tech/business/default)
  2. RunnableBranch:

    • 作用:根据条件选择不同的处理分支
    • 结构:(条件函数, 处理链) 的元组列表
    • 默认:最后一个参数作为默认处理分支
  3. 专业化提示词:

    • tech_prompt:以技术专家身份回答技术问题
    • business_prompt:以商业顾问身份回答商业问题
    • default_prompt:通用回答模式



总结

LangChain从传统的Chain类向LCEL的迁移代表了框架设计理念的重要演进:

  • API设计更现代化:管道符语法更直观,符合现代编程习惯

  • 性能更优秀:原生支持并行、批处理和流式处理

  • 功能更强大:更灵活的组合方式,更好的调试工具

  • 维护性更好:代码更简洁,逻辑更清晰

虽然旧版本的Chain类仍然可用,但建议在新项目中使用LCEL语法,在现有项目中逐步迁移。这不仅能享受到新版本的性能优势,还能确保代码的长期可维护性


网站公告

今日签到

点亮在社区的每一天
去签到