mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-22 19:51:54 +08:00
perf(ApiService): speed up api check (#8830)
* fix(ApiService): 加速api检查 修改createAbortPromise为泛型函数以支持不同类型 在checkApi中添加中止控制器和任务ID管理 当接收到chunk时立即中止请求并验证中止状态 * fix(ApiService): 修复API检查失败时的错误处理 在API检查失败时添加错误处理回调并统一错误消息 * fix(ApiService): 修复API检查中流错误处理逻辑 捕获流处理中的错误并正确抛出,移除冗余的成功状态检查 * fix(ApiService): 为API检查添加超时处理 为embedding模型和普通模型检查添加15秒超时处理,防止长时间无响应 使用Promise.race实现超时控制,并在超时时抛出错误或中止请求 * fix: 修复在finally块中错误移除abortController的问题 将removeAbortController从内部finally块移到外部finally块,确保在API检查完成后正确清理资源
This commit is contained in:
parent
028884ded6
commit
4f0638ac4f
@ -37,7 +37,13 @@ import {
|
||||
import { type Chunk, ChunkType } from '@renderer/types/chunk'
|
||||
import { Message } from '@renderer/types/newMessage'
|
||||
import { SdkModel } from '@renderer/types/sdk'
|
||||
import { removeSpecialCharactersForTopicName } from '@renderer/utils'
|
||||
import { removeSpecialCharactersForTopicName, uuid } from '@renderer/utils'
|
||||
import {
|
||||
abortCompletion,
|
||||
addAbortController,
|
||||
createAbortPromise,
|
||||
removeAbortController
|
||||
} from '@renderer/utils/abortController'
|
||||
import { isAbortError } from '@renderer/utils/error'
|
||||
import { extractInfoFromXML, ExtractResults } from '@renderer/utils/extract'
|
||||
import { findFileBlocks, getMainTextContent } from '@renderer/utils/messageUtils/find'
|
||||
@ -846,30 +852,66 @@ export function checkApiProvider(provider: Provider): void {
|
||||
export async function checkApi(provider: Provider, model: Model): Promise<void> {
|
||||
checkApiProvider(provider)
|
||||
|
||||
const timeout = 15000
|
||||
const controller = new AbortController()
|
||||
const abortFn = () => controller.abort()
|
||||
const taskId = uuid()
|
||||
addAbortController(taskId, abortFn)
|
||||
|
||||
const ai = new AiProvider(provider)
|
||||
|
||||
const assistant = getDefaultAssistant()
|
||||
assistant.model = model
|
||||
try {
|
||||
if (isEmbeddingModel(model)) {
|
||||
await ai.getEmbeddingDimensions(model)
|
||||
// race 超时 15s
|
||||
logger.silly("it's a embedding model")
|
||||
const timerPromise = new Promise((_, reject) => setTimeout(() => reject('Timeout'), timeout))
|
||||
await Promise.race([ai.getEmbeddingDimensions(model), timerPromise])
|
||||
} else {
|
||||
// 通过该状态判断abort原因
|
||||
let streamError: Error | undefined = undefined
|
||||
|
||||
// 15s超时
|
||||
const timer = setTimeout(() => {
|
||||
abortCompletion(taskId)
|
||||
streamError = new Error('Timeout')
|
||||
}, timeout)
|
||||
|
||||
const params: CompletionsParams = {
|
||||
callType: 'check',
|
||||
messages: 'hi',
|
||||
assistant,
|
||||
streamOutput: true,
|
||||
enableReasoning: false,
|
||||
shouldThrow: true
|
||||
onChunk: () => {
|
||||
// 接收到任意chunk都直接abort
|
||||
abortCompletion(taskId)
|
||||
},
|
||||
onError: (e) => {
|
||||
// 捕获stream error
|
||||
streamError = e
|
||||
abortCompletion(taskId)
|
||||
}
|
||||
}
|
||||
|
||||
// Try streaming check first
|
||||
const result = await ai.completions(params)
|
||||
if (!result.getText()) {
|
||||
throw new Error('No response received')
|
||||
try {
|
||||
await createAbortPromise(controller.signal, ai.completions(params))
|
||||
} catch (e: any) {
|
||||
if (isAbortError(e)) {
|
||||
if (streamError) {
|
||||
throw streamError
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
// FIXME: 这种判断方法无法严格保证错误是流式引起的
|
||||
if (error.message.includes('stream')) {
|
||||
const params: CompletionsParams = {
|
||||
callType: 'check',
|
||||
@ -878,12 +920,13 @@ export async function checkApi(provider: Provider, model: Model): Promise<void>
|
||||
streamOutput: false,
|
||||
shouldThrow: true
|
||||
}
|
||||
const result = await ai.completions(params)
|
||||
if (!result.getText()) {
|
||||
throw new Error('No response received')
|
||||
}
|
||||
// 超时判断
|
||||
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject('Timeout'), timeout))
|
||||
await Promise.race([ai.completions(params), timeoutPromise])
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
} finally {
|
||||
removeAbortController(taskId, abortFn)
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,8 +30,8 @@ export const abortCompletion = (id: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
export function createAbortPromise(signal: AbortSignal, finallyPromise: Promise<string>) {
|
||||
return new Promise<string>((_resolve, reject) => {
|
||||
export function createAbortPromise<T>(signal: AbortSignal, finallyPromise: Promise<T>) {
|
||||
return new Promise<T>((_resolve, reject) => {
|
||||
if (signal.aborted) {
|
||||
reject(new DOMException('Operation aborted', 'AbortError'))
|
||||
return
|
||||
|
||||
Loading…
Reference in New Issue
Block a user