mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-07-28 10:32:27 +08:00
Yeah, this kinda looks okay for tool execution
This commit is contained in:
parent
c9bb0fb219
commit
f252f53e82
@ -70,7 +70,7 @@ export async function setupStreamingResponse(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[${responseId}] LLM Stream message received via CustomEvent: session=${sessionId}, content=${!!message.content}, contentLength=${message.content?.length || 0}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${!!message.done}`);
|
console.log(`[${responseId}] LLM Stream message received via CustomEvent: session=${sessionId}, content=${!!message.content}, contentLength=${message.content?.length || 0}, thinking=${!!message.thinking}, toolExecution=${!!message.toolExecution}, done=${!!message.done}, type=${message.type || 'llm-stream'}`);
|
||||||
|
|
||||||
// Mark first message received
|
// Mark first message received
|
||||||
if (!receivedAnyMessage) {
|
if (!receivedAnyMessage) {
|
||||||
@ -84,6 +84,43 @@ export async function setupStreamingResponse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle specific message types
|
||||||
|
if (message.type === 'tool_execution_start') {
|
||||||
|
onThinkingUpdate('Executing tools...');
|
||||||
|
// Also trigger tool execution UI with a specific format
|
||||||
|
onToolExecution({
|
||||||
|
action: 'start',
|
||||||
|
tool: 'tools',
|
||||||
|
result: 'Executing tools...'
|
||||||
|
});
|
||||||
|
return; // Skip accumulating content from this message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === 'tool_result' && message.toolExecution) {
|
||||||
|
onToolExecution(message.toolExecution);
|
||||||
|
return; // Skip accumulating content from this message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === 'tool_execution_error' && message.toolExecution) {
|
||||||
|
onToolExecution({
|
||||||
|
...message.toolExecution,
|
||||||
|
action: 'error',
|
||||||
|
error: message.toolExecution.error || 'Unknown error during tool execution'
|
||||||
|
});
|
||||||
|
return; // Skip accumulating content from this message
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.type === 'tool_completion_processing') {
|
||||||
|
onThinkingUpdate('Generating response with tool results...');
|
||||||
|
// Also trigger tool execution UI with a specific format
|
||||||
|
onToolExecution({
|
||||||
|
action: 'generating',
|
||||||
|
tool: 'tools',
|
||||||
|
result: 'Generating response with tool results...'
|
||||||
|
});
|
||||||
|
return; // Skip accumulating content from this message
|
||||||
|
}
|
||||||
|
|
||||||
// Handle content updates
|
// Handle content updates
|
||||||
if (message.content) {
|
if (message.content) {
|
||||||
receivedAnyContent = true;
|
receivedAnyContent = true;
|
||||||
@ -111,10 +148,13 @@ export async function setupStreamingResponse(
|
|||||||
}, 30000);
|
}, 30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tool execution updates
|
// Handle tool execution updates (legacy format and standard format with llm-stream type)
|
||||||
if (message.toolExecution) {
|
if (message.toolExecution) {
|
||||||
console.log(`[${responseId}] Received tool execution update: action=${message.toolExecution.action || 'unknown'}`);
|
// Only process if we haven't already handled this message via specific message types
|
||||||
onToolExecution(message.toolExecution);
|
if (message.type === 'llm-stream' || !message.type) {
|
||||||
|
console.log(`[${responseId}] Received tool execution update: action=${message.toolExecution.action || 'unknown'}`);
|
||||||
|
onToolExecution(message.toolExecution);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle thinking state updates
|
// Handle thinking state updates
|
||||||
|
@ -7,10 +7,10 @@ import appContext from "../../components/app_context.js";
|
|||||||
import server from "../../services/server.js";
|
import server from "../../services/server.js";
|
||||||
import libraryLoader from "../../services/library_loader.js";
|
import libraryLoader from "../../services/library_loader.js";
|
||||||
|
|
||||||
import { TPL, addMessageToChat, showSources, hideSources, showLoadingIndicator, hideLoadingIndicator, renderToolStepsHtml } from "./ui.js";
|
import { TPL, addMessageToChat, showSources, hideSources, showLoadingIndicator, hideLoadingIndicator } from "./ui.js";
|
||||||
import { formatMarkdown } from "./utils.js";
|
import { formatMarkdown } from "./utils.js";
|
||||||
import { createChatSession, checkSessionExists, setupStreamingResponse, getDirectResponse } from "./communication.js";
|
import { createChatSession, checkSessionExists, setupStreamingResponse, getDirectResponse } from "./communication.js";
|
||||||
import { extractToolExecutionSteps, extractFinalResponse, extractInChatToolSteps } from "./message_processor.js";
|
import { extractInChatToolSteps } from "./message_processor.js";
|
||||||
import { validateEmbeddingProviders } from "./validation.js";
|
import { validateEmbeddingProviders } from "./validation.js";
|
||||||
import type { MessageData, ToolExecutionStep, ChatData } from "./types.js";
|
import type { MessageData, ToolExecutionStep, ChatData } from "./types.js";
|
||||||
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
|
import { applySyntaxHighlight } from "../../services/syntax_highlight.js";
|
||||||
@ -245,7 +245,7 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-execution-chat-steps">
|
<div class="tool-execution-chat-steps">
|
||||||
${renderToolStepsHtml(steps)}
|
${this.renderToolStepsHtml(steps)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -261,6 +261,52 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render HTML for tool execution steps
|
||||||
|
*/
|
||||||
|
private renderToolStepsHtml(steps: ToolExecutionStep[]): string {
|
||||||
|
if (!steps || steps.length === 0) return '';
|
||||||
|
|
||||||
|
return steps.map(step => {
|
||||||
|
let icon = 'bx-info-circle';
|
||||||
|
let className = 'info';
|
||||||
|
let content = '';
|
||||||
|
|
||||||
|
if (step.type === 'executing') {
|
||||||
|
icon = 'bx-code-block';
|
||||||
|
className = 'executing';
|
||||||
|
content = `<div>${step.content || 'Executing tools...'}</div>`;
|
||||||
|
} else if (step.type === 'result') {
|
||||||
|
icon = 'bx-terminal';
|
||||||
|
className = 'result';
|
||||||
|
content = `
|
||||||
|
<div>Tool: <strong>${step.name || 'unknown'}</strong></div>
|
||||||
|
<div class="mt-1 ps-3">${step.content || ''}</div>
|
||||||
|
`;
|
||||||
|
} else if (step.type === 'error') {
|
||||||
|
icon = 'bx-error-circle';
|
||||||
|
className = 'error';
|
||||||
|
content = `
|
||||||
|
<div>Tool: <strong>${step.name || 'unknown'}</strong></div>
|
||||||
|
<div class="mt-1 ps-3 text-danger">${step.content || 'Error occurred'}</div>
|
||||||
|
`;
|
||||||
|
} else if (step.type === 'generating') {
|
||||||
|
icon = 'bx-message-dots';
|
||||||
|
className = 'generating';
|
||||||
|
content = `<div>${step.content || 'Generating response...'}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="tool-step ${className} p-2 mb-2 rounded">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bx ${icon} me-2"></i>
|
||||||
|
${content}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
async refresh() {
|
async refresh() {
|
||||||
if (!this.isVisible()) {
|
if (!this.isVisible()) {
|
||||||
return;
|
return;
|
||||||
@ -493,10 +539,10 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the UI with streaming content as it arrives
|
* Update the UI with streaming content
|
||||||
*/
|
*/
|
||||||
private updateStreamingUI(assistantResponse: string) {
|
private updateStreamingUI(assistantResponse: string) {
|
||||||
const logId = `ui-update-${Date.now()}`;
|
const logId = `LlmChatPanel-${Date.now()}`;
|
||||||
console.log(`[${logId}] Updating UI with response text: ${assistantResponse.length} chars`);
|
console.log(`[${logId}] Updating UI with response text: ${assistantResponse.length} chars`);
|
||||||
|
|
||||||
if (!this.noteContextChatMessages) {
|
if (!this.noteContextChatMessages) {
|
||||||
@ -504,70 +550,19 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the tool execution steps and final response
|
// With our new structured message approach, we don't need to extract tool steps from
|
||||||
const toolSteps = extractToolExecutionSteps(assistantResponse);
|
// the assistantResponse anymore, as tool execution is handled separately via dedicated messages
|
||||||
const finalResponseText = extractFinalResponse(assistantResponse);
|
|
||||||
|
|
||||||
// Find existing assistant message or create one if needed
|
// Find existing assistant message or create one if needed
|
||||||
let assistantElement = this.noteContextChatMessages.querySelector('.assistant-message:last-child .message-content');
|
let assistantElement = this.noteContextChatMessages.querySelector('.assistant-message:last-child .message-content');
|
||||||
|
|
||||||
// First, check if we need to add the tool execution steps to the chat flow
|
// Now update or create the assistant message with the response
|
||||||
if (toolSteps.length > 0) {
|
if (assistantResponse) {
|
||||||
// Look for an existing tool execution element in the chat flow
|
|
||||||
let toolExecutionElement = this.noteContextChatMessages.querySelector('.chat-tool-execution');
|
|
||||||
|
|
||||||
if (!toolExecutionElement) {
|
|
||||||
// Create a new tool execution element in the chat flow
|
|
||||||
// Place it right before the assistant message if it exists, or at the end of chat
|
|
||||||
toolExecutionElement = document.createElement('div');
|
|
||||||
toolExecutionElement.className = 'chat-tool-execution mb-3';
|
|
||||||
|
|
||||||
// If there's an assistant message, insert before it
|
|
||||||
const assistantMessage = this.noteContextChatMessages.querySelector('.assistant-message:last-child');
|
|
||||||
if (assistantMessage) {
|
|
||||||
this.noteContextChatMessages.insertBefore(toolExecutionElement, assistantMessage);
|
|
||||||
} else {
|
|
||||||
// Otherwise append to the end
|
|
||||||
this.noteContextChatMessages.appendChild(toolExecutionElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the tool execution content
|
|
||||||
toolExecutionElement.innerHTML = `
|
|
||||||
<div class="tool-execution-container p-2 rounded mb-2">
|
|
||||||
<div class="tool-execution-header d-flex align-items-center justify-content-between mb-2">
|
|
||||||
<div>
|
|
||||||
<i class="bx bx-code-block text-primary me-2"></i>
|
|
||||||
<span class="fw-bold">Tool Execution</span>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="btn btn-sm btn-link p-0 text-muted tool-execution-chat-clear" title="Clear tool execution history">
|
|
||||||
<i class="bx bx-x"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="tool-execution-chat-steps">
|
|
||||||
${renderToolStepsHtml(toolSteps)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Add event listener for the clear button
|
|
||||||
const clearButton = toolExecutionElement.querySelector('.tool-execution-chat-clear');
|
|
||||||
if (clearButton) {
|
|
||||||
clearButton.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
toolExecutionElement?.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now update or create the assistant message with the final response
|
|
||||||
if (finalResponseText) {
|
|
||||||
if (assistantElement) {
|
if (assistantElement) {
|
||||||
console.log(`[${logId}] Found existing assistant message element, updating with final response`);
|
console.log(`[${logId}] Found existing assistant message element, updating with response`);
|
||||||
try {
|
try {
|
||||||
// Format the final response with markdown
|
// Format the response with markdown
|
||||||
const formattedResponse = formatMarkdown(finalResponseText);
|
const formattedResponse = formatMarkdown(assistantResponse);
|
||||||
|
|
||||||
// Update the content
|
// Update the content
|
||||||
assistantElement.innerHTML = formattedResponse || '';
|
assistantElement.innerHTML = formattedResponse || '';
|
||||||
@ -575,12 +570,12 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
// Apply syntax highlighting to any code blocks in the updated content
|
// Apply syntax highlighting to any code blocks in the updated content
|
||||||
applySyntaxHighlight($(assistantElement as HTMLElement));
|
applySyntaxHighlight($(assistantElement as HTMLElement));
|
||||||
|
|
||||||
console.log(`[${logId}] Successfully updated existing element with final response`);
|
console.log(`[${logId}] Successfully updated existing element with response`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[${logId}] Error updating existing element:`, err);
|
console.error(`[${logId}] Error updating existing element:`, err);
|
||||||
// Fallback to text content if HTML update fails
|
// Fallback to text content if HTML update fails
|
||||||
try {
|
try {
|
||||||
assistantElement.textContent = finalResponseText;
|
assistantElement.textContent = assistantResponse;
|
||||||
console.log(`[${logId}] Fallback to text content successful`);
|
console.log(`[${logId}] Fallback to text content successful`);
|
||||||
} catch (fallbackErr) {
|
} catch (fallbackErr) {
|
||||||
console.error(`[${logId}] Even fallback update failed:`, fallbackErr);
|
console.error(`[${logId}] Even fallback update failed:`, fallbackErr);
|
||||||
@ -589,7 +584,7 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`[${logId}] No existing assistant message element found, creating new one`);
|
console.log(`[${logId}] No existing assistant message element found, creating new one`);
|
||||||
// Create a new message in the chat
|
// Create a new message in the chat
|
||||||
this.addMessageToChat('assistant', finalResponseText);
|
this.addMessageToChat('assistant', assistantResponse);
|
||||||
console.log(`[${logId}] Successfully added new assistant message`);
|
console.log(`[${logId}] Successfully added new assistant message`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -683,7 +678,9 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
if (!stepsContainer) return;
|
if (!stepsContainer) return;
|
||||||
|
|
||||||
// Process based on action type
|
// Process based on action type
|
||||||
if (toolExecutionData.action === 'start') {
|
const action = toolExecutionData.action || '';
|
||||||
|
|
||||||
|
if (action === 'start' || action === 'executing') {
|
||||||
// Tool execution started
|
// Tool execution started
|
||||||
const step = document.createElement('div');
|
const step = document.createElement('div');
|
||||||
step.className = 'tool-step executing p-2 mb-2 rounded';
|
step.className = 'tool-step executing p-2 mb-2 rounded';
|
||||||
@ -692,21 +689,47 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
<i class="bx bx-code-block me-2"></i>
|
<i class="bx bx-code-block me-2"></i>
|
||||||
<span>Executing tool: <strong>${toolExecutionData.tool || 'unknown'}</strong></span>
|
<span>Executing tool: <strong>${toolExecutionData.tool || 'unknown'}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
|
${toolExecutionData.args ? `
|
||||||
<div class="tool-args mt-1 ps-3">
|
<div class="tool-args mt-1 ps-3">
|
||||||
<code>Args: ${JSON.stringify(toolExecutionData.args || {}, null, 2)}</code>
|
<code>Args: ${JSON.stringify(toolExecutionData.args || {}, null, 2)}</code>
|
||||||
</div>
|
</div>` : ''}
|
||||||
`;
|
`;
|
||||||
stepsContainer.appendChild(step);
|
stepsContainer.appendChild(step);
|
||||||
}
|
}
|
||||||
else if (toolExecutionData.action === 'result') {
|
else if (action === 'result' || action === 'complete') {
|
||||||
// Tool execution completed with results
|
// Tool execution completed with results
|
||||||
const step = document.createElement('div');
|
const step = document.createElement('div');
|
||||||
step.className = 'tool-step result p-2 mb-2 rounded';
|
step.className = 'tool-step result p-2 mb-2 rounded';
|
||||||
|
|
||||||
let resultDisplay = '';
|
let resultDisplay = '';
|
||||||
|
|
||||||
// Format the result based on type
|
// Special handling for search_notes tool which has a specific structure
|
||||||
if (typeof toolExecutionData.result === 'object') {
|
if (toolExecutionData.tool === 'search_notes' &&
|
||||||
|
typeof toolExecutionData.result === 'object' &&
|
||||||
|
toolExecutionData.result.results) {
|
||||||
|
|
||||||
|
const results = toolExecutionData.result.results;
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
resultDisplay = `<div class="text-muted">No notes found matching the search criteria.</div>`;
|
||||||
|
} else {
|
||||||
|
resultDisplay = `
|
||||||
|
<div class="search-results">
|
||||||
|
<div class="mb-2">Found ${results.length} notes:</div>
|
||||||
|
<ul class="list-unstyled ps-1">
|
||||||
|
${results.map((note: any) => `
|
||||||
|
<li class="mb-1">
|
||||||
|
<a href="#" class="note-link" data-note-id="${note.noteId}">${note.title}</a>
|
||||||
|
${note.similarity < 1 ? `<span class="text-muted small ms-1">(similarity: ${(note.similarity * 100).toFixed(0)}%)</span>` : ''}
|
||||||
|
</li>
|
||||||
|
`).join('')}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Format the result based on type for other tools
|
||||||
|
else if (typeof toolExecutionData.result === 'object') {
|
||||||
// For objects, format as pretty JSON
|
// For objects, format as pretty JSON
|
||||||
resultDisplay = `<pre class="mb-0"><code>${JSON.stringify(toolExecutionData.result, null, 2)}</code></pre>`;
|
resultDisplay = `<pre class="mb-0"><code>${JSON.stringify(toolExecutionData.result, null, 2)}</code></pre>`;
|
||||||
} else {
|
} else {
|
||||||
@ -724,8 +747,23 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
stepsContainer.appendChild(step);
|
stepsContainer.appendChild(step);
|
||||||
|
|
||||||
|
// Add event listeners for note links if this is a search_notes result
|
||||||
|
if (toolExecutionData.tool === 'search_notes') {
|
||||||
|
const noteLinks = step.querySelectorAll('.note-link');
|
||||||
|
noteLinks.forEach(link => {
|
||||||
|
link.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const noteId = (e.currentTarget as HTMLElement).getAttribute('data-note-id');
|
||||||
|
if (noteId) {
|
||||||
|
// Open the note in a new tab but don't switch to it
|
||||||
|
appContext.tabManager.openTabWithNoteWithHoisting(noteId, { activate: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (toolExecutionData.action === 'error') {
|
else if (action === 'error') {
|
||||||
// Tool execution failed
|
// Tool execution failed
|
||||||
const step = document.createElement('div');
|
const step = document.createElement('div');
|
||||||
step.className = 'tool-step error p-2 mb-2 rounded';
|
step.className = 'tool-step error p-2 mb-2 rounded';
|
||||||
@ -740,6 +778,18 @@ export default class LlmChatPanel extends BasicWidget {
|
|||||||
`;
|
`;
|
||||||
stepsContainer.appendChild(step);
|
stepsContainer.appendChild(step);
|
||||||
}
|
}
|
||||||
|
else if (action === 'generating') {
|
||||||
|
// Generating final response with tool results
|
||||||
|
const step = document.createElement('div');
|
||||||
|
step.className = 'tool-step generating p-2 mb-2 rounded';
|
||||||
|
step.innerHTML = `
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bx bx-message-dots me-2"></i>
|
||||||
|
<span>Generating response with tool results...</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
stepsContainer.appendChild(step);
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the loading indicator is shown during tool execution
|
// Make sure the loading indicator is shown during tool execution
|
||||||
this.loadingIndicator.style.display = 'flex';
|
this.loadingIndicator.style.display = 'flex';
|
||||||
|
@ -3,66 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
import type { ToolExecutionStep } from "./types.js";
|
import type { ToolExecutionStep } from "./types.js";
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract tool execution steps from the response
|
|
||||||
*/
|
|
||||||
export function extractToolExecutionSteps(content: string): ToolExecutionStep[] {
|
|
||||||
if (!content) return [];
|
|
||||||
|
|
||||||
const steps: ToolExecutionStep[] = [];
|
|
||||||
|
|
||||||
// Check for executing tools marker
|
|
||||||
if (content.includes('[Executing tools...]')) {
|
|
||||||
steps.push({
|
|
||||||
type: 'executing',
|
|
||||||
content: 'Executing tools...'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract tool results with regex
|
|
||||||
const toolResultRegex = /\[Tool: ([^\]]+)\]([\s\S]*?)(?=\[|$)/g;
|
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = toolResultRegex.exec(content)) !== null) {
|
|
||||||
const toolName = match[1];
|
|
||||||
const toolContent = match[2].trim();
|
|
||||||
|
|
||||||
steps.push({
|
|
||||||
type: toolContent.includes('Error:') ? 'error' : 'result',
|
|
||||||
name: toolName,
|
|
||||||
content: toolContent
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for generating response marker
|
|
||||||
if (content.includes('[Generating response with tool results...]')) {
|
|
||||||
steps.push({
|
|
||||||
type: 'generating',
|
|
||||||
content: 'Generating response with tool results...'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the final response without tool execution steps
|
|
||||||
*/
|
|
||||||
export function extractFinalResponse(content: string): string {
|
|
||||||
if (!content) return '';
|
|
||||||
|
|
||||||
// Remove all tool execution markers and their content
|
|
||||||
let finalResponse = content
|
|
||||||
.replace(/\[Executing tools\.\.\.\]\n*/g, '')
|
|
||||||
.replace(/\[Tool: [^\]]+\][\s\S]*?(?=\[|$)/g, '')
|
|
||||||
.replace(/\[Generating response with tool results\.\.\.\]\n*/g, '');
|
|
||||||
|
|
||||||
// Trim any extra whitespace
|
|
||||||
finalResponse = finalResponse.trim();
|
|
||||||
|
|
||||||
return finalResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract tool execution steps from the DOM that are within the chat flow
|
* Extract tool execution steps from the DOM that are within the chat flow
|
||||||
*/
|
*/
|
||||||
|
@ -345,9 +345,10 @@ export class ChatPipeline {
|
|||||||
// If streaming was enabled, send an update to the user
|
// If streaming was enabled, send an update to the user
|
||||||
if (isStreaming && streamCallback) {
|
if (isStreaming && streamCallback) {
|
||||||
streamingPaused = true;
|
streamingPaused = true;
|
||||||
// IMPORTANT: Don't send done:true here, as it causes the client to stop processing messages
|
// Send a dedicated message with a specific type for tool execution
|
||||||
// Instead, send a marker message that indicates tools will be executed
|
streamCallback('', false, {
|
||||||
streamCallback('\n\n[Executing tools...]\n\n', false);
|
type: 'tool_execution_start'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
while (toolCallIterations < maxToolCallIterations) {
|
while (toolCallIterations < maxToolCallIterations) {
|
||||||
@ -406,6 +407,7 @@ export class ChatPipeline {
|
|||||||
|
|
||||||
// Send the structured tool result directly so the client has the raw data
|
// Send the structured tool result directly so the client has the raw data
|
||||||
streamCallback('', false, {
|
streamCallback('', false, {
|
||||||
|
type: 'tool_result',
|
||||||
toolExecution: {
|
toolExecution: {
|
||||||
action: 'result',
|
action: 'result',
|
||||||
tool: toolName,
|
tool: toolName,
|
||||||
@ -414,15 +416,21 @@ export class ChatPipeline {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Still send the formatted text for backwards compatibility
|
// No longer need to send formatted text version
|
||||||
// This will be phased out once the client is updated
|
// The client should use the structured data instead
|
||||||
const formattedToolResult = `[Tool: ${toolName || 'unknown'}]\n${msg.content}\n\n`;
|
|
||||||
streamCallback(formattedToolResult, false);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error(`Error sending structured tool result: ${err}`);
|
log.error(`Error sending structured tool result: ${err}`);
|
||||||
// Fall back to the old format if there's an error
|
// Use structured format here too instead of falling back to text format
|
||||||
const formattedToolResult = `[Tool: ${toolName || 'unknown'}]\n${msg.content}\n\n`;
|
streamCallback('', false, {
|
||||||
streamCallback(formattedToolResult, false);
|
type: 'tool_result',
|
||||||
|
toolExecution: {
|
||||||
|
action: 'result',
|
||||||
|
tool: toolName || 'unknown',
|
||||||
|
toolCallId: msg.tool_call_id,
|
||||||
|
result: msg.content,
|
||||||
|
error: String(err)
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -437,7 +445,9 @@ export class ChatPipeline {
|
|||||||
|
|
||||||
// If streaming, show progress to the user
|
// If streaming, show progress to the user
|
||||||
if (isStreaming && streamCallback) {
|
if (isStreaming && streamCallback) {
|
||||||
streamCallback('[Generating response with tool results...]\n\n', false);
|
streamCallback('', false, {
|
||||||
|
type: 'tool_completion_processing'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract tool execution status information for Ollama feedback
|
// Extract tool execution status information for Ollama feedback
|
||||||
@ -513,7 +523,13 @@ export class ChatPipeline {
|
|||||||
|
|
||||||
// If streaming, show error to the user
|
// If streaming, show error to the user
|
||||||
if (isStreaming && streamCallback) {
|
if (isStreaming && streamCallback) {
|
||||||
streamCallback(`[Tool execution error: ${error.message || 'unknown error'}]\n\n`, false);
|
streamCallback('', false, {
|
||||||
|
type: 'tool_execution_error',
|
||||||
|
toolExecution: {
|
||||||
|
action: 'error',
|
||||||
|
error: error.message || 'unknown error'
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Ollama, create tool execution status with the error
|
// For Ollama, create tool execution status with the error
|
||||||
|
@ -6,14 +6,15 @@ import type { Message, ChatCompletionOptions, ChatResponse, StreamChunk } from "
|
|||||||
* Interface for WebSocket LLM streaming messages
|
* Interface for WebSocket LLM streaming messages
|
||||||
*/
|
*/
|
||||||
interface LLMStreamMessage {
|
interface LLMStreamMessage {
|
||||||
type: 'llm-stream';
|
type: 'llm-stream' | 'tool_execution_start' | 'tool_result' | 'tool_execution_error' | 'tool_completion_processing';
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
thinking?: string;
|
thinking?: string;
|
||||||
toolExecution?: {
|
toolExecution?: {
|
||||||
action?: string;
|
action?: string;
|
||||||
tool?: string;
|
tool?: string;
|
||||||
result?: string;
|
toolCallId?: string;
|
||||||
|
result?: string | Record<string, any>;
|
||||||
error?: string;
|
error?: string;
|
||||||
args?: Record<string, unknown>;
|
args?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
@ -64,7 +64,8 @@ interface Message {
|
|||||||
toolExecution?: {
|
toolExecution?: {
|
||||||
action?: string;
|
action?: string;
|
||||||
tool?: string;
|
tool?: string;
|
||||||
result?: string;
|
toolCallId?: string;
|
||||||
|
result?: string | Record<string, any>;
|
||||||
error?: string;
|
error?: string;
|
||||||
args?: Record<string, unknown>;
|
args?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user