This commit is contained in:
Eugene Yurtsev
2025-06-16 14:54:23 -04:00
parent 89b565bc23
commit e67f164ef6
@@ -1,3 +1,5 @@
:::python
# Add tools
To handle queries that your chatbot can't answer "from memory", integrate a web search tool. The chatbot can use this tool to find relevant information and provide better responses.
@@ -328,3 +330,329 @@ graph = graph_builder.compile()
## Next steps
The chatbot cannot remember past interactions on its own, which limits its ability to have coherent, multi-turn conversations. In the next part, you will [add **memory**](./3-add-memory.md) to address this.
:::
:::js
# Add tools
To handle queries that your chatbot can't answer "from memory", integrate a web search tool. The chatbot can use this tool to find relevant information and provide better responses.
!!! note
This tutorial builds on [Build a basic chatbot](./1-build-basic-chatbot.md).
## Prerequisites
Before you start this tutorial, ensure you have the following:
- An API key for the [Tavily Search Engine](https://js.langchain.com/docs/integrations/tools/tavily_search/).
## 1. Install the search engine
Install the requirements to use the [Tavily Search Engine](https://js.langchain.com/docs/integrations/tools/tavily_search/):
```bash
npm install @langchain/community
```
## 2. Configure your environment
Configure your environment with your search engine API key:
```typescript
process.env.TAVILY_API_KEY = "tvly-...";
```
## 3. Define the tool
Define the web search tool:
```typescript
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
const tool = new TavilySearchResults({ maxResults: 2 });
const tools = [tool];
await tool.invoke("What's a 'node' in LangGraph?");
```
The results are page summaries our chat bot can use to answer questions:
```
'[{"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":null},{"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":null}]'
```
## 4. Define the graph
For the `StateGraph` you created in the [first tutorial](./1-build-basic-chatbot.md), add `bindTools` on the LLM. This lets the LLM know the correct JSON format to use if it wants to use the search engine.
Let's first select our LLM:
```typescript
import { ChatOpenAI } from "@langchain/openai";
const llm = new ChatOpenAI({
model: "gpt-4o",
temperature: 0,
});
```
We can now incorporate it into a `StateGraph`:
```typescript hl_lines="15"
import { Annotation } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => x.concat(y),
}),
});
import { StateGraph, START, END } from "@langchain/langgraph";
const graphBuilder = new StateGraph(StateAnnotation);
// Modification: tell the LLM which tools it can call
const llmWithTools = llm.bindTools(tools);
const chatbot = async (state: typeof StateAnnotation.State) => {
return { messages: [await llmWithTools.invoke(state.messages)] };
};
graphBuilder.addNode("chatbot", chatbot);
```
## 5. Create a function to run the tools
Now, create a function to run the tools if they are called. Do this by adding the tools to a new node called `BasicToolNode` that checks the most recent message in the state and calls tools if the message contains `tool_calls`. It relies on the LLM's `tool_calling` support, which is available in Anthropic, OpenAI, Google Gemini, and a number of other LLM providers.
```typescript
import { ToolMessage } from "@langchain/core/messages";
class BasicToolNode {
private toolsByName: Record<string, any>;
constructor(tools: any[]) {
this.toolsByName = {};
for (const tool of tools) {
this.toolsByName[tool.name] = tool;
}
}
async __call__(inputs: Record<string, any>): Promise<{ messages: ToolMessage[] }> {
const messages = inputs.messages || [];
if (messages.length === 0) {
throw new Error("No message found in input");
}
const message = messages[messages.length - 1];
const outputs: ToolMessage[] = [];
for (const toolCall of message.tool_calls || []) {
const toolResult = await this.toolsByName[toolCall.name].invoke(toolCall.args);
outputs.push(
new ToolMessage({
content: JSON.stringify(toolResult),
name: toolCall.name,
tool_call_id: toolCall.id,
})
);
}
return { messages: outputs };
}
}
const toolNode = new BasicToolNode([tool]);
graphBuilder.addNode("tools", async (state) => toolNode.__call__(state));
```
!!! note
If you do not want to build this yourself in the future, you can use LangGraph's prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode).
## 6. Define the `conditional_edges`
With the tool node added, now you can define the `conditional_edges`.
**Edges** route the control flow from one node to the next. **Conditional edges** start from a single node and usually contain "if" statements to route to different nodes depending on the current graph state. These functions receive the current graph `state` and return a string or list of strings indicating which node(s) to call next.
Next, define a router function called `routeTools` that checks for `tool_calls` in the chatbot's output. Provide this function to the graph by calling `addConditionalEdges`, which tells the graph that whenever the `chatbot` node completes to check this function to see where to go next.
The condition will route to `tools` if tool calls are present and `END` if not. Because the condition can return `END`, you do not need to explicitly set a `finish_point` this time.
```typescript
import { AIMessage } from "@langchain/core/messages";
const routeTools = (state: typeof StateAnnotation.State) => {
/**
* Use in the conditional_edge to route to the ToolNode if the last message
* has tool calls. Otherwise, route to the end.
*/
const messages = state.messages;
const lastMessage = messages[messages.length - 1] as AIMessage;
if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {
return "tools";
}
return END;
};
// The `routeTools` 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.
graphBuilder.addConditionalEdges(
"chatbot",
routeTools,
// 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,
}
);
// Any time a tool is called, we return to the chatbot to decide the next step
graphBuilder.addEdge("tools", "chatbot");
graphBuilder.addEdge(START, "chatbot");
const graph = graphBuilder.compile();
```
!!! note
You can replace this with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition) to be more concise.
## 7. Visualize the graph (optional)
You can visualize the graph using the `getGraph` method and one of the "draw" methods, like `drawAscii` or `drawMermaidPng`. The `draw` methods each require additional dependencies.
```typescript
import * as tslab from "tslab";
try {
const representation = graph.getGraph();
const image = await representation.drawMermaidPng();
const arrayBuffer = await image.arrayBuffer();
await tslab.display.png(new Uint8Array(arrayBuffer));
} catch (error) {
// This requires some extra dependencies and is optional
console.log("Graph visualization not available");
}
```
![chatbot-with-tools-diagram](chatbot-with-tools.png)
## 8. Ask the bot questions
Now you can ask the chatbot questions outside its training data:
```typescript
import { HumanMessage } from "@langchain/core/messages";
const streamGraphUpdates = async (userInput: string) => {
const stream = await graph.stream(
{ messages: [new HumanMessage(userInput)] },
{ streamMode: "values" }
);
for await (const event of stream) {
const messages = event.messages;
const lastMessage = messages[messages.length - 1];
console.log("Assistant:", lastMessage.content);
}
};
// Example usage
const userInput = "What do you know about LangGraph?";
console.log("User:", userInput);
await streamGraphUpdates(userInput);
```
```
Assistant: I'll search for information about LangGraph to provide you with accurate details.
Assistant: [{"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": null}, {"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": null}]
Assistant: Based on the search results, I can provide you with comprehensive information about LangGraph:
## What is LangGraph?
LangGraph is a library within the LangChain ecosystem designed for building stateful, multi-actor applications with Large Language Models (LLMs). It provides a framework for defining, coordinating, and executing multiple LLM agents or chains in a structured and efficient manner.
## Key Features:
1. **Stateful Graph Architecture**: LangGraph revolves around the concept of a stateful graph where each node represents a step in your computation, and the graph maintains state that is passed around and updated as the computation progresses.
2. **Conditional Edges**: It supports conditional edges, allowing you to dynamically determine the next node to execute based on the current state of the graph.
3. **Multi-Agent Coordination**: LangGraph manages the flow of data and sequence of operations, allowing developers to focus on high-level logic rather than the intricacies of agent coordination.
## Use Cases:
- Building conversational agents
- Creating chatbots that can handle various types of user requests
- Developing multi-agent systems that perform complex tasks
- Complex task automation
- Custom LLM-backed experiences
## Benefits:
- **Simplified Development**: LangGraph significantly simplifies the development of complex LLM applications by providing a structured framework for managing state and coordinating agent interactions.
- **Flexibility**: It's a versatile tool for building complex, stateful applications with LLMs.
- **Focus on Logic**: Developers can focus on the high-level logic of their applications rather than coordination details.
LangGraph is particularly valuable for projects that require sophisticated AI workflows with multiple steps, decision points, and state management across different components.
```
## 9. Use prebuilts
For ease of use, adjust your code to replace the following with LangGraph prebuilt components. These have built in functionality like parallel API execution.
- `BasicToolNode` is replaced with the prebuilt [ToolNode](https://langchain-ai.github.io/langgraph/reference/prebuilt/#toolnode)
- `routeTools` is replaced with the prebuilt [tools_condition](https://langchain-ai.github.io/langgraph/reference/prebuilt/#tools_condition)
```typescript hl_lines="25 30"
import { Annotation } from "@langchain/langgraph";
import { BaseMessage } from "@langchain/core/messages";
import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
import { ChatOpenAI } from "@langchain/openai";
import { StateGraph, START, END } from "@langchain/langgraph";
import { ToolNode, toolsCondition } from "@langchain/langgraph/prebuilt";
const StateAnnotation = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => x.concat(y),
}),
});
const graphBuilder = new StateGraph(StateAnnotation);
const tool = new TavilySearchResults({ maxResults: 2 });
const tools = [tool];
const llm = new ChatOpenAI({ model: "gpt-4o", temperature: 0 });
const llmWithTools = llm.bindTools(tools);
const chatbot = async (state: typeof StateAnnotation.State) => {
return { messages: [await llmWithTools.invoke(state.messages)] };
};
graphBuilder.addNode("chatbot", chatbot);
const toolNode = new ToolNode(tools);
graphBuilder.addNode("tools", toolNode);
graphBuilder.addConditionalEdges(
"chatbot",
toolsCondition,
);
// Any time a tool is called, we return to the chatbot to decide the next step
graphBuilder.addEdge("tools", "chatbot");
graphBuilder.addEdge(START, "chatbot");
const graph = graphBuilder.compile();
```
**Congratulations!** You've created a conversational agent in LangGraph that can use a search engine to retrieve updated information when needed. Now it can handle a wider range of user queries. To inspect all the steps your agent just took, check out this [LangSmith trace](https://smith.langchain.com/public/4fbd7636-25af-4638-9587-5a02fdbb0172/r).
## Next steps
The chatbot cannot remember past interactions on its own, which limits its ability to have coherent, multi-turn conversations. In the next part, you will [add **memory**](./3-add-memory.md) to address this.
:::