content changes

This commit is contained in:
Eugene Yurtsev
2025-06-16 11:56:05 -04:00
parent 39d6bdf236
commit 4496c86d28
3 changed files with 324 additions and 0 deletions
+113
View File
@@ -11,7 +11,13 @@ hide:
# Human-in-the-loop
:::python
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [Human-In-the-Loop (HIL)](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`][langgraph.types.interrupt] primitive.
:::
:::js
To review, edit and approve tool calls in an agent you can use LangGraph's built-in [human-in-the-loop](../concepts/human_in_the_loop.md) features, specifically the [`interrupt()`](/langgraphjs/reference/functions/langgraph.interrupt-1.html) primitive.
:::
LangGraph allows you to pause execution **indefinitely** — for minutes, hours, or even days—until human input is received.
@@ -27,6 +33,8 @@ A human can review and edit the output from the agent before proceeding. This is
</figure>
:::python
## Review tool calls
To add a human approval step to a tool:
@@ -34,6 +42,7 @@ To add a human approval step to a tool:
1. Use `interrupt()` in the tool to pause execution.
2. Resume with a `Command(resume=...)` to continue based on human input.
```python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt
@@ -233,6 +242,110 @@ for chunk in agent.stream(
print("\n")
```
:::
:::js
## Review tool calls
To add a human approval step to a tool:
1. Use `interrupt()` in the tool to pause execution.
2. Resume with a `Command({ resume: ... })` to continue based on human input.
```ts
import { MemorySaver } from "@langchain/langgraph-checkpoint";
import { interrupt } from "@langchain/langgraph";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { initChatModel } from "langchain/chat_models/universal";
import { tool } from "@langchain/core/tools";
import { z } from "zod";
// An example of a sensitive tool that requires human review / approval
const bookHotel = tool(
async (input: { hotelName: string; }) => {
let hotelName = input.hotelName;
// highlight-next-line
const response = interrupt( // (1)!
`Trying to call \`book_hotel\` with args {'hotel_name': ${hotelName}}. ` +
`Please approve or suggest edits.`
)
if (response.type === "accept") {
// proceed to execute the tool logic
} else if (response.type === "edit") {
hotelName = response.args["hotel_name"]
} else {
throw new Error(`Unknown response type: ${response.type}`)
}
return `Successfully booked a stay at ${hotelName}.`;
},
{
name: "bookHotel",
schema: z.object({
hotelName: z.string().describe("Hotel to book"),
}),
description: "Book a hotel.",
}
);
// highlight-next-line
const checkpointer = new MemorySaver(); // (2)!
const llm = await initChatModel("anthropic:claude-3-7-sonnet-latest");
const agent = createReactAgent({
llm,
tools: [bookHotel],
// highlight-next-line
checkpointer // (3)!
});
```
1. The [`interrupt` function](/langgraphjs/reference/functions/langgraph.interrupt-1.html) pauses the agent graph at a specific node. In this case, we call `interrupt()` at the beginning of the tool function, which pauses the graph at the node that executes the tool. The information inside `interrupt()` (e.g., tool calls) can be presented to a human, and the graph can be resumed with the user input (tool call approval, edit or feedback).
2. The `InMemorySaver` is used to store the agent state at every step in the tool calling loop. This enables [short-term memory](./memory.md#short-term-memory) and [human-in-the-loop](./human-in-the-loop.md) capabilities. In this example, we use `InMemorySaver` to store the agent state in memory. In a production application, the agent state will be stored in a database.
3. Initialize the agent with the `checkpointer`.
Run the agent with the `stream()` method, passing the `config` object to specify the thread ID. This allows the agent to resume the same conversation on future invocations.
```ts
const config = {
configurable: {
// highlight-next-line
"thread_id": "1"
}
}
for await (const chunk of await agent.stream(
{ messages: "book a stay at McKittrick hotel" },
// highlight-next-line
config
)) {
console.log(chunk);
console.log("\n");
};
```
> You should see that the agent runs until it reaches the `interrupt()` call, at which point it pauses and waits for human input.
Resume the agent with a `Command({ resume: ... })` to continue based on human input.
```ts
import { Command } from "@langchain/langgraph";
for await (const chunk of await agent.stream(
new Command({ resume: { type: "accept" } }), // (1)!
// new Command({ resume: { type: "edit", args: { "hotel_name": "McKittrick Hotel" } } }),
// highlight-next-line
config
)) {
console.log(chunk);
console.log("\n");
};
```
1. The [`interrupt` function](/langgraphjs/reference/functions/langgraph.interrupt-1.html) is used in conjunction with the [`Command`](/langgraphjs/reference/classes/langgraph.Command.html) object to resume the graph with a value provided by the human.
:::
## Additional resources
* [Human-in-the-loop in LangGraph](../concepts/human_in_the_loop.md)
+53
View File
@@ -13,6 +13,8 @@ hide:
![MCP](./assets/mcp.png)
:::python
Install the `langchain-mcp-adapters` library to use MCP tools in LangGraph:
```bash
@@ -58,6 +60,57 @@ weather_response = await agent.ainvoke(
{"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)
```
:::
:::js
Install the `@langchain/mcp-adapters` library to use MCP tools in LangGraph:
```bash
npm install @langchain/mcp-adapters
```
## Use MCP tools
The `@langchain/mcp-adapters` package enables agents to use tools defined across one or more MCP servers.
```ts
// highlight-next-line
import { MultiServerMCPClient } from "@langchain/mcp-adapters";
import { initChatModel } from "langchain/chat_models/universal";
import { createReactAgent } from "@langchain/langgraph/prebuilt";
// highlight-next-line
const client = new MultiServerMCPClient({
mcpServers: {
"math": {
command: "python",
// Replace with absolute path to your math_server.py file
args: ["/path/to/math_server.py"],
transport: "stdio",
},
"weather": {
// Ensure your start your weather server on port 8000
url: "http://localhost:8000/sse",
transport: "sse",
}
}
})
const llm = await initChatModel("anthropic:claude-3-7-sonnet-latest");
const agent = createReactAgent({
llm,
// highlight-next-line
tools: await client.getTools()
});
const mathResponse = await agent.invoke(
{ messages: [ { role: "user", content: "what's (3 + 5) x 12?" } ] }
);
const weatherResponse = await agent.invoke(
{ messages: [ { role: "user", content: "what is the weather in nyc?" } ] }
);
await client.close();
```
:::
## Custom MCP servers
+158
View File
@@ -40,6 +40,8 @@ LangGraph comes with a set of prebuilt components that implement common agent be
Using LangGraph for agent development allows you to focus on your application's logic and behavior, instead of building and maintaining the supporting infrastructure for state, memory, and human feedback.
:::python
## Package ecosystem
The high-level components are organized into several packages, each with a specific focus.
@@ -189,3 +191,159 @@ function initializeWidget() {
window.addEventListener("DOMContentLoaded", initializeWidget);
document$.subscribe(initializeWidget);
</script>
:::
:::js
## Package ecosystem
The high-level components are organized into several packages, each with a specific focus.
| Package | Description | Installation |
| ------------------------ | --------------------------------------------------------------------------- | -------------------------------------------------- |
| `langgraph` | Prebuilt components to [**create agents**](./agents.md) | `npm install @langchain/langgraph @langchain/core` |
| `langgraph-supervisor` | Tools for building [**supervisor**](./multi-agent.md#supervisor) agents | `npm install @langchain/langgraph-supervisor` |
| `langgraph-swarm` | Tools for building a [**swarm**](./multi-agent.md#swarm) multi-agent system | `npm install @langchain/langgraph-swarm` |
| `langchain-mcp-adapters` | Interfaces to [**MCP servers**](./mcp.md) for tool and resource integration | `npm install @langchain/mcp-adapters` |
| `agentevals` | Utilities to [**evaluate agent performance**](./evals.md) | `npm install agentevals` |
## Visualize an agent graph
Use the following tool to visualize the graph generated by [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html) and to view an outline of the corresponding code. It allows you to explore the infrastructure of the agent as defined by the presence of:
- [`tools`](./tools.md): A list of tools (functions, APIs, or other callable objects) that the agent can use to perform tasks.
- `preModelHook`: A function that is called before the model is invoked. It can be used to condense messages or perform other preprocessing tasks.
- `postModelHook`: A function that is called after the model is invoked. It can be used to implement guardrails, human-in-the-loop flows, or other postprocessing tasks.
- [`responseFormat`](./agents.md#structured-output): A data structure used to constrain the type of the final output (via Zod schemas).
<div class="agent-layout">
<div class="agent-graph-features-container">
<div class="agent-graph-features">
<h3 class="agent-section-title">Features</h3>
<label><input type="checkbox" id="tools" checked> <code>tools</code></label>
<label><input type="checkbox" id="preModelHook"> <code>preModelHook</code></label>
<label><input type="checkbox" id="postModelHook"> <code>postModelHook</code></label>
<label><input type="checkbox" id="responseFormat"> <code>responseFormat</code></label>
</div>
</div>
<div class="agent-graph-container">
<h3 class="agent-section-title">Graph</h3>
<img id="agent-graph-img" src="../assets/react_agent_graphs/0001.svg" alt="graph image" style="max-width: 100%;"/>
</div>
</div>
The following code snippet shows how to create the above agent (and underlying graph) with [`createReactAgent`](/langgraphjs/reference/functions/langgraph_prebuilt.createReactAgent.html):
<div class="language-typescript">
<pre><code id="agent-code" class="language-typescript"></code></pre>
</div>
<script>
function getCheckedValue(id) {
return document.getElementById(id).checked ? "1" : "0";
}
function getKey() {
return [
getCheckedValue("responseFormat"),
getCheckedValue("postModelHook"),
getCheckedValue("preModelHook"),
getCheckedValue("tools")
].join("");
}
function dedent(strings, ...values) {
const str = String.raw({ raw: strings }, ...values)
const [space] = str.split("\n").filter(Boolean).at(0).match(/^(\s*)/)
const spaceLen = space.length
return str.split("\n").map(line => line.slice(spaceLen)).join("\n").trim()
}
Object.assign(dedent, {
offset: (size) => (strings, ...values) => {
return dedent(strings, ...values).split("\n").map(line => " ".repeat(size) + line).join("\n")
}
})
function generateCodeSnippet({ tools, pre, post, response }) {
const lines = []
lines.push(dedent`
import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatOpenAI } from "@langchain/openai";
`)
if (tools) lines.push(`import { tool } from "@langchain/core/tools";`);
if (response || tools) lines.push(`import { z } from "zod";`);
lines.push("", dedent`
const agent = createReactAgent({
llm: new ChatOpenAI({ model: "o4-mini" }),
`)
if (tools) {
lines.push(dedent.offset(2)`
tools: [
tool(() => "Sample tool output", {
name: "sampleTool",
schema: z.object({}),
}),
],
`)
}
if (pre) {
lines.push(dedent.offset(2)`
preModelHook: (state) => ({ llmInputMessages: state.messages }),
`)
}
if (post) {
lines.push(dedent.offset(2)`
postModelHook: (state) => state,
`)
}
if (response) {
lines.push(dedent.offset(2)`
responseFormat: z.object({ result: z.string() }),
`)
}
lines.push(`});`);
return lines.join("\n");
}
function render() {
const key = getKey();
document.getElementById("agent-graph-img").src = `../assets/react_agent_graphs/${key}.svg`;
const state = {
tools: document.getElementById("tools").checked,
pre: document.getElementById("preModelHook").checked,
post: document.getElementById("postModelHook").checked,
response: document.getElementById("responseFormat").checked
};
document.getElementById("agent-code").textContent = generateCodeSnippet(state);
}
function initializeWidget() {
render(); // no need for `await` here
document.querySelectorAll(".agent-graph-features input").forEach((input) => {
input.addEventListener("change", render);
});
}
// Init for both full reload and SPA nav (used by MkDocs Material)
window.addEventListener("DOMContentLoaded", initializeWidget);
document$.subscribe(initializeWidget);
</script>
:::