`message.id` changes when new `part` is added
Description
Version Information
- Previous version:
ai: 4.1.11 - Current version:
@ai-sdk/react: 1.1.23
Components affected:
-
useChathook from@ai-sdk/react
Description
- While attempting to upgrade the
aipackage from4.1.11to4.1.54, I noticed the behavior changed referenced in this issue. - I began to migrate to
@ai-sdk/react: 1.1.23, and using themessage.partsarray to replicate the behavior I was getting fromai: 4.1.11following this example. - After the migration was complete I noticed that the
<Message>component was re-rendering every time a newpartwas added to themessage(however not when an individualpart). - After a day and a half of head scratching and questioning my understanding of React, I realized that I was using the
message.idas thekeyprop of my<Message>component (similar to this) and themessage.idvalue was changing whenever a newpartwas added. - I believe this to be a bug, because I was not experiencing
message.idchanges inai: 4.1.11when a newtoolInvocationwas added to themessage.
Current Behavior
-
message.idchanges when a new value is added to themessage.partsarray.
Expected Behavior
-
message.iddoes not change once it has been added.
Impact
- Following this example the
messagere-renders every time a new value is added to themessage.partsarray.
Also saw this - looks like the experimental_generateMessageId: () => uuidv4() does not work either
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()inuseChat()andexperimental_generateMessageId()instreamText() -
streamText()is wrapped increateDataStreamResponse()
Workaround:
<Message key={message.role === 'user' ? message.id : index} />
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.
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;
}
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.
this issue has been fixed in ai sdk 5, which send a single message id from the server in the 'start' part.
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.
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?
this is not trivial to fix in ai sdk 4. ai sdk 5 is now in beta
Has anyone found a solid solution for this in ai sdk 4? Or is migrating to ai sdk 5 the only real option?
@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