langchain-ai / langchain-ai/langgraphjs
Feature request: Support features requiring async local storage (interrupt, functional API, etc) in web environment
- Dominant language
- TypeScript
- Stars
- 3.3k
- Forks
- 579
- Avg merge
- 1d 1h
- Merged PRs (30d)
- 56
Description
I am building a graph that is focused on iterative extraction of unstructured data (extraction agent), then when ready, I wanted to transfer to my cms agent to get the content into my CMS. I was building this out so it could run in a web environment and the combination of using the web functionality and the newer functional API caused me to run into issues.
## Context
* langgraph = 0.2.45(@langchain/core@0.3.39)
## Summary
I followed [this multi-agent functional API example](https://langchain-ai.github.io/langgraphjs/how-tos/multi-agent-multi-turn-convo-functional/#setup), but used the task function exported from web like this cannot be used in the web environment that i can tell:
### Task
```js
import { type BaseMessageLike } from "@langchain/core/messages"
import { task } from "@langchain/langgraph/web"
import { cmsAgent, extractionAgent } from "~agents/discountAgents"
export const callExtractionAgent = task(
"callExtractionAgent",
async (messages: BaseMessageLike[]) => {
const response = await extractionAgent.invoke({ messages })
return response.messages
}
)
```
### Agent
```js
import { createReactAgent } from "@langchain/langgraph/prebuilt"
import { ChatOpenAI } from "@langchain/openai"
...
export const extractionAgent = createReactAgent({
llm: new ChatOpenAI({
temperature: 0,
streaming: true,
openAIApiKey: process.env.PLASMO_PUBLIC_OPENAI_KEY,
model: "gpt-4o-mini"
}),
tools: [extractOfferTool, transferToCmsAgent],
stateModifier: extractionAgentSystemPrompt
})
```
### Error
However, when i got to the point of calling extractionAgent.invoke within my test within the jsdom environment using jest, I ran into an error that traced back to this code within `pregel/call.cjs`
```js
function call({ func, name, retry }, ...args) {
const config = singletons_1.AsyncLocalStorageProviderSingleton.getRunnableConfig(); // this will be undefined
if (typeof config.configurable?.[constants_js_1.CONFIG_KEY_CALL] === "function") {
return config.configurable[constants_js_1.CONFIG_KEY_CALL](func, name, args, {
retry,
callbacks: config.callbacks,
});
}
throw new Error("Async local storage not initialized. Please call initializeAsyncLocalStorageSingleton() before using this function.");
}
```
The config returned from `getRunnableConfig()` was undefined, so the if statement blew up at `config.configurable` since config was undefined. `TypeError: Cannot read properties of undefined (reading 'configurable')`
## Workaround
To fix this issue, I knew I needed to pass the config through as defined [here](https://langchain-ai.github.io/langgraphjs/how-tos/use-in-web-environments/). However, the `task()` api doesn't allow passing through the config
from what I can tell. So, I had to move away from using a `task()` and instead just used a function instead with the signature `callExtractionAgent(messages, config)`. Then I updated my function to transfer to the agent like this:
Original
```js
export const callExtractionAgent = task(
"callExtractionAgent",
async (messages: BaseMessageLike[]) => {
const response = await extractionAgent.invoke({ messages })
return response.messages
}
)
```
Updated
```js
// No longer a task
export async function callExtractionAgent(
messages: BaseMessageLike[],
config?: RunnableConfig
) {
const response = await extractionAgent.invoke({ messages }, config)
return response.messages
}
export async function callActiveAgent(
state: typeof DiscountFlowState.State,
config?: RunnableConfig
) {
const messages = state.messages
let agentMessages
if (state.activeAgent === "cms") {
// Pass `config` to callCMSAgent
agentMessages = await callCMSAgent(messages, config) // Cant pass config into a task
} else {
// Default to extraction agent
agentMessages = await callExtractionAgent(messages, config) // Cant pass config into a task
}
return {
messages: agentMessages
}
}
```
## Also Tried (Still Fails)
I did circle back and try passing through the config in the task, but ended up with the same error
```js
export const callExtractionAgent = task(
"callExtractionAgent",
async (messages: BaseMessageLike[], config: RunnableConfig) => {
const response = await extractionAgent.invoke({ messages }, config) // this still fails
return response.messages
}
)
```
## Issue:
1. The `task()` api doesn't allow passing through the config (I tried passing it through and still ended up with the same error)
2. There really should be a more clear warning or error when you are running in a web environment anytime something is called without passing in config, rather than just trying to read from async storage and triggering an obscure error
Contributor guide
Research direction
Start with pregel/call.cjs and the web task() API, then read the linked web-environment guidance and async-local-storage initialization path. Reproduce the failure in the jsdom/Jest setup described in the issue. Done means web tasks can support the required config behavior and missing configuration produces a clear error instead of an obscure TypeError.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- web-dev
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100