ai icon indicating copy to clipboard operation
ai copied to clipboard

`message.id` changes when new `part` is added

Open barretthafner opened this issue 9 months ago • 3 comments

Description

Version Information

  • Previous version: ai: 4.1.11
  • Current version: @ai-sdk/react: 1.1.23

Components affected:

  • useChat hook from @ai-sdk/react

Description

  • While attempting to upgrade the ai package from 4.1.11 to 4.1.54, I noticed the behavior changed referenced in this issue.
  • I began to migrate to @ai-sdk/react: 1.1.23, and using the message.parts array to replicate the behavior I was getting from ai: 4.1.11 following this example.
  • After the migration was complete I noticed that the <Message> component was re-rendering every time a new part was added to the message (however not when an individual part).
  • After a day and a half of head scratching and questioning my understanding of React, I realized that I was using the message.id as the key prop of my <Message> component (similar to this) and the message.id value was changing whenever a new part was added.
  • I believe this to be a bug, because I was not experiencing message.id changes in ai: 4.1.11 when a new toolInvocation was added to the message.

Current Behavior

  • message.id changes when a new value is added to the message.parts array.

Expected Behavior

  • message.id does not change once it has been added.

Impact

  • Following this example the message re-renders every time a new value is added to the message.parts array.

barretthafner avatar Mar 21 '25 18:03 barretthafner

Also saw this - looks like the experimental_generateMessageId: () => uuidv4() does not work either

jgalbsss avatar Apr 14 '25 20:04 jgalbsss

Also encountering this with ai@^4.3.2. Here's a screenshot of the messages being rendered (as the next part streams in). @lgrammel any idea what's going on here?

Some context on my setup:

  • Using both generateId() in useChat() and experimental_generateMessageId() in streamText()
  • streamText() is wrapped in createDataStreamResponse()
Image

raymondhechen avatar Apr 17 '25 04:04 raymondhechen

Workaround:

<Message key={message.role === 'user' ? message.id : index} />

zbeyens avatar Apr 27 '25 07:04 zbeyens

I'm running into a problem with this bug when trying to do message persistence combined with human-in-the-loop client operations. When the client continues, it submits the message with a different ID than the one that was originally persisted. This complicates persistence if the "same" message gets a new id across round trips.

zhm avatar May 16 '25 20:05 zhm

this patch seems to do what I want, but I don't know what other behavior it might break. The idea is to only assign the message id once per message, rather than every step-start part.

diff --git a/packages/ui-utils/src/process-chat-response.ts b/packages/ui-utils/src/process-chat-response.ts
index fd0196145..d0d706c1f 100644
--- a/packages/ui-utils/src/process-chat-response.ts
+++ b/packages/ui-utils/src/process-chat-response.ts
@@ -365,7 +365,7 @@ export async function processChatResponse({
     },
     onStartStepPart(value) {
       // keep message id stable when we are updating an existing message:
-      if (!replaceLastMessage) {
+      if (!replaceLastMessage && !message.parts.find((part) => part.type === "step-start")) {
         message.id = value.messageId;
       }

zhm avatar May 17 '25 03:05 zhm

This is also a problem in @ai-sdk/vue. It is quite a big problem for us because we store the messages in the backend too and have interactive messages that store data based on the message id. And since this bug leads to frontend and backend having inconsistent message IDs, it breaks it.

felixgabler avatar May 18 '25 09:05 felixgabler

this issue has been fixed in ai sdk 5, which send a single message id from the server in the 'start' part.

lgrammel avatar May 18 '25 09:05 lgrammel

Hey @lgrammel any suggestions regarding what to do while ai sdk 5 is in alpha? I'm having similar pbs than @zhm and @felixgabler. Using ai sdk in production, for a client, btw.

borisghidaglia avatar May 21 '25 17:05 borisghidaglia

Hello @lgrammel, I'm also encountering this error in production. I assume upgrading to the v5 alpha version is not recommended for production environments.

If a more stable release of v5 is still several weeks away, would you consider adding a fix for this issue to the v4 version, for example, the one proposed in #6267?

akselleirv avatar Jun 03 '25 17:06 akselleirv

this is not trivial to fix in ai sdk 4. ai sdk 5 is now in beta

lgrammel avatar Jun 25 '25 15:06 lgrammel

Has anyone found a solid solution for this in ai sdk 4? Or is migrating to ai sdk 5 the only real option?

fmarcheski avatar Jul 21 '25 06:07 fmarcheski

@fmarcheski You can resolve this issue by using experimental_transform with a custom TransformStream instance. This approach works with version [email protected]:

const persistentMessageId = (): StreamTextTransform<any> => {
  let initMessageId: string | null = null;

  return () => {
    return new TransformStream({
      transform(chunk, controller) {
        if (chunk.type === 'step-start') {
          if (!initMessageId) {
            initMessageId = chunk.messageId;
          }

          return controller.enqueue({
            ...chunk,
            messageId: initMessageId,
          });
        }

        return controller.enqueue(chunk);
      },
    });
  };
};

const result = streamText({
  ...
  experimental_transform: persistentMessageId(),
  ...
});

cc @lgrammel

KarovInal avatar Aug 08 '25 23:08 KarovInal