添加工具¶
为了处理聊天机器人无法"凭记忆"回答的查询,集成一个网络搜索工具。聊天机器人可以使用此工具查找相关信息并提供更好的响应。
Note
本教程基于构建基础聊天机器人。
前提条件¶
在开始本教程之前,请确保你具备以下条件:
- Tavily 搜索引擎的 API 密钥。
- Tavily 搜索引擎的 API 密钥。
1. 安装搜索引擎¶
安装使用 Tavily 搜索引擎所需的依赖项:
安装使用 Tavily 搜索引擎所需的依赖项:
2. 配置环境¶
使用搜索引擎 API 密钥配置你的环境:
3. 定义工具¶
定义网络搜索工具:
from langchain_tavily import TavilySearch
tool = TavilySearch(max_results=2)
tools = [tool]
tool.invoke("What's a 'node' in LangGraph?")
import { TavilySearch } from "@langchain/tavily";
const tool = new TavilySearch({ maxResults: 2 });
const tools = [tool];
await tool.invoke({ query: "What's a 'node' in LangGraph?" });
结果是页面摘要,我们的聊天机器人可以使用它来回答问题:
{'query': "What's a 'node' in LangGraph?",
'follow_up_questions': None,
'answer': None,
'images': [],
'results': [{'title': "Introduction to LangGraph: A Beginner's Guide - Medium",
'url': 'https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141',
'content': 'Stateful Graph: LangGraph revolves around the concept of a stateful graph, where each node in the graph represents a step in your computation, and the graph maintains a state that is passed around and updated as the computation progresses. LangGraph supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph. We define nodes for classifying the input, handling greetings, and handling search queries. def classify_input_node(state): LangGraph is a versatile tool for building complex, stateful applications with LLMs. By understanding its core concepts and working through simple examples, beginners can start to leverage its power for their projects. Remember to pay attention to state management, conditional edges, and ensuring there are no dead-end nodes in your graph.',
'score': 0.7065353,
'raw_content': None},
{'title': 'LangGraph Tutorial: What Is LangGraph and How to Use It?',
'url': 'https://www.datacamp.com/tutorial/langgraph-tutorial',
'content': 'LangGraph is a library within the LangChain ecosystem that provides a framework for defining, coordinating, and executing multiple LLM agents (or chains) in a structured and efficient manner. By managing the flow of data and the sequence of operations, LangGraph allows developers to focus on the high-level logic of their applications rather than the intricacies of agent coordination. Whether you need a chatbot that can handle various types of user requests or a multi-agent system that performs complex tasks, LangGraph provides the tools to build exactly what you need. LangGraph significantly simplifies the development of complex LLM applications by providing a structured framework for managing state and coordinating agent interactions.',
'score': 0.5008063,
'raw_content': None}],
'response_time': 1.38}
{
"query": "What's a 'node' in LangGraph?",
"follow_up_questions": null,
"answer": null,
"images": [],
"results": [
{
"url": "https://blog.langchain.dev/langgraph/",
"title": "LangGraph - LangChain Blog",
"content": "TL;DR: LangGraph is module built on top of LangChain to better enable creation of cyclical graphs, often needed for agent runtimes. This state is updated by nodes in the graph, which return operations to attributes of this state (in the form of a key-value store). After adding nodes, you can then add edges to create the graph. An example of this may be in the basic agent runtime, where we always want the model to be called after we call a tool. The state of this graph by default contains concepts that should be familiar to you if you've used LangChain agents: `input`, `chat_history`, `intermediate_steps` (and `agent_outcome` to represent the most recent agent outcome)",
"score": 0.7407191,
"raw_content": null
},
{
"url": "https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141",
"title": "Introduction to LangGraph: A Beginner's Guide - Medium",
"content": "* **Stateful Graph:** LangGraph revolves around the concept of a stateful graph, where each node in the graph represents a step in your computation, and the graph maintains a state that is passed around and updated as the computation progresses. LangGraph supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph. Image 10: Introduction to AI Agent with LangChain and LangGraph: A Beginner’s Guide Image 18: How to build LLM Agent with LangGraph — StateGraph and Reducer Image 20: Simplest Graphs using LangGraph Framework Image 24: Building a ReAct Agent with Langgraph: A Step-by-Step Guide Image 28: Building an Agentic RAG with LangGraph: A Step-by-Step Guide",
"score": 0.65279555,
"raw_content": null
}
],
"response_time": 1.34
}
4. 定义图¶
对于你在第一个教程中创建的 StateGraph,在 LLM 上添加 bind_tools。这让 LLM 知道如果它想使用搜索引擎,应该使用正确的 JSON 格式。
对于你在第一个教程中创建的 StateGraph,在 LLM 上添加 bindTools。这让 LLM 知道如果它想使用搜索引擎,应该使用正确的 JSON 格式。
让我们首先选择我们的 LLM:
import { ChatAnthropic } from "@langchain/anthropic";
const llm = new ChatAnthropic({ model: "claude-3-5-sonnet-latest" });
我们现在可以将它合并到 StateGraph 中:
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
# 修改:告诉 LLM 它可以调用哪些工具
# highlight-next-line
llm_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 { StateGraph, MessagesZodState } from "@langchain/langgraph";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const chatbot = async (state: z.infer<typeof State>) => {
// 修改:告诉 LLM 它可以调用哪些工具
const llmWithTools = llm.bindTools(tools);
return { messages: [await llmWithTools.invoke(state.messages)] };
};
5. 创建运行工具的函数¶
现在,创建一个函数来运行被调用的工具。通过将工具添加到名为 BasicToolNode 的新节点来实现,该节点检查状态中的最新消息,如果消息包含 tool_calls 则调用工具。它依赖于 LLM 的 tool_calling 支持,这在 Anthropic、OpenAI、Google Gemini 和许多其他 LLM 提供商中都可用。
import json
from langchain_core.messages import ToolMessage
class BasicToolNode:
"""运行最后一条 AI 消息中请求的工具的节点。"""
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(
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)
Note
如果你将来不想自己构建这个,可以使用 LangGraph 的预构建 ToolNode。
现在,创建一个函数来运行被调用的工具。通过将工具添加到名为 "tools" 的新节点来实现,该节点检查状态中的最新消息,如果消息包含 tool_calls 则调用工具。它依赖于 LLM 的工具调用支持,这在 Anthropic、OpenAI、Google Gemini 和许多其他 LLM 提供商中都可用。
import type { StructuredToolInterface } from "@langchain/core/tools";
import { isAIMessage, ToolMessage } from "@langchain/core/messages";
function createToolNode(tools: StructuredToolInterface[]) {
const toolByName: Record<string, StructuredToolInterface> = {};
for (const tool of tools) {
toolByName[tool.name] = tool;
}
return async (inputs: z.infer<typeof State>) => {
const { messages } = inputs;
if (!messages || messages.length === 0) {
throw new Error("No message found in input");
}
const message = messages.at(-1);
if (!message || !isAIMessage(message) || !message.tool_calls) {
throw new Error("Last message is not an AI message with tool calls");
}
const outputs: ToolMessage[] = [];
for (const toolCall of message.tool_calls) {
if (!toolCall.id) throw new Error("Tool call ID is required");
const tool = toolByName[toolCall.name];
if (!tool) throw new Error(`Tool ${toolCall.name} not found`);
const result = await tool.invoke(toolCall.args);
outputs.push(
new ToolMessage({
content: JSON.stringify(result),
name: toolCall.name,
tool_call_id: toolCall.id,
})
);
}
return { messages: outputs };
};
}
Note
如果你将来不想自己构建这个,可以使用 LangGraph 的预构建 ToolNode。
6. 定义 conditional_edges¶
添加工具节点后,现在可以定义 conditional_edges。
边(Edges) 将控制流从一个节点路由到下一个节点。条件边(Conditional edges) 从单个节点开始,通常包含 "if" 语句,根据当前图状态路由到不同的节点。这些函数接收当前图 state 并返回一个字符串或字符串列表,指示接下来要调用哪个节点。
接下来,定义一个名为 route_tools 的路由器函数,检查聊天机器人输出中的 tool_calls。通过调用 add_conditional_edges 将此函数提供给图,这告诉图每当 chatbot 节点完成时检查此函数以确定下一步去哪里。
接下来,定义一个名为 routeTools 的路由器函数,检查聊天机器人输出中的 tool_calls。通过调用 addConditionalEdges 将此函数提供给图,这告诉图每当 chatbot 节点完成时检查此函数以确定下一步去哪里。
条件将在存在工具调用时路由到 tools,否则路由到 END。因为条件可以返回 END,所以这次你不需要显式设置 finish_point。
def route_tools(
state: State,
):
"""
在 conditional_edge 中使用,如果最后一条消息
包含工具调用,则路由到 ToolNode。否则,路由到结束。
"""
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
# `tools_condition` 函数如果聊天机器人要求使用工具,则返回 "tools",如果
# 它可以直接响应,则返回 "END"。这个条件路由定义了主智能体循环。
graph_builder.add_conditional_edges(
"chatbot",
route_tools,
# 以下字典让你告诉图将条件的输出解释为特定节点
# 它默认为恒等函数,但如果你
# 想使用除 "tools" 之外的其他名称的节点,
# 你可以将字典的值更新为其他内容
# 例如,"tools": "my_tools"
{"tools": "tools", END: END},
)
# 每当调用工具时,我们返回到聊天机器人来决定下一步
graph_builder.add_edge("tools", "chatbot")
graph_builder.add_edge(START, "chatbot")
graph = graph_builder.compile()
Note
你可以用预构建的 tools_condition 替换它以更简洁。
import { END, START } from "@langchain/langgraph";
const routeTools = (state: z.infer<typeof State>) => {
/**
* 用作条件边,如果最后一条消息包含工具调用,
* 则路由到 ToolNode。
*/
const lastMessage = state.messages.at(-1);
if (
lastMessage &&
isAIMessage(lastMessage) &&
lastMessage.tool_calls?.length
) {
return "tools";
}
/** 否则,路由到结束。 */
return END;
};
const graph = new StateGraph(State)
.addNode("chatbot", chatbot)
// `routeTools` 函数如果聊天机器人要求使用工具,则返回 "tools",如果
// 它可以直接响应,则返回 "END"。这个条件路由定义了主智能体循环。
.addNode("tools", createToolNode(tools))
// 从聊天机器人开始图
.addEdge(START, "chatbot")
// `routeTools` 函数如果聊天机器人要求使用工具,则返回 "tools",如果
// 它可以直接响应,则返回 "END"。
.addConditionalEdges("chatbot", routeTools, ["tools", END])
// 每当调用工具时,我们需要返回到聊天机器人
.addEdge("tools", "chatbot")
.compile();
Note
你可以用预构建的 toolsCondition 替换它以更简洁。
7. 可视化图(可选)¶
你可以使用 get_graph 方法和其中一种"绘制"方法(如 draw_ascii 或 draw_png)来可视化图。draw 方法各需要额外的依赖项。
from IPython.display import Image, display
try:
display(Image(graph.get_graph().draw_mermaid_png()))
except Exception:
# 这需要一些额外的依赖项并且是可选的
pass
你可以使用 getGraph 方法可视化图,并使用 drawMermaidPng 方法渲染图。
import * as fs from "node:fs/promises";
const drawableGraph = await graph.getGraphAsync();
const image = await drawableGraph.drawMermaidPng();
const imageBuffer = new Uint8Array(await image.arrayBuffer());
await fs.writeFile("chatbot-with-tools.png", imageBuffer);

8. 向机器人提问¶
现在你可以向聊天机器人提出其训练数据之外的问题:
def stream_graph_updates(user_input: str):
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
for value in event.values():
print("Assistant:", value["messages"][-1].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:
# 如果 input() 不可用时的后备方案
user_input = "What do you know about LangGraph?"
print("User: " + user_input)
stream_graph_updates(user_input)
break
Assistant: [{'text': "To provide you with accurate and up-to-date information about LangGraph, I'll need to search for the latest details. Let me do that for you.", 'type': 'text'}, {'id': 'toolu_01Q588CszHaSvvP2MxRq9zRD', 'input': {'query': 'LangGraph AI tool information'}, 'name': 'tavily_search_results_json', 'type': 'tool_use'}]
Assistant: [{"url": "https://www.langchain.com/langgraph", "content": "LangGraph sets the foundation for how we can build and scale AI workloads \u2014 from conversational agents, complex task automation, to custom LLM-backed experiences that 'just work'. The next chapter in building complex production-ready features with LLMs is agentic, and with LangGraph and LangSmith, LangChain delivers an out-of-the-box solution ..."}, {"url": "https://github.com/langchain-ai/langgraph", "content": "Overview. LangGraph is a library for building stateful, multi-actor applications with LLMs, used to create agent and multi-agent workflows. Compared to other LLM frameworks, it offers these core benefits: cycles, controllability, and persistence. LangGraph allows you to define flows that involve cycles, essential for most agentic architectures ..."}]
Assistant: Based on the search results, I can provide you with information about LangGraph:
1. Purpose:
LangGraph is a library designed for building stateful, multi-actor applications with Large Language Models (LLMs). It's particularly useful for creating agent and multi-agent workflows.
2. Developer:
LangGraph is developed by LangChain, a company known for its tools and frameworks in the AI and LLM space.
3. Key Features:
- Cycles: LangGraph allows the definition of flows that involve cycles, which is essential for most agentic architectures.
- Controllability: It offers enhanced control over the application flow.
- Persistence: The library provides ways to maintain state and persistence in LLM-based applications.
4. Use Cases:
LangGraph can be used for various applications, including:
- Conversational agents
- Complex task automation
- Custom LLM-backed experiences
5. Integration:
LangGraph works in conjunction with LangSmith, another tool by LangChain, to provide an out-of-the-box solution for building complex, production-ready features with LLMs.
6. Significance:
...
LangGraph is noted to offer unique benefits compared to other LLM frameworks, particularly in its ability to handle cycles, provide controllability, and maintain persistence.
LangGraph 在基于 LLM 的应用程序开发不断发展的格局中似乎是一个重要的工具,为开发者提供了创建更复杂、有状态和交互式 AI 系统的新方法。
Goodbye!
import readline from "node:readline/promises";
const prompt = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
async function generateText(content: string) {
const stream = await graph.stream(
{ messages: [{ type: "human", content }] },
{ streamMode: "values" }
);
for await (const event of stream) {
const lastMessage = event.messages.at(-1);
if (lastMessage?.getType() === "ai" || lastMessage?.getType() === "tool") {
console.log(`Assistant: ${lastMessage?.text}`);
}
}
}
while (true) {
const human = await prompt.question("User: ");
if (["quit", "exit", "q"].includes(human.trim())) break;
await generateText(human || "What do you know about LangGraph?");
}
prompt.close();
User: What do you know about LangGraph?
Assistant: I'll search for the latest information about LangGraph for you.
Assistant: [{"title":"Introduction to LangGraph: A Beginner's Guide - Medium","url":"https://medium.com/@cplog/introduction-to-langgraph-a-beginners-guide-14f9be027141","content":"..."}]
Assistant: 根据搜索结果,我可以为你提供有关 LangGraph 的信息:
LangGraph 是 LangChain 生态系统中的一个库,专为使用大型语言模型(LLM)构建有状态、多参与者应用程序而设计。以下是关键方面:
**核心目的:**
- LangGraph 专门设计用于创建智能体和多智能体工作流
- 它提供了一个框架,用于以结构化的方式定义、协调和执行多个 LLM 智能体
**主要特性:**
1. **有状态图架构**:LangGraph 围绕有状态图展开,其中每个节点代表计算中的一个步骤,图维护在计算过程中传递和更新的状态
2. **条件边**:它支持条件边,允许你根据图的当前状态动态确定要执行的下一个节点
3. **循环**:与其他 LLM 框架不同,LangGraph 允许你定义涉及循环的流程,这对于大多数智能体架构至关重要
4. **可控性**:它提供了对应用程序流程的增强控制
5. **持久性**:该库提供了在基于 LLM 的应用程序中维护状态和持久性的方法
**用例:**
- 对话智能体
- 复杂任务自动化
- 自定义 LLM 支持的体验
- 执行复杂任务的多智能体系统
**Benefits:**
LangGraph 允许开发者专注于应用程序的高级逻辑,而不是智能体协调的复杂细节,从而更容易使用 LLM 构建复杂的、生产就绪的功能。
这使得 LangGraph 在基于 LLM 的应用程序开发不断发展的格局中成为一个重要的工具。
9. 使用预构建组件¶
为了便于使用,调整你的代码以用 LangGraph 预构建组件替换以下内容。这些具有内置功能,如并行 API 执行。
BasicToolNode用预构建的 ToolNode 替换route_tools用预构建的 tools_condition 替换
from typing import Annotated
from langchain_tavily import TavilySearch
from langchain_core.messages import BaseMessage
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
class State(TypedDict):
messages: Annotated[list, add_messages]
graph_builder = StateGraph(State)
tool = TavilySearch(max_results=2)
tools = [tool]
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
return {"messages": [llm_with_tools.invoke(state["messages"])]}
graph_builder.add_node("chatbot", chatbot)
tool_node = ToolNode(tools=[tool])
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()
createToolNode用预构建的 ToolNode 替换routeTools用预构建的 toolsCondition 替换
import { TavilySearch } from "@langchain/tavily";
import { ChatOpenAI } from "@langchain/openai";
import { StateGraph, START, MessagesZodState, END } from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
import { z } from "zod";
const State = z.object({ messages: MessagesZodState.shape.messages });
const tools = [new TavilySearch({ maxResults: 2 })];
const llm = new ChatOpenAI({ model: "gpt-4o-mini" }).bindTools(tools);
const graph = new StateGraph(State)
.addNode("chatbot", async (state) => ({
messages: [await llm.invoke(state.messages)],
}))
.addNode("tools", new ToolNode(tools))
.addConditionalEdges("chatbot", toolsCondition, ["tools", END])
.addEdge("tools", "chatbot")
.addEdge(START, "chatbot")
.compile();
恭喜! 你已经在 LangGraph 中创建了一个对话智能体,可以在需要时使用搜索引擎检索更新的信息。现在它可以处理更广泛的用户查询。
要检查你的智能体刚刚执行的所有步骤,请查看此 LangSmith trace。
下一步¶
聊天机器人自身无法记住过去的交互,这限制了它进行连贯的多轮对话的能力。在下一部分中,你将添加**记忆**来解决这个问题。