fix: user message usage (#5657)

* fix: user message usage estimation

* fix: estimate usage on resending edited user message

* refactor: renaming

* refactor: renaming
This commit is contained in:
one 2025-05-09 14:17:28 +08:00 committed by GitHub
parent 759fa78a30
commit a2d8beafcf
4 changed files with 70 additions and 23 deletions

View File

@ -1,5 +1,6 @@
import { createSelector } from '@reduxjs/toolkit'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { estimateUserPromptUsage } from '@renderer/services/TokenService'
import store, { type RootState, useAppDispatch, useAppSelector } from '@renderer/store'
import { messageBlocksSelectors, updateOneBlock } from '@renderer/store/messageBlock'
import { newMessagesActions, selectMessagesForTopic } from '@renderer/store/newMessage'
@ -20,6 +21,7 @@ import type { Assistant, Model, Topic } from '@renderer/types'
import type { Message, MessageBlock } from '@renderer/types/newMessage'
import { MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
import { abortCompletion } from '@renderer/utils/abortController'
import { findFileBlocks } from '@renderer/utils/messageUtils/find'
import { useCallback } from 'react'
const findMainTextBlockId = (message: Message): string | undefined => {
@ -128,6 +130,12 @@ export function useMessageOperations(topic: Topic) {
return
}
const files = findFileBlocks(message).map((block) => block.file)
const usage = await estimateUserPromptUsage({ content: editedContent, files })
await dispatch(updateMessageAndBlocksThunk(topic.id, { id: message.id, usage }, []))
await dispatch(resendUserMessageWithEditThunk(topic.id, message, mainTextBlockId, editedContent, assistant))
},
[dispatch, topic.id]

View File

@ -22,7 +22,7 @@ import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import FileManager from '@renderer/services/FileManager'
import { checkRateLimit, getUserMessage } from '@renderer/services/MessagesService'
import { getModelUniqId } from '@renderer/services/ModelService'
import { estimateMessageUsage, estimateTextTokens as estimateTxtTokens } from '@renderer/services/TokenService'
import { estimateTextTokens as estimateTxtTokens, estimateUserPromptUsage } from '@renderer/services/TokenService'
import { translateText } from '@renderer/services/TranslateService'
import WebSearchService from '@renderer/services/WebSearchService'
import { useAppDispatch } from '@renderer/store'
@ -215,7 +215,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
)
}
baseUserMessage.usage = await estimateMessageUsage(baseUserMessage)
baseUserMessage.usage = await estimateUserPromptUsage(baseUserMessage)
const { message, blocks } = getUserMessage(baseUserMessage)

View File

@ -6,7 +6,7 @@ import { fetchMessagesSummary } from '@renderer/services/ApiService'
import store from '@renderer/store'
import { messageBlocksSelectors, removeManyBlocks } from '@renderer/store/messageBlock'
import { selectMessagesForTopic } from '@renderer/store/newMessage'
import type { Assistant, FileType, MCPServer, Model, Topic } from '@renderer/types'
import type { Assistant, FileType, MCPServer, Model, Topic, Usage } from '@renderer/types'
import { FileTypes } from '@renderer/types'
import type { Message, MessageBlock } from '@renderer/types/newMessage'
import { AssistantMessageStatus, MessageBlockStatus, MessageBlockType } from '@renderer/types/newMessage'
@ -110,7 +110,8 @@ export function getUserMessage({
// Keep other potential params if needed by createMessage
knowledgeBaseIds,
mentions,
enabledMCPs
enabledMCPs,
usage
}: {
assistant: Assistant
topic: Topic
@ -120,6 +121,7 @@ export function getUserMessage({
knowledgeBaseIds?: string[]
mentions?: Model[]
enabledMCPs?: MCPServer[]
usage?: Usage
}): { message: Message; blocks: MessageBlock[] } {
const defaultModel = getDefaultModel()
const model = assistant.model || defaultModel
@ -163,7 +165,8 @@ export function getUserMessage({
// 移除knowledgeBaseIds
mentions,
enabledMCPs,
type
type,
usage
}
)

View File

@ -1,5 +1,5 @@
import { Assistant, FileType, FileTypes, Usage } from '@renderer/types'
import type { Message, MessageInputBaseParams } from '@renderer/types/newMessage'
import type { Message } from '@renderer/types/newMessage'
import { findFileBlocks, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
import { flatten, takeRight } from 'lodash'
import { approximateTokenSize } from 'tokenx'
@ -56,16 +56,59 @@ export function estimateImageTokens(file: FileType) {
return Math.floor(file.size / 100)
}
export async function estimateMessageUsage(message: Partial<Message>, params?: MessageInputBaseParams): Promise<Usage> {
/**
* token
*
* content files
* Message
*
* @param {Object} params -
* @param {string} [params.content] -
* @param {FileType[]} [params.files] -
* @returns {Promise<Usage>} Usage prompt_tokenscompletion_tokenstotal_tokens
*/
export async function estimateUserPromptUsage({
content,
files
}: {
content?: string
files?: FileType[]
}): Promise<Usage> {
let imageTokens = 0
let files: FileType[] = []
if (params?.files) {
files = params.files
} else {
const fileBlocks = findFileBlocks(message as Message)
files = fileBlocks.map((f) => f.file)
if (files && files.length > 0) {
const images = files.filter((f) => f.type === FileTypes.IMAGE)
if (images.length > 0) {
for (const image of images) {
imageTokens = estimateImageTokens(image) + imageTokens
}
}
}
const tokens = estimateTextTokens(content || '')
return {
prompt_tokens: tokens,
completion_tokens: tokens,
total_tokens: tokens + (imageTokens ? imageTokens - 7 : 0)
}
}
/**
* Message token
*
* message reasoningContent
* token usage
*
* @param {Partial<Message>} message - Message
* @returns {Promise<Usage>} Usage prompt_tokenscompletion_tokenstotal_tokens
*/
export async function estimateMessageUsage(message: Partial<Message>): Promise<Usage> {
const fileBlocks = findFileBlocks(message as Message)
const files = fileBlocks.map((f) => f.file)
let imageTokens = 0
if (files.length > 0) {
const images = files.filter((f) => f.type === FileTypes.IMAGE)
if (images.length > 0) {
@ -74,16 +117,9 @@ export async function estimateMessageUsage(message: Partial<Message>, params?: M
}
}
}
let content = ''
if (params?.content) {
content = params.content
} else {
content = getMainTextContent(message as Message)
}
let reasoningContent = ''
if (!params) {
reasoningContent = getThinkingContent(message as Message)
}
const content = getMainTextContent(message as Message)
const reasoningContent = getThinkingContent(message as Message)
const combinedContent = [content, reasoningContent].filter((s) => s !== undefined).join(' ')
const tokens = estimateTextTokens(combinedContent)