bguiz
bguiz

Reputation: 28627

How can I add an "agent" to a "graph" in LangGraphJs?

Here's the graph that I've constructed with LangGraphJs:

const workflow = new StateGraph(MessagesAnnotation)
  .addNode('fooAgent', fooAgent)
  .addNode('barAgent', barAgent)
  .addEdge(START, 'fooAgent')
  .addEdge('fooAgent', 'barAgent')
  .addEdge('barAgent', END);
const graph = workflow.compile({
  checkpointer: checkpointSaver,
});

When I run the graph, I get the following error:

file:///Users/user/code/foobar/node_modules/@langchain/core/dist/language_models/chat_models.js:64
        return chatGeneration.message;
                              ^

TypeError: Cannot read properties of undefined (reading 'message')
    at ChatOpenAI.invoke (file:///Users/user/code/foobar/node_modules/@langchain/core/dist/language_models/chat_models.js:64:31)
    at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
    at async RunnableSequence.invoke (file:///Users/user/code/foobar/node_modules/@langchain/core/dist/runnables/base.js:1280:27)
    at async RunnableCallable.callModel [as func] (file:///Users/user/code/foobar/node_modules/@langchain/langgraph/dist/prebuilt/react_agent_executor.js:227:29)
    at async RunnableCallable.invoke (file:///Users/user/code/foobar/node_modules/@langchain/langgraph/dist/utils.js:79:27)
    at async RunnableSequence.invoke (file:///Users/user/code/foobar/node_modules/@langchain/core/dist/runnables/base.js:1274:33)
    at async _runWithRetry (file:///Users/user/code/foobar/node_modules/@langchain/langgraph/dist/pregel/retry.js:67:22)
    at async PregelRunner._executeTasksWithRetry (file:///Users/user/code/foobar/node_modules/@langchain/langgraph/dist/pregel/runner.js:208:33)
    at async PregelRunner.tick (file:///Users/user/code/foobar/node_modules/@langchain/langgraph/dist/pregel/runner.js:45:40)
    at async CompiledStateGraph._runLoop (file:///Users/user/code/foobar/node_modules/@langchain/langgraph/dist/pregel/index.js:1015:17) {
  pregelTaskId: 'c9848e05-19d9-5587-b2b4-4728ad808c34'
}

Node.js v23.3.0

I assumed, incorrectly, that the addNode function would accept the agent. However:

I believe that there may be 2 possible ways to solve this issue:

Are either of these possible?


more details:

The agents are initialised using createReactAgent, like so:

const checkpointSaver = new MemorySaver();
const fooAgent = createReactAgent({
  llm,
  tools,
  checkpointSaver,
});
/* code to modify the agent to give it specific capabilities */
const barAgent = createReactAgent({
  llm,
  tools,
  checkpointSaver,
});
/* code to modify the agent to give it specific capabilities */

After the agents are added to graph, they are execute like so:

const inputs = { messages: [new HumanMessage('Please do the following tasks ...')] }
const stream = await graph.stream(inputs, { streamMode: 'values' });
for await (const { messages } of stream) {
  console.log(messages);
}

Upvotes: 0

Views: 28

Answers (1)

bguiz
bguiz

Reputation: 28627

Agents can indeed be passed into addNode, but both the agents themselves need to be initialised differently, and the top-level graph that is composed of those agents needs to be invoked/parsed differently.

Code details below:

(1) When initialising the agents, do not use the checkpoint saver. There should only be one of these, and it needs to be in the top-level graph - i.e. the main workflow graph.

const checkpointSaver = new MemorySaver();
const fooAgent = createReactAgent({
  llm,
  tools,
  // checkpointSaver, // NOTE Do NOT use a checkpoint saver for each agent
});
/* code to modify the agent to give it specific capabilities */
const barAgent = createReactAgent({
  llm,
  tools,
  // checkpointSaver, // NOTE Do NOT use a checkpoint saver for each agent
});
/* code to modify the agent to give it specific capabilities */

(2) When compiling the graph, that's when the checkpoint saver is necessary.

// NOTE define `workflow`, same as above.
const graph = workflow.compile({
  checkpointer: checkpointSaver, // NOTE **only** the top level graph should use a checkpoint saver
});

(3) When invoking graph.stream, need to set subgraphs. Optionally, also set recursionLimit.

const config = {
  configurable: { thread_id: '0x0002' },
  subgraphs: true, // NOTE the agents are technically subgraphs
  streamMode: 'values',
  recursionLimit: 10, // NOTE add a limit, in case the edges between the nodes in the graph were defined incorrectly (as mine was)
};
const stream = graph.stream({ messages: [humanMsg] }, config);

(4) When using subgraphs, the structure of the events in the stream needs to be parsed differently, like so:

let eventIdx = 0;
for await (const event of await stream) {
  console.log(`===AGENT=== ${event[0]}`);
  const eventMessages = event[1].messages;
  if (eventMessages) {
    const msg = eventMessages[eventMessages.length - 1];
    console.log(`===MSG=== (${++eventIdx}) type: ${msg._getType()}`);
    console.log(msg.content);
  }
}

Upvotes: 0

Related Questions