Timestamps and strings in concatenated streaming responses are added

I am streaming message chunks, that I want to concatenate once I receive them. The AIMessageChunk.concat() method makes weird assumptions on the structure of my messages and subsequently merges them incorrectly.

Examples:

Strings

When I have a string typed field under additional_kwargs, e.g. “role": "assistant" in each chunk, the .concat() method merges two chunks to "role”: “assistantassistant”.

Timestamps

When I have a number typed field under additional_kwargs, e.g. “created": 1734524005 in each chunk, the .concat() method adds two chunks to "created”: 3469048010.

This is quite unexpected. Understandibly, it is difficult to make assumption on the fields, but I think there should be a way to influence this, right?

Hi,

concat() deep-merges content but shallow-concats/adds lc_kwargs/additional_kwargs scalars (strings/numbers) langchainjs/libs/langchain-core/src/messages/ai.ts at main · langchain-ai/langchainjs · GitHub (Line 393)

Here is an example of manually safe merging.

import { AIMessageChunk } from "@langchain/core/messages";

function safeConcat(chunks) {
  if (!chunks.length) throw new Error("No chunks");
  
  // Clone FIRST chunk (preserves ID, role, timestamp)
  const merged = structuredClone(chunks[0]);
  
  // Append content only
  for (let i = 1; i < chunks.length; i++) {
    merged.content += chunks[i].content;
  }
  
  // Preserve FIRST chunk metadata (no summing/concat)
  merged.lc_kwargs = { ...chunks[0].lc_kwargs };
  merged.responseMetadata = { ...chunks[0].responseMetadata };
  
  // Optional: Override timestamp with LAST
  if (chunks[chunks.length - 1].lc_kwargs.created) {
    merged.lc_kwargs.created = chunks[chunks.length - 1].lc_kwargs.created;
  }
  
  return merged;
}

let chunks = [];
for await (const chunk of await model.stream(messages)) {
  if (chunk instanceof AIMessageChunk) chunks.push(chunk);
}

const fullMessage = safeConcat(chunks);

Let me know if this solves your issue.