掘金 人工智能 8小时前
你应该懂的AI大模型(四)之 LangChain
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

LangChain是一个用于开发大型语言模型(LLM)应用的框架,它通过提供通用接口和组件,简化了LLM的集成和应用开发流程。该框架支持多种模型类型,包括LLMs、聊天模型和嵌入模型,并具备数据连接和行动执行两大核心特点,可以连接到各种数据源并执行特定操作。本文深入探讨了LangChain的核心组件,如模型I/O封装、数据连接封装、对话历史管理、架构封装和回调机制,并通过代码示例演示了如何在实际开发中使用LangChain,包括模型调用、Prompt模板、结构化输出和函数调用等。对于希望构建企业内部机器人助手或类似应用的开发者来说,LangChain是一个值得尝试的工具。

💡 LangChain的核心在于简化LLM应用开发,它是一个连接LLMs、外部计算和数据源的框架。

🔗 LangChain提供I/O封装、数据连接封装、对话历史管理、架构封装等核心组件,方便开发者构建复杂应用。

⚙️ 通过Prompt模板、结构化输出和函数调用等功能,LangChain提供了丰富的工具,用于控制LLM的输入和输出。

这篇文章里面会有一些 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 是个啥,那么就索性把LangChain类比数据库连接的JDBC,JDBC屏蔽数据库之间连接差异,LangChain 屏蔽了 LLM 之间的差异,是一个桥梁和中介的角色。

三、LangChain核心组件

3.1、模型的I/O封装

3.2、数据连接封装

对 RAG 的封装在这部分。

3.3、对话历史管理

3.4、架构封装

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_URLllm = ChatOpenAI(model="gpt-4o-mini")  # 默认是gpt-3.5-turboresponse = 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 PromptTemplatefrom langchain_openai import ChatOpenAItemplate = PromptTemplate.from_template("给我讲个关于{subject}的笑话")print("********Template********")print(template)print("********Prompt********")print(template.format(subject='李华'))# 定义 LLMllm = ChatOpenAI(model="gpt-4o-mini")# 通过 Prompt 调用 LLMret = llm.invoke(template.format(subject='李华'))# 打印输出print(ret.content)
2、ChatPromptTemplate 用模板表示的对话上下文
from langchain.prompts import (    ChatPromptTemplate,    HumanMessagePromptTemplate,    SystemMessagePromptTemplate,)from langchain_openai import ChatOpenAItemplate = 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, HumanMessagehuman_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 PromptTemplatetemplate = 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, HumanMessagePromptTemplatefrom langchain_openai import ChatOpenAIfrom langchain_core.output_parsers import PydanticOutputParsermodel_name = 'gpt-4o-mini'temperature = 0llm = 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 JsonOutputParserparser = 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 PydanticOutputParserparser = 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 OutputFixingParsernew_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@tooldef add(a: int, b: int) -> int:    """Add two integers.    Args:        a: First integer        b: Second integer    """    return a + b@tooldef multiply(a: int, b: int) -> int:    """Multiply two integers.    Args:        a: First integer        b: Second integer    """    return a * b
import jsonllm_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 PyMuPDFLoaderloader = 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、向量数据库与向量检索

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

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

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

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

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

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

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

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

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

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

    无缝 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 是拼好的乐高。

原文地址:https://www.cnblogs.com/bricheersz/p/18928399

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

LangChain LLM 框架 AI应用开发
相关文章