你应该懂的AI大模型(四)之 LangChain

这篇文章里面会有一些 python 代码,能看懂就行,可能会有语法错误大家当伪代码看吧, AI 应用开发也不会去敲这些代码(甚至整个过程都不太需要敲代码),文章中的这些代码只是为了更好的理解这些组件,实际应用开发中大概率是见不到文中用来示例的这种代码的。

一、LangChain是什么

LangChain is a framework for developing applications powered by large language models (LLMs).(来自LangChain 官网的描述)

LangChain是一个开源框架,是一个 LLM 变成框架,它允许开发人员将大模型、外部计算、数据源结合起来。

它是围绕LLMs(大语言模型)建立的一个框架。LangChain自身并不开发LLMs,它的核心理念是为各种LLMs实现通用的接口,把LLMs相关的组件“链接”在一起,简化LLMs应用的开发难度,方便开发者快速地开发复杂的LLMs应用。

LangChain目前支持三种类型的模型:LLMs、Chat Models(聊天模型)、Embeddings Models(嵌入模型).

二、为什么使用LangChain

如果我们要做一个企业内部的机器人助手,我们期望这个机器人可以能回答通用的问题、能从企业知识库中或者数据库中提取信息、并且能够根据我们指令去执行发送邮件等的操作,LangChain就是可以实现这一目标。

数据连接和行动执行是 LangChain 的两大特点。

  • 数据连接:Langchain 允许你将大型语言模型连接到你自己的数据源,比如数据库、PDF文件或其他文档。这意味着你可以使模型从你的私有数据中提取信息。
  • 行动执行:不仅可以提取信息,Langchain 还可以帮助你根据这些信息执行特定操作,如发送邮件。无需硬编码:它提供了灵活的方式来动态生成查询,避免了硬编码的需求。

到此大家如果还不是太理解 LangChain 是个啥,那么就索性把LangChain类比数据库连接的JDBC,JDBC屏蔽数据库之间连接差异,LangChain 屏蔽了 LLM 之间的差异,是一个桥梁和中介的角色。

三、LangChain核心组件

3.1、模型的I/O封装

  • LLMs:大语言模型
  • Chat Models:一般基于 LLMs,但按对话结构重新封装
  • PromptTemple:提示词模板
  • OutputParser:解析输出

3.2、数据连接封装

对 RAG 的封装在这部分。

  • Document Loaders:各种格式文件的加载器
  • Document Transformers:对文档的常用操作,如:split, filter, translate, extract metadata, etc
  • Text Embedding Models:文本向量化表示,用于检索等操作(啥意思?别急,后面详细讲)
  • Verctorstores: (面向检索的)向量的存储
  • Retrievers: 向量的检索

3.3、对话历史管理

  • 对话历史的存储、加载与剪裁

3.4、架构封装

  • Chain:实现一个功能或者一系列顺序功能组合

  • Agent:根据用户输入,自动规划执行步骤,自动选择每步需要的工具,最终完成用户指定的功能

    • Tools:调用外部功能的函数,例如:调 google 搜索、文件 I/O、Linux Shell 等等
    • Toolkits:操作某软件的一组工具集,例如:操作 DB、操作 Gmail 等等

3.5、Callbacks

在LangChain中,Callback 是一种非常重要的机制,它允许用户监听和处理在执行链式任务 (Chain) 过程中的各种事件。举个例子,在你问完模型问题之后需要记录时间回答所用时长、统计答案多多少个字、回答问题之后主动提醒你回答完了,以上这些在AI 执行任务的节点所出发的设定操作都可以用 CallBaks实现。

四、通过代码体验LangChain

笔者在调用的 openAI的远程接口来展示功能,大家看的时候不要迷糊。

笔者下面做的验证都使用的是 OpenAI的远程接口,因此如果想要用笔者的代码请先确保环境变量里面配置好了OPENAI_API_KEY, OPENAI_BASE_URL

4.1 、模型 I/O封装

把不同模型统一封装成一个接口,方便更换模型而不用重构代码,同时通过模型的封装实现模型的统一接口调用。

# 没配过 python 环境的同学,直接搜Anaconda搞一个吧,能用明白命令行的同学装个minni也行。
# 这是第一步。
!pip install --upgrade langchain
!pip install --upgrade langchain-openai
!pip install --upgrade langchain-community

4.1.1、OpenAI封装

from langchain_openai import ChatOpenAI

# 如果不想在代码中显式的使用key,就得保证操作系统的环境变量里面配置好了OPENAI_API_KEY, OPENAI_BASE_URL
llm = ChatOpenAI(model="gpt-4o-mini")  # 默认是gpt-3.5-turbo
response = llm.invoke("你是谁")
print(response.content)

以上代码的执行结果是

4.1.2、多轮对话 Session 封装

多轮对话是指用户与系统之间进行多次信息交换的对话形式。这种对话模式需要系统能够维护对话状态,理解并利用上下文信息。

from langchain.schema import (
'''
在LangChain框架中,四类消息通过角色分工实现对LLM的精细化控制:
SystemMessage是“导演”,设定对话规则;
HumanMessage是“用户代理”,传递具体需求;
AIMessage是“执行者”,生成响应或触发工具;
ChatMessage是“灵活扩展”,但需谨慎使用。
'''
#下面这三个参数具体的概念和用法大家可以单独查一下。
    AIMessage,  # 表示LLM生成的响应,可能包含文本或工具调用请求。在多轮对话中,历史AIMessage可作为上下文,影响后续响应。
    HumanMessage,  # 表示用户向LLM发送的输入,对应对话中的“用户”角色。如提问“帮我翻译这段文本”。
    SystemMessage  # 是对话中的初始化指令,用于设置LLM的行为模式、上下文或背景信息。通常作为对话的首条消息传入。
)
)

messages = [
    SystemMessage(content="你是机器人助手"),
    HumanMessage(content="我是使用者,我叫张三。"),
    AIMessage(content="欢迎!"),
    HumanMessage(content="我是谁")
]

ret = llm.invoke(messages)
# 将上下文都拿到进行输出。
print(ret.content)

以上代码在获取上下文后进行输出,结果是:

4.2、模型的输入与输出

4.2.1、Prompt模板封装

Prompt模板是预定义的提示词(Prompt)结构,包含可填充的占位符,用于规范与大语言模型(LLM)交互的输入格式,以提高输出结果的准确性和一致性。

1、PromptTemplate在模版中自定义变量

from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI

template = PromptTemplate.from_template("给我讲个关于{subject}的笑话")
print("********Template********")
print(template)
print("********Prompt********")
print(template.format(subject='李华'))

# 定义 LLM
llm = ChatOpenAI(model="gpt-4o-mini")
# 通过 Prompt 调用 LLM
ret = llm.invoke(template.format(subject='李华'))
# 打印输出
print(ret.content)

2、ChatPromptTemplate 用模板表示的对话上下文

from langchain.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    SystemMessagePromptTemplate,
)
from langchain_openai import ChatOpenAI

template = ChatPromptTemplate.from_messages(
    [
        SystemMessagePromptTemplate.from_template("你是{product}的客服助手。你的名字叫{name}"),
        HumanMessagePromptTemplate.from_template("{query}"),
    ]
)

llm = ChatOpenAI(model="gpt-4o-mini")
prompt = template.format_messages(
    product="XXX公司",
    name="小 XX",
    query="你是谁"
)

print(prompt)

ret = llm.invoke(prompt)

print(ret.content)

3、MessagesPlaceholder 把多轮对话变成模板

from langchain.prompts import (
    ChatPromptTemplate,
    HumanMessagePromptTemplate,
    MessagesPlaceholder,
)

human_prompt = "Translate your answer to {language}."
human_message_template = HumanMessagePromptTemplate.from_template(human_prompt)

chat_prompt = ChatPromptTemplate.from_messages(
    # variable_name 是 message placeholder 在模板中的变量名
    # 用于在赋值时使用
    [MessagesPlaceholder("history"), human_message_template]
)
from langchain_core.messages import AIMessage, HumanMessage

human_message = HumanMessage(content="Who is Elon Musk?")
ai_message = AIMessage(
    content="Elon Musk is a billionaire entrepreneur, inventor, and industrial designer"
)

messages = chat_prompt.format_prompt(
    # 对 "history" 和 "language" 赋值
    history=[human_message, ai_message], language="中文"
)

print(messages.to_messages())
result = llm.invoke(messages)
print(result.content)

4.2.2、从文件加载Prompt模板

新建一个 prompt_template.txt文件,内容如下:

举一个{topic}的例子

from langchain.prompts import PromptTemplate

template = PromptTemplate.from_file("prompt_template.txt")
print("********Template********")
print(template)
print("********Prompt********")
print(template.format(topic='冷笑话'))

4.2.3 、结构化输出

1、直接输出Pydntic 对象

‌*Pydantic 对象**‌是 Python 中的一个库,主要用于数据验证和解析。Pydantic通过使用Python的类型注解来定义模型的字段类型,并在运行时自动进行数据验证和解析。

from pydantic import BaseModel, Field

# 定义你的输出对象
class Date(BaseModel):
    year: int = Field(description="Year")
    month: int = Field(description="Month")
    day: int = Field(description="Day")
    era: str = Field(description="BC or AD")
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI

from langchain_core.output_parsers import PydanticOutputParser


model_name = 'gpt-4o-mini'
temperature = 0
llm = ChatOpenAI(model_name=model_name, temperature=temperature)

# 定义结构化输出的模型
structured_llm = llm.with_structured_output(Date)

template = """提取用户输入中的日期。
用户输入:
{query}"""

prompt = PromptTemplate(
    template=template,
)

query = "2025年六月8日天气 多云..."
input_prompt = prompt.format_prompt(query=query)

structured_llm.invoke(input_prompt)

输出结果如下:

2、输出指定格式的JSON

json_schema = {
    "title": "Date",
    "description": "Formated date expression",
    "type": "object",
    "properties": {
        "year": {
            "type": "integer",
            "description": "year, YYYY",
        },
        "month": {
            "type": "integer",
            "description": "month, MM",
        },
        "day": {
            "type": "integer",
            "description": "day, DD",
        },
        "era": {
            "type": "string",
            "description": "BC or AD",
        },
    },
}
structured_llm = llm.with_structured_output(json_schema)

structured_llm.invoke(input_prompt)

输出结果如下:

3、OutputParser

使用OutputParser可以按指定格式解析模型的输出,示例如下:

from langchain_core.output_parsers import JsonOutputParser

parser = JsonOutputParser(pydantic_object=Date)

prompt = PromptTemplate(
    template="提取用户输入中的日期。\n用户输入:{query}\n{format_instructions}",
    input_variables=["query"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

input_prompt = prompt.format_prompt(query=query)
output = llm.invoke(input_prompt)
print("原始输出:\n"+output.content)

print("\n解析后:")
parser.invoke(output)

输出结果如下:

4、 PydanticOutputParser

PydanticOutputParser 是一个用于解析 语言模型 输出的工具,它允许用户指定一个 Pydantic 模型,并指导语言模型生成符合该模型的 JSON 输出。通过这种方式,PydanticOutputParser确保从语言模型获得的结构化数据符合预期的格式,从而简化了数据处理和集成的过程‌

from langchain_core.output_parsers import PydanticOutputParser

parser = PydanticOutputParser(pydantic_object=Date)

input_prompt = prompt.format_prompt(query=query)
output = llm.invoke(input_prompt)
print("原始输出:\n"+output.content)

print("\n解析后:")
parser.invoke(output)

输出结果如下:

5、OutputFixingParser

‌OutputFixingParser 是一个用于修复API或模型输出格式错误的工具,它通过调用一个语言模型(LLM)来自动修复解析错误。OutputFixingParser封装了其他输出解析器,当初次解析失败时,它会调用LLM来尝试修复格式错误,从而提供了一种自动化解决方案‌。

from langchain.output_parsers import OutputFixingParser

new_parser = OutputFixingParser.from_llm(parser=parser, llm=ChatOpenAI(model="gpt-4o"))

bad_output = output.content.replace("5","五")
print("PydanticOutputParser:")
try:
    parser.invoke(bad_output)
except Exception as e:
    print(e)

print("OutputFixingParser:")
new_parser.invoke(bad_output)

输出结果如下

4.2.4、Function Calling

不知道 Function Calling 的请看博主写技术架构的那篇文章。

from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """Add two integers.

    Args:
        a: First integer
        b: Second integer
    """
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two integers.

    Args:
        a: First integer
        b: Second integer
    """
    return a * b
import json

llm_with_tools = llm.bind_tools([add, multiply])

query = "3的4倍是多少?"
messages = [HumanMessage(query)]

output = llm_with_tools.invoke(messages)

print(json.dumps(output.tool_calls, indent=4))
#回传 Function Call的结果
messages.append(output)

available_tools = {"add": add, "multiply": multiply}

for tool_call in output.tool_calls:
    selected_tool = available_tools[tool_call["name"].lower()]
    tool_msg = selected_tool.invoke(tool_call)
    messages.append(tool_msg)

new_output = llm_with_tools.invoke(messages)
for message in messages:
    print(json.dumps(message.dict(), indent=4, ensure_ascii=False))
print(new_output.content)

这里的结果运行图太长了,大家看看代码知道怎么回事儿就行了,实际开发中也不会这么去写着用。

4.3、数据连接封装

笔者这里准备了一个临时的 testFile.pdf,内容大概就是几大段文字三页的那个 pdf。

!pip install pymupdf
from langchain_community.document_loaders import PyMuPDFLoader

loader = PyMuPDFLoader("testFile.pdf")
pages = loader.load_and_split()

print(pages[0].page_content)

4.3.1、文档加载器Document Loaders

!pip install --upgrade langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 简单的文本内容切割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=100, 
    length_function=len,
    add_start_index=True,
)

paragraphs = text_splitter.create_documents([pages[0].page_content])
for para in paragraphs:
    print(para.page_content)
    print('-------')

4.3.2、文档处理器

1、TextSplitter

!pip install --upgrade langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 简单的文本内容切割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=200,
    chunk_overlap=100, 
    length_function=len,
    add_start_index=True,
)

paragraphs = text_splitter.create_documents([pages[0].page_content])
for para in paragraphs:
    print(para.page_content)
    print('-------')

4.3.3、向量数据库与向量检索

向量数据库的连接部分本质是接口的封装,数据库需要自己选型。

更多的三方检索组件链接,参考:https://python.langchain.com/v0.3/docs/integrations/vectorstores/

# 安装一个向量检索库Faiss
'''
‌向量检索库和向量数据库不是同一个概念。‌
向量数据库是一种专门设计用于存储和查询向量数据的数据库,常用于机器学习和数据科学领域。向量数据库的主要功能是高效地存储和检索高维向量数据,常用于推荐系统或检索系统中‌,而向量检索库则是指从向量库中检索出距离目标向量最近的K个向量的过程,通常使用欧式距离或余弦距离来衡量两个向量间的相似度‌。
'''
conda install -c pytorch faiss-cpu
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

# 加载文档
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-3 结果
retriever = db.as_retriever(search_kwargs={"k": 3})

docs = retriever.invoke("为什么觉着你适合当产品经理?")

for doc in docs:
    print(doc.page_content)
    print("----")

4.4、对话历史管理

4.4.1、历史记录的剪裁

在开发一个对话系统时,为了避免输入的上下文信息超过了模型的最大长度(max token),所以需要对历史对话内容进行裁剪,LangChain就提供了一个已经封装好的函数trim_messages来解决这个问题。

4.4.2、过滤带标识的历史记录

from langchain_core.messages import (
    AIMessage,
    HumanMessage,
    SystemMessage,
    filter_messages,
)

messages = [
    SystemMessage("you are a good assistant", id="1"),
    HumanMessage("example input", id="2", name="example_user"),
    AIMessage("example output", id="3", name="example_assistant"),
    HumanMessage("real input", id="4", name="bob"),
    AIMessage("real output", id="5", name="alice"),
]

filter_messages(messages, include_types="human")

五、Chain 和 LangChain Expression Language (LCEL)

在LangChain框架中,Chain(链) 和 LCEL(LangChain Expression Language) 是两个密切相关但本质不同的概念。

Chain(链): 是LangChain中处理流程的抽象概念,指将多个组件(模型、工具、逻辑)串联成一个可执行的任务序列。

LangChain Expression Language(LCEL)是一种声明式语言,可轻松组合不同的调用顺序构成 Chain。LCEL 自创立之初就被设计为能够支持将原型投入生产环境,无需代码更改,从最简单的“提示+LLM”链到最复杂的链(已有用户成功在生产环境中运行包含数百个步骤的 LCEL Chain)。

LCEL 的一些亮点包括:

  1. 流支持:使用 LCEL 构建 Chain 时,你可以获得最佳的首个令牌时间(即从输出开始到首批输出生成的时间)。对于某些 Chain,这意味着可以直接从 LLM 流式传输令牌到流输出解析器,从而以与 LLM 提供商输出原始令牌相同的速率获得解析后的、增量的输出。

  2. 异步支持:任何使用 LCEL 构建的链条都可以通过同步 API(例如,在 Jupyter 笔记本中进行原型设计时)和异步 API(例如,在 LangServe 服务器中)调用。这使得相同的代码可用于原型设计和生产环境,具有出色的性能,并能够在同一服务器中处理多个并发请求。

  3. 优化的并行执行:当你的 LCEL 链条有可以并行执行的步骤时(例如,从多个检索器中获取文档),我们会自动执行,无论是在同步还是异步接口中,以实现最小的延迟。

  4. 重试和回退:为 LCEL 链的任何部分配置重试和回退。这是使链在规模上更可靠的绝佳方式。目前我们正在添加重试/回退的流媒体支持,因此你可以在不增加任何延迟成本的情况下获得增加的可靠性。

  5. 访问中间结果:对于更复杂的链条,访问在最终输出产生之前的中间步骤的结果通常非常有用。这可以用于让最终用户知道正在发生一些事情,甚至仅用于调试链条。你可以流式传输中间结果,并且在每个 LangServe 服务器上都可用。

  6. 输入和输出模式:输入和输出模式为每个 LCEL 链提供了从链的结构推断出的 Pydantic 和 JSONSchema 模式。这可以用于输入和输出的验证,是 LangServe 的一个组成部分。

  7. 无缝 LangSmith 跟踪集成:随着链条变得越来越复杂,理解每一步发生了什么变得越来越重要。通过 LCEL,所有步骤都自动记录到 LangSmith,以实现最大的可观察性和可调试性。

  8. 无缝 LangServe 部署集成:任何使用 LCEL 创建的链都可以轻松地使用 LangServe 进行部署。

    使用 LCEL 的价值,也就是 LangChain 的核心价值。

    LCEL 笔者会写一篇专门的文章给大家示例

六、LangServe

‌LangServe **‌是一个用于构建和部署基于自然语言处理(NLP)模型的应用程序的框架,它帮助开发者将 LangChain 的可运行对象(Runnable)和链(Chain)部署为REST API。LangServe通过与 FastAPI 集成,并使用 Pydantic 进行数据验证,简化了从开发到生产的过渡,并确保服务的高性能和安全性‌。

七、LangChain 与LIamaIndex

LangChain 与 LlamaIndex 的错位竞争

八、LangChain与Dify

LangChain 是乐高,Dify 是拼好的乐高。

  • Dify:是低代码 AI 开发平台,强调易用性和快速开发,有直观的可视化界面,通过简单地拖拽和组合不同的模块来构建 Agent,即使是非技术人员也能参与到 AI 应用的定义和数据运营过程中。

  • LangChain:是开源的 Python 库,主要面向有编程经验的开发者,需要编写代码来定义逻辑、集成数据源以及协调大语言模型与其他组件之间的交互,更适合对代码编写有较高要求、希望深度定制 Agent 功能的开发者。

    开发过程的区别:

  • Dify:开发过程简单快捷,通过可视化界面和拖拽操作,利用预构建的模型、API 和集成,开发者可以快速组装出所需的 AI 应用功能,无需大量编写代码。

  • LangChain:遵循以代码为中心的传统开发过程,开发者需要熟练掌握 Python 编程以及 LangChain 框架,通过编写代码来实现各种功能,开发过程相对更复杂,需要更多的时间和精力来学习和实践。

posted @ 2025-06-14 16:06  BricheersZ  阅读(252)  评论(0)    收藏  举报