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:
Phantom 2025-08-05 10:00:28 +08:00 committed by GitHub
parent 028884ded6
commit 4f0638ac4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 12 deletions

View File

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

View File

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