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