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:
chenxi 2025-04-19 01:21:27 +08:00 committed by GitHub
parent fd3d9f17b8
commit 0a28df132d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 179 additions and 1 deletions

View File

@ -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'))
}

View File

@ -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(

View 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
}

View 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'
})
})
})