这篇文章里面会有一些 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, etcText 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_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 侧重与 LLM 本身交互的封装
- Prompt、LLM、Message、OutputParser 等工具丰富在数据处理和 RAG 方面提供的工具相对粗糙主打 LCEL 流程封装配套 Agent、LangGraph 等智能体与工作流工具另有 LangServe 部署工具和 LangSmith 监控调试工具
LlamaIndex 侧重与数据交互的封装
- 数据加载、切割、索引、检索、排序等相关工具丰富Prompt、LLM 等底层封装相对单薄配套实现 RAG 相关工具有 Agent 相关工具,不突出
LlamaIndex 为 LangChain 提供了集成
- 在 LlamaIndex 中调用 LangChain 封装的 LLM 接口:docs.llamaindex.ai/en/stable/a…将 LlamaIndex 的 Query Engine 作为 LangChain Agent 的工具:docs.llamaindex.ai/en/v0.10.17…LangChain 也 曾经 集成过 LlamaIndex,目前相关接口仍在:api.python.langchain.com/en/latest/r…
八、LangChain与Dify
LangChain 是乐高,Dify 是拼好的乐高。
Dify:是低代码 AI 开发平台,强调易用性和快速开发,有直观的可视化界面,通过简单地拖拽和组合不同的模块来构建 Agent,即使是非技术人员也能参与到 AI 应用的定义和数据运营过程中。
LangChain:是开源的 Python 库,主要面向有编程经验的开发者,需要编写代码来定义逻辑、集成数据源以及协调大语言模型与其他组件之间的交互,更适合对代码编写有较高要求、希望深度定制 Agent 功能的开发者。
开发过程的区别:
Dify:开发过程简单快捷,通过可视化界面和拖拽操作,利用预构建的模型、API 和集成,开发者可以快速组装出所需的 AI 应用功能,无需大量编写代码。
LangChain:遵循以代码为中心的传统开发过程,开发者需要熟练掌握 Python 编程以及 LangChain 框架,通过编写代码来实现各种功能,开发过程相对更复杂,需要更多的时间和精力来学习和实践。
原文地址:https://www.cnblogs.com/bricheersz/p/18928399