fix: 修复工具调用确认后卡死问题
根本原因:
1. 模型能力检测缺少 tools 检查 — gemma4 等模型不支持 Tool Calling 但代码允许启用
2. 流式调用无超时保护 — 模型无限思考时无法自动恢复
3. AbortController 竞态 — 确认对话框后状态同步问题
修复内容:
- 模型能力检测增加 tools 字段检查,模型栏显示 🔧 Tools 标签
- Agent Loop 流式调用增加 2 分钟单次超时保护
- 开启 Tool Calling 时自动检测模型兼容性并警告
- 设置面板开启 Tool Calling 时检查并提示不兼容模型
- 输入区域 Agent Loop 分支增加模型支持检测和用户提示
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
import { state, KEYS } from '../state/state.js';
|
||||
import { fileToBase64, fileToText, detectLanguage, truncate, escapeHtml, formatSize } from '../utils/utils.js';
|
||||
import { getSelectedModel, isThinkEnabled, isVisionAvailable } from './model-bar.js';
|
||||
import { getSelectedModel, isThinkEnabled, isVisionAvailable, isToolCallingSupported } from './model-bar.js';
|
||||
import {
|
||||
renderMessages, appendAssistantPlaceholder, appendSystemMessage,
|
||||
updateLastAssistantMessage, clearMessages, safeMarkdown, enableAutoScroll
|
||||
@@ -260,6 +260,10 @@ export async function sendMessage(): Promise<void> {
|
||||
// ── Tool Calling Agent Loop 分支 ──
|
||||
const toolCallingEnabled = state.get<boolean>('toolCallingEnabled', false);
|
||||
if (toolCallingEnabled) {
|
||||
// 检查模型是否支持 Tool Calling
|
||||
if (!isToolCallingSupported()) {
|
||||
appendSystemMessage('⚠️ 当前模型未声明支持 Tool Calling,Agent Loop 可能不稳定。建议使用 Qwen3、Llama 3.1+ 或 Mistral 等支持工具调用的模型。');
|
||||
}
|
||||
await sendMessageWithAgentLoop(text, currentSession, model);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,21 +6,23 @@ import { state, KEYS } from '../state/state.js';
|
||||
import { formatSize } from '../utils/utils.js';
|
||||
import { OllamaAPI } from '../api/ollama.js';
|
||||
import { ChatDB } from '../db/chat-db.js';
|
||||
import type { OllamaModelDetail } from '../types.js';
|
||||
import type { OllamaModelDetail, ModelCaps } from '../types.js';
|
||||
|
||||
let modelSelectEl: HTMLSelectElement;
|
||||
let badgeThinkEl: HTMLElement;
|
||||
let badgeVisionEl: HTMLElement;
|
||||
let badgeToolsEl: HTMLElement;
|
||||
let thinkCheckbox: HTMLInputElement;
|
||||
let thinkWrap: HTMLElement;
|
||||
let btnAttachImg: HTMLButtonElement;
|
||||
|
||||
const modelCapabilityCache = new Map<string, { think: boolean; vision: boolean; completion: boolean }>();
|
||||
const modelCapabilityCache = new Map<string, ModelCaps>();
|
||||
|
||||
export function initModelBar(): void {
|
||||
modelSelectEl = document.querySelector('#modelSelect')!;
|
||||
badgeThinkEl = document.querySelector('#badgeThink')!;
|
||||
badgeVisionEl = document.querySelector('#badgeVision')!;
|
||||
badgeToolsEl = document.querySelector('#badgeTools')!;
|
||||
thinkCheckbox = document.querySelector('#toggleThink')!;
|
||||
thinkWrap = document.querySelector('#thinkToggleWrap')!;
|
||||
btnAttachImg = document.querySelector('#btnAttachImg')!;
|
||||
@@ -38,6 +40,8 @@ export function initModelBar(): void {
|
||||
setVisionAvailable(false);
|
||||
badgeThinkEl.style.display = 'none';
|
||||
badgeVisionEl.style.display = 'none';
|
||||
badgeToolsEl.style.display = 'none';
|
||||
state.set('modelSupportsTools', false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -99,6 +103,7 @@ async function filterEmbedModels(models: Array<{ name: string }>): Promise<void>
|
||||
modelCapabilityCache.set(m.name, {
|
||||
think: caps.includes('thinking'),
|
||||
vision: caps.includes('vision'),
|
||||
tools: caps.includes('tools'),
|
||||
completion: isCompletion,
|
||||
});
|
||||
if (!isCompletion) {
|
||||
@@ -132,23 +137,26 @@ async function checkModelCapability(modelName: string): Promise<void> {
|
||||
const caps = {
|
||||
think: capabilities.includes('thinking'),
|
||||
vision: capabilities.includes('vision'),
|
||||
tools: capabilities.includes('tools'),
|
||||
completion: capabilities.includes('completion'),
|
||||
};
|
||||
modelCapabilityCache.set(modelName, caps);
|
||||
applyCapability(modelName, caps);
|
||||
} catch (err) {
|
||||
console.warn('[ModelBar] 获取模型能力失败:', err);
|
||||
const fallback = { think: false, vision: false, completion: true };
|
||||
const fallback = { think: false, vision: false, tools: false, completion: true };
|
||||
modelCapabilityCache.set(modelName, fallback);
|
||||
applyCapability(modelName, fallback);
|
||||
}
|
||||
}
|
||||
|
||||
function applyCapability(_modelName: string, caps: { think: boolean; vision: boolean }): void {
|
||||
function applyCapability(_modelName: string, caps: { think: boolean; vision: boolean; tools: boolean }): void {
|
||||
setThinkAvailable(caps.think);
|
||||
setVisionAvailable(caps.vision);
|
||||
badgeThinkEl.style.display = caps.think ? '' : 'none';
|
||||
badgeVisionEl.style.display = caps.vision ? '' : 'none';
|
||||
badgeToolsEl.style.display = caps.tools ? '' : 'none';
|
||||
state.set('modelSupportsTools', caps.tools);
|
||||
}
|
||||
|
||||
function setThinkAvailable(available: boolean): void {
|
||||
@@ -193,3 +201,11 @@ export function setSelectedModel(modelName: string): void {
|
||||
export function isThinkEnabled(): boolean {
|
||||
return thinkCheckbox.checked && !thinkCheckbox.disabled;
|
||||
}
|
||||
|
||||
/** 检查当前选中的模型是否支持 Tool Calling */
|
||||
export function isToolCallingSupported(): boolean {
|
||||
const model = modelSelectEl?.value;
|
||||
if (!model) return false;
|
||||
const caps = modelCapabilityCache.get(model);
|
||||
return caps?.tools === true;
|
||||
}
|
||||
|
||||
@@ -113,7 +113,16 @@ export function initSettingsModal(): void {
|
||||
const db = state.get<ChatDB | null>(KEYS.DB);
|
||||
state.set('toolCallingEnabled', toggleToolCalling.checked);
|
||||
if (db) await db.saveSetting('toolCallingEnabled', toggleToolCalling.checked);
|
||||
showToast(toggleToolCalling.checked ? '工具调用已开启' : '工具调用已关闭', 'info');
|
||||
if (toggleToolCalling.checked) {
|
||||
const modelSupportsTools = state.get<boolean>('modelSupportsTools', false);
|
||||
if (!modelSupportsTools) {
|
||||
showToast('⚠️ 当前模型可能不支持 Tool Calling,建议使用 Qwen3、Llama 3.1+ 等模型', 'warning', 5000);
|
||||
} else {
|
||||
showToast('工具调用已开启', 'info');
|
||||
}
|
||||
} else {
|
||||
showToast('工具调用已关闭', 'info');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user