LangChain 是 2022 年 10 月底,由哈佛大学的 Harrison Chase 发起的基于开源大语言模型的 AI 工程开发框架。当然也可以问一下 AI:
通义千问2.5
:LangChain 是一个开源框架,专注于简化开发者利用大型语言模型(LLM)创建应用程序的过程。这些大型语言模型,如GPT-3、GPT-4、LaMDA等,能够处理复杂的语言任务,但直接集成到实际应用中可能具有挑战性。LangChain通过提供一系列工具、组件和接口来解决这一问题,它使得开发者能够更容易地管理与LLM的交互、设计自定义提示策略、构建包含多个逻辑步骤的“链式”工作流,并且能够整合外部API、数据库等资源,从而极大地扩展了模型的功能和应用场景。
核心特点包括:
LangChain的应用场景广泛,涵盖聊天机器人、问答系统、文本生成、翻译、代码生成、数据分析等多个领域,为希望利用先进语言AI技术构建创新应用的开发者提供了强大的基础设施。
很明显,LLM 作为 LangChain 能力的基础,是了解 LangChain 工程化设计的前提。接下来我们就从最基础的 LLM API 使用谈起,一步步了解 LangChain 的工程化构建过程及其背后的设计理念。
export OPENAI_API_KEY="<Your-OpenAI-SK>"
pip install langchain langchain-openai
可插拔架构是一种设计思想,旨在提高系统的灵活性、可维护性和可扩展性。
在软件开发中,可插拔架构强调的是模块化和组件化设计,使得系统中的各个部分(模块、服务、组件)能够独立开发、测试、部署和维护,而不会相互影响。这种设计通常依赖于清晰定义的接口(APIs)和契约,使得组件之间保持低耦合度。具体表现为:
可插拔架构的核心价值在于提升系统的适应性和未来扩展能力,使得系统能够更容易地应对需求变化和技术迭代,同时降低复杂性和故障传播的风险。LangChain 实际上也遵循了可插拔架构的思想
文本生成模型服务是 OpenAI 提供的最核心的 API 服务,自 ChatGPT 发布后经历过几次版本迭代。
当下最新的是 Chat Completion API,是 AI 与 LLM 交互的核心入口。
代码示例参考:
import os
import requests
#API Key
api_key = os.getenv('OPENAI_API_KEY')
#头部信息
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {api_key}'
}
#准备数据
data = {
'model': 'gpt-4',
'messages': [{'role': 'user', 'content': '什么是图计算?'}],
'temperature': 0.7
}
#调用API
url = 'https://api.openai.com/v1/chat/completions'
response = requests.post(url, json=data, headers=headers)
answer = response.json()['choices'][0]['message']['content']
print(answer)
代码示例输出:
图计算是一种计算模型,用于处理大规模图形结构的数据,并执行各种复杂的算法和计算。这种计算模型主要用于社交网络分析、Web 搜索、生物信息学、网络路由优化、数据挖掘等领域。图计算模型的核心是将数据表示为图形结构(节点和边),这样可以更好地揭示数据之间的关系和互动。在图计算中,算法通常以迭代的方式运行,每次迭代都会更新图中节点的状态,直到达到某种停止条件。
早先的 Completion API 已经在 2023 年 7 月后不再维护,和最新的 Chat Completion API 参数和结果格式有所不同,最明显的是 Prompt 是以纯文本方式传递,而非 Message 格式。
#准备数据
data = {
'model': 'gpt-3.5-turbo-instruct',
'prompt': ['什么是图计算?'],
'max_tokens': 1024
}
#调用API
url = 'https://api.openai.com/v1/completions'
response = requests.post(url, json=data, headers=headers)
answer = response.json()['choices'][0]['text']
print(answer)
除了文本生成服务,OpenAI 也提供了大量的 LLM 的周边服务,以协助 AI 工程构建更复杂的应用能力。如:函数调用、嵌入、微调、多模态等,具体可参考 OpenAI 开发文档的内容。
自 2022 年 11 月底 ChatGPT 发布以来,AI 的大门才真正地向人类打开,其中给用户留下最深印象的功能,自然是智能对话。OpenAI 的 Chat Completion API 参数支持传入消息历史,可以轻松地实现简单的对话服务。
代码示例参考:
#对话历史
messages = []
def chat_with_ai(message):
#记录历史
messages.append({'role': 'user', 'content': message})
print(f'me: {message}')
#对话请求
data = {
'model': 'gpt-4',
'messages': messages,
'temperature': 0.7
}
url = 'https://api.openai.com/v1/chat/completions'
response = requests.post(url, json=data, headers=headers)
#解析回答
if response.status_code == 200:
answer = response.json()['choices'][0]['message']['content']
messages.append({'role': 'assistant', 'content': answer})
print(f"ai: {answer}")
else:
print(f'Error: {response.status_code}', response.json())
#多轮对话
chat_with_ai('什么是图计算?')
chat_with_ai('刚才我问了什么问题?')
代码示例输出:
me: 什么是图计算? ai: 图计算是一种计算模型,用于处理大规模图形结构数据的计算和分析。在这种计算模型中,数据被表示为图形,其中节点代表实体,边代表实体之间的关系。图计算可以用于解决许多实际问题,如社交网络分析、网络路由、生物信息学等。图计算的主要挑战是如何有效地处理大规模的图形数据,并提供快速的计算和分析结果。 me: 刚才我问了什么问题? ai: 你问的问题是:“什么是图计算?”
到目前为止,我们还只是用 OpenAI 最原始的 RESTful API 构建 LLM 工程能力,甚至连 OpenAI 提供的 SDK 都未使用。显然这不是一个高效的方式,使用前边安装的 LangChain-OpenAI 集成包langchain-openai
可以大大降低代码的开发成本。
代码示例参考:
from langchain_openai import ChatOpenAI
#调用Chat Completion API
llm = ChatOpenAI(model_name='gpt-4')
response = llm.invoke('什么是图计算?')
print(response)
代码示例输出:
content=‘图计算是一种计算模型,主要用于处理图形结构数据的计算和分析。图计算的对象是图,图由节点和边组成,节点代表实体对象,边代表实体对象之间的关系。图计算主要用于解决实体关系复杂、关系密集的问题,如社交网络分析、网络拓扑分析、推荐系统等。图计算的主要任务是通过对图中节点和边的计算,发现和提取出图中隐含的知识和信息。’
对于文本生成模型服务来说,实际的输入和输出本质上都是字符串,因此直接裸调用 LLM 服务带来的问题是要在输入格式化和输出结果解析上做大量的重复的文本处理工作。LangChain 当然考虑到这一点,提供了 Prompt 和 OutputParser 抽象,用户可以根据自己的需要选择具体的实现类型使用。
代码示例参考:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
#创建LLM
llm = ChatOpenAI(model_name='gpt-4')
#创建Prompt
prompt = ChatPromptTemplate.from_template("{question}")
#创建输出解析器
output_parser = StrOutputParser()
#调用LLM
message = prompt.invoke({'question': '什么是图计算?'})
response = llm.invoke(message)
answer = output_parser.invoke(response)
print(answer)
模型的 IO 组件确实可以减少重复的文本处理工作,但形式上依然不够清晰,这里就引入了 LangChain 中的关键概念:链(Chain)。
LangChain 的表达式语言(LCEL)通过重载__or__
运算符的思路,构建了类似 Unix 管道运算符的设计,实现更简洁的 LLM 调用形式。
代码示例参考:
#创建Chain
chain = prompt | llm | output_parser
#调用Chain
answer = chain.invoke({'question': '什么是图计算?'})
print(answer)
至此,我们终于看到了 LangChain 版的 “HelloWorld”……
当然,为了简化 Chain 的参数调用格式,也可以借助RunnablePassthrough
透传上游参数输入。
代码示例参考:
from langchain_core.runnables import RunnablePassthrough
#创建Chain
chain = {"question": RunnablePassthrough()} | prompt | llm | output_parser
#调用Chain
answer = chain.invoke('什么是图计算?')
print(answer)
另外,Chain 也可以分叉、合并,组合出更复杂的 DAG 计算图结构。
代码示例参考:
from operator import itemgetter
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
#创建LLM
llm = ChatOpenAI(model_name='gpt-4')
#创建输出解析器
output_parser = StrOutputParser()
#创建Prompt
topic_prompt = ChatPromptTemplate.from_template("生成一种'{input}'的名称")
good_prompt = ChatPromptTemplate.from_template("列举{topic}的好处:")
bad_prompt = ChatPromptTemplate.from_template("列举{topic}的坏处:")
summary_prompt = ChatPromptTemplate.from_messages(
[
("ai", "{topic}"),
("human", "好处:\n{good}\n\n坏处:\n{bad}"),
("system", "生成最终结论"),
]
)
#创建组合Chain
topic_chain = topic_prompt | llm | output_parser | {"topic": RunnablePassthrough()}
goods_chain = good_prompt | llm | output_parser
bads_chain = bad_prompt | llm | output_parser
summary_chain = summary_prompt | llm | output_parser
chain = (
topic_chain
| {
"good": goods_chain,
"bad": bads_chain,
"topic": itemgetter("topic"),
}
| summary_chain
)
#调用chain
answer = chain.invoke({"input": '常见水果'})
print(answer)
代码示例输出:
苹果是一种营养丰富的水果,具有帮助消化、保护心脏、降低糖尿病风险、强化免疫系统、帮助减肥、保护视力、预防哮喘、抗癌和提升记忆力等多种好处。然而,过度食用或者不适当的食用方式也可能带来一些不利影响,如引发过敏、导致腹泻、对牙齿造成伤害、可能携带农药残留、影响正常饮食和钙质吸收、增加蛀牙风险和引发胃痛等。因此,我们在享受苹果带来的好处的同时,也需要注意适量和正确的食用方式。
通过调用chain.get_graph().print_ascii()
可以查看 Chain 的计算图结构。当然,使用 LangSmith 能更清晰的跟踪每一步的计算结果。
Tips:开启 LangSmith 需要申请 LangChain 的 AK,并配置环境变量:
export LANGCHAIN_TRACING_V2="true"
export LANGCHAIN_API_KEY="<Your-LangChain-AK>"
基于 LCEL 确实能描述比较复杂的 LangChain 计算图结构,但依然有 DAG 天然的设计限制,即不能支持 “循环”。于是 LangChain 社区推出了一个新的项目——LangGraph,期望基于 LangChain 构建支持循环和跨多链的计算图结构,以描述更复杂的,甚至具备自动化属性的 AI 工程应用逻辑,比如智能体应用。其具体使用方式可以参考 LangGraph 文档。
LangGraph 声称其设计理念受 Pregel/Beam 的启发,构建支持多步迭代的计算能力
通过 Chain,LangChain 相当于以 “工作流” 的形式,将 LLM 与 IO 组件进行了有秩序的连接,从而具备构建复杂 AI 工程流程的能力。而我们都知道 LLM 提供的文本生成服务本身不提供记忆功能,需要用户自己管理对话历史。因此引入 Memory 组件,可以很好地扩展 AI 工程的能力边界。
LangChain 的BaseMemory
接口提供了 Memory 的统一抽象(截至 v0.1.12 还是 Beta 版本),提供了多种类型的 Memory 组件的实现,我们选用最简单的ConversationBufferMemory
实现类型。
需要注意的是,要将 Memory 组件应用到 Chain 上,需要使用子类LLMChain
进行创建 Chain。
代码示例参考:
from langchain.chains import LLMChain
from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate
from langchain_openai import ChatOpenAI
#创建LLM
llm = ChatOpenAI(model_name='gpt-4')
#创建Prompt
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name='chat_history'),
HumanMessagePromptTemplate.from_template('{question}')
])
#创建Memory
memory = ConversationBufferMemory(memory_key='chat_history',
return_messages=True)
#创建LLMChain
llm_chain = LLMChain(llm=llm, memory=memory, prompt=prompt)
#调用LLMChain
print(llm_chain.predict(question='什么是图计算?'))
print(llm_chain.predict(question='刚才我问了什么问题?'))
代码示例输出:
图计算是一种计算类型,主要处理的数据结构是图。图是由节点(或顶点)和边组成的,节点代表实体,边代表实体之间的关系。在图计算中,主要解决的问题是如何在图的结构上进行有效的计算和分析。 你问的问题是:“什么是图计算?”
这里可以看到,创建带 Memory 功能的 Chain,并不能使用统一的 LCEL 语法。调用LLMChain
使用的是 predict 而非 invoke 方法,直接调用 invoke 会返回一个LLMResult
类型的结果。因此,LLMChain
也不能使用管道运算符接StrOutputParser
。这些设计上的问题,个人推测也是目前 Memory 模块还是 Beta 版本的原因之一吧。
但是,LangChain 提供了工具类RunnableWithMessageHistory
,支持了为 Chain 追加 History 的能力,从某种程度上缓解了上述问题。不过需要指定 Lambda 函数 get_session_history 以区分不同的会话,并需要在调用时通过 config 参数指定具体的会话 ID。
SessionHistory 必须是 History 接口类型BaseChatMessageHistory
,用户可以根据需要选择不同的存储实现。这里为了简化,全局只用了一份内存类型的ChatMessageHistory
。
代码示例参考:
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
#创建LLM
llm = ChatOpenAI(model_name='gpt-4')
#创建输出解析器
output_parser = StrOutputParser()
#创建Prompt
prompt = ChatPromptTemplate.from_messages([
MessagesPlaceholder(variable_name="chat_history"),
HumanMessagePromptTemplate.from_template("{question}")
])
#创建Chain
chain = prompt | llm | output_parser
#添加History
history = ChatMessageHistory()
chain_with_history = RunnableWithMessageHistory(
chain,
lambda session_id: history,
input_messages_key="question",
history_messages_key="chat_history",
)
#调用Chain
print(chain_with_history.invoke({'question': '什么是图计算?'},
config={"configurable": {"session_id": None}}))
print(chain_with_history.invoke({'question': '刚才我问了什么问题?'},
config={"configurable": {"session_id": None}}))
调用形式看起来是复杂了一些,不过代码结构相比 Memory 组件更清晰一些,聊胜于无……
拥有记忆后,确实扩展了 AI 工程的应用场景。但是在专有领域,LLM 无法学习到所有的专业知识细节,因此在面向专业领域知识的提问时,无法给出可靠准确的回答,甚至会 “胡言乱语”,这种现象称之为 LLM 的 “幻觉”。
检索增强生成(RAG)把信息检索技术和大模型结合起来,将检索出来的文档和提示词一起提供给大模型服务,从而生成更可靠的答案,有效的缓解大模型推理的 “幻觉” 问题。
如果说 LangChain 相当于给 LLM 这个 “大脑” 安装了 “四肢和躯干”,RAG 则是为 LLM 提供了接入“人类知识图书馆” 的能力。
相比提示词工程,RAG 有更丰富的上下文和数据样本,可以不需要用户提供过多的背景描述,即能生成比较符合用户预期的答案。相比于模型微调,RAG 可以提升问答内容的时效性和可靠性,同时在一定程度上保护了业务数据的隐私性。
但由于每次问答都涉及外部系统数据检索,因此 RAG 的响应时延相对较高。另外,引用的外部知识数据会消耗大量的模型 Token 资源。因此,用户需要结合自身的实际应用场景做合适的技术选型。
借助 LCEL 提供的RunnableParallel
可以清晰描述 RAG 的计算图结构,其中最关键的部分是通过 context 键注入向量存储(Vector Store)的查询器(Retriever)。
代码示例参考:
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores.faiss import FAISS
from langchain_core.documents import Document
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
#创建LLM
llm = ChatOpenAI(model_name='gpt-4')
#创建Prompt
prompt = ChatPromptTemplate.from_template('基于上下文:{context}\n回答:{input}')
#创建输出解析器
output_parser = StrOutputParser()
#模拟文档
docs = [Document(page_content="图数据库Nebula Graph简介")]
#文档嵌入
splits = RecursiveCharacterTextSplitter().split_documents(docs)
vector_store = FAISS.from_documents(splits, OpenAIEmbeddings())
retriever = vector_store.as_retriever()
#创建Chain
chain_no_context = RunnablePassthrough() | llm | output_parser
chain = (
{"context": retriever, "input": RunnablePassthrough()}
| prompt | llm | output_parser
)
#调用Chain
print(chain_no_context.invoke('Nebula Graph介绍'))
代码示例输出:
Nebula图数据库是一款开源的分布式图数据库系统,由中国的石墨烯数据库团队开发。它专为处理大规模图数据而设计,支持高效的存储、查询和分析,适用于社交网络、知识图谱、推荐系统等复杂数据关系的应用场景。Nebula图数据库采用分布式架构,支持水平扩展,提供多语言客户端和查询接口,具备数据持久性、一致性、多版本控制及安全性等特点,是处理复杂图数据的理想选择。
结合示例和向量数据库的存取过程,我们简单理解一下 RAG 中关键组件。
BaseLoader
的接口抽象和大量实现,具体可根据自身需要选择使用。RecursiveCharacterTextSplitter
,其他参考 LangChain 的TextSplitter
接口和实现。Embeddings
接口和实现。VectorStore
接口和实现。示例采用了 Meta 的 Faiss 向量数据库,本地安装方式:pip install faiss-cpu
。需要额外提及的是,对于图数据库,可以将相似性搜索问题转化为图遍历问题,并具备更强的知识可解释性。BaseRetriever
。要构建更强大的 AI 工程应用,只有生成文本这样的 “纸上谈兵” 能力自然是不够的。工具不仅仅是 “肢体” 的延伸,更是为 “大脑” 插上了想象力的 “翅膀”。借助工具,才能让 AI 应用的能力真正具备无限的可能,才能从“认识世界” 走向“改变世界”。
这里不得不提到 OpenAI 的 Chat Completion API 提供的函数调用能力(注意这里不是 Assistant 的函数调用),通过在对话请求内附加 tools 参数描述工具的定义格式(原先的 functions 参数已过期),LLM 会根据提示词推断出需要调用哪些工具,并提供具体的调用参数信息。用户需要根据返回的工具调用信息,自行触发相关工具的回调。下一章内容我们可以看到工具的调用动作可以通过 Agent 自主接管。
为了简化代码实现,我们用 LangChain 的注解 @tool 定义了一个测试用的 “获取指定城市的当前气温” 的工具函数。然后通过bind_tools
方法绑定到 LLM 对象即可。需要注意的是这里需要用JsonOutputToolsParser
解析结果输出。
代码示例参考:
import random
from langchain_core.output_parsers.openai_tools import JsonOutputToolsParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
#定义Tool
@tool
def get_temperature(city: str) -> int:
"""获取指定城市的当前气温"""
return random.randint(-20, 50)
#创建LLM
llm = ChatOpenAI(model_name='gpt-4')
#创建JSON输出解析器
output_parser = JsonOutputToolsParser()
#创建Chain
chain = (
RunnablePassthrough()
| llm.bind_tools(tools=[get_temperature])
| output_parser
)
#调用Chain
print(chain.invoke('杭州今天多少度?'))
代码示例输出:
[{‘type’: ‘get_temperature’, ‘args’: {‘city’: ‘杭州’}}]
实际上 LangChain 提供了大量的内置工具和工具库的支持。@tool 只是提供了简洁的工具创建的支持,要定制复杂的工具行为需要自行实现BaseTool
工具接口。同时工具库接口BaseToolkit
下也有大量的实现,如向量存储、SQL 数据库、GitHub 等等。用户可以根据自身需求选用或自行扩展。
通用人工智能(AGI)将是 AI 的终极形态,几乎已成为业界共识。类比之,构建智能体(Agent)则是 AI 工程应用当下的 “终极形态”。
引用 LangChain 中 Agent 的定义,可以一窥 Agent 与 Chain 的区别。
Agent 的核心思想是使用大型语言模型(LLM)来选择要采取的行动序列。在 Chain 中行动序列是硬编码的,而 Agent 则采用语言模型作为推理引擎来确定以什么样的顺序采取什么样的行动。
Agent 相比 Chain 最典型的特点是 “自治”,它可以通过借助 LLM 专长的推理能力,自动化地决策获取什么样的知识,采取什么样的行动,直到完成用户设定的最终目标。
因此,作为一个智能体,需要具备以下核心能力:
我们使用 Agent 继续完成前边 Tool 部分没有完成的例子。这里使用 create_openai_tools_agent 方法创建一个简单的 OpenAI 工具 Agent,AgentExecutor 会自动接管工具调用的动作。如果希望给 Agent 添加记忆能力,依然可以采用前边 Memory 章节提过的 RunnableWithMessageHistory 的方案。
代码示例参考:
import random
from langchain.agents import create_openai_tools_agent, \
AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, \
HumanMessagePromptTemplate, SystemMessagePromptTemplate
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
#创建LLM
llm = ChatOpenAI()
#定义Tool
@tool
def get_temperature(city: str) -> int:
"""获取指定城市的当前气温"""
return random.randint(-20, 50)
#创建Agent提示词模板
prompt = ChatPromptTemplate.from_messages([
SystemMessagePromptTemplate.from_template('You are a helpful assistant'),
MessagesPlaceholder(variable_name='chat_history', optional=True),
HumanMessagePromptTemplate.from_template('{input}'),
MessagesPlaceholder(variable_name='agent_scratchpad')
])
#创建Agent
tools = [get_temperature]
agent = create_openai_tools_agent(llm, tools, prompt=prompt)
#执行Agent
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
print(agent_executor.invoke({'input': '今天杭州多少度?'})['output'])
代码示例输出:
> > Entering new AgentExecutor chain...
> Invoking: `get_temperature` with `{'city': 'Hangzhou'}`
> 16 今天杭州的气温是 16 度。
>
> > Finished chain.
> 今天杭州的气温是 16 度。
需要补充说明的是,LangChain 提供了 Hub 功能,帮助大家管理共享 Agent 的提示词模板。上述示例代码的 Agent 提示词模板和 hwchase17/openai-tools-agent 的定义等价。
通过代码prompt = hub.pull("hwchase17/openai-tools-agent")
可以直接引用创建 prompt。
最后看一下 LangChain 的产品架构。除了本文未介绍的 LangServe——将 Chain 部署为 RESTful 服务,其他不再赘述。
LangGraph
:[https://github.com/langchain-ai/langgraph]
LangGraph是构建在LangChain框架之上的一个高级库,专为开发具备状态、多参与者特性的应用程序而设计,特别是在利用大型语言模型(LLMs)的场景中。它通过引入图数据结构来表示和管理智能体(Agents)及其运行时环境,使得构建复杂的应用逻辑和多步骤交互过程更为直观和高效。
相比于LangChain,LangGraph的主要优势包括:
更多优质内容请关注公号:汀丶人工智能;会提供一些相关的资源和优质文章,免费获取阅读。