Skip to content

Commit 1fd2302

Browse files
chore: tweak migration script to handle message order (#969)
1 parent 1a948af commit 1fd2302

File tree

2 files changed

+74
-19
lines changed

2 files changed

+74
-19
lines changed

lib/db/helpers/01-core-to-parts.ts

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import postgres from 'postgres';
33
import {
44
chat,
55
message,
6+
type MessageDeprecated,
67
messageDeprecated,
78
vote,
89
voteDeprecated,
910
} from '../schema';
1011
import { drizzle } from 'drizzle-orm/postgres-js';
1112
import { inArray } from 'drizzle-orm';
12-
import { appendResponseMessages, UIMessage } from 'ai';
13+
import { appendResponseMessages, type UIMessage } from 'ai';
1314

1415
config({
1516
path: '.env.local',
@@ -22,8 +23,8 @@ if (!process.env.POSTGRES_URL) {
2223
const client = postgres(process.env.POSTGRES_URL);
2324
const db = drizzle(client);
2425

25-
const BATCH_SIZE = 50; // Process 10 chats at a time
26-
const INSERT_BATCH_SIZE = 100; // Insert 100 messages at a time
26+
const BATCH_SIZE = 100; // Process 100 chats at a time
27+
const INSERT_BATCH_SIZE = 1000; // Insert 1000 messages at a time
2728

2829
type NewMessageInsert = {
2930
id: string;
@@ -40,16 +41,66 @@ type NewVoteInsert = {
4041
isUpvoted: boolean;
4142
};
4243

43-
async function createNewTable() {
44+
interface MessageDeprecatedContentPart {
45+
type: string;
46+
content: unknown;
47+
}
48+
49+
function getMessageRank(message: MessageDeprecated): number {
50+
if (
51+
message.role === 'assistant' &&
52+
(message.content as MessageDeprecatedContentPart[]).some(
53+
(contentPart) => contentPart.type === 'tool-call',
54+
)
55+
) {
56+
return 0;
57+
}
58+
59+
if (
60+
message.role === 'tool' &&
61+
(message.content as MessageDeprecatedContentPart[]).some(
62+
(contentPart) => contentPart.type === 'tool-result',
63+
)
64+
) {
65+
return 1;
66+
}
67+
68+
if (message.role === 'assistant') {
69+
return 2;
70+
}
71+
72+
return 3;
73+
}
74+
75+
function dedupeParts<T extends { type: string; [k: string]: any }>(
76+
parts: T[],
77+
): T[] {
78+
const seen = new Set<string>();
79+
return parts.filter((p) => {
80+
const key = `${p.type}|${JSON.stringify(p.content ?? p)}`;
81+
if (seen.has(key)) return false;
82+
seen.add(key);
83+
return true;
84+
});
85+
}
86+
87+
function sanitizeParts<T extends { type: string; [k: string]: any }>(
88+
parts: T[],
89+
): T[] {
90+
return parts.filter(
91+
(part) => !(part.type === 'reasoning' && part.reasoning === 'undefined'),
92+
);
93+
}
94+
95+
async function migrateMessages() {
4496
const chats = await db.select().from(chat);
97+
4598
let processedCount = 0;
4699

47-
// Process chats in batches
48100
for (let i = 0; i < chats.length; i += BATCH_SIZE) {
49101
const chatBatch = chats.slice(i, i + BATCH_SIZE);
50102
const chatIds = chatBatch.map((chat) => chat.id);
51103

52-
// Fetch all messages and votes for the current batch of chats in bulk
53104
const allMessages = await db
54105
.select()
55106
.from(messageDeprecated)
@@ -60,20 +111,25 @@ async function createNewTable() {
60111
.from(voteDeprecated)
61112
.where(inArray(voteDeprecated.chatId, chatIds));
62113

63-
// Prepare batches for insertion
64114
const newMessagesToInsert: NewMessageInsert[] = [];
65115
const newVotesToInsert: NewVoteInsert[] = [];
66116

67-
// Process each chat in the batch
68117
for (const chat of chatBatch) {
69118
processedCount++;
70119
console.info(`Processed ${processedCount}/${chats.length} chats`);
71120

72-
// Filter messages and votes for this specific chat
73-
const messages = allMessages.filter((msg) => msg.chatId === chat.id);
121+
const messages = allMessages
122+
.filter((message) => message.chatId === chat.id)
123+
.sort((a, b) => {
124+
const differenceInTime =
125+
new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
126+
if (differenceInTime !== 0) return differenceInTime;
127+
128+
return getMessageRank(a) - getMessageRank(b);
129+
});
130+
74131
const votes = allVotes.filter((v) => v.chatId === chat.id);
75132

76-
// Group messages into sections
77133
const messageSection: Array<UIMessage> = [];
78134
const messageSections: Array<Array<UIMessage>> = [];
79135

@@ -93,7 +149,6 @@ async function createNewTable() {
93149
messageSections.push([...messageSection]);
94150
}
95151

96-
// Process each message section
97152
for (const section of messageSections) {
98153
const [userMessage, ...assistantMessages] = section;
99154

@@ -121,10 +176,14 @@ async function createNewTable() {
121176
attachments: [],
122177
} as NewMessageInsert;
123178
} else if (message.role === 'assistant') {
179+
const cleanParts = sanitizeParts(
180+
dedupeParts(message.parts || []),
181+
);
182+
124183
return {
125184
id: message.id,
126185
chatId: chat.id,
127-
parts: message.parts || [],
186+
parts: cleanParts,
128187
role: message.role,
129188
createdAt: message.createdAt,
130189
attachments: [],
@@ -134,7 +193,6 @@ async function createNewTable() {
134193
})
135194
.filter((msg): msg is NewMessageInsert => msg !== null);
136195

137-
// Add messages to batch
138196
for (const msg of projectedUISection) {
139197
newMessagesToInsert.push(msg);
140198

@@ -155,11 +213,9 @@ async function createNewTable() {
155213
}
156214
}
157215

158-
// Batch insert messages
159216
for (let j = 0; j < newMessagesToInsert.length; j += INSERT_BATCH_SIZE) {
160217
const messageBatch = newMessagesToInsert.slice(j, j + INSERT_BATCH_SIZE);
161218
if (messageBatch.length > 0) {
162-
// Ensure all required fields are present
163219
const validMessageBatch = messageBatch.map((msg) => ({
164220
id: msg.id,
165221
chatId: msg.chatId,
@@ -173,7 +229,6 @@ async function createNewTable() {
173229
}
174230
}
175231

176-
// Batch insert votes
177232
for (let j = 0; j < newVotesToInsert.length; j += INSERT_BATCH_SIZE) {
178233
const voteBatch = newVotesToInsert.slice(j, j + INSERT_BATCH_SIZE);
179234
if (voteBatch.length > 0) {
@@ -185,7 +240,7 @@ async function createNewTable() {
185240
console.info(`Migration completed: ${processedCount} chats processed`);
186241
}
187242

188-
createNewTable()
243+
migrateMessages()
189244
.then(() => {
190245
console.info('Script completed successfully');
191246
process.exit(0);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ai-chatbot",
3-
"version": "3.0.13",
3+
"version": "3.0.14",
44
"private": true,
55
"scripts": {
66
"dev": "next dev --turbo",

0 commit comments

Comments
 (0)