Simplify tool call nudging and refine action detection

Replace the 3-attempt retry loop with a single nudge when the model
explicitly mentioned taking action. Remove misleading nudges for models
that never expressed intent, which wasted round-trips and confused
responses. Also streamline the userWantsVaultOps phrase list to remove
weak triggers like "ok", "yes", and "please", and add fallback content
for empty responses.
This commit is contained in:
2026-05-21 17:45:00 +02:00
parent c2c2d51da5
commit 3d9be7dfdb
2 changed files with 37 additions and 90 deletions
+16 -40
View File
@@ -11837,7 +11837,7 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`,
);
} else {
this.updateMessageById(assistantMessageId, {
content: followUpContent || fullResponse,
content: followUpContent || fullResponse || "(No response)",
isStreaming: false,
isThinking: false
});
@@ -12182,39 +12182,29 @@ ${actualMessage}` : actualMessage;
if (isToolCapable && toolCalls.length === 0) {
if (modelMentionedActions || userWantsVaultOps) {
shouldFallbackToReadTools = true;
const priorResponse = fullResponse;
fullResponse = "";
this.updateMessageById(assistantMessageId, {
content: "",
isStreaming: false,
isThinking: false
});
let attempts = 0;
const maxAttempts = 3;
let currentMessages = [...messagesWithMemory];
let currentResponse = "";
while (attempts < maxAttempts && toolCalls.length === 0) {
attempts++;
this.showActivityIndicator(
attempts === 1 ? "Thinking\u2026" : `Retrying (${attempts}/${maxAttempts})\u2026`
);
if (modelMentionedActions) {
this.showActivityIndicator("Thinking\u2026");
const nudgeMessages = [
...currentMessages,
...currentResponse.trim() ? [{ role: "assistant", content: currentResponse }] : [],
...messagesWithMemory,
...priorResponse.trim() ? [{ role: "assistant", content: priorResponse }] : [],
{
role: "user",
content: attempts === 1 ? "You indicated you would take action but did not emit any tool_calls. Emit the required tool_calls now. Do not output explanatory text." : "You still have not emitted any tool_calls. Remember: when you need vault information, you MUST call tools immediately. Emit the tool_calls now. No text."
content: "You indicated you would take action but did not emit any tool_calls. Emit the required tool_calls now. Do not output explanatory text."
}
];
try {
const nudgeStream = activeClient.streamChat(nudgeMessages, tools);
currentResponse = "";
let nudgeResponse = "";
for await (const chunk of nudgeStream) {
if (chunk.content) {
currentResponse += chunk.content;
}
if (chunk.tool_calls) {
toolCalls = [...toolCalls, ...chunk.tool_calls];
}
if (chunk.content) nudgeResponse += chunk.content;
if (chunk.tool_calls) toolCalls = [...toolCalls, ...chunk.tool_calls];
}
if (toolCalls.length > 0) {
this.showActivityIndicator("Using tools\u2026");
@@ -12222,27 +12212,23 @@ ${actualMessage}` : actualMessage;
toolCalls,
nudgeMessages,
tools,
currentResponse,
nudgeResponse,
assistantMessageId
);
break;
}
} catch {
break;
}
currentMessages = nudgeMessages;
}
if (toolCalls.length === 0) {
const autoToolCalls = this.buildAutomaticReadToolCalls(actualMessage, tools);
if (autoToolCalls.length > 0) {
this.showActivityIndicator("Using tools\u2026");
toolCalls = autoToolCalls;
fullResponse = "";
await this.processToolCalls(
autoToolCalls,
messagesWithMemory,
tools,
fullResponse,
"",
assistantMessageId
);
}
@@ -12361,25 +12347,15 @@ ${actualMessage}` : actualMessage;
"files",
"continue",
"go ahead",
"start",
"proceed",
"do it",
"execute",
"run",
"proceed",
"next",
"now",
"yes",
"ok",
"okay",
"sure",
"please",
"step",
"implement",
"apply",
"merge",
"clean",
"fix",
"update",
"implement",
"apply"
"update"
];
return operationPhrases.some((phrase) => lower.includes(phrase));
}
+22 -51
View File
@@ -1092,7 +1092,7 @@ export class ChatView extends ItemView {
);
} else {
this.updateMessageById(assistantMessageId, {
content: followUpContent || fullResponse,
content: followUpContent || fullResponse || '(No response)',
isStreaming: false,
isThinking: false,
});
@@ -1520,6 +1520,7 @@ export class ChatView extends ItemView {
shouldFallbackToReadTools = true;
// Suppress the model's "Let me..." text — clear it from the DOM immediately
const priorResponse = fullResponse;
fullResponse = '';
this.updateMessageById(assistantMessageId, {
content: '',
@@ -1527,43 +1528,27 @@ export class ChatView extends ItemView {
isThinking: false,
});
// Try aggressive retry loop first (hidden from user)
let attempts = 0;
const maxAttempts = 3;
let currentMessages: OllamaMessage[] = [...messagesWithMemory];
let currentResponse = '';
while (attempts < maxAttempts && toolCalls.length === 0) {
attempts++;
this.showActivityIndicator(
attempts === 1 ? 'Thinking…' : `Retrying (${attempts}/${maxAttempts})…`
);
// Build nudge messages — skip empty assistant content to avoid API issues
// Single nudge — only when the model actually expressed intent to act.
// Lying to models that never mentioned action ("you said you would...") confuses
// them and wastes a full LLM round-trip without benefit.
if (modelMentionedActions) {
this.showActivityIndicator('Thinking…');
const nudgeMessages: OllamaMessage[] = [
...currentMessages,
...(currentResponse.trim()
? [{ role: 'assistant' as const, content: currentResponse }]
: []),
...messagesWithMemory,
...(priorResponse.trim() ? [{ role: 'assistant' as const, content: priorResponse }] : []),
{
role: 'user',
content:
attempts === 1
? 'You indicated you would take action but did not emit any tool_calls. Emit the required tool_calls now. Do not output explanatory text.'
: 'You still have not emitted any tool_calls. Remember: when you need vault information, you MUST call tools immediately. Emit the tool_calls now. No text.',
'You indicated you would take action but did not emit any tool_calls. Emit the required tool_calls now. Do not output explanatory text.',
},
];
try {
const nudgeStream = activeClient.streamChat(nudgeMessages, tools);
currentResponse = '';
let nudgeResponse = '';
for await (const chunk of nudgeStream) {
if (chunk.content) {
currentResponse += chunk.content;
}
if (chunk.tool_calls) {
toolCalls = [...toolCalls, ...chunk.tool_calls];
}
if (chunk.content) nudgeResponse += chunk.content;
if (chunk.tool_calls) toolCalls = [...toolCalls, ...chunk.tool_calls];
}
if (toolCalls.length > 0) {
@@ -1572,31 +1557,27 @@ export class ChatView extends ItemView {
toolCalls,
nudgeMessages,
tools,
currentResponse,
nudgeResponse,
assistantMessageId
);
break;
}
} catch {
// Stream failed during retry — stop retrying and fall back to automatic tools
break;
// Stream failed — fall through to auto tool calls
}
}
currentMessages = nudgeMessages;
}
// If retries all failed, force automatic read tools immediately
// Auto tool calls: fire immediately when the user's request implies vault operations
// and the model (with or without nudging) still hasn't called any tools.
if (toolCalls.length === 0) {
const autoToolCalls = this.buildAutomaticReadToolCalls(actualMessage, tools);
if (autoToolCalls.length > 0) {
this.showActivityIndicator('Using tools…');
toolCalls = autoToolCalls;
fullResponse = '';
await this.processToolCalls(
autoToolCalls,
messagesWithMemory,
tools,
fullResponse,
'',
assistantMessageId
);
}
@@ -1733,25 +1714,15 @@ export class ChatView extends ItemView {
'files',
'continue',
'go ahead',
'start',
'proceed',
'do it',
'execute',
'run',
'proceed',
'next',
'now',
'yes',
'ok',
'okay',
'sure',
'please',
'step',
'implement',
'apply',
'merge',
'clean',
'fix',
'update',
'implement',
'apply',
];
return operationPhrases.some((phrase) => lower.includes(phrase));
}