mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-18 04:20:57 +08:00
feat: AI模型错误时显式报错
This commit is contained in:
@@ -37,7 +37,13 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
|
||||
constructor(apiKey: string, model?: string, baseUrl?: string, disableThinking?: boolean) {
|
||||
this.apiKey = apiKey || 'sk-no-key-required' // 本地服务可能不需要 API Key
|
||||
this.baseUrl = baseUrl || DEFAULT_BASE_URL
|
||||
// 智能处理 baseUrl:如果用户已经包含 /chat/completions,则去掉它
|
||||
let processedBaseUrl = baseUrl || DEFAULT_BASE_URL
|
||||
processedBaseUrl = processedBaseUrl.replace(/\/+$/, '') // 去掉尾部斜杠
|
||||
if (processedBaseUrl.endsWith('/chat/completions')) {
|
||||
processedBaseUrl = processedBaseUrl.slice(0, -'/chat/completions'.length)
|
||||
}
|
||||
this.baseUrl = processedBaseUrl
|
||||
this.model = model || 'llama3.2'
|
||||
this.disableThinking = disableThinking ?? true // 默认禁用思考模式
|
||||
}
|
||||
@@ -72,7 +78,12 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
msg.tool_call_id = m.tool_call_id
|
||||
}
|
||||
if (m.role === 'assistant' && m.tool_calls) {
|
||||
msg.tool_calls = m.tool_calls
|
||||
// 确保 thoughtSignature 被传递(Gemini 3+ 通过 OpenAI 兼容 API 需要)
|
||||
msg.tool_calls = m.tool_calls.map((tc) => ({
|
||||
...tc,
|
||||
// 如果没有签名,使用虚拟签名(用于 Vertex AI/Gemini 后端)
|
||||
thought_signature: tc.thoughtSignature || 'context_engineering_is_the_way_to_go',
|
||||
}))
|
||||
}
|
||||
return msg
|
||||
}),
|
||||
@@ -129,6 +140,8 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
name: (tc.function as Record<string, unknown>)?.name as string,
|
||||
arguments: (tc.function as Record<string, unknown>)?.arguments as string,
|
||||
},
|
||||
// 提取 thoughtSignature(Gemini 3+ 通过 OpenAI 兼容 API 可能返回此字段)
|
||||
thoughtSignature: (tc.thought_signature || tc.thoughtSignature) as string | undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -155,7 +168,12 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
msg.tool_call_id = m.tool_call_id
|
||||
}
|
||||
if (m.role === 'assistant' && m.tool_calls) {
|
||||
msg.tool_calls = m.tool_calls
|
||||
// 确保 thoughtSignature 被传递(Gemini 3+ 通过 OpenAI 兼容 API 需要)
|
||||
msg.tool_calls = m.tool_calls.map((tc) => ({
|
||||
...tc,
|
||||
// 如果没有签名,使用虚拟签名(用于 Vertex AI/Gemini 后端)
|
||||
thought_signature: tc.thoughtSignature || 'context_engineering_is_the_way_to_go',
|
||||
}))
|
||||
}
|
||||
return msg
|
||||
}),
|
||||
@@ -234,6 +252,8 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
name: tc.name,
|
||||
arguments: tc.arguments,
|
||||
},
|
||||
// 传递 thoughtSignature(如果存在)
|
||||
thoughtSignature: tc.thoughtSignature,
|
||||
}))
|
||||
yield { content: '', isFinished: true, finishReason: 'tool_calls', tool_calls: toolCalls }
|
||||
} else {
|
||||
@@ -247,11 +267,11 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
const delta = parsed.choices?.[0]?.delta
|
||||
const finishReason = parsed.choices?.[0]?.finish_reason
|
||||
|
||||
// 调试:如果有 delta 但没有 content,记录其他可能的内容字段
|
||||
// 调试:如果有 delta 但没有 content,记录其他可能的内容字段(只写日志文件,不输出控制台)
|
||||
if (delta && !delta.content && !delta.tool_calls && !finishReason) {
|
||||
const deltaKeys = Object.keys(delta)
|
||||
if (deltaKeys.length > 0 && !deltaKeys.every((k) => ['role', 'name', 'audio_content'].includes(k))) {
|
||||
aiLogger.warn('OpenAI-Compatible', '检测到未处理的 delta 字段', { deltaKeys, delta })
|
||||
aiLogger.debug('OpenAI-Compatible', '检测到未处理的 delta 字段', { deltaKeys, delta })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,11 +292,18 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
if (tc.function?.arguments) {
|
||||
existing.arguments += tc.function.arguments
|
||||
}
|
||||
// 更新 thoughtSignature(如果存在)
|
||||
if (tc.thought_signature || tc.thoughtSignature) {
|
||||
existing.thoughtSignature = tc.thought_signature || tc.thoughtSignature
|
||||
}
|
||||
} else {
|
||||
toolCallsAccumulator.set(index, {
|
||||
id: tc.id || '',
|
||||
name: tc.function?.name || '',
|
||||
arguments: tc.function?.arguments || '',
|
||||
// 提取 thoughtSignature(Gemini 3+ 通过 OpenAI 兼容 API 可能返回此字段)
|
||||
// 如果 API 不返回,使用 Gemini 文档提供的虚拟签名绕过验证
|
||||
thoughtSignature: tc.thought_signature || tc.thoughtSignature || 'context_engineering_is_the_way_to_go',
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -309,6 +336,8 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
name: tc.name,
|
||||
arguments: tc.arguments,
|
||||
},
|
||||
// 传递 thoughtSignature(如果存在)
|
||||
thoughtSignature: tc.thoughtSignature,
|
||||
}))
|
||||
yield { content: '', isFinished: true, finishReason: reason, tool_calls: toolCalls, usage }
|
||||
} else {
|
||||
@@ -340,6 +369,8 @@ export class OpenAICompatibleService implements ILLMService {
|
||||
name: tc.name,
|
||||
arguments: tc.arguments,
|
||||
},
|
||||
// 传递 thoughtSignature(如果存在)
|
||||
thoughtSignature: tc.thoughtSignature,
|
||||
}))
|
||||
yield { content: '', isFinished: true, finishReason: 'tool_calls', tool_calls: toolCalls }
|
||||
} else {
|
||||
|
||||
+17
-2
@@ -374,6 +374,7 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
* @param historyMessages 对话历史(可选,用于上下文关联)
|
||||
* @param chatType 聊天类型('group' | 'private')
|
||||
* @param promptConfig 用户自定义提示词配置(可选)
|
||||
* @param locale 语言设置(可选,默认 'zh-CN')
|
||||
*/
|
||||
ipcMain.handle(
|
||||
'agent:runStream',
|
||||
@@ -384,7 +385,8 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
context: ToolContext,
|
||||
historyMessages?: Array<{ role: 'user' | 'assistant'; content: string }>,
|
||||
chatType?: 'group' | 'private',
|
||||
promptConfig?: PromptConfig
|
||||
promptConfig?: PromptConfig,
|
||||
locale?: string
|
||||
) => {
|
||||
aiLogger.info('IPC', `收到 Agent 流式请求: ${requestId}`, {
|
||||
userMessage: userMessage.slice(0, 100),
|
||||
@@ -411,7 +413,8 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
{ abortSignal: abortController.signal },
|
||||
formattedHistory,
|
||||
chatType ?? 'group',
|
||||
promptConfig
|
||||
promptConfig,
|
||||
locale ?? 'zh-CN'
|
||||
)
|
||||
|
||||
// 异步执行,通过事件发送流式数据
|
||||
@@ -462,10 +465,22 @@ export function registerAIHandlers({ win }: IpcContext): void {
|
||||
return
|
||||
}
|
||||
aiLogger.error('IPC', `Agent 执行出错: ${requestId}`, { error: String(error) })
|
||||
// 发送错误 chunk
|
||||
win.webContents.send('agent:streamChunk', {
|
||||
requestId,
|
||||
chunk: { type: 'error', error: String(error), isFinished: true },
|
||||
})
|
||||
// 发送完成事件(带错误信息),确保前端 promise 能 resolve
|
||||
win.webContents.send('agent:complete', {
|
||||
requestId,
|
||||
result: {
|
||||
content: '',
|
||||
toolsUsed: [],
|
||||
toolRounds: 0,
|
||||
totalUsage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
||||
error: String(error),
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
// 清理请求追踪
|
||||
activeAgentRequests.delete(requestId)
|
||||
|
||||
Reference in New Issue
Block a user