state can't be altered in preModelHook with createReactAgent #284

Closed
opened 2026-02-15 18:15:29 -05:00 by yindo · 2 comments
Owner

Originally created by @terrytz on GitHub (Jun 11, 2025).

const agent = createReactAgent({
        llm: model,
        tools,
        preModelHook: async (state, update) => {

          let trimmedMessages = await trimMessages(stateManagedMessages, {
            strategy: 'last',
            maxTokens: 3000,
            allowPartial: true,
            includeSystem: true,
            tokenCounter: countTokens, // just simple a tiktoken counter
          });

          return {llmInputMessages: trimmedMessages};
        },
      });



      const result = await agent.invoke(
        {
          messages: [new HumanMessage("Do something")],
        },
        {
          recursionLimit: 10000,
          callbacks,
          

          configurable: {
            // threadId: '1',
            // thread_id: '1',
            threadId: '1',
          },
        },
      );

Expected behavior:
the state.llmInputMessages gets overriden by the hook's return {llmInputMessages: trimmedMessages};
then the trimmedMessages is sent to the next LLM call.

Actual behavior:
The trimmedMessages is confirmed to be trimmed properly, but the state doesn't update, and the full state.messages gets sent to the next LLM call.

Worth noting:
if i immediately set:
state.llmInputMessages = [new HumanMessage('Hello world')]
The "Hello world" actually gets sent to the next LLM call.

It's very unpredictable.

Originally created by @terrytz on GitHub (Jun 11, 2025). ```tsx const agent = createReactAgent({ llm: model, tools, preModelHook: async (state, update) => { let trimmedMessages = await trimMessages(stateManagedMessages, { strategy: 'last', maxTokens: 3000, allowPartial: true, includeSystem: true, tokenCounter: countTokens, // just simple a tiktoken counter }); return {llmInputMessages: trimmedMessages}; }, }); const result = await agent.invoke( { messages: [new HumanMessage("Do something")], }, { recursionLimit: 10000, callbacks, configurable: { // threadId: '1', // thread_id: '1', threadId: '1', }, }, ); ``` Expected behavior: the state.llmInputMessages gets overriden by the hook's return {llmInputMessages: trimmedMessages}; then the trimmedMessages is sent to the next LLM call. Actual behavior: The trimmedMessages is confirmed to be trimmed properly, but the state doesn't update, and the full state.messages gets sent to the next LLM call. Worth noting: if i immediately set: ` state.llmInputMessages = [new HumanMessage('Hello world')] ` The "Hello world" actually gets sent to the next LLM call. It's very unpredictable.
yindo closed this issue 2026-02-15 18:15:29 -05:00
Author
Owner

@dqbd commented on GitHub (Jun 17, 2025):

Hello @terrytz! Can you clarify if you're using LangGraph CLI / LangGraph Platform? Or are you running standalone?

If you're running standalone, you most likely need to (1) pass checkpointer in createReactAgent and (2) set configurable.thread_id (not threadId!), after which the state is preserved in-between executions.

@dqbd commented on GitHub (Jun 17, 2025): Hello @terrytz! Can you clarify if you're using LangGraph CLI / LangGraph Platform? Or are you running standalone? If you're running standalone, you most likely need to (1) pass checkpointer in `createReactAgent` and (2) set `configurable.thread_id` (not `threadId`!), after which the state is preserved in-between executions.
Author
Owner

@dqbd commented on GitHub (Jul 7, 2025):

Closing the issue for staleness.

@dqbd commented on GitHub (Jul 7, 2025): Closing the issue for staleness.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraphjs#284