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:
@@ -11837,7 +11837,7 @@ ${writePreviews.map((a) => `- ${a.description}`).join("\n")}`,
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.updateMessageById(assistantMessageId, {
|
this.updateMessageById(assistantMessageId, {
|
||||||
content: followUpContent || fullResponse,
|
content: followUpContent || fullResponse || "(No response)",
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
isThinking: false
|
isThinking: false
|
||||||
});
|
});
|
||||||
@@ -12182,39 +12182,29 @@ ${actualMessage}` : actualMessage;
|
|||||||
if (isToolCapable && toolCalls.length === 0) {
|
if (isToolCapable && toolCalls.length === 0) {
|
||||||
if (modelMentionedActions || userWantsVaultOps) {
|
if (modelMentionedActions || userWantsVaultOps) {
|
||||||
shouldFallbackToReadTools = true;
|
shouldFallbackToReadTools = true;
|
||||||
|
const priorResponse = fullResponse;
|
||||||
fullResponse = "";
|
fullResponse = "";
|
||||||
this.updateMessageById(assistantMessageId, {
|
this.updateMessageById(assistantMessageId, {
|
||||||
content: "",
|
content: "",
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
isThinking: false
|
isThinking: false
|
||||||
});
|
});
|
||||||
let attempts = 0;
|
if (modelMentionedActions) {
|
||||||
const maxAttempts = 3;
|
this.showActivityIndicator("Thinking\u2026");
|
||||||
let currentMessages = [...messagesWithMemory];
|
|
||||||
let currentResponse = "";
|
|
||||||
while (attempts < maxAttempts && toolCalls.length === 0) {
|
|
||||||
attempts++;
|
|
||||||
this.showActivityIndicator(
|
|
||||||
attempts === 1 ? "Thinking\u2026" : `Retrying (${attempts}/${maxAttempts})\u2026`
|
|
||||||
);
|
|
||||||
const nudgeMessages = [
|
const nudgeMessages = [
|
||||||
...currentMessages,
|
...messagesWithMemory,
|
||||||
...currentResponse.trim() ? [{ role: "assistant", content: currentResponse }] : [],
|
...priorResponse.trim() ? [{ role: "assistant", content: priorResponse }] : [],
|
||||||
{
|
{
|
||||||
role: "user",
|
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 {
|
try {
|
||||||
const nudgeStream = activeClient.streamChat(nudgeMessages, tools);
|
const nudgeStream = activeClient.streamChat(nudgeMessages, tools);
|
||||||
currentResponse = "";
|
let nudgeResponse = "";
|
||||||
for await (const chunk of nudgeStream) {
|
for await (const chunk of nudgeStream) {
|
||||||
if (chunk.content) {
|
if (chunk.content) nudgeResponse += chunk.content;
|
||||||
currentResponse += chunk.content;
|
if (chunk.tool_calls) toolCalls = [...toolCalls, ...chunk.tool_calls];
|
||||||
}
|
|
||||||
if (chunk.tool_calls) {
|
|
||||||
toolCalls = [...toolCalls, ...chunk.tool_calls];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
this.showActivityIndicator("Using tools\u2026");
|
this.showActivityIndicator("Using tools\u2026");
|
||||||
@@ -12222,27 +12212,23 @@ ${actualMessage}` : actualMessage;
|
|||||||
toolCalls,
|
toolCalls,
|
||||||
nudgeMessages,
|
nudgeMessages,
|
||||||
tools,
|
tools,
|
||||||
currentResponse,
|
nudgeResponse,
|
||||||
assistantMessageId
|
assistantMessageId
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
currentMessages = nudgeMessages;
|
|
||||||
}
|
}
|
||||||
if (toolCalls.length === 0) {
|
if (toolCalls.length === 0) {
|
||||||
const autoToolCalls = this.buildAutomaticReadToolCalls(actualMessage, tools);
|
const autoToolCalls = this.buildAutomaticReadToolCalls(actualMessage, tools);
|
||||||
if (autoToolCalls.length > 0) {
|
if (autoToolCalls.length > 0) {
|
||||||
this.showActivityIndicator("Using tools\u2026");
|
this.showActivityIndicator("Using tools\u2026");
|
||||||
toolCalls = autoToolCalls;
|
toolCalls = autoToolCalls;
|
||||||
fullResponse = "";
|
|
||||||
await this.processToolCalls(
|
await this.processToolCalls(
|
||||||
autoToolCalls,
|
autoToolCalls,
|
||||||
messagesWithMemory,
|
messagesWithMemory,
|
||||||
tools,
|
tools,
|
||||||
fullResponse,
|
"",
|
||||||
assistantMessageId
|
assistantMessageId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -12361,25 +12347,15 @@ ${actualMessage}` : actualMessage;
|
|||||||
"files",
|
"files",
|
||||||
"continue",
|
"continue",
|
||||||
"go ahead",
|
"go ahead",
|
||||||
"start",
|
"proceed",
|
||||||
"do it",
|
"do it",
|
||||||
"execute",
|
"execute",
|
||||||
"run",
|
"implement",
|
||||||
"proceed",
|
"apply",
|
||||||
"next",
|
|
||||||
"now",
|
|
||||||
"yes",
|
|
||||||
"ok",
|
|
||||||
"okay",
|
|
||||||
"sure",
|
|
||||||
"please",
|
|
||||||
"step",
|
|
||||||
"merge",
|
"merge",
|
||||||
"clean",
|
"clean",
|
||||||
"fix",
|
"fix",
|
||||||
"update",
|
"update"
|
||||||
"implement",
|
|
||||||
"apply"
|
|
||||||
];
|
];
|
||||||
return operationPhrases.some((phrase) => lower.includes(phrase));
|
return operationPhrases.some((phrase) => lower.includes(phrase));
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-50
@@ -1092,7 +1092,7 @@ export class ChatView extends ItemView {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
this.updateMessageById(assistantMessageId, {
|
this.updateMessageById(assistantMessageId, {
|
||||||
content: followUpContent || fullResponse,
|
content: followUpContent || fullResponse || '(No response)',
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
isThinking: false,
|
isThinking: false,
|
||||||
});
|
});
|
||||||
@@ -1520,6 +1520,7 @@ export class ChatView extends ItemView {
|
|||||||
shouldFallbackToReadTools = true;
|
shouldFallbackToReadTools = true;
|
||||||
|
|
||||||
// Suppress the model's "Let me..." text — clear it from the DOM immediately
|
// Suppress the model's "Let me..." text — clear it from the DOM immediately
|
||||||
|
const priorResponse = fullResponse;
|
||||||
fullResponse = '';
|
fullResponse = '';
|
||||||
this.updateMessageById(assistantMessageId, {
|
this.updateMessageById(assistantMessageId, {
|
||||||
content: '',
|
content: '',
|
||||||
@@ -1527,43 +1528,27 @@ export class ChatView extends ItemView {
|
|||||||
isThinking: false,
|
isThinking: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try aggressive retry loop first (hidden from user)
|
// Single nudge — only when the model actually expressed intent to act.
|
||||||
let attempts = 0;
|
// Lying to models that never mentioned action ("you said you would...") confuses
|
||||||
const maxAttempts = 3;
|
// them and wastes a full LLM round-trip without benefit.
|
||||||
let currentMessages: OllamaMessage[] = [...messagesWithMemory];
|
if (modelMentionedActions) {
|
||||||
let currentResponse = '';
|
this.showActivityIndicator('Thinking…');
|
||||||
|
|
||||||
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
|
|
||||||
const nudgeMessages: OllamaMessage[] = [
|
const nudgeMessages: OllamaMessage[] = [
|
||||||
...currentMessages,
|
...messagesWithMemory,
|
||||||
...(currentResponse.trim()
|
...(priorResponse.trim() ? [{ role: 'assistant' as const, content: priorResponse }] : []),
|
||||||
? [{ role: 'assistant' as const, content: currentResponse }]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
content:
|
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 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.',
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nudgeStream = activeClient.streamChat(nudgeMessages, tools);
|
const nudgeStream = activeClient.streamChat(nudgeMessages, tools);
|
||||||
currentResponse = '';
|
let nudgeResponse = '';
|
||||||
for await (const chunk of nudgeStream) {
|
for await (const chunk of nudgeStream) {
|
||||||
if (chunk.content) {
|
if (chunk.content) nudgeResponse += chunk.content;
|
||||||
currentResponse += chunk.content;
|
if (chunk.tool_calls) toolCalls = [...toolCalls, ...chunk.tool_calls];
|
||||||
}
|
|
||||||
if (chunk.tool_calls) {
|
|
||||||
toolCalls = [...toolCalls, ...chunk.tool_calls];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toolCalls.length > 0) {
|
if (toolCalls.length > 0) {
|
||||||
@@ -1572,31 +1557,27 @@ export class ChatView extends ItemView {
|
|||||||
toolCalls,
|
toolCalls,
|
||||||
nudgeMessages,
|
nudgeMessages,
|
||||||
tools,
|
tools,
|
||||||
currentResponse,
|
nudgeResponse,
|
||||||
assistantMessageId
|
assistantMessageId
|
||||||
);
|
);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Stream failed during retry — stop retrying and fall back to automatic tools
|
// Stream failed — fall through to auto tool calls
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (toolCalls.length === 0) {
|
||||||
const autoToolCalls = this.buildAutomaticReadToolCalls(actualMessage, tools);
|
const autoToolCalls = this.buildAutomaticReadToolCalls(actualMessage, tools);
|
||||||
if (autoToolCalls.length > 0) {
|
if (autoToolCalls.length > 0) {
|
||||||
this.showActivityIndicator('Using tools…');
|
this.showActivityIndicator('Using tools…');
|
||||||
toolCalls = autoToolCalls;
|
toolCalls = autoToolCalls;
|
||||||
fullResponse = '';
|
|
||||||
await this.processToolCalls(
|
await this.processToolCalls(
|
||||||
autoToolCalls,
|
autoToolCalls,
|
||||||
messagesWithMemory,
|
messagesWithMemory,
|
||||||
tools,
|
tools,
|
||||||
fullResponse,
|
'',
|
||||||
assistantMessageId
|
assistantMessageId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1733,25 +1714,15 @@ export class ChatView extends ItemView {
|
|||||||
'files',
|
'files',
|
||||||
'continue',
|
'continue',
|
||||||
'go ahead',
|
'go ahead',
|
||||||
'start',
|
'proceed',
|
||||||
'do it',
|
'do it',
|
||||||
'execute',
|
'execute',
|
||||||
'run',
|
'implement',
|
||||||
'proceed',
|
'apply',
|
||||||
'next',
|
|
||||||
'now',
|
|
||||||
'yes',
|
|
||||||
'ok',
|
|
||||||
'okay',
|
|
||||||
'sure',
|
|
||||||
'please',
|
|
||||||
'step',
|
|
||||||
'merge',
|
'merge',
|
||||||
'clean',
|
'clean',
|
||||||
'fix',
|
'fix',
|
||||||
'update',
|
'update',
|
||||||
'implement',
|
|
||||||
'apply',
|
|
||||||
];
|
];
|
||||||
return operationPhrases.some((phrase) => lower.includes(phrase));
|
return operationPhrases.some((phrase) => lower.includes(phrase));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user