Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: Support features requiring async local storage (interrupt, functional API, etc) in web environment #879

Open
rothnic opened this issue Feb 19, 2025 · 6 comments

Comments

@rothnic
Copy link

rothnic commented Feb 19, 2025

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, but used the task function exported from web like this cannot be used in the web environment that i can tell:

Task

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

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

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. 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

export const callExtractionAgent = task(
  "callExtractionAgent",
  async (messages: BaseMessageLike[]) => {
    const response = await extractionAgent.invoke({ messages })
    return response.messages
  }
)

Updated

// 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

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
@vadimmelnicuk
Copy link

vadimmelnicuk commented Mar 2, 2025

I have bumped into the same issue while experimenting with using Functional API in web environment.

@rothnic do you know whether there are any downsides of not using task() wrapper?

Any updates on this issue?

@rothnic
Copy link
Author

rothnic commented Mar 2, 2025

@rothnic do you know whether there are any downsides of not using task() wrapper?

No idea. It feels like the whole langchain API is rather hard to follow and there are so few examples of langchainjs already, then of the functional API there are fewer, then using it in a web environment are even fewer, so we are in a narrow use case.

@benjamincburns
Copy link
Collaborator

I'm in the process of releasing it (should go out within an hour), but #933 should address this for you. I'll also aim to improve the docs around this particular case (will add a howto).

Preemptive link to the API reference for getConfig: https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.getConfig.html

@benjamincburns
Copy link
Collaborator

Ahh, my apologies - I missed that this was specific to the web environment. Unfortunately unless you're shimming async_hooks, I don't expect functional API to work on web at the moment. getConfig and even basic task execution rely on async local storage. Sorry for the back-and-forth, I'll leave this one open!

@benjamincburns benjamincburns changed the title Can't pass RunnableConfig through task within web environment Feature request: Support functional API in web environment Mar 4, 2025
@benjamincburns benjamincburns changed the title Feature request: Support functional API in web environment Feature request: Support features requiring async local storage (interrupt, functional API, etc) in web environment Mar 4, 2025
@rothnic
Copy link
Author

rothnic commented Mar 10, 2025

@benjamincburns I was unable to find an actively maintained shim for async_hooks. For context, I was using langgraph within a chrome extension, where having an always running external server seemed like overkill. Having a hard dependency on async hooks feels like it will make development for the web forever difficult

@benjamincburns
Copy link
Collaborator

@rothnic Just a heads up that we are actively looking into how we can better support environments that don't have ALS. Unfortunately I don't have any conclusive details to share just yet, as we're still in the exploratory phase.

cc @littledivy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants