chore: 版本号 0.14.0 → 0.14.1

fix: 去掉流式空闲30s超时(大模型本地推理响应慢易误中止)
fix: await filterEmbedModels 确保启动时模型能力徽章正常显示
fix: 剩余上下文改用 estimateTokens 估算而非 Ollama eval_count(KV Cache 导致虚高)
This commit is contained in:
thzxx
2026-06-28 13:25:59 +08:00
parent 2a9d8606ba
commit b5a8a793e2
8 changed files with 24 additions and 39 deletions
+3 -19
View File
@@ -499,7 +499,7 @@ export interface AgentCallbacks {
onToolCallStart: (call: ToolCall) => void;
onToolCallResult: (name: string, result: ToolResult, call: ToolCall) => void;
onToolCallError: (name: string, error: string, call: ToolCall) => void;
onDone: (finalContent: string, toolRecords?: ToolCallRecord[], stats?: { eval_count?: number; prompt_eval_count?: number; total_duration?: number }) => void;
onDone: (finalContent: string, toolRecords?: ToolCallRecord[], stats?: { eval_count?: number; prompt_eval_count?: number; total_duration?: number; ctx_tokens?: number }) => void;
onConfirmTool: (call: ToolCall) => Promise<boolean>;
onNewIteration?: (toolCalls?: ToolCall[]) => void;
/** Plan Mode: 计划已生成,等待用户确认 */
@@ -1341,23 +1341,10 @@ async function handleThinking(
const abortController = registerAbortController();
// ── P1-5: 流式超时分级 ──
// ── 流式超时保护 ──
const STREAM_TIMEOUT_MS = state.get<number>('streamTimeout', 300_000); // 总超时 300s
const IDLE_TIMEOUT_MS = 30_000; // 空闲超时 30s(无新 token
let totalTimer: ReturnType<typeof setTimeout> | null = null;
let idleTimer: ReturnType<typeof setTimeout> | null = null;
let lastContentTime = Date.now();
const resetIdleTimer = (): void => {
lastContentTime = Date.now();
if (idleTimer) clearTimeout(idleTimer);
idleTimer = setTimeout(() => {
const idleSec = Math.round((Date.now() - lastContentTime) / 1000);
logWarn(`流式空闲超时 (${idleSec}s 无新 token),中止本轮`);
abortController.abort();
}, IDLE_TIMEOUT_MS);
};
if (STREAM_TIMEOUT_MS > 0) {
totalTimer = setTimeout(() => {
@@ -1365,7 +1352,6 @@ async function handleThinking(
abortController.abort();
}, STREAM_TIMEOUT_MS);
}
resetIdleTimer(); // 启动空闲超时
let progressTimer: ReturnType<typeof setInterval> | null = null;
@@ -1414,7 +1400,6 @@ async function handleThinking(
if (chunk.message?.content) {
ctx.content += chunk.message.content;
callbacks.onContent(ctx.content);
resetIdleTimer(); // P1-5: 收到内容 token → 重置空闲超时
}
if (chunk.eval_count) { ctx.loopEvalCount = chunk.eval_count; }
if (chunk.prompt_eval_count) { ctx.loopPromptEvalCount = chunk.prompt_eval_count; }
@@ -1481,14 +1466,12 @@ async function handleThinking(
ctx.loopInferenceNs = 0;
if (totalTimer) { clearTimeout(totalTimer); totalTimer = null; }
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
if (progressTimer) clearInterval(progressTimer as unknown as number);
transition(ctx, S.PARSING);
} catch (err) {
if (totalTimer) { clearTimeout(totalTimer); totalTimer = null; }
if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
if (progressTimer) clearInterval(progressTimer as unknown as number);
if (abortController.signal.aborted) {
logInfo('流式调用已中止');
@@ -2079,6 +2062,7 @@ function makeStats(ctx: LoopContext) {
eval_count: ctx.totalEvalCount || undefined,
prompt_eval_count: ctx.totalPromptEvalCount || undefined,
total_duration: ctx.totalInferenceNs || undefined,
ctx_tokens: estimateTokens(ctx.messages.map(m => m.content || '').join('')), // 实际上下文占用
};
}