从 API 到 Agent:LangChain 工程化设计详解
本文探讨了 LangChain 框架的工程化设计理念,从基础 API 调用开始,逐步构建链(Chain)、记忆(Memory)、检索增强生成(RAG)及智能体(Agent)。文章介绍了如何利用 LCEL 声明式语言简化开发,结合向量数据库解决幻觉问题,并通过工具调用实现自主决策。最后总结了 LangChain 的架构组件,为开发者构建复杂 AI 应用提供了完整的技术路径和代码示例。

本文探讨了 LangChain 框架的工程化设计理念,从基础 API 调用开始,逐步构建链(Chain)、记忆(Memory)、检索增强生成(RAG)及智能体(Agent)。文章介绍了如何利用 LCEL 声明式语言简化开发,结合向量数据库解决幻觉问题,并通过工具调用实现自主决策。最后总结了 LangChain 的架构组件,为开发者构建复杂 AI 应用提供了完整的技术路径和代码示例。

正式开始前,有必要从定义开始。LangChain 是 2022 年 10 月底,由哈佛大学的 Harrison Chase 发起的基于开源大语言模型的 AI 工程开发框架。
我:LangChain 是什么? GPT-4:LangChain 是一个开源的语言模型工具链框架,旨在使研究人员和开发人员能够更轻松地构建、实验和部署以自然语言处理(NLP)为中心的应用程序。它提供了多种组件和工具,可帮助用户利用最近的语言模型进展,如大型 Transformer 模型等,并且可以与 Hugging Face 等平台集成。LangChain 的核心理念是将语言模型用作协作工具,通过它,开发者可以构建出处理复杂任务的系统,并且可以高效地对接不同的数据源和应用程序接口(APIs)。
顾名思义,LangChain 中的'Lang'自然是指大语言模型,'Chain'即'链',也就是将大模型与其他组件连接成链,借此构建 AI 工程应用。那么 LangChain 该如何做到这一点的呢?解答这个问题之前,需要先回答什么是工程?什么是 AI 工程?
我们先 Review 一下'工程'的百科定义:
工程是指以某组设想的目标为依据,应用有关的科学知识和技术手段,通过有组织的一群人将某个(或某些)现有实体(自然的或人造的)转化为具有预期使用价值的人造产品过程。
其中,'目标'定义了要解决的问题域,决定了工程的顶层设计和能力边界,同时也定义了'产品'的最终形态。提升'人'的 ROI 是工程设计的价值归属。'实体'是工程的生产材料输入,'科学 | 技术'是工程有序运行的基础,对它们的合理利用可以提升工程的整体产出效率。
![AI 工程关键概念剖析]
于是,我们可以这样解读'AI 工程'中的关键概念:
因此,如果我们是 LangChain 的设计者,希望构建通用的 AI 工程框架。需要回答如下问题:
当然,作为'事后诸葛',这些问题目前有比较明确的答案:
很明显,LLM 作为 LangChain 能力的基础,是了解 LangChain 工程化设计的前提。接下来我们就从最基础的 LLM API 使用谈起,一步步了解 LangChain 的工程化构建过程及其背后的设计理念。
export OPENAI_API_KEY=""pip install langchain langchain-openai架构设计领域有个比较流行的术语——乐高架构,当然也可以叫可插拔架构。说白就是通过对系统基本组件的合理抽象,找到构造复杂系统的统一规律和可达路径,从而实现在降低系统实现复杂度的同时,提升系统整体的扩展性。
LangChain 实际上也遵循了乐高架构的思想。当然,作为最关键的乐高组件之一,LLM 的能力自然是我们优先了解的对象,那我们就从 OpenAI 的 API 开始吧!
文本生成模型服务是 OpenAI 提供的最核心的 API 服务,自 ChatGPT 发布后经历过几次版本迭代。
当下最新的是 Chat Completion API,是 AI 与 LLM 交互的核心入口。
代码示例参考:
import openai
openai.api_key = "sk-..."
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "图计算是一种计算模型吗?"}]
)
print(response.choices[0].message.content)
代码示例输出:
图计算是一种计算模型,用于处理大规模图形结构的数据,并执行各种复杂的算法和计算。这种计算模型主要用于社交网络分析、Web 搜索、生物信息学、网络路由优化、数据挖掘等领域。图计算模型的核心是将数据表示为图形结构(节点和边),这样可以更好地揭示数据之间的关系和互动。在图计算中,算法通常以迭代的方式运行,每次迭代都会更新图中节点的状态,直到达到某种停止条件。
早先的 Completion API 已经在 2023 年 7 月后不再维护,和最新的 Chat Completion API 参数和结果格式有所不同,最明显的是 Prompt 是以纯文本方式传递,而非 Message 格式。
除了文本生成服务,OpenAI 也提供了大量的 LLM 的周边服务,以协助 AI 工程构建更复杂的应用能力。如:函数调用、嵌入、微调、多模态等,具体可参考 OpenAI 开发文档的内容。
自 2022 年 11 月底 ChatGPT 发布以来,AI 的大门才真正地向人类打开,其中给用户留下最深印象的功能,自然是智能对话。OpenAI 的 Chat Completion API 参数支持传入消息历史,可以轻松地实现简单的对话服务。
代码示例参考:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "什么是图计算?"}
]
response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=messages)
print(response.choices[0].message.content)
代码示例输出:
me: 什么是图计算? ai: 图计算是一种计算模型,用于处理大规模图形结构数据的计算和分析。在这种计算模型中,数据被表示为图形,其中节点代表实体,边代表实体之间的关系。图计算可以用于解决许多实际问题,如社交网络分析、网络路由、生物信息学等。图计算的主要挑战是如何有效地处理大规模的图形数据,并提供快速的计算和分析结果。 me: 刚才我问了什么问题? ai: 你问的问题是:'什么是图计算?'
到目前为止,我们还只是用 OpenAI 最原始的 RESTful API 构建 LLM 工程能力,甚至连 OpenAI 提供的 SDK 都未使用。显然这不是一个高效的方式,使用前边安装的 LangChain-OpenAI 集成包 langchain-openai 可以大大降低代码的开发成本。
代码示例参考:
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
print(llm.invoke("图计算是什么?"))
代码示例输出:
content='图计算是一种计算模型,主要用于处理图形结构数据的计算和分析。图计算的对象是图,图由节点和边组成,节点代表实体对象,边代表实体对象之间的关系。图计算主要用于解决实体关系复杂、关系密集的问题,如社交网络分析、网络拓扑分析、推荐系统等。图计算的主要任务是通过对图中节点和边的计算,发现和提取出图中隐含的知识 和信息。'
对于文本生成模型服务来说,实际的输入和输出本质上都是字符串,因此直接裸调用 LLM 服务带来的问题是要在输入格式化和输出结果解析上做大量的重复的文本处理工作。LangChain 当然考虑到这一点,提供了 Prompt 和 OutputParser 抽象,用户可以根据自己的需要选择具体的实现类型使用。
![IO 数据抽象示意]
代码示例参考:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_template("What is {topic}?")
output_parser = StrOutputParser()
chain = prompt | llm | output_parser
print(chain.invoke({"topic": "图计算"}))
模型的 IO 组件确实可以减少重复的文本处理工作,但形式上依然不够清晰,这里就引入了 LangChain 中的关键概念:链(Chain)。
LangChain 的表达式语言(LCEL)通过重载 __or__ 运算符的思路,构建了类似 Unix 管道运算符的设计,实现更简洁的 LLM 调用形式。
代码示例参考:
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
chain = prompt | llm | StrOutputParser()
print(chain.invoke({"topic": "programming"}))
至此,我们终于看到了 LangChain 版的'HelloWorld'……
当然,为了简化 Chain 的参数调用格式,也可以借助 RunnablePassthrough 透传上游参数输入。
代码示例参考:
from langchain_core.runnables import RunnablePassthrough
chain = RunnablePassthrough() | prompt | llm | StrOutputParser()
print(chain.invoke({"topic": "programming"}))
另外,Chain 也可以分叉、合并,组合出更复杂的 DAG 计算图结构。
代码示例参考:
from langchain_core.runnables import RunnableParallel, RunnableLambda
def add_one(x):
return x + 1
chain = RunnableParallel({"x": RunnableLambda(lambda x: x), "y": RunnableLambda(lambda x: x+1)})
print(chain.invoke(1))
代码示例输出:
苹果是一种营养丰富的水果,具有帮助消化、保护心脏、降低糖尿病风险、强化免疫系统、帮助减肥、保护视力、预防哮喘、抗癌和提升记忆力等多种好处。然而,过度食用或者不适当的食用方式也可能带来一些不利影响,如引发过敏、导致腹泻、对牙齿造成伤害、可能携带农药残留、影响正常饮食和钙质吸收、增加蛀牙风险和引发胃痛等。因此,我们在享受苹果带来的好处的同时,也需要注意适量和正确的食用方式。
通过调用 chain.get_graph().print_ascii() 可以查看 Chain 的计算图结构。当然,使用 LangSmith 能更清晰的跟踪每一步的计算结果。
![LangChain 的计算图与 Trace]
Tips:开启 LangSmith 需要配置环境变量:
export LANGCHAIN_TRACING_V2="true" export LANGCHAIN_API_KEY=""
基于 LCEL 确实能描述比较复杂的 LangChain 计算图结构,但依然有 DAG 天然的设计限制,即不能支持'循环'。于是 LangChain 社区推出了一个新的项目——LangGraph,期望基于 LangChain 构建支持循环和跨多链的计算图结构,以描述更复杂的,甚至具备自动化属性的 AI 工程应用逻辑,比如智能体应用。其具体使用方式可以参考 LangGraph 文档。
LangGraph 声称其设计理念受 Pregel/Beam 的启发,构建支持多步迭代的计算能力,这部分设计理念和我们设计的支持'流/批/图'计算一体化的图计算引擎也十分相似,感兴趣的朋友可以访问相关图计算项目进行学习。
通过 Chain,LangChain 相当于以'工作流'的形式,将 LLM 与 IO 组件进行了有秩序的连接,从而具备构建复杂 AI 工程流程的能力。而我们都知道 LLM 提供的文本生成服务本身不提供记忆功能,需要用户自己管理对话历史。因此引入 Memory 组件,可以很好地扩展 AI 工程的能力边界。
![带记忆的问答处理]
LangChain 的 BaseMemory 接口提供了 Memory 的统一抽象,提供了多种类型的 Memory 组件的实现,我们选用最简单的 ConversationBufferMemory 实现类型。
需要注意的是,要将 Memory 组件应用到 Chain 上,需要使用子类 LLMChain 进行创建 Chain。
代码示例参考:
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
llm = ChatOpenAI()
memory = ConversationBufferMemory()
conversation = ConversationChain(llm=llm, memory=memory)
print(conversation.predict(input="你好"))
print(conversation.predict(input="我叫小明"))
print(conversation.predict(input="我叫什么?"))
代码示例输出:
图计算是一种计算类型,主要处理的数据结构是图。图是由节点(或顶点)和边组成的,节点代表实体,边代表实体之间的关系。在图计算中,主要解决的问题是如何在图的结构上进行有效的计算和分析。 你问的问题是:'什么是图计算?'
这里可以看到,创建带 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 可以提升问答内容的时效性和可靠性,同时在一定程度上保护了业务数据的隐私性。
但由于每次问答都涉及外部系统数据检索,因此 RAG 的响应时延相对较高。另外,引用的外部知识数据会消耗大量的模型 Token 资源。因此,用户需要结合自身的实际应用场景做合适的技术选型。
![大语言模型优化技术]
借助 LCEL 提供的 RunnableParallel 可以清晰描述 RAG 的计算图结构,其中最关键的部分是通过 context 键注入向量存储(Vector Store)的查询器(Retriever)。
代码示例参考:
from langchain.chains import RetrievalQA
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
from langchain.document_loaders import TextLoader
loader = TextLoader('document.txt')
documents = loader.load()
vectorstore = FAISS.from_documents(documents, OpenAIEmbeddings())
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever())
print(qa.run("关于文档的问题?"))
代码示例输出:
蚂蚁图数据库目前没有公开信息表明已经开源。开源状态可能会随时间和公司政策变化,建议直接查阅蚂蚁集团或相关开源平台的官方信息获取最新和准确的消息。是的,蚂蚁的图数据库产品 TuGraph 是开源的。
![向量存储的写入与查询]
结合示例和向量数据库的存取过程,我们简单理解一下 RAG 中关键组件。
BaseLoader 的接口抽象和大量实现,具体可根据自身需要选择使用。RecursiveCharacterTextSplitter,其他参考 LangChain 的 TextSplitter 接口和实现。Embeddings 接口和实现。VectorStore 接口和实现。示例采用了 Meta 的 Faiss 向量数据库,本地安装方式:pip install faiss-cpu。需要额外提及的是,对于图数据库,可以将相似性搜索问题转化为图遍历问题,并具备更强的知识可解释性。蚂蚁开源的 TuGraph 数据库目前正在做类似的技术探索。BaseRetriever。'会使用工具'是人类和动物的根本区别。
要构建更强大的 AI 工程应用,只有生成文本这样的'纸上谈兵'能力自然是不够的。工具不仅仅是'肢体'的延伸,更是为'大脑'插上了想象力的'翅膀'。借助工具,才能让 AI 应用的能力真正具备无限的可能,才能从'认识世界'走向'改变世界'。
这里不得不提到 OpenAI 的 Chat Completion API 提供的函数调用能力,通过在对话请求内附加 tools 参数描述工具的定义格式(原先的 functions 参数已过期),LLM 会根据提示词推断出需要调用哪些工具,并提供具体的调用参数信息。用户需要根据返回的工具调用信息,自行触发相关工具的回调。下一章内容我们可以看到工具的调用动作可以通过 Agent 自主接管。
![LLM Tools 执行流程示意]
为了简化代码实现,我们用 LangChain 的注解 @tool 定义了一个测试用的'获取指定城市的当前气温'的工具函数。然后通过 bind_tools 方法绑定到 LLM 对象即可。需要注意的是这里需要用 JsonOutputToolsParser 解析结果输出。
代码示例参考:
from langchain.tools import tool
from langchain_openai import ChatOpenAI
@tool
def get_temperature(city: str) -> int:
"""获取指定城市的当前气温"""
import random
return random.randint(-20, 50)
llm = ChatOpenAI()
llm_with_tools = llm.bind_tools([get_temperature])
result = llm_with_tools.invoke("杭州今天多少度?")
print(result)
代码示例输出:
[{'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 专长的推理能力,自动化地决策获取什么样的知识,采取什么样的行动,直到完成用户设定的最终目标。
![LangChain Agent 工作流程示意图]
因此,作为一个智能体,需要具备以下核心能力:
![Agent 的核心能力]
我们使用 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_temperaturewith{'city': 'Hangzhou'}16 今天杭州的气温是 16 度。 Finished chain. 今天杭州的气温是 16 度。
需要补充说明的是,LangChain 提供了 Hub 功能,帮助大家管理共享 Agent 的提示词模板。上述示例代码的 Agent 提示词模板和 hwchase17/openai-tools-agent 的定义等价。
通过代码 prompt = hub.pull("hwchase17/openai-tools-agent") 可以直接引用创建 prompt。
![hwchase17/openai-tools-agent 的定义]
从 API 到 Agent,我们'脑暴'了一个完整 AI 工程应用的发展轨迹,借此我相信大家对 LangChain 的设计理念应该有了进一步的理解。
最后,我们再看一眼 LangChain 的产品架构。除了本文未介绍的 LangServe——将 Chain 部署为 RESTful 服务,其他不再赘述。
![LangChain 产品架构]
可能会有小伙伴疑问,为啥我一个搞图计算的,在这捣鼓起 AI 工具来了。抛开'拥抱行业,跟进趋势'这样的大口号不谈,单纯从工作需要角度,使用 AI 工具加速内容的产出,本身就可以帮助我节省大量的时间和精力,空出的'闲暇'时间去带娃也是极好的……
线上关于 LangChain 的文章,虽不说汗牛充栋,但也能随手拈来。作为'后入场'者,我过去从事了多年的数仓、中台、云产品的架构和研发,最近两三年一直在搞图计算这样的基础软件,就想试试结合自身多面的工程经验去理解 LangChain 背后的设计理念,给大家一个别样的视角去观察、体验和思考 AI 工程。这两天被

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online