Setting streamMode: "messages" changes properties of messages stored in checkpoints #200

Closed
opened 2026-02-15 17:17:12 -05:00 by yindo · 13 comments
Owner

Originally created by @Toze on GitHub (Mar 14, 2025).

Edit from @benjamincburns: See this comment for the MRE for this issue. This isn't specific to any particular checkpointer as originally reported. Also you'll observe that if you change the MRE to use any streamMode other than "messages" the problem goes away.

A fix for this issue should not focus on retaining the name property of streamed messages. Rather, the issue as I see it is that we're mutating application state in a way that's conditioned on streamMode. It probably does make sense to mutate messages in LangGraph in order to preserve important message properties (e.g. rules around message IDs, tool call IDs, etc), but any such mutation shouldn't be conditioned on the value of streamMode.


Subgraph AI message names from previous runs are not reloaded with their names when using a (Postgres?) checkpointer.

Simple react subgraph with console log node

    const reactAgent = createReactAgent({
      llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
      tools: [],
      name: 'react_agent'
    })

    const consoleLogNode = (state: typeof MessagesAnnotation.State) => {
      console.log(
        '| message.id | message.getType() | message.name | message.content |\n' +
          '|-----|------|------|----------|\n' +
          state.messages
            .map(
              (msg) =>
                `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |`
            )
            .join('\n')
      )
      return {}
    }

    const graph = new StateGraph(TopAssistantState.spec)
      .addNode('node1', reactAgent)
      .addNode('node2', consoleLogNode)
      .addEdge(START, 'node1')
      .addEdge('node1', 'node2')

    const checkpointer = PostgresSaver.fromConnString(databaseUrl)
    return graph.compile({ name: 'graph', checkpointer })

Run 1

message.id message.getType() message.name message.content
5a1e16c5-a4eb-4094-affd-028e30c30796 human undefined hello
chatcmpl-BAuk850FOKxLzYwgAqkd5zAt1nuEF ai react_agent Hello! How can I assist you today?

Run 2

message.id message.getType() message.name message.content
5a1e16c5-a4eb-4094-affd-028e30c30796 human undefined hello
chatcmpl-BAuk850FOKxLzYwgAqkd5zAt1nuEF ai undefined Hello! How can I assist you today?
a5fce2d0-dd11-4232-bfae-df773b81cee6 human undefined Hello again !
chatcmpl-BAumGdSK0P52pmUQROOcCIsMNUEuj ai react_agent Hello again! What’s on your mind today?

chatcmpl-BAuk850FOKxLzYwgAqkd5zAt1nuEF had correctly the react_agent name in run 1 but not in run 2.
This can affect badly the performance of multi agent systems (because they struggle knowing who did what)

Note: in other tests, I was able to see that this bug doesn't affect tool messages.

Originally created by @Toze on GitHub (Mar 14, 2025). Edit from @benjamincburns: See [this comment](https://github.com/langchain-ai/langgraphjs/issues/993#issuecomment-2759854207) for the MRE for this issue. This isn't specific to any particular checkpointer as originally reported. Also you'll observe that if you change the MRE to use any `streamMode` other than `"messages"` the problem goes away. A fix for this issue should not focus on retaining the `name` property of streamed messages. Rather, the issue as I see it is that we're mutating application state in a way that's conditioned on `streamMode`. It probably _does_ make sense to mutate messages in LangGraph in order to preserve important message properties (e.g. rules around message IDs, tool call IDs, etc), but any such mutation shouldn't be conditioned on the value of `streamMode`. --- Subgraph AI message names from previous runs are not reloaded with their names when using a (Postgres?) checkpointer. ## Simple react subgraph with console log node ``` const reactAgent = createReactAgent({ llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }), tools: [], name: 'react_agent' }) const consoleLogNode = (state: typeof MessagesAnnotation.State) => { console.log( '| message.id | message.getType() | message.name | message.content |\n' + '|-----|------|------|----------|\n' + state.messages .map( (msg) => `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |` ) .join('\n') ) return {} } const graph = new StateGraph(TopAssistantState.spec) .addNode('node1', reactAgent) .addNode('node2', consoleLogNode) .addEdge(START, 'node1') .addEdge('node1', 'node2') const checkpointer = PostgresSaver.fromConnString(databaseUrl) return graph.compile({ name: 'graph', checkpointer }) ``` ## Run 1 | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | 5a1e16c5-a4eb-4094-affd-028e30c30796 | human | undefined | hello | | chatcmpl-BAuk850FOKxLzYwgAqkd5zAt1nuEF | ai | react_agent | Hello! How can I assist you today? | ## Run 2 | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | 5a1e16c5-a4eb-4094-affd-028e30c30796 | human | undefined | hello | | chatcmpl-BAuk850FOKxLzYwgAqkd5zAt1nuEF | ai | undefined | Hello! How can I assist you today? | | a5fce2d0-dd11-4232-bfae-df773b81cee6 | human | undefined | Hello again ! | | chatcmpl-BAumGdSK0P52pmUQROOcCIsMNUEuj | ai | react_agent | Hello again! What’s on your mind today? | `chatcmpl-BAuk850FOKxLzYwgAqkd5zAt1nuEF` had correctly the `react_agent` name in run 1 but not in run 2. This can affect badly the performance of multi agent systems (because they struggle knowing who did what) Note: in other tests, I was able to see that this bug doesn't affect tool messages.
yindo closed this issue 2026-02-15 17:17:12 -05:00
Author
Owner

@vbarda commented on GitHub (Mar 14, 2025):

could you provide code for how you're invoking the agent?

@vbarda commented on GitHub (Mar 14, 2025): could you provide code for how you're invoking the agent?
Author
Owner

@Toze commented on GitHub (Mar 17, 2025):

@vbarda like this

  async streamUserMessage(
    message: string,
    config: { threadId: string; streamMode?: StreamMode[]; runId?: string }
  ) {
    const { threadId, streamMode = ['messages'], runId } = config
    const graph = await this.getGraphWithCheckpointer()

    const stream = await graph.stream(
      { messages: [new HumanMessage({ content: message })] },
      { configurable: { thread_id: threadId }, streamMode, runId }
    )

    return stream
  }
@Toze commented on GitHub (Mar 17, 2025): @vbarda like this ``` async streamUserMessage( message: string, config: { threadId: string; streamMode?: StreamMode[]; runId?: string } ) { const { threadId, streamMode = ['messages'], runId } = config const graph = await this.getGraphWithCheckpointer() const stream = await graph.stream( { messages: [new HumanMessage({ content: message })] }, { configurable: { thread_id: threadId }, streamMode, runId } ) return stream } ```
Author
Owner

@benjamincburns commented on GitHub (Mar 17, 2025):

@Toze sorry it has taken us a few days to get back to you on this. Either @vbarda or myself hope to start digging in sometime in the next 24h. In the meantime, I'm curious whether you've tested your MRE against other checkpointers (should hopefully be easy to check MemorySaver and SqliteSaver), and whether you've observed the problem with those as well?

@benjamincburns commented on GitHub (Mar 17, 2025): @Toze sorry it has taken us a few days to get back to you on this. Either @vbarda or myself hope to start digging in sometime in the next 24h. In the meantime, I'm curious whether you've tested your MRE against other checkpointers (should hopefully be easy to check `MemorySaver` and `SqliteSaver`), and whether you've observed the problem with those as well?
Author
Owner

@benjamincburns commented on GitHub (Mar 17, 2025):

Also @vbarda if you get to this before I do - there's some time-saving code in the CheckpointTestInitializer for the PostgresSaver in the @langchain/langgraph-checkpoint-validation package - it automates spinning-up and tearing-down PostgreSQL for testing using TestContainers.

@benjamincburns commented on GitHub (Mar 17, 2025): Also @vbarda if you get to this before I do - there's some time-saving code in [the `CheckpointTestInitializer` for the `PostgresSaver` in the `@langchain/langgraph-checkpoint-validation` package](https://github.com/langchain-ai/langgraphjs/blob/8df58b6ff2acfdbb10c6d63d009d06d3ddbe2408/libs/checkpoint-validation/src/tests/postgres_initializer.ts) - it automates [spinning](https://github.com/langchain-ai/langgraphjs/blob/8df58b6ff2acfdbb10c6d63d009d06d3ddbe2408/libs/checkpoint-validation/src/tests/postgres_initializer.ts#L14-L17)-[up](https://github.com/langchain-ai/langgraphjs/blob/8df58b6ff2acfdbb10c6d63d009d06d3ddbe2408/libs/checkpoint-validation/src/tests/postgres_initializer.ts#L26) and [tearing](https://github.com/langchain-ai/langgraphjs/blob/8df58b6ff2acfdbb10c6d63d009d06d3ddbe2408/libs/checkpoint-validation/src/tests/postgres_initializer.ts#L55-L57)-[down](https://github.com/langchain-ai/langgraphjs/blob/8df58b6ff2acfdbb10c6d63d009d06d3ddbe2408/libs/checkpoint-validation/src/tests/postgres_initializer.ts#L32) PostgreSQL for testing using [TestContainers](https://node.testcontainers.org/modules/postgresql/).
Author
Owner

@vbarda commented on GitHub (Mar 18, 2025):

@Toze i just tested your example and i can't reproduce the issue either using the postgres checkpointer or using the in-memory one. the only difference is that i was using const graph = new StateGraph(MemoryAnnotation.spec)... instead of TopAssistantState.spec. this makes me think that there is potentially an issue in the TopAssistantState state schema - are you defining a custom reducer for messages key by any chance?

@vbarda commented on GitHub (Mar 18, 2025): @Toze i just tested your example and i can't reproduce the issue either using the postgres checkpointer or using the in-memory one. the only difference is that i was using `const graph = new StateGraph(MemoryAnnotation.spec)...` instead of `TopAssistantState.spec`. this makes me think that there is potentially an issue in the `TopAssistantState` state schema - are you defining a custom reducer for `messages` key by any chance?
Author
Owner

@Toze commented on GitHub (Mar 19, 2025):

Hey! Thanks for looking into it. I don't have a custom reducer. I just tested with a MemorySaver and I don't have the issue.
I think this might be because I manually created the tables for the Postgres memory and maybe something is wrong. I'll do a proper setup with the langgraph setup function and I'll let you know if I still have the issue.
Thanks again and sorry if this wasn't a bug 😬

@Toze commented on GitHub (Mar 19, 2025): Hey! Thanks for looking into it. I don't have a custom reducer. I just tested with a MemorySaver and I don't have the issue. I think this might be because I manually created the tables for the Postgres memory and maybe something is wrong. I'll do a proper setup with the langgraph setup function and I'll let you know if I still have the issue. Thanks again and sorry if this wasn't a bug 😬
Author
Owner

@Toze commented on GitHub (Mar 19, 2025):

I don't have the issue with MemorySaver but I do have it with PostgresSaver.
I didn't have any custom reducer but to be sure, I'm now also using the MessagesAnnotation.spec, so my code is:

async getGraph() {
   const reactAgent = createReactAgent({
      llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
      tools: [],
      name: 'react_agent'
    })

    const consoleLogNode = (state: typeof MessagesAnnotation.State) => {
      console.log(
        '| message.id | message.getType() | message.name | message.content |\n' +
          '|-----|------|------|----------|\n' +
          state.messages
            .map(
              (msg) =>
                `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |`
            )
            .join('\n')
      )
      return {}
    }

    const graph = new StateGraph(MessagesAnnotation.spec)
      .addNode('node1', reactAgent)
      .addNode('node2', consoleLogNode)
      .addEdge(START, 'node1')
      .addEdge('node1', 'node2')

    return graph
}
  async getCheckpointer(): Promise<PostgresSaver> {
    if (!this._checkpointer) {
      const databaseUrl = this.configService.get<string>('DATABASE_URL')
      if (!databaseUrl) {
        throw new BadRequestException('DATABASE_URL environment variable is not set')
      }

      // Create checkpointer with connection string (recommended for production)
      this._checkpointer = PostgresSaver.fromConnString(databaseUrl)

      // Setup checkpointer if not already set up
      if (!this._isSetup) {
        await this._checkpointer.setup()
        this._isSetup = true
      }
    }

    return this._checkpointer
  }
  async getGraphWithCheckpointer() {
    // Get PostgreSQL checkpointer from service
    const checkpointer = await this.checkpointerService.getCheckpointer()
    const graph = await this.getGraph()
    return graph.compile({ name: 'pm-top-assistant', checkpointer })
  }
  async stream(
    message: string,
    config: { threadId: string; streamMode?: StreamMode[]; runId?: string }
  ) {
    const { threadId, streamMode = ['messages'], runId } = config
    const graph = await this.getGraphWithCheckpointer()

    const stream = await graph.stream(
      { messages: [new HumanMessage({ content: message })] },
      { configurable: { thread_id: threadId }, streamMode, runId }
    )

    return stream
  }

Example of blobs I see saved in the database (in the table checkpoint_blobs) with channel messages

[
  {
    "lc": 1,
    "type": "constructor",
    "id": ["langchain_core", "messages", "HumanMessage"],
    "kwargs": {
      "content": "hello",
      "additional_kwargs": {},
      "response_metadata": {},
      "id": "2e953022-dd45-4667-b6a4-58d20c875da6"
    }
  },
  {
    "lc": 1,
    "type": "constructor",
    "id": ["langchain_core", "messages", "AIMessageChunk"],
    "kwargs": {
      "content": "Hello! How can I assist you today?",
      "additional_kwargs": {},
      "response_metadata": {
        "usage": {
          "prompt_tokens": 8,
          "completion_tokens": 10,
          "total_tokens": 18,
          "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": 0 },
          "completion_tokens_details": {
            "reasoning_tokens": 0,
            "audio_tokens": 0,
            "accepted_prediction_tokens": 0,
            "rejected_prediction_tokens": 0
          }
        }
      },
      "tool_call_chunks": [],
      "id": "chatcmpl-BCiJRByV5FIaAWgluw1meQ15Qatxp",
      "usage_metadata": {
        "input_tokens": 8,
        "output_tokens": 10,
        "total_tokens": 18,
        "input_token_details": { "audio": 0, "cache_read": 0 },
        "output_token_details": { "audio": 0, "reasoning": 0 }
      },
      "tool_calls": [],
      "invalid_tool_calls": []
    }
  }
]

I don't see any message name in it.
My version of PostgresSaver:

    "@langchain/langgraph-checkpoint": "0.0.16",
    "@langchain/langgraph-checkpoint-postgres": "0.0.4",

@Toze commented on GitHub (Mar 19, 2025): I don't have the issue with MemorySaver but I do have it with PostgresSaver. I didn't have any custom reducer but to be sure, I'm now also using the MessagesAnnotation.spec, so my code is: ``` async getGraph() { const reactAgent = createReactAgent({ llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }), tools: [], name: 'react_agent' }) const consoleLogNode = (state: typeof MessagesAnnotation.State) => { console.log( '| message.id | message.getType() | message.name | message.content |\n' + '|-----|------|------|----------|\n' + state.messages .map( (msg) => `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |` ) .join('\n') ) return {} } const graph = new StateGraph(MessagesAnnotation.spec) .addNode('node1', reactAgent) .addNode('node2', consoleLogNode) .addEdge(START, 'node1') .addEdge('node1', 'node2') return graph } ``` ``` async getCheckpointer(): Promise<PostgresSaver> { if (!this._checkpointer) { const databaseUrl = this.configService.get<string>('DATABASE_URL') if (!databaseUrl) { throw new BadRequestException('DATABASE_URL environment variable is not set') } // Create checkpointer with connection string (recommended for production) this._checkpointer = PostgresSaver.fromConnString(databaseUrl) // Setup checkpointer if not already set up if (!this._isSetup) { await this._checkpointer.setup() this._isSetup = true } } return this._checkpointer } ``` ``` async getGraphWithCheckpointer() { // Get PostgreSQL checkpointer from service const checkpointer = await this.checkpointerService.getCheckpointer() const graph = await this.getGraph() return graph.compile({ name: 'pm-top-assistant', checkpointer }) } ``` ``` async stream( message: string, config: { threadId: string; streamMode?: StreamMode[]; runId?: string } ) { const { threadId, streamMode = ['messages'], runId } = config const graph = await this.getGraphWithCheckpointer() const stream = await graph.stream( { messages: [new HumanMessage({ content: message })] }, { configurable: { thread_id: threadId }, streamMode, runId } ) return stream } ``` Example of blobs I see saved in the database (in the table checkpoint_blobs) with channel `messages` ``` [ { "lc": 1, "type": "constructor", "id": ["langchain_core", "messages", "HumanMessage"], "kwargs": { "content": "hello", "additional_kwargs": {}, "response_metadata": {}, "id": "2e953022-dd45-4667-b6a4-58d20c875da6" } }, { "lc": 1, "type": "constructor", "id": ["langchain_core", "messages", "AIMessageChunk"], "kwargs": { "content": "Hello! How can I assist you today?", "additional_kwargs": {}, "response_metadata": { "usage": { "prompt_tokens": 8, "completion_tokens": 10, "total_tokens": 18, "prompt_tokens_details": { "cached_tokens": 0, "audio_tokens": 0 }, "completion_tokens_details": { "reasoning_tokens": 0, "audio_tokens": 0, "accepted_prediction_tokens": 0, "rejected_prediction_tokens": 0 } } }, "tool_call_chunks": [], "id": "chatcmpl-BCiJRByV5FIaAWgluw1meQ15Qatxp", "usage_metadata": { "input_tokens": 8, "output_tokens": 10, "total_tokens": 18, "input_token_details": { "audio": 0, "cache_read": 0 }, "output_token_details": { "audio": 0, "reasoning": 0 } }, "tool_calls": [], "invalid_tool_calls": [] } } ] ``` I don't see any message name in it. My version of PostgresSaver: ``` "@langchain/langgraph-checkpoint": "0.0.16", "@langchain/langgraph-checkpoint-postgres": "0.0.4", ```
Author
Owner

@vbarda commented on GitHub (Mar 19, 2025):

@Toze and just to confirm - are you on the latest @langchain/core version?

@vbarda commented on GitHub (Mar 19, 2025): @Toze and just to confirm - are you on the latest `@langchain/core` version?
Author
Owner

@Toze commented on GitHub (Mar 20, 2025):

I'm on latest version of langchain/core: 0.3.42

I've also tried running it through Langgraph Studio JS, just to be sure it wasn't related to my Postgres setup.
Same issue:

First run

message.id message.getType() message.name message.content
c4227cbd-0378-4d22-8d40-12aacbb12b1b human undefined hello
chatcmpl-BD3idBVX5a1wmDyOqGiW1uNK16bPi ai react_agent Hello! How can I assist you today?

Second run

See name in message chatcmpl-BD3idBVX5a1wmDyOqGiW1uNK16bPi

message.id message.getType() message.name message.content
c4227cbd-0378-4d22-8d40-12aacbb12b1b human undefined hello
chatcmpl-BD3idBVX5a1wmDyOqGiW1uNK16bPi ai undefined Hello! How can I assist you today?
64bdaed4-7f58-4dc0-9598-fcb8121d3d87 human undefined how are you?
chatcmpl-BD3ijcednNMLPiS8LZnDA1WPLIGFC ai react_agent I'm just a program, but I'm here and ready to help
@Toze commented on GitHub (Mar 20, 2025): I'm on latest version of langchain/core: 0.3.42 I've also tried running it through Langgraph Studio JS, just to be sure it wasn't related to my Postgres setup. Same issue: ## First run | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | c4227cbd-0378-4d22-8d40-12aacbb12b1b | human | undefined | hello | | chatcmpl-BD3idBVX5a1wmDyOqGiW1uNK16bPi | ai | **react_agent** | Hello! How can I assist you today? | ## Second run See name in message `chatcmpl-BD3idBVX5a1wmDyOqGiW1uNK16bPi` | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | c4227cbd-0378-4d22-8d40-12aacbb12b1b | human | undefined | hello | | chatcmpl-BD3idBVX5a1wmDyOqGiW1uNK16bPi | ai | **undefined** | Hello! How can I assist you today? | | 64bdaed4-7f58-4dc0-9598-fcb8121d3d87 | human | undefined | how are you? | | chatcmpl-BD3ijcednNMLPiS8LZnDA1WPLIGFC | ai | react_agent | I'm just a program, but I'm here and ready to help |
Author
Owner

@vbarda commented on GitHub (Mar 27, 2025):

@Toze sadly i still cannot reproduce your issue. also, i am not sure how you could be seeing this issue with LangGraph Studio JS (assuming you're using the web version) - it should be using in-memory checkpointer.

i wiped local postgres DB and recreated tables with .setup(). this is what i am running:

import { createReactAgent } from "@langchain/langgraph/prebuilt"
import { ChatOpenAI } from "@langchain/openai"
import { MemorySaver, MessagesAnnotation, START } from "@langchain/langgraph"
import { StateGraph } from "@langchain/langgraph"
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres"
import { HumanMessage } from "@langchain/core/messages"
import { v4 } from "uuid";


const reactAgent = createReactAgent({
  llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
  tools: [],
  name: 'react_agent'
})

const databaseUrl = 'postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable'

const consoleLogNode = (state: typeof MessagesAnnotation.State) => {
  console.log(
    '| message.id | message.getType() | message.name | message.content |\n' +
      '|-----|------|------|----------|\n' +
      state.messages
        .map(
          (msg) =>
            `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |`
        )
        .join('\n')
  )
  return {}
}

const checkpointer = PostgresSaver.fromConnString(databaseUrl)
await checkpointer.setup()
// const checkpointer = new MemorySaver()
const graph = new StateGraph(MessagesAnnotation.spec)
  .addNode('node1', reactAgent)
  .addNode('node2', consoleLogNode)
  .addEdge(START, 'node1')
  .addEdge('node1', 'node2')
  .compile({ name: 'graph', checkpointer })


 
const threadId = v4()
const runId = v4()
let stream = await graph.stream(
  { messages: [new HumanMessage({ content: "hi i am bob!"})] },
  { configurable: { thread_id: threadId, runId } }
)

for await (const _ of stream) {
  // Exhaust stream without logging
}

stream = await graph.stream(
  { messages: [new HumanMessage({ content: "what's my name?"})] },
  { configurable: { thread_id: threadId, runId } }
)

for await (const _ of stream) {
  // Exhaust stream without logging
}

and this is what i am getting:

message.id message.getType() message.name message.content
99926212-f5ec-4d2f-97d6-94974b04f095 human undefined hi i am bob!
chatcmpl-BFnsICCmwR2ObxlQKBcvqDpQIIYjP ai react_agent Hi Bob! How can I assist you today?
message.id message.getType() message.name message.content
----- ------ ------ ----------
99926212-f5ec-4d2f-97d6-94974b04f095 human undefined hi i am bob!
chatcmpl-BFnsICCmwR2ObxlQKBcvqDpQIIYjP ai react_agent Hi Bob! How can I assist you today?
3b31b87d-e395-46e8-a081-7233044320a7 human undefined what's my name?
chatcmpl-BFnsItc3rv8uRsWBfAn2Qe38KJnkU ai react_agent Your name is Bob! How can I help you today?
@vbarda commented on GitHub (Mar 27, 2025): @Toze sadly i still cannot reproduce your issue. also, i am not sure how you could be seeing this issue with `LangGraph Studio JS` (assuming you're using the web version) - it should be using in-memory checkpointer. i wiped local postgres DB and recreated tables with .setup(). this is what i am running: ```ts import { createReactAgent } from "@langchain/langgraph/prebuilt" import { ChatOpenAI } from "@langchain/openai" import { MemorySaver, MessagesAnnotation, START } from "@langchain/langgraph" import { StateGraph } from "@langchain/langgraph" import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres" import { HumanMessage } from "@langchain/core/messages" import { v4 } from "uuid"; const reactAgent = createReactAgent({ llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }), tools: [], name: 'react_agent' }) const databaseUrl = 'postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable' const consoleLogNode = (state: typeof MessagesAnnotation.State) => { console.log( '| message.id | message.getType() | message.name | message.content |\n' + '|-----|------|------|----------|\n' + state.messages .map( (msg) => `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |` ) .join('\n') ) return {} } const checkpointer = PostgresSaver.fromConnString(databaseUrl) await checkpointer.setup() // const checkpointer = new MemorySaver() const graph = new StateGraph(MessagesAnnotation.spec) .addNode('node1', reactAgent) .addNode('node2', consoleLogNode) .addEdge(START, 'node1') .addEdge('node1', 'node2') .compile({ name: 'graph', checkpointer }) const threadId = v4() const runId = v4() let stream = await graph.stream( { messages: [new HumanMessage({ content: "hi i am bob!"})] }, { configurable: { thread_id: threadId, runId } } ) for await (const _ of stream) { // Exhaust stream without logging } stream = await graph.stream( { messages: [new HumanMessage({ content: "what's my name?"})] }, { configurable: { thread_id: threadId, runId } } ) for await (const _ of stream) { // Exhaust stream without logging } ``` and this is what i am getting: | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | 99926212-f5ec-4d2f-97d6-94974b04f095 | human | undefined | hi i am bob! | | chatcmpl-BFnsICCmwR2ObxlQKBcvqDpQIIYjP | ai | react_agent | Hi Bob! How can I assist you today? | | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | 99926212-f5ec-4d2f-97d6-94974b04f095 | human | undefined | hi i am bob! | | chatcmpl-BFnsICCmwR2ObxlQKBcvqDpQIIYjP | ai | react_agent | Hi Bob! How can I assist you today? | | 3b31b87d-e395-46e8-a081-7233044320a7 | human | undefined | what's my name? | | chatcmpl-BFnsItc3rv8uRsWBfAn2Qe38KJnkU | ai | react_agent | Your name is Bob! How can I help you today? |
Author
Owner

@benjamincburns commented on GitHub (Mar 27, 2025):

I'm able to reproduce this issue when I add messages stream mode to the above script. It also appears to reproduce for me regardless of whether I use PostgresSaver or MemorySaver.

import { createReactAgent } from "@langchain/langgraph/prebuilt"
import { ChatOpenAI } from "@langchain/openai"
import { MemorySaver, MessagesAnnotation, START } from "@langchain/langgraph"
import { StateGraph } from "@langchain/langgraph"
// import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres"
import { HumanMessage } from "@langchain/core/messages"
import { v4 } from "uuid";

// import pg from "pg";
// import {
//   PostgreSqlContainer,
//   type StartedPostgreSqlContainer,
// } from "@testcontainers/postgresql";
// 
// const dbName = "test_db";

// const container = new PostgreSqlContainer("postgres:16.2")
//   .withDatabase("postgres")
//   .withUsername("postgres")
//   .withPassword("postgres");
// 
// const startedContainer = await container.start();
// 
// const client = new pg.Pool({
//   connectionString: startedContainer.getConnectionUri(),
// });
// 
// await client?.query(`CREATE DATABASE ${dbName}`);



const reactAgent = createReactAgent({
  llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }),
  tools: [],
  name: 'react_agent'
})

// const url = new URL("", "postgres://");
// url.hostname = startedContainer.getHost();
// url.port = startedContainer.getPort().toString();
// url.pathname = dbName;
// url.username = startedContainer.getUsername();
// url.password = startedContainer.getPassword();
// 
// const databaseUrl = url.toString();

const consoleLogNode = (state: typeof MessagesAnnotation.State) => {
  console.log(
    '| message.id | message.getType() | message.name | message.content |\n' +
      '|-----|------|------|----------|\n' +
      state.messages
        .map(
          (msg) =>
            `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |`
        )
        .join('\n')
  )
  return {}
}

// const checkpointer = PostgresSaver.fromConnString(databaseUrl);
// await checkpointer.setup();
const checkpointer = new MemorySaver();

const graph = new StateGraph(MessagesAnnotation.spec)
  .addNode('node1', reactAgent)
  .addNode('node2', consoleLogNode)
  .addEdge(START, 'node1')
  .addEdge('node1', 'node2')
  .compile({ name: 'graph', checkpointer })


 
const threadId = v4()
const runId = v4()
let stream = await graph.stream(
  { messages: [new HumanMessage({ content: "hi i am bob!"})] },
  { configurable: { thread_id: threadId, runId }, streamMode: 'messages' }
)

for await (const _ of stream) {
  // Exhaust stream without logging
}

stream = await graph.stream(
  { messages: [new HumanMessage({ content: "what's my name?"})] },
  { configurable: { thread_id: threadId, runId }, streamMode: 'messages' }
)

for await (const _ of stream) {
  // Exhaust stream without logging
}

// await client.end();
// await checkpointer.end();
// await startedContainer.stop();

Produces the following output:

message.id message.getType() message.name message.content
b20430dc-e0aa-47dc-b175-be8147d09047 human undefined hi i am bob!
chatcmpl-BFrRLRxkeCVePPKdMhlkh4tXpZUjm ai react_agent Hi Bob! How can I assist you today?
message.id message.getType() message.name message.content
b20430dc-e0aa-47dc-b175-be8147d09047 human undefined hi i am bob!
chatcmpl-BFrRLRxkeCVePPKdMhlkh4tXpZUjm ai undefined Hi Bob! How can I assist you today?
9f95326e-393e-4ea3-b7f8-3512fbf3c850 human undefined what's my name?
chatcmpl-BFrRMB3SA8o4qeELZl7HtBPDjEMKt ai react_agent Your name is Bob! How can I help you today?
@benjamincburns commented on GitHub (Mar 27, 2025): I'm able to reproduce this issue when I add `messages` stream mode to the above script. It also appears to reproduce for me regardless of whether I use `PostgresSaver` or `MemorySaver`. ```typescript import { createReactAgent } from "@langchain/langgraph/prebuilt" import { ChatOpenAI } from "@langchain/openai" import { MemorySaver, MessagesAnnotation, START } from "@langchain/langgraph" import { StateGraph } from "@langchain/langgraph" // import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres" import { HumanMessage } from "@langchain/core/messages" import { v4 } from "uuid"; // import pg from "pg"; // import { // PostgreSqlContainer, // type StartedPostgreSqlContainer, // } from "@testcontainers/postgresql"; // // const dbName = "test_db"; // const container = new PostgreSqlContainer("postgres:16.2") // .withDatabase("postgres") // .withUsername("postgres") // .withPassword("postgres"); // // const startedContainer = await container.start(); // // const client = new pg.Pool({ // connectionString: startedContainer.getConnectionUri(), // }); // // await client?.query(`CREATE DATABASE ${dbName}`); const reactAgent = createReactAgent({ llm: new ChatOpenAI({ modelName: 'gpt-4o-mini' }), tools: [], name: 'react_agent' }) // const url = new URL("", "postgres://"); // url.hostname = startedContainer.getHost(); // url.port = startedContainer.getPort().toString(); // url.pathname = dbName; // url.username = startedContainer.getUsername(); // url.password = startedContainer.getPassword(); // // const databaseUrl = url.toString(); const consoleLogNode = (state: typeof MessagesAnnotation.State) => { console.log( '| message.id | message.getType() | message.name | message.content |\n' + '|-----|------|------|----------|\n' + state.messages .map( (msg) => `| ${msg.id} | ${msg.getType()} | ${msg.name} | ${String(msg.content).substring(0, 50)} |` ) .join('\n') ) return {} } // const checkpointer = PostgresSaver.fromConnString(databaseUrl); // await checkpointer.setup(); const checkpointer = new MemorySaver(); const graph = new StateGraph(MessagesAnnotation.spec) .addNode('node1', reactAgent) .addNode('node2', consoleLogNode) .addEdge(START, 'node1') .addEdge('node1', 'node2') .compile({ name: 'graph', checkpointer }) const threadId = v4() const runId = v4() let stream = await graph.stream( { messages: [new HumanMessage({ content: "hi i am bob!"})] }, { configurable: { thread_id: threadId, runId }, streamMode: 'messages' } ) for await (const _ of stream) { // Exhaust stream without logging } stream = await graph.stream( { messages: [new HumanMessage({ content: "what's my name?"})] }, { configurable: { thread_id: threadId, runId }, streamMode: 'messages' } ) for await (const _ of stream) { // Exhaust stream without logging } // await client.end(); // await checkpointer.end(); // await startedContainer.stop(); ``` Produces the following output: | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | b20430dc-e0aa-47dc-b175-be8147d09047 | human | undefined | hi i am bob! | | chatcmpl-BFrRLRxkeCVePPKdMhlkh4tXpZUjm | ai | react_agent | Hi Bob! How can I assist you today? | | message.id | message.getType() | message.name | message.content | |-----|------|------|----------| | b20430dc-e0aa-47dc-b175-be8147d09047 | human | undefined | hi i am bob! | | chatcmpl-BFrRLRxkeCVePPKdMhlkh4tXpZUjm | ai | undefined | Hi Bob! How can I assist you today? | | 9f95326e-393e-4ea3-b7f8-3512fbf3c850 | human | undefined | what's my name? | | chatcmpl-BFrRMB3SA8o4qeELZl7HtBPDjEMKt | ai | react_agent | Your name is Bob! How can I help you today? |
Author
Owner

@dqbd commented on GitHub (Mar 28, 2025):

Reopening until #1058 lands

@dqbd commented on GitHub (Mar 28, 2025): Reopening until #1058 lands
Author
Owner

@dqbd commented on GitHub (Mar 28, 2025):

Fixed in 0.2.61

@dqbd commented on GitHub (Mar 28, 2025): Fixed in 0.2.61
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraphjs#200