LangGraph入门教程

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

目录

  1. 简介
  2. 前置条件
  3. 环境配置
  4. 安装必要的库
  5. 创建知识图谱
  6. 集成 LangChain 与知识图谱
  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 的响应速度。
  • 安全性增强:进一步增强工具函数的安全性,防止潜在的代码注入和其他安全风险。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值