掘金 人工智能 07月03日 13:48
LangGraph官方文档笔记(2)——使用工具增强聊天机器人
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了如何通过集成Tavily搜索引擎工具来增强聊天机器人的信息检索能力,使其能够回答更复杂的查询。文章详细阐述了Tavily工具的配置、调用方法,以及如何将其集成到LangGraph框架中,构建一个能够根据用户提问进行在线搜索并提供回复的智能聊天机器人。通过代码示例,展示了工具的初始化、状态图的构建、节点函数的实现以及用户交互循环的完整流程,帮助读者理解并实践这一技术。

🤖 **工具初始化与配置**: 首先,你需要获取Tavily API密钥,并安装必要的依赖。通过TavilySearch工具,可以配置搜索结果的最大数量,例如限制为2条。

💬 **LangGraph状态图构建**: 核心在于构建一个状态图,定义了聊天机器人的工作流程。这包括定义状态(例如消息列表)、初始化LLM(例如Claude-3-5-Sonnet),并将其与Tavily工具绑定。

⚙️ **节点函数的实现**: 实现关键的节点函数,包括chatbot节点用于生成AI回复,以及BasicToolNode节点用于处理工具调用请求。BasicToolNode会根据AI消息中的工具调用,执行Tavily搜索,并返回搜索结果。

🔄 **状态图路由逻辑**: 定义路由逻辑,决定在何时调用工具。这通过route_tools函数实现,该函数根据AI消息中是否包含工具调用来决定下一步流向,最终实现对Tavily工具的调用。

💻 **用户交互循环**: 最后,实现一个用户交互循环,允许用户输入问题。系统接收用户输入,处理并流式输出回答。还包含了异常处理,确保在无法获取用户输入时能够提供默认问题。

为了处理我们的聊天机器人无法“凭借记忆”回答的查询,我们将集成一个网络搜索工具。我们的机器人可以使用此工具查找相关信息并提供更好的回复。

这里的案例使用的是Tavily搜索引擎工具,也就是说,给我们的聊天机器人配置好这个工具后,我们的机器人就可以按需求去在线搜索相关的信息来辅助回答。

项目前的准备工作

获取TAVILY_API_KEY

首先前往tavily官网获取你的TAVILY_API_KEY。

安装环境依赖

pip install -U langchain-tavily

尝试调用搜索工具

我们可以先通过以下方式看看是否能够成功使用tavily工具:
TavilySearch API资料

from langchain_tavily import TavilySearchos.environ['TAVILY_API_KEY'] = 'YOUR_TAVILY_API_KEY'tool = TavilySearch(max_results=2)#返回两条结果tools = [tool]tool.invoke("What's a 'edge' in LangGraph?")

返回结果:

{'query': "What's a 'edge' in LangGraph?", 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'title': 'LangGraph Basics: Understanding State, Schema, Nodes, and Edges',   'url': 'https://medium.com/@vivekvjnk/langgraph-basics-understanding-state-schema-nodes-and-edges-77f2fd17cae5',   'content': 'LangGraph Basics: Understanding State, Schema, Nodes, and Edges | by Story_Teller | Medium LangGraph Basics: Understanding State, Schema, Nodes, and Edges LangGraph Basics: Understanding State, Schema, Nodes, and Edges These predefined structures in the messaging app are synonymous with the schema of the state in LangGraph. Just as a messaging app ensures all interactions (messages) follow a consistent format, the schema in LangGraph ensures the state passed along edges is structured and interpretable. This static schema allows nodes to rely on a consistent state format, ensuring seamless communication along edges throughout the graph. In this article, we explored the foundational concepts of graph-based systems, drawing parallels to familiar messaging applications to illustrate how edges, nodes, and state transitions function seamlessly in dynamic workflows.',   'score': 0.72042775,   'raw_content': None},  {'title': 'LangGraph for Beginners, Part 3: Conditional Edges - Medium',   'url': 'https://medium.com/ai-agents/langgraph-for-beginners-part-3-conditional-edges-16a3aaad9f31',   'content': 'In this article, we will create a simple graph that explains conditional edges in LangGraph. In the previous articles we discussed, we learnt how to create a simple graph and call LLM. We will create a simple graph, that forecasts weather as sunny or rainy based on a random number. Its dummy weather forecast function that predicts sunny weather 50% of the time and rainy weather 50% of the time. We define two more nodes, namely rainy\_weather and sunny\_weather. We will define another function forecast\_weather() that will decide whether the weather is sunny or rainy. ## Create the graph instance ## Add the nodes to the graph ## Test the graph In this article we learnt how to create a conditional edge.',   'score': 0.48250288,   'raw_content': None}], 'response_time': 2.16}

可以看到工具返回的是JSON格式的页面摘要,这意味着我们的聊天机器人可以使用它们来回答问题。

看官方的示例

#==============================第一部分===============================#from langchain_tavily import TavilySearchtool = TavilySearch(max_results=2)tools = [tool]#==============================第二部分===============================#from typing import Annotatedfrom langchain.chat_models import init_chat_modelfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesclass State(TypedDict):    messages: Annotated[list, add_messages]graph_builder = StateGraph(State)llm = init_chat_model("anthropic:claude-3-5-sonnet-latest")# Modification: tell the LLM which tools it can callllm_with_tools = llm.bind_tools(tools)def chatbot(state: State):    return {"messages": [llm_with_tools.invoke(state["messages"])]}graph_builder.add_node("chatbot", chatbot)#==============================第三部分===============================#import jsonfrom langchain_core.messages import ToolMessageclass BasicToolNode:    """A node that runs the tools requested in the last AIMessage."""    def __init__(self, tools: list) -> None:        self.tools_by_name = {tool.name: tool for tool in tools}    def __call__(self, inputs: dict):        if messages := inputs.get("messages", []):            message = messages[-1]# 使用最后一条消息        else:            raise ValueError("No message found in input")        outputs = []# 存储工具调用结果的消息列表        for tool_call in message.tool_calls:            tool_result = self.tools_by_name[tool_call["name"]].invoke(                tool_call["args"]# 传递工具所需参数            )            outputs.append(# 将工具执行结果封装为ToolMessage                ToolMessage(                    content=json.dumps(tool_result),                    name=tool_call["name"],                    tool_call_id=tool_call["id"],                )            )        return {"messages": outputs}# 返回工具调用结果消息列表tool_node = BasicToolNode(tools=[tool])graph_builder.add_node("tools", tool_node)#==============================第四部分===============================#def route_tools(    state: State,):    """    Use in the conditional_edge to route to the ToolNode if the last message    has tool calls. Otherwise, route to the end.    """    if isinstance(state, list):        ai_message = state[-1]    elif messages := state.get("messages", []):        ai_message = messages[-1]    else:        raise ValueError(f"No messages found in input state to tool_edge: {state}")    if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:# 判断消息是否包含工具调用请求        return "tools"# 存在工具调用,路由至工具节点    return END# 无工具调用,路由至结束节点# The `tools_condition` function returns "tools" if the chatbot asks to use a tool, and "END" if# it is fine directly responding. This conditional routing defines the main agent loop.graph_builder.add_conditional_edges(    "chatbot",    route_tools,    # The following dictionary lets you tell the graph to interpret the condition's outputs as a specific node    # It defaults to the identity function, but if you    # want to use a node named something else apart from "tools",    # You can update the value of the dictionary to something else    # e.g., "tools": "my_tools"    {"tools": "tools", END: END},# 路由映射表:将route_tools的返回值映射到实际节点名称:"tools"返回值 → "tools"节点,END返回值 → END节点)# Any time a tool is called, we return to the chatbot to decide the next stepgraph_builder.add_edge("tools", "chatbot")graph_builder.add_edge(START, "chatbot")graph = graph_builder.compile()#==============================第五部分===============================#while True:    try:        user_input = input("User: ")        if user_input.lower() in ["quit", "exit", "q"]:            print("Goodbye!")            break        stream_graph_updates(user_input)    except:        # fallback if input() is not available        user_input = "What do you know about LangGraph?"        print("User: " + user_input)        stream_graph_updates(user_input)        break

这里我把官方的代码流程分为了五大部分:

第一部分:工具初始化

初始化了一个Tavily搜索引擎工具,用于获取外部信息。该工具被配置为最多返回2个结果,并被添加到工具列表中。

第二部分:聊天模型与状态图初始化

    定义了一个状态类型State,包含消息列表。初始化了Claude-3-5-Sonnet大型语言模型,并将其与之前定义的工具绑定。创建了一个名为chatbot的节点函数,负责生成AI回复,并将其添加到状态图构建器中。

第三部分:工具调用节点实现

实现了一个BasicToolNode类,用于处理AI消息中包含的工具调用请求。该类会:

    根据工具名称查找对应的工具。执行工具调用并获取结果。将结果封装为工具消息返回。
    这个工具节点随后被添加到状态图构建器中。

第四部分:状态图路由逻辑

    定义了一个路由函数route_tools,根据AI消息中是否包含工具调用来决定下一步流向。

    设置了状态图的边,定义了节点间的流转逻辑:

      chatbot节点出发,根据工具调用情况决定流向工具节点或结束。工具节点执行后返回chatbot节点。初始状态流向chatbot节点。

    编译状态图,完成代理的逻辑构建。

第五部分:用户交互循环

实现了一个简单的命令行交互界面,允许用户输入问题:

    用户输入问题后,系统会处理并流式输出回答。支持通过特定命令(如"quit")退出对话。包含异常处理逻辑,确保在无法获取用户输入时能提供默认问题并退出。

实践使用工具增强聊天机器人

import getpassimport osos.environ['TAVILY_API_KEY'] = 'YOUR_TAVILY_API_KEY'def _set_env(var: str):    print(f"Checking if {var} is set...")    if not os.environ.get(var):        print(f"{var} is not set, prompting user for input.")        os.environ[var] = getpass.getpass(f"{var}: ")        print(f"{var} has been set.{os.environ.get(var)}")    else:        print(f"{var} is already set to: {os.environ[var]}")_set_env("TAVILY_API_KEY")from langchain_community.tools.tavily_search import TavilySearchResultstool = TavilySearchResults(max_results=2)tools = [tool]# tool.invoke("What's a 'node' in LangGraph?")# 接下来就跟第一部分一样,创建LLM,创建图、节点、起终点# 把上面的tavily变成一个节点塞到graph里面from typing import Annotatedfrom typing_extensions import TypedDictfrom langgraph.graph import StateGraph, START, ENDfrom langgraph.graph.message import add_messagesfrom IPython.display import Image, displayfrom langchain.schema import HumanMessage, AIMessagefrom langgraph.prebuilt import ToolNode, tools_conditionclass State(TypedDict):    messages: Annotated[list, add_messages]graph_builder = StateGraph(State)# tavily作为工具被deepseek LLM用起来,想象一下,DS大帅哥抄起一个tavily扳手from langchain_openai import ChatOpenAIllm = ChatOpenAI(    max_retries=2,    base_url="https://api.deepseek.com/v1",    api_key="YOUR_API_KEY",  # 替换为你的API密钥    model="deepseek-chat"  # 根据实际模型名称修改)llm_with_tools = llm.bind_tools(tools)def chatbot(state: State):    messages = llm_with_tools.invoke(state["messages"])    print("模型原始响应:", messages)  # 检查是否包含正确的tool_calls    return {"messages": messages}graph_builder.add_node("chatbot", chatbot)import jsontool_node = ToolNode(tools=tools)graph_builder.add_node("tools", tool_node)graph_builder.add_conditional_edges(    "chatbot",    tools_condition,)graph_builder.add_edge("tools", "chatbot")graph_builder.add_edge(START, "chatbot")graph = graph_builder.compile()try:    display(Image(graph.get_graph().draw_mermaid_png()))except Exception:    # This requires some extra dependencies and is optional    passdef stream_graph_updates(user_input: str):    for event in graph.stream({"messages": [HumanMessage(content=user_input)]}):        for node, data in event.items():            if isinstance(data, dict) and "messages" in data:  # 添加类型检查                msg_list = data["messages"]                if msg_list and isinstance(msg_list, list):                    msg = msg_list[-1]                    print(f"{node.upper()}:", msg.content)while True:    try:        user_input = input("User: ")        if user_input.lower() in ["quit", "exit", "q"]:            print("Goodbye!")            break        stream_graph_updates(user_input)    except:        # fallback if input() is not available        user_input = "What do you know about LangGraph?"        print("User: " + user_input)        stream_graph_updates(user_input)        break

测试一下,返回结果:

Checking if TAVILY_API_KEY is set...TAVILY_API_KEY is already set to: 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: 一句话介绍你自己模型原始响应: content='我是一个智能助手,随时为你提供帮助、解答问题和完成任务!' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 147, 'total_tokens': 160, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 147}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '7847c72d-55dc-4cfc-9214-e161fcbad52b', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--0f510a2a-d21d-4893-b594-c87bdee9a307-0' usage_metadata={'input_tokens': 147, 'output_tokens': 13, 'total_tokens': 160, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}User: 介绍LangGraph模型原始响应: content='' additional_kwargs={'tool_calls': [{'id': 'call_0_11596b59-6946-4d50-9807-aae8fe62a2a9', 'function': {'arguments': '{"query":"LangGraph 介绍"}', 'name': 'tavily_search_results_json'}, 'type': 'function', 'index': 0}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 25, 'prompt_tokens': 147, 'total_tokens': 172, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 19}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '29625e2a-ae3d-4e28-b24d-73e2e50b7c69', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None} id='run--7d93a7d3-3069-427f-862b-63fdc64285d4-0' tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'LangGraph 介绍'}, 'id': 'call_0_11596b59-6946-4d50-9807-aae8fe62a2a9', 'type': 'tool_call'}] usage_metadata={'input_tokens': 147, 'output_tokens': 25, 'total_tokens': 172, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}}TOOLS: [{"title": "LangGraph:基于图结构的大模型智能体开发框架 - 博客园", "url": "https://www.cnblogs.com/deeplearningmachine/p/18631512", "content": "[LangGraph:基于图结构的大模型智能体开发框架](https://www.cnblogs.com/deeplearningmachine/p/18631512 "发布于 2024-12-25 21:56")\n===========================================================================================================\n\nLangGraph 是LangChainAI开发的一个工具库,用于创建代理和多代理智能体工作流。它提供了以下核心优势:周期、可控性和持久性,对于Agent智能体开发者来说无疑减少了许多工作量。以下篇幅仅从本人角度阐述LangGraph在开发过程中的亮点以及使用方法。\n\n基本介绍\n====\n\nLangGraph的StateGraph是一种状态机,包含了节点和边,节点一般是定义好的函数,边用于连接不同的节点,用于表示图的执行顺序。简单来说,使用LangGraph构建工作流的步骤如下:", "score": 0.9055316}, {"title": "LangGraph实战- 哥不是小萝莉- 博客园", "url": "https://www.cnblogs.com/smartloli/p/18276355", "content": "LangGraph是一个功能强大的库,用于构建基于大型语言模型(LLM)的有状态、多参与者应用程序。它旨在创建代理和多代理工作流,以实现复杂的任务和交互。 2.1", "score": 0.8889231}]模型原始响应: content='LangGraph 是一个由 LangChainAI 开发的工具库,专门用于创建代理(Agent)和多代理智能体工作流。它基于图结构设计,旨在帮助开发者构建有状态、多参与者的应用程序,尤其适用于基于大型语言模型(LLM)的复杂任务和交互。\n\n### 核心特点\n1. **基于图结构的工作流**:\n   - LangGraph 使用状态机(StateGraph)来定义工作流,其中节点是预定义的函数,边用于连接节点并控制执行顺序。\n   - 这种设计使得工作流更加灵活和可控。\n\n2. **周期性和持久性**:\n   - 支持周期性任务和状态持久化,适合需要长期运行或多步骤交互的智能体应用。\n\n3. **多代理支持**:\n   - 可以轻松构建多代理系统,实现复杂的协作任务。\n\n### 使用场景\n- 开发基于 LLM 的智能体(Agent)。\n- 构建多代理协作的工作流。\n- 实现复杂的任务自动化或交互式应用。\n\n### 示例资源\n1. [LangGraph:基于图结构的大模型智能体开发框架](https://www.cnblogs.com/deeplearningmachine/p/18631512) - 详细介绍 LangGraph 的设计理念和使用方法。\n2. [LangGraph实战](https://www.cnblogs.com/smartloli/p/18276355) - 提供实际应用案例和代码示例。\n\n如果你对 LangGraph 的具体实现或代码示例感兴趣,可以参考上述链接中的内容。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 314, 'prompt_tokens': 504, 'total_tokens': 818, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 376}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'e14c811a-69db-4b54-97de-df2bbfca94af', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--a882e43d-7092-4e2d-ade4-e5d1ac2cb0ba-0' usage_metadata={'input_tokens': 504, 'output_tokens': 314, 'total_tokens': 818, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}}User: qGoodbye!

可以看到当我提出“介绍LangGraph”时,聊天机器人调用了tavily搜索工具,并结合工具的返回消息给出了回答。
恭喜!  您已经在langgraph中创建了一个会话代理,该代理可以在需要时使用搜索引擎检索更新的信息。现在它可以处理更广泛的用户查询。

原文地址:https://www.cnblogs.com/LiShengTrip/p/18924107#top

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Tavily LangGraph 聊天机器人 AI工具
相关文章