Use in web environments¶
LangGraph.js uses the async_hooks
API to more conveniently allow
for tracing and callback propagation within nodes. This API is supported in many environments, such as
Node.js,
Deno,
Cloudflare Workers,
and the Edge runtime, but not all,
such as within web browsers.
To allow usage of LangGraph.js in environments that do not have the async_hooks
API available, we've added a separate
@langchain/langgraph/web
entrypoint. This entrypoint exports everything that the primary @langchain/langgraph
exports,
but will not initialize or even import async_hooks
. Here's a simple example:
// Import from "@langchain/langgraph/web"
import {
END,
START,
StateGraph,
StateGraphArgs
} from "@langchain/langgraph/web";
import { HumanMessage } from "@langchain/core/messages";
// Define the state interface
interface AgentState {
messages: HumanMessage[];
}
// Define the graph state
const graphState: StateGraphArgs<AgentState>["channels"] = {
messages: {
value: (x: HumanMessage[], y: HumanMessage[]) => x.concat(y),
default: () => [],
},
};
const nodeFn = async (_state: AgentState) => {
return { messages: [new HumanMessage("Hello from the browser!")] }
}
// Define a new graph
const workflow = new StateGraph<AgentState>({ channels: graphState })
.addNode("node", nodeFn)
.addEdge(START, "node")
.addEdge("node", END);
const app = workflow.compile({});
// Use the Runnable
const finalState = await app.invoke(
{ messages: [] },
);
console.log(finalState.messages[finalState.messages.length - 1].content);
Hello from the browser!
Other entrypoints, such as @langchain/langgraph/prebuilt
, can be used in either environment.
Caution
If you are using LangGraph.js on the frontend, make sure you are not exposing any private keys! For chat models, this means you need to use something like WebLLM that can run client-side without authentication.
Passing config¶
The lack of async_hooks
support in web browsers means that if you are calling a
Runnable
within a node (for example, when calling a chat model),
you need to manually pass a config
object through to properly support tracing,
.streamEvents()
to stream intermediate steps,
and other callback related functionality. This config object will passed in as the second argument of each node, and should be
used as the second parameter of any Runnable
method.
To illustrate this, let's set up our graph again as before, but with a Runnable
within our node. First, we'll
avoid passing config
through into the nested function, then try to use .streamEvents()
to see the intermediate results of the
nested function:
// Import from "@langchain/langgraph/web"
import {
END,
START,
StateGraph,
StateGraphArgs
} from "@langchain/langgraph/web";
import { HumanMessage } from "@langchain/core/messages";
import {
RunnableLambda,
} from "@langchain/core/runnables";
import { type StreamEvent } from "@langchain/core/tracers/log_stream";
// Define the state interface
interface AgentState {
messages: HumanMessage[];
}
// Define the graph state
const graphState: StateGraphArgs<AgentState>["channels"] = {
messages: {
value: (x: HumanMessage[], y: HumanMessage[]) => x.concat(y),
default: () => [],
},
};
const nodeFn = async (_state: AgentState) => {
// Note that we do not pass any `config` through here
const nestedFn = RunnableLambda.from(async (input: string) => {
return new HumanMessage(`Hello from ${input}!`)
}).withConfig({ runName: "nested" });
const responseMessage = await nestedFn.invoke("a nested function");
return { messages: [responseMessage] }
}
// Define a new graph
const workflow = new StateGraph<AgentState>({ channels: graphState })
.addNode("node", nodeFn)
.addEdge(START, "node")
.addEdge("node", END);
const app = workflow.compile({});
// Stream intermediate steps from the graph
const eventStream = await app.streamEvents(
{ messages: [] },
{ version: "v2" },
{ includeNames: ["nested"] }
);
const events: StreamEvent[] = [];
for await (const event of eventStream) {
console.log(event);
events.push(event);
}
console.log(`Received ${events.length} events from the nested function`);
Received 0 events from the nested function
We can see that we get no events.
Next, let's try redeclaring the graph with a node that passes config through correctly:
// Import from "@langchain/langgraph/web"
import {
END,
START,
StateGraph,
StateGraphArgs
} from "@langchain/langgraph/web";
import { HumanMessage } from "@langchain/core/messages";
import {
RunnableLambda,
type RunnableConfig
} from "@langchain/core/runnables";
import { type StreamEvent } from "@langchain/core/tracers/log_stream";
// Define the state interface
interface AgentState {
messages: HumanMessage[];
}
// Define the graph state
const graphState: StateGraphArgs<AgentState>["channels"] = {
messages: {
value: (x: HumanMessage[], y: HumanMessage[]) => x.concat(y),
default: () => [],
},
};
// Note the second argument here.
const nodeFn = async (_state: AgentState, config?: RunnableConfig) => {
// If you need to nest deeper, remember to pass `_config` when invoking
const nestedFn = RunnableLambda.from(async (input: string, _config?: RunnableConfig) => {
return new HumanMessage(`Hello from ${input}!`)
}).withConfig({ runName: "nested" });
const responseMessage = await nestedFn.invoke("a nested function", config);
return { messages: [responseMessage] }
}
// Define a new graph
const workflow = new StateGraph<AgentState>({ channels: graphState })
.addNode("node", nodeFn)
.addEdge(START, "node")
.addEdge("node", END);
const app = workflow.compile({});
// Stream intermediate steps from the graph
const eventStream = await app.streamEvents(
{ messages: [] },
{ version: "v2" },
{ includeNames: ["nested"] }
);
const events: StreamEvent[] = [];
for await (const event of eventStream) {
console.log(event);
events.push(event);
}
console.log(`Received ${events.length} events from the nested function`);
{ event: "on_chain_start", data: { input: { messages: [] } }, name: "nested", tags: [], run_id: "9a7c5a55-f9f1-4058-8c58-7be43078468c", metadata: {} } { event: "on_chain_end", data: { output: HumanMessage { lc_serializable: true, lc_kwargs: { content: "Hello from a nested function!", additional_kwargs: {}, response_metadata: {} }, lc_namespace: [ "langchain_core", "messages" ], content: "Hello from a nested function!", name: undefined, additional_kwargs: {}, response_metadata: {} } }, run_id: "9a7c5a55-f9f1-4058-8c58-7be43078468c", name: "nested", tags: [], metadata: {} } Received 2 events from the nested function
You can see that we get events from the nested function as expected.
Next steps¶
You've now learned about some special considerations around using LangGraph.js in web environments.
Next, check out some how-to guides on core functionality.