Refactor/reasoning time (#10393)

This commit is contained in:
MyPrototypeWhat 2025-09-28 19:38:44 +08:00 committed by GitHub
parent bb0ec0a3ec
commit 06ab2822be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 49 additions and 41 deletions

View File

@ -163,14 +163,13 @@ export class AiSdkToChunkAdapter {
final.reasoningContent += chunk.text || '' final.reasoningContent += chunk.text || ''
this.onChunk({ this.onChunk({
type: ChunkType.THINKING_DELTA, type: ChunkType.THINKING_DELTA,
text: final.reasoningContent || '', text: final.reasoningContent || ''
thinking_millsec: (chunk.providerMetadata?.metadata?.thinking_millsec as number) || 0
}) })
break break
case 'reasoning-end': case 'reasoning-end':
this.onChunk({ this.onChunk({
type: ChunkType.THINKING_COMPLETE, type: ChunkType.THINKING_COMPLETE,
text: (chunk.providerMetadata?.metadata?.thinking_content as string) || final.reasoningContent text: final.reasoningContent || ''
}) })
final.reasoningContent = '' final.reasoningContent = ''
break break

View File

@ -5,7 +5,6 @@ import { getEnableDeveloperMode } from '@renderer/hooks/useSettings'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { AiSdkMiddlewareConfig } from '../middleware/AiSdkMiddlewareBuilder' import { AiSdkMiddlewareConfig } from '../middleware/AiSdkMiddlewareBuilder'
import reasoningTimePlugin from './reasoningTimePlugin'
import { searchOrchestrationPlugin } from './searchOrchestrationPlugin' import { searchOrchestrationPlugin } from './searchOrchestrationPlugin'
import { createTelemetryPlugin } from './telemetryPlugin' import { createTelemetryPlugin } from './telemetryPlugin'
@ -39,9 +38,9 @@ export function buildPlugins(
} }
// 3. 推理模型时添加推理插件 // 3. 推理模型时添加推理插件
if (middlewareConfig.enableReasoning) { // if (middlewareConfig.enableReasoning) {
plugins.push(reasoningTimePlugin) // plugins.push(reasoningTimePlugin)
} // }
// 4. 启用Prompt工具调用时添加工具插件 // 4. 启用Prompt工具调用时添加工具插件
if (middlewareConfig.isPromptToolUse) { if (middlewareConfig.isPromptToolUse) {

View File

@ -5,7 +5,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue' import { useTemporaryValue } from '@renderer/hooks/useTemporaryValue'
import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage' import { MessageBlockStatus, type ThinkingMessageBlock } from '@renderer/types/newMessage'
import { Collapse, message as antdMessage, Tooltip } from 'antd' import { Collapse, message as antdMessage, Tooltip } from 'antd'
import { memo, useCallback, useEffect, useMemo, useState } from 'react' import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
@ -105,30 +105,37 @@ const ThinkingBlock: React.FC<Props> = ({ block }) => {
const ThinkingTimeSeconds = memo( const ThinkingTimeSeconds = memo(
({ blockThinkingTime, isThinking }: { blockThinkingTime: number; isThinking: boolean }) => { ({ blockThinkingTime, isThinking }: { blockThinkingTime: number; isThinking: boolean }) => {
const { t } = useTranslation() const { t } = useTranslation()
// const [thinkingTime, setThinkingTime] = useState(blockThinkingTime || 0) const [displayTime, setDisplayTime] = useState(blockThinkingTime)
// FIXME: 这里统计的和请求处统计的有一定误差 const timer = useRef<NodeJS.Timeout | null>(null)
// useEffect(() => {
// let timer: NodeJS.Timeout | null = null
// if (isThinking) {
// timer = setInterval(() => {
// setThinkingTime((prev) => prev + 100)
// }, 100)
// } else if (timer) {
// // 立即清除计时器
// clearInterval(timer)
// timer = null
// }
// return () => { useEffect(() => {
// if (timer) { if (isThinking) {
// clearInterval(timer) if (!timer.current) {
// timer = null timer.current = setInterval(() => {
// } setDisplayTime((prev) => prev + 100)
// } }, 100)
// }, [isThinking]) }
} else {
if (timer.current) {
clearInterval(timer.current)
timer.current = null
}
setDisplayTime(blockThinkingTime)
}
const thinkingTimeSeconds = useMemo(() => (blockThinkingTime / 1000).toFixed(1), [blockThinkingTime]) return () => {
if (timer.current) {
clearInterval(timer.current)
timer.current = null
}
}
}, [isThinking, blockThinkingTime])
const thinkingTimeSeconds = useMemo(
() => ((displayTime < 1000 ? 100 : displayTime) / 1000).toFixed(1),
[displayTime]
)
return isThinking return isThinking
? t('chat.thinking', { ? t('chat.thinking', {

View File

@ -235,13 +235,12 @@ describe('ThinkingBlock', () => {
renderThinkingBlock(thinkingBlock) renderThinkingBlock(thinkingBlock)
const activeTimeText = getThinkingTimeText() const activeTimeText = getThinkingTimeText()
expect(activeTimeText).toHaveTextContent('1.0s')
expect(activeTimeText).toHaveTextContent('Thinking...') expect(activeTimeText).toHaveTextContent('Thinking...')
}) })
it('should handle extreme thinking times correctly', () => { it('should handle extreme thinking times correctly', () => {
const testCases = [ const testCases = [
{ thinking_millsec: 0, expectedTime: '0.0s' }, { thinking_millsec: 0, expectedTime: '0.1s' }, // New logic: values < 1000ms display as 0.1s
{ thinking_millsec: 86400000, expectedTime: '86400.0s' }, // 1 day { thinking_millsec: 86400000, expectedTime: '86400.0s' }, // 1 day
{ thinking_millsec: 259200000, expectedTime: '259200.0s' } // 3 days { thinking_millsec: 259200000, expectedTime: '259200.0s' } // 3 days
] ]

View File

@ -15,7 +15,7 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
// 内部维护的状态 // 内部维护的状态
let thinkingBlockId: string | null = null let thinkingBlockId: string | null = null
let _thinking_millsec = 0 let thinking_millsec_now: number = 0
return { return {
onThinkingStart: async () => { onThinkingStart: async () => {
@ -24,27 +24,27 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
type: MessageBlockType.THINKING, type: MessageBlockType.THINKING,
content: '', content: '',
status: MessageBlockStatus.STREAMING, status: MessageBlockStatus.STREAMING,
thinking_millsec: _thinking_millsec thinking_millsec: 0
} }
thinkingBlockId = blockManager.initialPlaceholderBlockId! thinkingBlockId = blockManager.initialPlaceholderBlockId!
blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true) blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true)
} else if (!thinkingBlockId) { } else if (!thinkingBlockId) {
const newBlock = createThinkingBlock(assistantMsgId, '', { const newBlock = createThinkingBlock(assistantMsgId, '', {
status: MessageBlockStatus.STREAMING, status: MessageBlockStatus.STREAMING,
thinking_millsec: _thinking_millsec thinking_millsec: 0
}) })
thinkingBlockId = newBlock.id thinkingBlockId = newBlock.id
await blockManager.handleBlockTransition(newBlock, MessageBlockType.THINKING) await blockManager.handleBlockTransition(newBlock, MessageBlockType.THINKING)
} }
thinking_millsec_now = performance.now()
}, },
onThinkingChunk: async (text: string, thinking_millsec?: number) => { onThinkingChunk: async (text: string) => {
_thinking_millsec = thinking_millsec || 0
if (thinkingBlockId) { if (thinkingBlockId) {
const blockChanges: Partial<MessageBlock> = { const blockChanges: Partial<MessageBlock> = {
content: text, content: text,
status: MessageBlockStatus.STREAMING, status: MessageBlockStatus.STREAMING
thinking_millsec: _thinking_millsec // thinking_millsec: performance.now() - thinking_millsec_now
} }
blockManager.smartBlockUpdate(thinkingBlockId, blockChanges, MessageBlockType.THINKING) blockManager.smartBlockUpdate(thinkingBlockId, blockChanges, MessageBlockType.THINKING)
} }
@ -52,14 +52,15 @@ export const createThinkingCallbacks = (deps: ThinkingCallbacksDependencies) =>
onThinkingComplete: (finalText: string) => { onThinkingComplete: (finalText: string) => {
if (thinkingBlockId) { if (thinkingBlockId) {
const now = performance.now()
const changes: Partial<MessageBlock> = { const changes: Partial<MessageBlock> = {
content: finalText, content: finalText,
status: MessageBlockStatus.SUCCESS, status: MessageBlockStatus.SUCCESS,
thinking_millsec: _thinking_millsec thinking_millsec: now - thinking_millsec_now
} }
blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true) blockManager.smartBlockUpdate(thinkingBlockId, changes, MessageBlockType.THINKING, true)
thinkingBlockId = null thinkingBlockId = null
_thinking_millsec = 0 thinking_millsec_now = 0
} else { } else {
logger.warn( logger.warn(
`[onThinkingComplete] Received thinking.complete but last block was not THINKING (was ${blockManager.lastBlockType}) or lastBlockId is null.` `[onThinkingComplete] Received thinking.complete but last block was not THINKING (was ${blockManager.lastBlockType}) or lastBlockId is null.`

View File

@ -425,7 +425,10 @@ describe('streamCallback Integration Tests', () => {
expect(thinkingBlock).toBeDefined() expect(thinkingBlock).toBeDefined()
expect(thinkingBlock?.content).toBe('Final thoughts') expect(thinkingBlock?.content).toBe('Final thoughts')
expect(thinkingBlock?.status).toBe(MessageBlockStatus.SUCCESS) expect(thinkingBlock?.status).toBe(MessageBlockStatus.SUCCESS)
expect((thinkingBlock as any)?.thinking_millsec).toBe(3000) // thinking_millsec 现在是本地计算的,只验证它存在且是一个合理的数字
expect((thinkingBlock as any)?.thinking_millsec).toBeDefined()
expect(typeof (thinkingBlock as any)?.thinking_millsec).toBe('number')
expect((thinkingBlock as any)?.thinking_millsec).toBeGreaterThanOrEqual(0)
}) })
it('should handle tool call flow', async () => { it('should handle tool call flow', async () => {