LangChain初入门 - 简化LLM开发难度的利器

前言

随着大语言模型(LLM)的快速发展,越来越多的开发者希望将AI能力集成到自己的应用中。然而,直接调用LLM API面临着诸多工程挑战:如何保存聊天上下文?如何进行网络检索?如何加载本地数据?如何便捷管理Prompt?更重要的是,当需要切换不同的LLM时,模型的输入输出结构差异巨大,微小的需求变更可能导致大量代码修改。

LangChain应运而生,作为当前最热门的AI应用开发框架,它提供了一站式解决方案,让开发者能够像搭积木一样快速构建LLM应用。本文将从零开始,带你深入理解LangChain的核心组件与最佳实践。

一、为什么选择LangChain

1.1 为什么需要大模型应用框架

大模型一般有两种形态呈现给我们:一种是训练好的二进制文件,另一种是部署后暴露的接口。无论哪种形式,LLM只提供了非常基础的调用方式。当我们要构建复杂的Chat Bot时,需要考虑大量工程问题。

传统开发模式的痛点:

  1. 上下文管理复杂:需要自行处理聊天历史存储
  2. 模型切换成本高:不同LLM的接口差异巨大
  3. 缺乏可观测性:难以监控运行流程、Token消耗、错误追踪
  4. 功能重复开发:网络检索、本地数据加载等功能需要反复实现

以OpenAI和文心一言为例,两者的SDK调用方式截然不同:

python 复制代码
# OpenAI调用方式
from openai import OpenAI
client = OpenAI()
completion = client.chat.completions.create(
    model="gpt-3.5-turbo",
    messages=[{"role": "user", "content": "你好"}]
)
content = completion.choices[0].message.content

# 文心一言调用方式
import qianfan
client = qianfan.ChatCompletion()
resp = client.do(messages=[{"role": "user", "content": "你好"}])
content = resp["body"]

1.2 LangChain的核心优势

优势维度 说明
认可度 GitHub 86.3k+ star,最热门的AI应用开发框架
持续维护 累计融资数千万美金,10000+次commit更新
易用性 屏蔽不同LLM的接口差异,统一调用方式
可扩展性 近700种集成,涵盖LLM、向量存储、工具等
可观测性 提供LangSmith平台,实现全链路监控
稳定性 2024年1月发布0.1.0稳定版本

使用LangChain后的代码对比:

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_community.chat_models.baidu_qianfan_endpoint import QianfanChatEndpoint
from langchain_core.output_parsers import StrOutputParser

# 切换LLM只需修改一行代码
llm = ChatOpenAI()  # OpenAI
# llm = QianfanChatEndpoint()  # 文心一言

chain = llm | StrOutputParser()
content = chain.invoke("你好")

二、LangChain核心概念

2.1 六大核心组件

LangChain提供了6大核心组件,帮助开发者更好地使用大语言模型:

复制代码
┌─────────────────────────────────────────────────────────┐
│                    LangChain 核心组件                     │
├─────────────┬─────────────┬─────────────┬───────────────┤
│   Models    │   Prompts   │   Indexes   │    Memory     │
│   模型组件   │   提示组件   │   索引组件   │    记忆组件    │
├─────────────┼─────────────┼─────────────┼───────────────┤
│   Chains    │   Agents    │             │               │
│   链组件     │   代理组件   │             │               │
└─────────────┴─────────────┴─────────────┴───────────────┘
  • Models(模型):封装不同类型的LLM,包括文本生成模型和聊天模型
  • Prompts(提示):管理和优化提示词,支持模板化和动态变量
  • Indexes(索引):处理文档索引和检索
  • Memory(记忆):管理对话历史和状态
  • Chains(链):组合多个组件形成处理流程
  • Agents(代理):让LLM自主决策使用哪些工具

2.2 框架安装

bash 复制代码
# 安装LangChain核心包
pip install langchain langchain-community

# 指定版本安装(课程使用0.2.1版本)
pip install langchain==0.2.1 langchain-community==0.2.1

安装后会自动包含以下扩展包:

  • langchain-core:基础抽象和表达式语言
  • langchain-community:第三方集成
  • langchain-text-splitters:文本分割器
  • langsmith:调试、测试、监控平台

三、Prompt组件详解

3.1 Prompt组件的基本组成

Prompt是所有AI应用交互的起点。LangChain封装了高可移植性的Prompt组件,同一个Prompt可以支持各种LLM,切换模型时无需修改Prompt。

Prompt组件分类:

组件类型 说明
PromptTemplate 文本消息提示模板
ChatPromptTemplate 聊天消息提示模板
SystemMessagePromptTemplate 系统消息模板
HumanMessagePromptTemplate 用户消息模板
AIMessagePromptTemplate AI消息模板
MessagesPlaceholder 消息占位符

3.2 基础用法示例

python 复制代码
from datetime import datetime
from langchain_core.prompts import (
    PromptTemplate,
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    MessagesPlaceholder,
)
from langchain_core.messages import AIMessage

# 文本提示模板
prompt = PromptTemplate.from_template("请讲一个关于{subject}的冷笑话")
prompt_str = prompt.format(subject="程序员")
print(prompt_str)  # 输出: 请讲一个关于程序员的冷笑话

# 聊天提示模板
chat_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是OpenAI开发的聊天机器人,当前时间:{now}"),
    MessagesPlaceholder("chat_history"),
    HumanMessagePromptTemplate.from_template("请讲一个关于{subject}的冷笑话"),
]).partial(now=datetime.now())

# 调用模板
chat_prompt_value = chat_prompt.invoke({
    "subject": "程序员",
    "chat_history": [
        ("human", "我叫慕小课"),
        AIMessage("我是ChatGPT, 有什么能帮到你的么?")
    ],
})
print(chat_prompt_value.to_string())

3.3 字符串提示拼接

LangChain支持使用+运算符拼接提示模板:

python 复制代码
from langchain_core.prompts import PromptTemplate

prompt = (
    PromptTemplate.from_template("请讲一个关于{subject}的冷笑话")
    + ",让我开心下"
    + "\n使用{language}语言。"
)
print(prompt.format(subject="程序员", language="中文"))

3.4 复用提示模板

使用PipelinePromptTemplate可以实现提示模板的复用:

python 复制代码
from langchain_core.prompts import PipelinePromptTemplate, PromptTemplate

# 完整模板
full_template = """{instruction}
{example}
{start}"""
full_prompt = PromptTemplate.from_template(full_template)

# 子模板
instruction_prompt = PromptTemplate.from_template("你正在模拟{person}。")
example_prompt = PromptTemplate.from_template("""
下面是一个交互例子:
Q: {example_q}
A: {example_a}""")
start_prompt = PromptTemplate.from_template("""
现在,你是一个真实的人,请回答用户的问题!
Q: {input}
A:""")

# 组装管道提示
pipeline_prompt = PipelinePromptTemplate(
    final_prompt=full_prompt,
    pipeline_prompts=[
        ("instruction", instruction_prompt),
        ("example", example_prompt),
        ("start", start_prompt),
    ]
)

print(pipeline_prompt.format(
    person="雷军",
    example_q="你最喜欢的汽车是什么?",
    example_a="小米su7",
    input="你最喜欢的手机是什么?"
))

四、Model组件详解

4.1 Model组件概述

LangChain为两种类型的模型提供接口:

  • LLM:使用纯文本作为输入输出的大语言模型
  • Chat Model:使用聊天消息列表作为输入输出的聊天模型

核心调用方法:

方法 说明
invoke 单次调用,生成内容
batch 批量调用,一次生成多个内容
stream 流式输出,实时返回生成内容

4.2 基础使用示例

python 复制代码
import dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

dotenv.load_dotenv()

# 1. 编排Prompt
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是OpenAI开发的聊天机器人,现在的时间是{now}"),
    ("human", "{query}"),
]).partial(now=datetime.now())

# 2. 创建大语言模型
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")

# 3. 生成内容
prompt_value = prompt.invoke({"query": "请讲一个关于程序员的冷笑话"})
ai_message = llm.invoke(prompt_value)

# 4. 提取内容
print("type:", ai_message.type)
print("content:", ai_message.content)
print("response_metadata:", ai_message.response_metadata)

4.3 批处理与流式输出

python 复制代码
# 批处理示例
prompt = ChatPromptTemplate.from_template("请讲一个关于{subject}的冷笑话")
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")

ai_messages = llm.batch([
    prompt.invoke({"subject": "程序员"}),
    prompt.invoke({"subject": "Python"}),
])

for ai_message in ai_messages:
    print(ai_message.content)

# 流式输出示例
response = llm.stream(prompt.invoke({"subject": "LLM和LLMOps"}))
for chunk in response:
    print(chunk.content, flush=True, end="")

五、OutputParser组件详解

5.1 为什么需要输出解析器

LLM的输出通常是字符串,但在实际应用中,我们往往需要结构化的数据:

python 复制代码
llm = ChatOpenAI()
llm.invoke("告诉我3个动物的名字。")
# 输出: 好的,这里有三种动物的名字:\n\n1. 狮子\n2. 大熊猫\n3. 斑马

输出解析器可以将非结构化文本转换为结构化数据:

输出解析器 = 预设提示 + 解析功能

5.2 StrOutputParser

最简单的输出解析器,将内容原样返回:

python 复制代码
from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()
result = parser.parse("程序员的梦工厂")  # 输出: 程序员的梦工厂

5.3 JsonOutputParser

将LLM输出转换为JSON格式:

python 复制代码
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.pydantic_v1 import BaseModel, Field

# 定义输出结构
class Joke(BaseModel):
    joke: str = Field(description="回答用户的冷笑话")
    punchline: str = Field(description="冷笑话的笑点")

# 创建解析器
parser = JsonOutputParser(pydantic_object=Joke)
prompt = ChatPromptTemplate.from_template(
    "回答用户的问题。\n{format_instructions}\n{query}\n"
).partial(format_instructions=parser.get_format_instructions())

# 构建链
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
chain = prompt | llm | parser

# 调用
result = chain.invoke({"query": "请讲一个关于程序员的冷笑话"})
# 输出: {'joke': '为什么程序员总是冷静的?', 'punchline': '因为他们总是有一堆bug在身边。'}

六、LCEL表达式与Runnable协议

6.1 传统嵌套写法的问题

python 复制代码
# 嵌套式写法(不推荐)
content = parser.invoke(
    llm.invoke(
        prompt.invoke({"query": req.query.data})
    )
)

问题:

  1. 可读性差,维护困难
  2. 无法得知每步执行进度
  3. 组件增多时代码变成"一次性"代码

6.2 Runnable协议

LangChain实现了Runnable协议,所有组件支持统一的调用方式:

方法 说明
invoke 单次调用
batch 批量调用
stream 流式输出
ainvoke 异步单次调用
abatch 异步批量调用
astream 异步流式输出

6.3 使用管道操作符

python 复制代码
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("{query}")
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
parser = StrOutputParser()

# 使用 | 操作符连接组件
chain = prompt | llm | parser

# 等价于
composed_chain = (
    RunnableParallel({"query": RunnablePassthrough()})
    .pipe(prompt)
    .pipe(llm)
    .pipe(parser)
)

# 调用
content = chain.invoke({"query": "你好,你是?"})

6.4 RunnableParallel并行运行

python 复制代码
from langchain_core.runnables import RunnableParallel

# 创建两条链
joke_chain = joke_prompt | llm | parser
poem_chain = poem_prompt | llm | parser

# 并行执行
map_chain = RunnableParallel(joke=joke_chain, poem=poem_chain)
result = map_chain.invoke({"subject": "程序员"})
# 输出: {'joke': '...', 'poem': '...'}

6.5 RunnablePassthrough透传数据

python 复制代码
from langchain_core.runnables import RunnablePassthrough

# 透传输入数据
chain = {"query": RunnablePassthrough()} | prompt | llm | StrOutputParser()
content = chain.invoke("你好,你是")

七、Callback回调机制

7.1 Callback功能介绍

Callback是LangChain提供的回调机制,允许在LLM应用程序的各个阶段使用钩子(hook),对于记录日志、监控、流式传输等任务非常有用。

主要事件:

事件 触发时机 方法
on_chat_model_start 聊天模型启动时
on_llm_start 大语言模型启动时
on_llm_new_token 生成新token时
on_llm_end 模型结束时
on_chain_start 链开始运行时
on_chain_end 链结束时
on_tool_start 工具开始运行时

7.2 使用StdOutCallbackHandler

python 复制代码
from langchain_core.callbacks import StdOutCallbackHandler

chain = {"query": RunnablePassthrough()} | prompt | llm | StrOutputParser()

# 使用回调
content = chain.stream(
    "你好,你是?",
    config={"callbacks": [StdOutCallbackHandler()]}
)

7.3 自定义回调处理器

python 复制代码
from langchain_core.callbacks import BaseCallbackHandler
from typing import Dict, Any, List, Optional
from uuid import UUID

class LLMOpsCallbackHandler(BaseCallbackHandler):
    """自定义回调处理器"""
    
    def on_llm_start(
        self,
        serialized: Dict[str, Any],
        prompts: List[str],
        *,
        run_id: UUID,
        **kwargs: Any,
    ) -> Any:
        print("LLM开始执行:", prompts)
    
    def on_llm_new_token(self, token: str, **kwargs) -> Any:
        print("新token:", token)
    
    def on_llm_end(self, response, **kwargs) -> Any:
        print("LLM执行完成")

总结

本文深入介绍了LangChain框架的核心组件与使用技巧:

  1. Prompt组件:支持模板化、拼接、复用,提高提示词管理效率
  2. Model组件:统一不同LLM的调用方式,支持批处理和流式输出
  3. OutputParser组件:将非结构化输出转换为结构化数据
  4. LCEL表达式:使用管道操作符优雅地组合组件
  5. Runnable协议:提供统一的调用接口和并行执行能力
  6. Callback机制:实现全链路监控和调试
相关推荐
多彩电脑1 小时前
Kivy如何自定义事件
开发语言·python
sleven fung1 小时前
llama-cpp-python 本地部署入门
开发语言·python·算法·llama
头歌实践平台1 小时前
C++面向对象 - 运算符重载的应用
开发语言·c++·算法
福大大架构师每日一题1 小时前
rust 1.96.0 更新:语言、编译器、Cargo、Rustdoc、兼容性全面升级,必看完整解读
android·开发语言·rust
思麟呀1 小时前
C++11并发编程:互斥锁
linux·开发语言·c++·windows
li星野1 小时前
RAG优化系列:基于用户反馈的检索权重调整(Feedback Loop)——让系统越用越聪明
python·学习
特立独行的猫a1 小时前
鸿蒙 PC 平台 Python 第三方库移植全景指南
python·华为·harmonyos·三方库移植·鸿蒙pc
郭涤生1 小时前
C++ 各类数据的内存分区与读写性能详解
开发语言·c++
Pluchon1 小时前
萌萌技术分享笔记——Java综合项目
java·开发语言·笔记·git·github·mybatis·postman