fix(deepseek-reasoner) doesn't support successive user or assistant messages (#5051)
fix(deepseek-reasoner) does not support successive user or assistant messages
This commit is contained in:
parent
fd3d9f17b8
commit
0a28df132d
@ -243,6 +243,7 @@ export class WindowService {
|
||||
private loadMainWindowContent(mainWindow: BrowserWindow) {
|
||||
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
|
||||
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
|
||||
// mainWindow.webContents.openDevTools()
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
filterEmptyMessages,
|
||||
filterUserRoleStartMessages
|
||||
} from '@renderer/services/MessagesService'
|
||||
import { processReqMessages } from '@renderer/services/ModelMessageService'
|
||||
import store from '@renderer/store'
|
||||
import {
|
||||
Assistant,
|
||||
@ -504,7 +505,10 @@ export default class OpenAIProvider extends BaseProvider {
|
||||
|
||||
await processToolUses(content, idx)
|
||||
}
|
||||
// console.log('reqMessages', reqMessages)
|
||||
|
||||
// console.log('[before] reqMessages', reqMessages)
|
||||
reqMessages = processReqMessages(model, reqMessages)
|
||||
// console.log('[after] reqMessages', reqMessages)
|
||||
const stream = await this.sdk.chat.completions
|
||||
// @ts-ignore key is not typed
|
||||
.create(
|
||||
|
||||
49
src/renderer/src/services/ModelMessageService.ts
Normal file
49
src/renderer/src/services/ModelMessageService.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import { Model } from '@renderer/types'
|
||||
import { ChatCompletionMessageParam } from 'openai/resources'
|
||||
|
||||
export function processReqMessages(
|
||||
model: Model,
|
||||
reqMessages: ChatCompletionMessageParam[]
|
||||
): ChatCompletionMessageParam[] {
|
||||
if (!needStrictlyInterleaveUserAndAssistantMessages(model)) {
|
||||
return reqMessages
|
||||
}
|
||||
|
||||
return mergeSameRoleMessages(reqMessages)
|
||||
}
|
||||
|
||||
function needStrictlyInterleaveUserAndAssistantMessages(model: Model) {
|
||||
return model.id === 'deepseek-reasoner'
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge successive messages with the same role
|
||||
*/
|
||||
function mergeSameRoleMessages(messages: ChatCompletionMessageParam[]): ChatCompletionMessageParam[] {
|
||||
const split = '\n'
|
||||
const processedMessages: ChatCompletionMessageParam[] = []
|
||||
let currentGroup: ChatCompletionMessageParam[] = []
|
||||
|
||||
for (const message of messages) {
|
||||
if (currentGroup.length === 0 || currentGroup[0].role === message.role) {
|
||||
currentGroup.push(message)
|
||||
} else {
|
||||
// merge the current group and add to processed messages
|
||||
processedMessages.push({
|
||||
...currentGroup[0],
|
||||
content: currentGroup.map((m) => m.content).join(split)
|
||||
})
|
||||
currentGroup = [message]
|
||||
}
|
||||
}
|
||||
|
||||
// process the last group
|
||||
if (currentGroup.length > 0) {
|
||||
processedMessages.push({
|
||||
...currentGroup[0],
|
||||
content: currentGroup.map((m) => m.content).join(split)
|
||||
})
|
||||
}
|
||||
|
||||
return processedMessages
|
||||
}
|
||||
124
src/renderer/src/services/__tests__/ModelMessageService.test.ts
Normal file
124
src/renderer/src/services/__tests__/ModelMessageService.test.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import assert from 'node:assert'
|
||||
import { test } from 'node:test'
|
||||
|
||||
import { ChatCompletionMessageParam } from 'openai/resources'
|
||||
|
||||
const { processReqMessages } = require('../ModelMessageService')
|
||||
|
||||
test('ModelMessageService', async (t) => {
|
||||
const mockMessages: ChatCompletionMessageParam[] = [
|
||||
{ role: 'user', content: 'First question' },
|
||||
{ role: 'user', content: 'Additional context' },
|
||||
{ role: 'assistant', content: 'First answer' },
|
||||
{ role: 'assistant', content: 'Additional information' },
|
||||
{ role: 'user', content: 'Second question' },
|
||||
{ role: 'assistant', content: 'Second answer' }
|
||||
]
|
||||
|
||||
await t.test('should merge successive messages with same role for deepseek-reasoner model', () => {
|
||||
const model = { id: 'deepseek-reasoner' }
|
||||
const result = processReqMessages(model, mockMessages)
|
||||
|
||||
assert.strictEqual(result.length, 4)
|
||||
assert.deepStrictEqual(result[0], {
|
||||
role: 'user',
|
||||
content: 'First question\nAdditional context'
|
||||
})
|
||||
assert.deepStrictEqual(result[1], {
|
||||
role: 'assistant',
|
||||
content: 'First answer\nAdditional information'
|
||||
})
|
||||
assert.deepStrictEqual(result[2], {
|
||||
role: 'user',
|
||||
content: 'Second question'
|
||||
})
|
||||
assert.deepStrictEqual(result[3], {
|
||||
role: 'assistant',
|
||||
content: 'Second answer'
|
||||
})
|
||||
})
|
||||
|
||||
await t.test('should not merge messages for other models', () => {
|
||||
const model = { id: 'gpt-4' }
|
||||
const result = processReqMessages(model, mockMessages)
|
||||
|
||||
assert.strictEqual(result.length, mockMessages.length)
|
||||
assert.deepStrictEqual(result, mockMessages)
|
||||
})
|
||||
|
||||
await t.test('should handle empty messages array', () => {
|
||||
const model = { id: 'deepseek-reasoner' }
|
||||
const result = processReqMessages(model, [])
|
||||
|
||||
assert.strictEqual(result.length, 0)
|
||||
assert.deepStrictEqual(result, [])
|
||||
})
|
||||
|
||||
await t.test('should handle single message', () => {
|
||||
const model = { id: 'deepseek-reasoner' }
|
||||
const singleMessage = [{ role: 'user', content: 'Single message' }]
|
||||
const result = processReqMessages(model, singleMessage)
|
||||
|
||||
assert.strictEqual(result.length, 1)
|
||||
assert.deepStrictEqual(result, singleMessage)
|
||||
})
|
||||
|
||||
await t.test('should preserve other message properties when merging', () => {
|
||||
const model = { id: 'deepseek-reasoner' }
|
||||
const messagesWithProps = [
|
||||
{
|
||||
role: 'user',
|
||||
content: 'First message',
|
||||
name: 'user1',
|
||||
function_call: { name: 'test', arguments: '{}' }
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: 'Second message',
|
||||
name: 'user1'
|
||||
}
|
||||
] as ChatCompletionMessageParam[]
|
||||
|
||||
const result = processReqMessages(model, messagesWithProps)
|
||||
|
||||
assert.strictEqual(result.length, 1)
|
||||
assert.deepStrictEqual(result[0], {
|
||||
role: 'user',
|
||||
content: 'First message\nSecond message',
|
||||
name: 'user1',
|
||||
function_call: { name: 'test', arguments: '{}' }
|
||||
})
|
||||
})
|
||||
|
||||
await t.test('should handle alternating roles correctly', () => {
|
||||
const model = { id: 'deepseek-reasoner' }
|
||||
const alternatingMessages = [
|
||||
{ role: 'user', content: 'Q1' },
|
||||
{ role: 'assistant', content: 'A1' },
|
||||
{ role: 'user', content: 'Q2' },
|
||||
{ role: 'assistant', content: 'A2' }
|
||||
] as ChatCompletionMessageParam[]
|
||||
|
||||
const result = processReqMessages(model, alternatingMessages)
|
||||
|
||||
assert.strictEqual(result.length, 4)
|
||||
assert.deepStrictEqual(result, alternatingMessages)
|
||||
})
|
||||
|
||||
await t.test('should handle messages with empty content', () => {
|
||||
const model = { id: 'deepseek-reasoner' }
|
||||
const messagesWithEmpty = [
|
||||
{ role: 'user', content: 'Q1' },
|
||||
{ role: 'user', content: '' },
|
||||
{ role: 'user', content: 'Q2' }
|
||||
] as ChatCompletionMessageParam[]
|
||||
|
||||
const result = processReqMessages(model, messagesWithEmpty)
|
||||
|
||||
assert.strictEqual(result.length, 1)
|
||||
assert.deepStrictEqual(result[0], {
|
||||
role: 'user',
|
||||
content: 'Q1\n\nQ2'
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user