LangGraph入门教程

LangGraph 教程:在 LangChain 中集成知识图谱

目录

  1. 简介
  2. 前置条件
  3. 环境配置
  4. 安装必要的库
  5. 创建知识图谱
  6. 集成 LangChain 与知识图谱
    • 定义工具
    • 构建 Agent 类
    • 自定义模板和输出解析
  7. 运行示例
  8. 扩展与优化
  9. 常见问题与故障排除
  10. 总结

简介

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,能够查询知识图谱并结合语言模型生成回答。

定义工具

我们将定义两个工具:

  1. 查询知识图谱:根据用户的问题,从知识图谱中检索相关信息。
  2. 执行数学计算(之前定义的 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}"

解释

  1. 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)

详细解释

  1. 提示词模板 (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}:最终的回答。
  2. 语言模型(LLM)

    使用 ChatTongyi 作为语言模型。

    self.llm = ChatTongyi(model='qwen-plus')
    

    说明

    • ChatTongyi 是来自 langchain_community 的自定义语言模型。请根据实际使用的模型调整。
  3. 工具集成

    初始化工具列表,并获取所有工具的名称。

    self.tools = MyAgentTool().tools()
    self.toolnames = [tool.name for tool in self.tools]
    
  4. 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 负责将提示词模板传递给语言模型,并获取生成的响应。
  5. 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)

解释

  1. 实例化 Agent:创建 MyAgent 的实例。
  2. 定义问题
    • question_math:一个数学计算问题。
    • question_graph:一个关于知识图谱的问题。
  3. 运行 Agent:通过 run 方法将问题传递给 Agent。
  4. 输出结果:打印 Agent 的回答。

预期输出

Agent 的回答 (数学): 计算结果为 2 + 3 * 4 - 5 / 2 = 10.5
Agent 的回答 (知识图谱): OpenAI CEO Sam Altman

:实际输出可能会因语言模型的响应而有所不同。

扩展与优化

添加更多工具

您可以在 MyAgentTool 类中定义更多工具,以增强 Agent 的功能。例如,添加一个比较两个数大小的工具。

示例:添加比较工具
  1. 修改 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' 形式。"
  1. 使用新工具
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 的响应速度。
  • 安全性增强:进一步增强工具函数的安全性,防止潜在的代码注入和其他安全风险。

你可能感兴趣的:(python)