LangGraph 教程:在 LangChain 中集成知识图谱
目录
简介
LangGraph 是一个结合 LangChain 与知识图谱(Knowledge Graph)的应用,旨在通过结构化的知识库增强语言模型的理解和响应能力。通过将知识图谱与 LangChain 结合,Agent 可以利用图谱中的关系和实体,更准确地回答复杂问题,执行任务并提供深入的分析。
本教程将引导您如何创建一个简单的知识图谱,并将其集成到 LangChain Agent 中,实现基于图谱的问答功能。
前置条件
在开始之前,请确保您具备以下条件:
- Python 环境:建议使用 Python 3.7 及以上版本。
- 基础知识:了解 Python 编程、面向对象编程(OOP)、自然语言处理(NLP)基础。
- 安装必要的库:熟悉如何使用
pip
安装 Python 库。
环境配置
首先,确保您的开发环境已经准备好。建议使用虚拟环境来隔离项目依赖。
# 创建虚拟环境
python -m venv langgraph_env
# 激活虚拟环境
# Windows
langgraph_env\Scripts\activate
# macOS/Linux
source langgraph_env/bin/activate
安装必要的库
安装 LangChain、NetworkX(用于知识图谱)、以及其他必要的库。
pip install langchain python-dotenv openai networkx
注:如果您计划使用其他类型的知识图谱存储(如 Neo4j),请根据需要安装相应的库。
创建知识图谱
我们将使用 NetworkX 创建一个简单的知识图谱。NetworkX 是一个用于创建、操作和研究复杂网络结构的 Python 库。
示例知识图谱
假设我们要创建一个关于科技公司的知识图谱,包括公司、CEO 和产品的信息。
import networkx as nx
# 创建一个有向图
G = nx.DiGraph()
# 添加节点和边
G.add_node("OpenAI", type="Company")
G.add_node("Sam Altman", type="Person")
G.add_node("ChatGPT", type="Product")
G.add_edge("OpenAI", "Sam Altman", relation="CEO")
G.add_edge("OpenAI", "ChatGPT", relation="Produces")
# 保存图谱到文件(可选)
nx.write_gml(G, "knowledge_graph.gml")
解释
- 节点(Nodes):图中的实体,如公司、个人、产品。
- 边(Edges):节点之间的关系,如“CEO”、“Produces”。
集成 LangChain 与知识图谱
接下来,我们将创建一个 LangChain Agent,能够查询知识图谱并结合语言模型生成回答。
定义工具
我们将定义两个工具:
- 查询知识图谱:根据用户的问题,从知识图谱中检索相关信息。
- 执行数学计算(之前定义的
math
工具)。
from langchain.agents import Tool
from typing import Any, List
import networkx as nx
class MyAgentTool:
def __init__(self) -> None:
# 初始化工具,可以在此添加更多本地工具
# 加载知识图谱
self.graph = nx.read_gml("knowledge_graph.gml")
def tools(self):
return [
Tool(
name="math",
description="用于执行基本的数学计算,比如加减乘除。",
func=self.math_tool,
),
Tool(
name="query_graph",
description="用于查询知识图谱,输入格式为 '实体1 关系 实体2' 或 '实体 信息'。",
func=self.query_graph_tool,
)
]
def math_tool(self, input: str) -> str:
"""
简单的数学计算工具,解析输入的数学表达式并返回结果。
"""
try:
# 安全地计算数学表达式
# 仅允许数字和基本运算符
allowed_chars = "0123456789+-*/(). "
if not all(char in allowed_chars for char in input):
return "输入包含不允许的字符。"
result = eval(input)
return str(result)
except Exception as e:
return f"计算错误: {e}"
def query_graph_tool(self, input: str) -> str:
"""
查询知识图谱工具,根据输入返回相关信息。
输入格式示例:
- "OpenAI CEO"
- "OpenAI Produces"
- "ChatGPT Information"
"""
try:
tokens = input.split()
if len(tokens) == 2:
entity, info_type = tokens
if info_type.lower() == "information":
return f"{entity} 是一家科技公司,致力于人工智能的研究和开发。"
else:
# 搜索关系
if self.graph.has_edge(entity, info_type):
related_entities = list(self.graph.successors(entity))
return f"{entity} 与 {info_type} 的关系如下: {related_entities}"
else:
return f"在知识图谱中未找到 {entity} 和 {info_type} 之间的关系。"
elif len(tokens) == 3:
entity1, relation, entity2 = tokens
if self.graph.has_edge(entity1, entity2, relation=relation):
return f"{entity1} {relation} {entity2}"
else:
return f"在知识图谱中未找到 {entity1} {relation} {entity2} 的关系。"
else:
return "输入格式错误。请使用 '实体 信息' 或 '实体1 关系 实体2' 格式。"
except Exception as e:
return f"查询错误: {e}"
解释
MyAgentTool
类:- 初始化:加载预先创建的知识图谱文件
knowledge_graph.gml
。 tools
方法:返回一个工具列表,包括数学计算工具和知识图谱查询工具。math_tool
方法:执行简单的数学计算。query_graph_tool
方法:根据输入格式从知识图谱中检索相关信息。
- 初始化:加载预先创建的知识图谱文件
构建 Agent 类
from langchain.agents import AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import StringPromptTemplate
from langchain.chains import LLMChain
from typing import List, Union, Any
from langchain.schema import AgentAction, AgentFinish, OutputParserException
import re
from langchain_community.chat_models.tongyi import ChatTongyi
class MyAgent:
def __init__(self) -> None:
# Agent 的提示词模板
self.template = """请尽可能详细地回答下面的问题,你将始终用中文回答。当需要时,你可以使用以下工具:
{tools}
请按照以下格式回答:
Question: {input}
Thought: 你应该思考下一步该做什么
Action: 选择一个操作,必须是以下工具之一 [{tool_names}]
Action Input: 该操作的输入
Observation: 该操作的结果
...(此 Thought/Action/Action Input/Observation 可以重复多次)
Thought: 我现在知道最终的答案了
Final Answer: {final_answer}
现在开始! 记住使用中文回答,如果使用英文回答将受到惩罚。
Question: {input}
{agent_scratchpad}"""
# 定义语言模型(LLM)
self.llm = ChatTongyi(model='qwen-plus')
# 初始化工具列表
self.tools = MyAgentTool().tools()
# 创建 Agent 的提示词
self.prompt = self.MyTemplate(
template=self.template,
tools=self.tools,
input_variables=["input", "intermediate_steps"],
)
# 定义 LLMChain
self.llm_chain = LLMChain(
llm=self.llm,
prompt=self.prompt
)
# 获取工具名称列表
self.toolnames = [tool.name for tool in self.tools]
# 定义一个 LLMSingleActionAgent
self.agent = LLMSingleActionAgent(
llm_chain=self.llm_chain,
allowed_tools=self.toolnames,
output_parser=self.MyOutputParser(),
stop=["\nObservation:"],
)
# 运行 Agent 的方法
def run(self, input: str) -> str:
agent_executor = AgentExecutor.from_agent_and_tools(
agent=self.agent,
tools=self.tools,
handle_parsing_errors=True,
verbose=True
)
return agent_executor.run(input=input)
# 自定义模板渲染类
class MyTemplate(StringPromptTemplate):
template: str
tools: List[Tool]
def format(self, **kwargs: Any) -> str:
# 获取中间步骤
intermediate_steps = kwargs.pop("intermediate_steps")
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\nObservation: {observation}\nThought: "
# 将 agent_scratchpad 设置为该值
kwargs["agent_scratchpad"] = thoughts
# 从提供的工具列表中创建一个名为 tools 的变量
kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
# 创建一个提供的工具名称列表
kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
# 确保传递 final_answer 变量
if "final_answer" not in kwargs:
kwargs["final_answer"] = "暂时没有答案"
return self.template.format(**kwargs)
# 自定义输出解析类
class MyOutputParser(AgentOutputParser):
def parse(self, output: str) -> Union[AgentAction, AgentFinish]:
if "Final Answer:" in output:
return AgentFinish(
return_values={"output": output.split("Final Answer:")[-1].strip()},
log=output,
)
# 使用正则解析出动作和动作输入
regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\s*:(.*)"
match = re.search(regex, output, re.DOTALL)
if not match:
raise OutputParserException(f"无法解析 LLM 输出: `{output}`")
action = match.group(1).strip()
action_input = match.group(2).strip(" ").strip('"')
# 返回操作和操作输入
return AgentAction(tool=action, tool_input=action_input, log=output)
详细解释
-
提示词模板 (
self.template
)提示词模板定义了 Agent 如何组织其思考和行动流程,是与语言模型交互的关键部分。
self.template = """请尽可能详细地回答下面的问题,你将始终用中文回答。当需要时,你可以使用以下工具: {tools} 请按照以下格式回答: Question: {input} Thought: 你应该思考下一步该做什么 Action: 选择一个操作,必须是以下工具之一 [{tool_names}] Action Input: 该操作的输入 Observation: 该操作的结果 ...(此 Thought/Action/Action Input/Observation 可以重复多次) Thought: 我现在知道最终的答案了 Final Answer: {final_answer} 现在开始! 记住使用中文回答,如果使用英文回答将受到惩罚。 Question: {input} {agent_scratchpad}"""
占位符解释:
{tools}
:列出所有可用工具及其描述。{input}
:用户输入的问题。{tool_names}
:所有工具的名称列表。{agent_scratchpad}
:中间步骤记录,包括思考、行动、观察结果。{final_answer}
:最终的回答。
-
语言模型(LLM)
使用
ChatTongyi
作为语言模型。self.llm = ChatTongyi(model='qwen-plus')
说明:
ChatTongyi
是来自langchain_community
的自定义语言模型。请根据实际使用的模型调整。
-
工具集成
初始化工具列表,并获取所有工具的名称。
self.tools = MyAgentTool().tools() self.toolnames = [tool.name for tool in self.tools]
-
LLMChain
将语言模型和提示词模板结合,创建一个
LLMChain
实例。self.prompt = self.MyTemplate( template=self.template, tools=self.tools, input_variables=["input", "intermediate_steps"], ) self.llm_chain = LLMChain( llm=self.llm, prompt=self.prompt )
说明:
LLMChain
负责将提示词模板传递给语言模型,并获取生成的响应。
-
Agent 初始化
使用
LLMSingleActionAgent
定义 Agent 的行为。self.agent = LLMSingleActionAgent( llm_chain=self.llm_chain, allowed_tools=self.toolnames, output_parser=self.MyOutputParser(), stop=["\nObservation:"], )
参数说明:
llm_chain
:与语言模型和提示词模板关联的链。allowed_tools
:Agent 允许调用的工具名称列表。output_parser
:自定义的输出解析器,用于解析语言模型的输出。stop
:停止词,指示语言模型在生成特定内容后停止。
自定义模板渲染
通过继承 StringPromptTemplate
,自定义提示词的渲染逻辑。
class MyTemplate(StringPromptTemplate):
template: str
tools: List[Tool]
def format(self, **kwargs: Any) -> str:
# 获取中间步骤
intermediate_steps = kwargs.pop("intermediate_steps")
thoughts = ""
for action, observation in intermediate_steps:
thoughts += action.log
thoughts += f"\nObservation: {observation}\nThought: "
# 将 agent_scratchpad 设置为该值
kwargs["agent_scratchpad"] = thoughts
# 从提供的工具列表中创建一个名为 tools 的变量
kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
# 创建一个提供的工具名称列表
kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
# 确保传递 final_answer 变量
if "final_answer" not in kwargs:
kwargs["final_answer"] = "暂时没有答案"
return self.template.format(**kwargs)
功能:
- 处理中间步骤(
intermediate_steps
),记录 Agent 的思考和观察。 - 填充工具列表和工具名称。
- 确保模板中所有占位符都有对应的值。
自定义输出解析
通过继承 AgentOutputParser
,自定义如何解析语言模型的输出。
class MyOutputParser(AgentOutputParser):
def parse(self, output: str) -> Union[AgentAction, AgentFinish]:
if "Final Answer:" in output:
return AgentFinish(
return_values={"output": output.split("Final Answer:")[-1].strip()},
log=output,
)
# 使用正则解析出动作和动作输入
regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\s*:(.*)"
match = re.search(regex, output, re.DOTALL)
if not match:
raise OutputParserException(f"无法解析 LLM 输出: `{output}`")
action = match.group(1).strip()
action_input = match.group(2).strip(" ").strip('"')
# 返回操作和操作输入
return AgentAction(tool=action, tool_input=action_input, log=output)
功能:
- 如果输出包含
Final Answer:
,则表示 Agent 已完成任务,返回最终答案。 - 否则,使用正则表达式提取下一步的动作(即调用的工具)和动作输入。
- 如果无法解析输出,则抛出异常。
运行示例
现在,我们可以创建一个 MyAgent
实例,并运行它来回答问题。
示例代码
if __name__ == "__main__":
myagent = MyAgent()
question_math = "请计算以下表达式的结果:2 + 3 * 4 - 5 / 2"
result_math = myagent.run(question_math)
print("Agent 的回答 (数学):", result_math)
question_graph = "OpenAI CEO"
result_graph = myagent.run(question_graph)
print("Agent 的回答 (知识图谱):", result_graph)
解释
- 实例化 Agent:创建
MyAgent
的实例。 - 定义问题:
question_math
:一个数学计算问题。question_graph
:一个关于知识图谱的问题。
- 运行 Agent:通过
run
方法将问题传递给 Agent。 - 输出结果:打印 Agent 的回答。
预期输出
Agent 的回答 (数学): 计算结果为 2 + 3 * 4 - 5 / 2 = 10.5
Agent 的回答 (知识图谱): OpenAI CEO Sam Altman
注:实际输出可能会因语言模型的响应而有所不同。
扩展与优化
添加更多工具
您可以在 MyAgentTool
类中定义更多工具,以增强 Agent 的功能。例如,添加一个比较两个数大小的工具。
示例:添加比较工具
- 修改
MyAgentTool
类
class MyAgentTool:
def __init__(self) -> None:
# 初始化工具,可以在此添加更多本地工具
# 加载知识图谱
self.graph = nx.read_gml("knowledge_graph.gml")
def tools(self):
return [
Tool(
name="math",
description="用于执行基本的数学计算,比如加减乘除。",
func=self.math_tool,
),
Tool(
name="query_graph",
description="用于查询知识图谱,输入格式为 '实体 信息' 或 '实体1 关系 实体2'。",
func=self.query_graph_tool,
),
Tool(
name="compare_numbers",
description="比较两个数的大小,输入格式为 'num1,num2'。",
func=self.compare_numbers,
)
]
def math_tool(self, input: str) -> str:
"""
简单的数学计算工具,解析输入的数学表达式并返回结果。
"""
try:
# 安全地计算数学表达式
# 仅允许数字和基本运算符
allowed_chars = "0123456789+-*/(). "
if not all(char in allowed_chars for char in input):
return "输入包含不允许的字符。"
result = eval(input)
return str(result)
except Exception as e:
return f"计算错误: {e}"
def query_graph_tool(self, input: str) -> str:
"""
查询知识图谱工具,根据输入返回相关信息。
输入格式示例:
- "OpenAI CEO"
- "OpenAI Produces"
- "ChatGPT Information"
"""
try:
tokens = input.split()
if len(tokens) == 2:
entity, info_type = tokens
if info_type.lower() == "information":
return f"{entity} 是一家科技公司,致力于人工智能的研究和开发。"
else:
# 搜索关系
related_entities = list(self.graph.successors(entity))
if related_entities:
return f"{entity} 与 {info_type} 的关系如下: {related_entities}"
else:
return f"在知识图谱中未找到 {entity} 和 {info_type} 之间的关系。"
elif len(tokens) == 3:
entity1, relation, entity2 = tokens
if self.graph.has_edge(entity1, entity2, relation=relation):
return f"{entity1} {relation} {entity2}"
else:
return f"在知识图谱中未找到 {entity1} {relation} {entity2} 的关系。"
else:
return "输入格式错误。请使用 '实体 信息' 或 '实体1 关系 实体2' 格式。"
except Exception as e:
return f"查询错误: {e}"
def compare_numbers(self, input: str) -> str:
"""
比较两个数的大小,输入格式为 'num1,num2'。
返回比较结果:num1 > num2, num1 < num2 或 num1 = num2。
"""
try:
# 解析输入字符串为两个数字
num1_str, num2_str = input.split(',')
num1 = float(num1_str.strip())
num2 = float(num2_str.strip())
# 比较两个数字的大小
if num1 > num2:
return f"{num1} > {num2}"
elif num1 < num2:
return f"{num1} < {num2}"
else:
return f"{num1} = {num2}"
except ValueError:
return "输入格式错误,请使用 'num1,num2' 形式。"
- 使用新工具
if __name__ == "__main__":
myagent = MyAgent()
# 使用 math 工具
question_math = "请计算以下表达式的结果:2 + 3 * 4 - 5 / 2"
result_math = myagent.run(question_math)
print("Agent 的回答 (数学):", result_math)
# 使用 query_graph 工具
question_graph = "OpenAI CEO"
result_graph = myagent.run(question_graph)
print("Agent 的回答 (知识图谱):", result_graph)
# 使用 compare_numbers 工具
question_compare = "请比较以下两个数字的大小:10, 20"
result_compare = myagent.run(question_compare)
print("Agent 的回答 (比较):", result_compare)
预期输出:
Agent 的回答 (数学): 计算结果为 2 + 3 * 4 - 5 / 2 = 10.5
Agent 的回答 (知识图谱): OpenAI CEO Sam Altman
Agent 的回答 (比较): 10.0 < 20.0
优化提示词模板
根据新增工具,您可以优化提示词模板以更好地指导 Agent 的行为。例如,提供更多上下文或示例。
常见问题与故障排除
1. KeyError: 'final_answer'
问题描述:在运行 Agent 时,出现 KeyError: 'final_answer'
错误。
原因:提示词模板中使用了 {final_answer}
占位符,但在渲染模板时未传递 final_answer
变量。
解决方法:
确保在模板渲染时,final_answer
变量被正确传递。可以在 MyTemplate
类的 format
方法中设置默认值。
代码修正:
if "final_answer" not in kwargs:
kwargs["final_answer"] = "暂时没有答案"
2. 工具调用失败
问题描述:Agent 尝试调用工具时失败,返回错误信息或无响应。
原因:
- 工具函数定义有误。
- 输入格式不正确。
- 工具函数中存在安全漏洞(如使用
eval
)。
解决方法:
- 检查工具函数的实现,确保逻辑正确。
- 确保输入格式符合工具函数的预期。
- 在使用
eval
时,严格限制允许的字符,防止代码注入。
3. 语言模型响应不符合预期
问题描述:Agent 的回答不符合预期,如语言混乱、无法使用工具等。
原因:
- 提示词模板设计不合理,未能正确引导语言模型。
- 工具描述不清晰,导致语言模型无法正确选择工具。
解决方法:
- 优化提示词模板,确保结构清晰,指令明确。
- 为每个工具提供详细且准确的描述,帮助语言模型正确调用工具。
- 调整语言模型的参数,如
temperature
,以获得更确定的回答。
总结
通过本教程,您已经学习了如何在 LangChain 中集成知识图谱(LangGraph),实现基于图谱的问答功能。以下是关键要点的总结:
- 环境配置:设置虚拟环境并安装必要的库。
- 创建知识图谱:使用 NetworkX 创建并管理知识图谱。
- 定义工具:封装知识图谱查询和其他功能为工具(Tools)。
- 构建 Agent 类:整合语言模型、工具、提示词模板和输出解析器。
- 运行示例:通过实例化 Agent 并运行问题,观察 Agent 的回答。
- 扩展与优化:添加更多工具,优化提示词模板,提升 Agent 的功能和性能。
- 故障排除:解决常见问题,确保 Agent 的稳定性和可靠性。
关键要点
- 环境配置:使用虚拟环境和
.env
文件管理 API 密钥,确保安全性和可移植性。 - 知识图谱:利用 NetworkX 构建和管理知识图谱,为 Agent 提供结构化的知识基础。
- 工具(Tools):通过定义工具,Agent 可以执行特定任务,如数学计算和知识查询。
- 提示词模板:设计清晰、结构化的提示词模板,引导语言模型的思考和行动。
- 输出解析器:自定义输出解析器,确保语言模型的响应能够被正确解析和执行。
- 扩展功能:根据需求添加更多工具,增强 Agent 的多样性和实用性。
- 故障排除:掌握常见问题的解决方法,提升 Agent 的稳定性和可靠性。
后续扩展
- 添加更多知识图谱:集成更复杂和丰富的知识图谱,涵盖更多领域和关系。
- 多轮对话支持:扩展 Agent 以支持多轮对话,管理对话历史,提升用户体验。
- 高级查询功能:实现更复杂的知识图谱查询功能,如模糊匹配、路径查找等。
- 性能优化:优化工具调用和知识图谱查询的效率,提升 Agent 的响应速度。
- 安全性增强:进一步增强工具函数的安全性,防止潜在的代码注入和其他安全风险。