什么是LangChain?
正式开始前,还是有必要从定义(What)开始。LangChain是2022年10月底,由哈佛大学的Harrison Chase发起的基于开源大语言模型的AI工程开发框架。当然也可以问一下AI:
我:LangChain是什么?
GPT-4:LangChain 是一个开源的语言模型工具链框架,旨在使研究人员和开发人员能够更轻松地构建、实验和部署以自然语言处理(NLP)为中心的应用程序。它提供了多种组件和工具,可帮助用户利用最近的语言模型进展,如大型 Transformer 模型等,并且可以与 Hugging Face 等平台集成。
LangChain 的核心理念是将语言模型用作协作工具,通过它,开发者可以构建出处理复杂任务的系统,并且可以高效地对接不同的数据源和应用程序接口(APIs)。这个框架试图简化连接不同的输入和输出流,以及在流中实现语言模型的过程。
顾名思义,LangChain中的“Lang”自然是指大语言模型,“Chain”即“链”,也就是将大模型与其他组件连接成链,借此构建AI工程应用。那么LangChain该如何(How)做到这一点的呢?解答这个问题之前,需要先回答什么是工程?什么是AI工程?
1.2 什么是AI工程?
我们先Review一下“工程”的百科定义:
工程是指以某组设想的目标为依据,应用有关的科学知识和技术手段,通过有组织的一群人将某个(或某些)现有实体(自然的或人造的)转化为具有预期使用价值的人造产品过程。
其中,“目标”定义了要解决的问题域,决定了工程的顶层设计和能力边界,同时也定义了“产品”的最终形态。提升“人”的ROI是工程设计的价值归属。“实体”是工程的生产材料输入,“科学 | 技术”是工程有序运行的基础,对它们的合理利用可以提升工程的整体产出效率。
于是,我们可以这样解读“AI工程”中的关键概念:
- 目标:待解决的特定AI用户需求。如内容创作、智能问答、文档摘要、图像识别等等。
- 人:实施AI工程的具体角色。可以是程序员,或者AI应用的研发团队、创业公司。
- 科学 | 技术:显然是大模型与相关工具服务,以及其后的计算科学理论。
- 实体:已有的文档、知识库、业务数据等生产材料。
- 产品:能满足目标需求的具体产品。如聊天机器人、内容生成工具等。
1.3 如何设计LangChain?
因此,如果我们是LangChain的设计者,希望构建通用的AI工程框架。需要回答如下问题:
- 【目标 | 产品】LangChain的设计目标是什么,能解决哪些AI工程问题?
- 【人】LangChain的编程接口如何定义,才能提升AI工程师的研发效率?
- 【实体 | 科学 | 技术】LangChain的核心组件如何抽象,以提升框架的扩展能力?
当然,作为“事后诸葛”,这些问题目前有比较明确的答案:
- 作为AI工程框架,LangChain实际是对LLM能力的扩展和补充。如果把LLM比作人的大脑,LangChain则是人的躯干和四肢,协助LLM完成“思考”之外的“脏活累活”。它的能力边界只取决于LLM的智力水平和LangChain能提供的工具集的丰富程度。
- LangChain提供了LCEL(LangChain Expression Language)声明式编程语言,降低AI工程师的研发成本。
- LangChain提供了Models、Prompts、Indexes、Memory、Chains、Agents六大核心抽象,用于构建复杂的AI应用,同时保持了良好的扩展能力。
很明显,LLM作为LangChain能力的基础,是了解LangChain工程化设计的前提。接下来我们就从最基础的LLM API使用谈起,一步步了解LangChain的工程化构建过程及其背后的设计理念。
2. 环境准备
- Python环境:建议3.8版本以上。
- 下载链接:https://www.python.org/downloads
- OpenAI SK:自备。
- 申请地址:https://platform.openai.com/api-keys
- 环境变量:
export OPENAI_API_KEY="<Your-OpenAI-SK>"
- 安装LangChain:
- 执行命令:
pip install langchain langchain-openai
- 执行命令:
3. 设计推演
架构设计领域有个比较流行的术语——乐高架构,当然也可以叫可插拔架构。说白就是通过对系统基本组件的合理抽象,找到构造复杂系统的统一规律和可达路径,从而实现在降低系统实现复杂度的同时,提升系统整体的扩展性。(非官方表达,大家能Get到我的意思就好……)
LangChain实际上也遵循了乐高架构的思想。当然,作为最关键的乐高组件之一,LLM的能力自然是我们优先了解的对象,那我们就从OpenAI的API开始吧!
3.1 造梦基础——API
文本生成模型服务是OpenAI提供的最核心的API服务,自ChatGPT发布后经历过几次版本迭代。
3.1.1 Chat Completion API
当下最新的是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搜索、生物信息学、网络路由优化、数据挖掘等领域。图计算模型的核心是将数据表示为图形结构(节点和边),这样可以更好地揭示数据之间的关系和互动。在图计算中,算法通常以迭代的方式运行,每次迭代都会更新图中节点的状态,直到达到某种停止条件。
3.1.2 Completion API
早先的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开发文档的内容。
3.2 智能开端——Chat
自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: 你问的问题是:“什么是图计算?”
3.3 初步封装——SDK
到目前为止,我们还只是用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='图计算是一种计算模型,主要用于处理图形结构数据的计算和分析。图计算的对象是图,图由节点和边组成,节点代表实体对象,边代表实体对象之间的关系。图计算主要用于解决实体关系复杂、关系密集的问题,如社交网络分析、网络拓扑分析、推荐系统等。图计算的主要任务是通过对图中节点和边的计算,发现和提取出图中隐含的知识和信息。'
3.4 数据抽象——IO
对于文本生成模型服务来说,实际的输入和输出本质上都是字符串,因此直接裸调用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)3.5 组装成链——Chain
模型的IO组件确实可以减少重复的文本处理工作,但形式上依然不够清晰,这里就引入了LangChain中的关键概念:链(Chain)。
3.5.1 HelloWorld
LangChain的表达式语言(LCEL)通过重载__or__运算符的思路,构建了类似Unix管道运算符的设计,实现更简洁的LLM调用形式。
代码示例参考:
# 创建Chain chain = prompt | llm | output_parser # 调用Chain answer = chain.invoke({'question': '什么是图计算?'}) print(answer)至此,我们终于看到了LangChain版的“HelloWorld”……
3.5.2 RunnablePassthrough
当然,为了简化Chain的参数调用格式,也可以借助RunnablePassthrough透传上游参数输入。
代码示例参考:
from langchain_core.runnables import RunnablePassthrough # 创建Chain chain = {"question": RunnablePassthrough()} | prompt | llm | output_parser # 调用Chain answer = chain.invoke('什么是图计算?') print(answer)3.5.3 DAG
另外,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>"
3.5.4 LangGraph
基于LCEL确实能描述比较复杂的LangChain计算图结构,但依然有DAG天然的设计限制,即不能支持“循环”。于是LangChain社区推出了一个新的项目——LangGraph,期望基于LangChain构建支持循环和跨多链的计算图结构,以描述更复杂的,甚至具备自动化属性的AI工程应用逻辑,比如智能体应用。其具体使用方式可以参考LangGraph文档。
LangGraph声称其设计理念受Pregel/Beam的启发,构建支持多步迭代的计算能力,这部分设计理念和我们设计的支持“流/批/图”计算一体化的图计算引擎TuGraph也十分相似,感兴趣的朋友可以访问TuGraph Analytics项目进行学习。
3.6 开启记忆——Memory
通过Chain,LangChain相当于以“工作流”的形式,将LLM与IO组件进行了有秩序的连接,从而具备构建复杂AI工程流程的能力。而我们都知道LLM提供的文本生成服务本身不提供记忆功能,需要用户自己管理对话历史。因此引入Memory组件,可以很好地扩展AI工程的能力边界。
3.6.1 Memory接口
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