mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-01 20:32:19 +08:00
agent tools do something now
This commit is contained in:
parent
0d4b6a71fc
commit
90db570e30
@ -20,388 +20,392 @@ import aiServiceManager from "../ai_service_manager.js";
|
|||||||
* Represents a single reasoning step taken by the agent
|
* Represents a single reasoning step taken by the agent
|
||||||
*/
|
*/
|
||||||
export interface ThinkingStep {
|
export interface ThinkingStep {
|
||||||
id: string;
|
id: string;
|
||||||
content: string;
|
content: string;
|
||||||
type: 'observation' | 'hypothesis' | 'question' | 'evidence' | 'conclusion';
|
type: 'observation' | 'hypothesis' | 'question' | 'evidence' | 'conclusion';
|
||||||
confidence?: number;
|
confidence?: number;
|
||||||
sources?: string[];
|
sources?: string[];
|
||||||
parentId?: string;
|
parentId?: string;
|
||||||
children?: string[];
|
children?: string[];
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains the full reasoning process
|
* Contains the full reasoning process
|
||||||
*/
|
*/
|
||||||
export interface ThinkingProcess {
|
export interface ThinkingProcess {
|
||||||
id: string;
|
id: string;
|
||||||
query: string;
|
query: string;
|
||||||
steps: ThinkingStep[];
|
steps: ThinkingStep[];
|
||||||
status: 'in_progress' | 'completed';
|
status: 'in_progress' | 'completed';
|
||||||
startTime: number;
|
startTime: number;
|
||||||
endTime?: number;
|
endTime?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ContextualThinkingTool {
|
export class ContextualThinkingTool {
|
||||||
private static thinkingCounter = 0;
|
private static thinkingCounter = 0;
|
||||||
private static stepCounter = 0;
|
private static stepCounter = 0;
|
||||||
private activeProcId?: string;
|
private activeProcId?: string;
|
||||||
private processes: Record<string, ThinkingProcess> = {};
|
private processes: Record<string, ThinkingProcess> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start a new thinking process for a query
|
* Start a new thinking process for a query
|
||||||
*
|
*
|
||||||
* @param query The user's query
|
* @param query The user's query
|
||||||
* @returns The created thinking process ID
|
* @returns The created thinking process ID
|
||||||
*/
|
*/
|
||||||
startThinking(query: string): string {
|
startThinking(query: string): string {
|
||||||
const thinkingId = `thinking_${Date.now()}_${ContextualThinkingTool.thinkingCounter++}`;
|
const thinkingId = `thinking_${Date.now()}_${ContextualThinkingTool.thinkingCounter++}`;
|
||||||
|
|
||||||
log.info(`Starting thinking process: ${thinkingId} for query "${query.substring(0, 50)}..."`);
|
log.info(`Starting thinking process: ${thinkingId} for query "${query.substring(0, 50)}..."`);
|
||||||
|
|
||||||
this.processes[thinkingId] = {
|
this.processes[thinkingId] = {
|
||||||
id: thinkingId,
|
id: thinkingId,
|
||||||
query,
|
query,
|
||||||
steps: [],
|
steps: [],
|
||||||
status: 'in_progress',
|
status: 'in_progress',
|
||||||
startTime: Date.now()
|
startTime: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set as active process
|
// Set as active process
|
||||||
this.activeProcId = thinkingId;
|
this.activeProcId = thinkingId;
|
||||||
|
|
||||||
// Initialize with some starter thinking steps
|
// Initialize with some starter thinking steps
|
||||||
this.addThinkingStep(thinkingId, {
|
this.addThinkingStep(thinkingId, {
|
||||||
type: 'observation',
|
type: 'observation',
|
||||||
content: `Starting analysis of the query: "${query}"`
|
content: `Starting analysis of the query: "${query}"`
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addThinkingStep(thinkingId, {
|
this.addThinkingStep(thinkingId, {
|
||||||
type: 'question',
|
type: 'question',
|
||||||
content: `What are the key components of this query that need to be addressed?`
|
content: `What are the key components of this query that need to be addressed?`
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addThinkingStep(thinkingId, {
|
this.addThinkingStep(thinkingId, {
|
||||||
type: 'observation',
|
type: 'observation',
|
||||||
content: `Breaking down the query to understand its requirements and context.`
|
content: `Breaking down the query to understand its requirements and context.`
|
||||||
});
|
});
|
||||||
|
|
||||||
return thinkingId;
|
return thinkingId;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a thinking step to a process
|
|
||||||
*
|
|
||||||
* @param processId The ID of the process to add to
|
|
||||||
* @param step The thinking step to add
|
|
||||||
* @returns The ID of the added step
|
|
||||||
*/
|
|
||||||
addThinkingStep(
|
|
||||||
processId: string,
|
|
||||||
step: Omit<ThinkingStep, 'id'>,
|
|
||||||
parentId?: string
|
|
||||||
): string {
|
|
||||||
const process = this.processes[processId];
|
|
||||||
|
|
||||||
if (!process) {
|
|
||||||
throw new Error(`Thinking process ${processId} not found`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create full step with ID
|
/**
|
||||||
const fullStep: ThinkingStep = {
|
* Add a thinking step to a process
|
||||||
id: `step_${Date.now()}_${Math.floor(Math.random() * 10000)}`,
|
*
|
||||||
...step,
|
* @param processId The ID of the process to add to
|
||||||
parentId
|
* @param step The thinking step to add
|
||||||
};
|
* @returns The ID of the added step
|
||||||
|
*/
|
||||||
|
addThinkingStep(
|
||||||
|
processId: string,
|
||||||
|
step: Omit<ThinkingStep, 'id'>,
|
||||||
|
parentId?: string
|
||||||
|
): string {
|
||||||
|
const process = this.processes[processId];
|
||||||
|
|
||||||
// Add to process steps
|
if (!process) {
|
||||||
process.steps.push(fullStep);
|
throw new Error(`Thinking process ${processId} not found`);
|
||||||
|
|
||||||
// If this step has a parent, update the parent's children list
|
|
||||||
if (parentId) {
|
|
||||||
const parentStep = process.steps.find(s => s.id === parentId);
|
|
||||||
if (parentStep) {
|
|
||||||
if (!parentStep.children) {
|
|
||||||
parentStep.children = [];
|
|
||||||
}
|
}
|
||||||
parentStep.children.push(fullStep.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the step addition with more detail
|
// Create full step with ID
|
||||||
log.info(`Added thinking step to process ${processId}: [${step.type}] ${step.content.substring(0, 100)}...`);
|
const fullStep: ThinkingStep = {
|
||||||
|
id: `step_${Date.now()}_${Math.floor(Math.random() * 10000)}`,
|
||||||
|
...step,
|
||||||
|
parentId
|
||||||
|
};
|
||||||
|
|
||||||
return fullStep.id;
|
// Add to process steps
|
||||||
}
|
process.steps.push(fullStep);
|
||||||
|
|
||||||
/**
|
// If this step has a parent, update the parent's children list
|
||||||
* Complete the current thinking process
|
if (parentId) {
|
||||||
*
|
const parentStep = process.steps.find(s => s.id === parentId);
|
||||||
* @param processId The ID of the process to complete (defaults to active process)
|
if (parentStep) {
|
||||||
* @returns The completed thinking process
|
if (!parentStep.children) {
|
||||||
*/
|
parentStep.children = [];
|
||||||
completeThinking(processId?: string): ThinkingProcess | null {
|
}
|
||||||
const id = processId || this.activeProcId;
|
parentStep.children.push(fullStep.id);
|
||||||
|
}
|
||||||
if (!id || !this.processes[id]) {
|
|
||||||
log.error(`Thinking process ${id} not found`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.processes[id].status = 'completed';
|
|
||||||
this.processes[id].endTime = Date.now();
|
|
||||||
|
|
||||||
if (id === this.activeProcId) {
|
|
||||||
this.activeProcId = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.processes[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a thinking process by ID
|
|
||||||
*/
|
|
||||||
getThinkingProcess(processId: string): ThinkingProcess | null {
|
|
||||||
return this.processes[processId] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the active thinking process
|
|
||||||
*/
|
|
||||||
getActiveThinkingProcess(): ThinkingProcess | null {
|
|
||||||
if (!this.activeProcId) return null;
|
|
||||||
return this.processes[this.activeProcId] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visualize the thinking process as HTML for display in the UI
|
|
||||||
*
|
|
||||||
* @param thinkingId The ID of the thinking process to visualize
|
|
||||||
* @returns HTML representation of the thinking process
|
|
||||||
*/
|
|
||||||
visualizeThinking(thinkingId: string): string {
|
|
||||||
log.info(`Visualizing thinking process: thinkingId=${thinkingId}`);
|
|
||||||
|
|
||||||
const process = this.getThinkingProcess(thinkingId);
|
|
||||||
if (!process) {
|
|
||||||
log.info(`No thinking process found for id: ${thinkingId}`);
|
|
||||||
return "<div class='thinking-process'>No thinking process found</div>";
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(`Found thinking process with ${process.steps.length} steps for query: "${process.query.substring(0, 50)}..."`);
|
|
||||||
|
|
||||||
let html = "<div class='thinking-process'>";
|
|
||||||
html += `<h4>Reasoning Process</h4>`;
|
|
||||||
html += `<div class='thinking-query'>${process.query}</div>`;
|
|
||||||
|
|
||||||
// Show overall time taken for the thinking process
|
|
||||||
const duration = process.endTime ?
|
|
||||||
Math.round((process.endTime - process.startTime) / 1000) :
|
|
||||||
Math.round((Date.now() - process.startTime) / 1000);
|
|
||||||
|
|
||||||
html += `<div class='thinking-meta'>Analysis took ${duration} seconds</div>`;
|
|
||||||
|
|
||||||
// Create a more structured visualization with indentation for parent-child relationships
|
|
||||||
const renderStep = (step: ThinkingStep, level: number = 0) => {
|
|
||||||
const indent = level * 20; // 20px indentation per level
|
|
||||||
|
|
||||||
let stepHtml = `<div class='thinking-step ${step.type || ""}' style='margin-left: ${indent}px'>`;
|
|
||||||
|
|
||||||
// Add an icon based on step type
|
|
||||||
const icon = this.getStepIcon(step.type);
|
|
||||||
stepHtml += `<span class='bx ${icon}'></span> `;
|
|
||||||
|
|
||||||
// Add the step content
|
|
||||||
stepHtml += step.content;
|
|
||||||
|
|
||||||
// Show confidence if available
|
|
||||||
if (step.metadata?.confidence) {
|
|
||||||
const confidence = Math.round((step.metadata.confidence as number) * 100);
|
|
||||||
stepHtml += ` <span class='thinking-confidence'>(Confidence: ${confidence}%)</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show sources if available
|
|
||||||
if (step.sources && step.sources.length > 0) {
|
|
||||||
stepHtml += `<div class='thinking-sources'>Sources: ${step.sources.join(', ')}</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
stepHtml += `</div>`;
|
|
||||||
|
|
||||||
return stepHtml;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to render a step and all its children recursively
|
|
||||||
const renderStepWithChildren = (stepId: string, level: number = 0) => {
|
|
||||||
const step = process.steps.find(s => s.id === stepId);
|
|
||||||
if (!step) return '';
|
|
||||||
|
|
||||||
let html = renderStep(step, level);
|
|
||||||
|
|
||||||
if (step.children && step.children.length > 0) {
|
|
||||||
for (const childId of step.children) {
|
|
||||||
html += renderStepWithChildren(childId, level + 1);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return html;
|
// Log the step addition with more detail
|
||||||
};
|
log.info(`Added thinking step to process ${processId}: [${step.type}] ${step.content.substring(0, 100)}...`);
|
||||||
|
|
||||||
// Render top-level steps and their children
|
return fullStep.id;
|
||||||
const topLevelSteps = process.steps.filter(s => !s.parentId);
|
}
|
||||||
for (const step of topLevelSteps) {
|
|
||||||
html += renderStep(step);
|
|
||||||
|
|
||||||
if (step.children && step.children.length > 0) {
|
/**
|
||||||
for (const childId of step.children) {
|
* Complete the current thinking process
|
||||||
html += renderStepWithChildren(childId, 1);
|
*
|
||||||
|
* @param processId The ID of the process to complete (defaults to active process)
|
||||||
|
* @returns The completed thinking process
|
||||||
|
*/
|
||||||
|
completeThinking(processId?: string): ThinkingProcess | null {
|
||||||
|
const id = processId || this.activeProcId;
|
||||||
|
|
||||||
|
if (!id || !this.processes[id]) {
|
||||||
|
log.error(`Thinking process ${id} not found`);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
this.processes[id].status = 'completed';
|
||||||
|
this.processes[id].endTime = Date.now();
|
||||||
|
|
||||||
|
if (id === this.activeProcId) {
|
||||||
|
this.activeProcId = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.processes[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
html += "</div>";
|
/**
|
||||||
return html;
|
* Get a thinking process by ID
|
||||||
}
|
*/
|
||||||
|
getThinkingProcess(processId: string): ThinkingProcess | null {
|
||||||
/**
|
return this.processes[processId] || null;
|
||||||
* Get an appropriate icon for a thinking step type
|
|
||||||
*/
|
|
||||||
private getStepIcon(type: string): string {
|
|
||||||
switch (type) {
|
|
||||||
case 'observation':
|
|
||||||
return 'bx-search';
|
|
||||||
case 'hypothesis':
|
|
||||||
return 'bx-bulb';
|
|
||||||
case 'evidence':
|
|
||||||
return 'bx-list-check';
|
|
||||||
case 'conclusion':
|
|
||||||
return 'bx-check-circle';
|
|
||||||
default:
|
|
||||||
return 'bx-message-square-dots';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a plain text summary of the thinking process
|
|
||||||
*
|
|
||||||
* @param thinkingId The ID of the thinking process to summarize
|
|
||||||
* @returns Text summary of the thinking process
|
|
||||||
*/
|
|
||||||
getThinkingSummary(thinkingId: string): string {
|
|
||||||
const process = this.getThinkingProcess(thinkingId);
|
|
||||||
if (!process) {
|
|
||||||
return "No thinking process available.";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = `## Reasoning Process for Query: "${process.query}"\n\n`;
|
/**
|
||||||
|
* Get the active thinking process
|
||||||
// Group steps by type for better organization
|
*/
|
||||||
const observations = process.steps.filter(s => s.type === 'observation');
|
getActiveThinkingProcess(): ThinkingProcess | null {
|
||||||
const questions = process.steps.filter(s => s.type === 'question');
|
if (!this.activeProcId) return null;
|
||||||
const hypotheses = process.steps.filter(s => s.type === 'hypothesis');
|
return this.processes[this.activeProcId] || null;
|
||||||
const evidence = process.steps.filter(s => s.type === 'evidence');
|
|
||||||
const conclusions = process.steps.filter(s => s.type === 'conclusion');
|
|
||||||
|
|
||||||
// Add observations
|
|
||||||
if (observations.length > 0) {
|
|
||||||
summary += "### Observations:\n";
|
|
||||||
observations.forEach(step => {
|
|
||||||
summary += `- ${step.content}\n`;
|
|
||||||
});
|
|
||||||
summary += "\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add questions
|
/**
|
||||||
if (questions.length > 0) {
|
* Visualize the thinking process as HTML for display in the UI
|
||||||
summary += "### Questions Considered:\n";
|
*
|
||||||
questions.forEach(step => {
|
* @param thinkingId The ID of the thinking process to visualize
|
||||||
summary += `- ${step.content}\n`;
|
* @returns HTML representation of the thinking process
|
||||||
});
|
*/
|
||||||
summary += "\n";
|
visualizeThinking(thinkingId: string): string {
|
||||||
|
log.info(`Visualizing thinking process: thinkingId=${thinkingId}`);
|
||||||
|
|
||||||
|
const process = this.getThinkingProcess(thinkingId);
|
||||||
|
if (!process) {
|
||||||
|
log.info(`No thinking process found for id: ${thinkingId}`);
|
||||||
|
return "<div class='thinking-process'>No thinking process found</div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Found thinking process with ${process.steps.length} steps for query: "${process.query.substring(0, 50)}..."`);
|
||||||
|
|
||||||
|
let html = "<div class='thinking-process'>";
|
||||||
|
html += `<h4>Reasoning Process</h4>`;
|
||||||
|
html += `<div class='thinking-query'>${process.query}</div>`;
|
||||||
|
|
||||||
|
// Show overall time taken for the thinking process
|
||||||
|
const duration = process.endTime ?
|
||||||
|
Math.round((process.endTime - process.startTime) / 1000) :
|
||||||
|
Math.round((Date.now() - process.startTime) / 1000);
|
||||||
|
|
||||||
|
html += `<div class='thinking-meta'>Analysis took ${duration} seconds</div>`;
|
||||||
|
|
||||||
|
// Create a more structured visualization with indentation for parent-child relationships
|
||||||
|
const renderStep = (step: ThinkingStep, level: number = 0) => {
|
||||||
|
const indent = level * 20; // 20px indentation per level
|
||||||
|
|
||||||
|
let stepHtml = `<div class='thinking-step ${step.type || ""}' style='margin-left: ${indent}px'>`;
|
||||||
|
|
||||||
|
// Add an icon based on step type
|
||||||
|
const icon = this.getStepIcon(step.type);
|
||||||
|
stepHtml += `<span class='bx ${icon}'></span> `;
|
||||||
|
|
||||||
|
// Add the step content
|
||||||
|
stepHtml += step.content;
|
||||||
|
|
||||||
|
// Show confidence if available
|
||||||
|
if (step.metadata?.confidence) {
|
||||||
|
const confidence = Math.round((step.metadata.confidence as number) * 100);
|
||||||
|
stepHtml += ` <span class='thinking-confidence'>(Confidence: ${confidence}%)</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show sources if available
|
||||||
|
if (step.sources && step.sources.length > 0) {
|
||||||
|
stepHtml += `<div class='thinking-sources'>Sources: ${step.sources.join(', ')}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
stepHtml += `</div>`;
|
||||||
|
|
||||||
|
return stepHtml;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to render a step and all its children recursively
|
||||||
|
const renderStepWithChildren = (stepId: string, level: number = 0) => {
|
||||||
|
const step = process.steps.find(s => s.id === stepId);
|
||||||
|
if (!step) return '';
|
||||||
|
|
||||||
|
let html = renderStep(step, level);
|
||||||
|
|
||||||
|
if (step.children && step.children.length > 0) {
|
||||||
|
for (const childId of step.children) {
|
||||||
|
html += renderStepWithChildren(childId, level + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render top-level steps and their children
|
||||||
|
const topLevelSteps = process.steps.filter(s => !s.parentId);
|
||||||
|
for (const step of topLevelSteps) {
|
||||||
|
html += renderStep(step);
|
||||||
|
|
||||||
|
if (step.children && step.children.length > 0) {
|
||||||
|
for (const childId of step.children) {
|
||||||
|
html += renderStepWithChildren(childId, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "</div>";
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add hypotheses
|
/**
|
||||||
if (hypotheses.length > 0) {
|
* Get an appropriate icon for a thinking step type
|
||||||
summary += "### Hypotheses:\n";
|
*/
|
||||||
hypotheses.forEach(step => {
|
private getStepIcon(type: string): string {
|
||||||
summary += `- ${step.content}\n`;
|
switch (type) {
|
||||||
});
|
case 'observation':
|
||||||
summary += "\n";
|
return 'bx-search';
|
||||||
|
case 'hypothesis':
|
||||||
|
return 'bx-bulb';
|
||||||
|
case 'evidence':
|
||||||
|
return 'bx-list-check';
|
||||||
|
case 'conclusion':
|
||||||
|
return 'bx-check-circle';
|
||||||
|
default:
|
||||||
|
return 'bx-message-square-dots';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add evidence
|
/**
|
||||||
if (evidence.length > 0) {
|
* Get a plain text summary of the thinking process
|
||||||
summary += "### Evidence Gathered:\n";
|
*
|
||||||
evidence.forEach(step => {
|
* @param thinkingId The ID of the thinking process to summarize
|
||||||
summary += `- ${step.content}\n`;
|
* @returns Text summary of the thinking process
|
||||||
});
|
*/
|
||||||
summary += "\n";
|
getThinkingSummary(thinkingId: string): string {
|
||||||
|
const process = this.getThinkingProcess(thinkingId);
|
||||||
|
if (!process) {
|
||||||
|
log.error(`No thinking process found for id: ${thinkingId}`);
|
||||||
|
return "No thinking process available.";
|
||||||
|
}
|
||||||
|
|
||||||
|
let summary = `## Reasoning Process for Query: "${process.query}"\n\n`;
|
||||||
|
|
||||||
|
// Group steps by type for better organization
|
||||||
|
const observations = process.steps.filter(s => s.type === 'observation');
|
||||||
|
const questions = process.steps.filter(s => s.type === 'question');
|
||||||
|
const hypotheses = process.steps.filter(s => s.type === 'hypothesis');
|
||||||
|
const evidence = process.steps.filter(s => s.type === 'evidence');
|
||||||
|
const conclusions = process.steps.filter(s => s.type === 'conclusion');
|
||||||
|
|
||||||
|
log.info(`Generating thinking summary with: ${observations.length} observations, ${questions.length} questions, ${hypotheses.length} hypotheses, ${evidence.length} evidence, ${conclusions.length} conclusions`);
|
||||||
|
|
||||||
|
// Add observations
|
||||||
|
if (observations.length > 0) {
|
||||||
|
summary += "### Observations:\n";
|
||||||
|
observations.forEach(step => {
|
||||||
|
summary += `- ${step.content}\n`;
|
||||||
|
});
|
||||||
|
summary += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add questions
|
||||||
|
if (questions.length > 0) {
|
||||||
|
summary += "### Questions Considered:\n";
|
||||||
|
questions.forEach(step => {
|
||||||
|
summary += `- ${step.content}\n`;
|
||||||
|
});
|
||||||
|
summary += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add hypotheses
|
||||||
|
if (hypotheses.length > 0) {
|
||||||
|
summary += "### Hypotheses:\n";
|
||||||
|
hypotheses.forEach(step => {
|
||||||
|
summary += `- ${step.content}\n`;
|
||||||
|
});
|
||||||
|
summary += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add evidence
|
||||||
|
if (evidence.length > 0) {
|
||||||
|
summary += "### Evidence Gathered:\n";
|
||||||
|
evidence.forEach(step => {
|
||||||
|
summary += `- ${step.content}\n`;
|
||||||
|
});
|
||||||
|
summary += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add conclusions
|
||||||
|
if (conclusions.length > 0) {
|
||||||
|
summary += "### Conclusions:\n";
|
||||||
|
conclusions.forEach(step => {
|
||||||
|
summary += `- ${step.content}\n`;
|
||||||
|
});
|
||||||
|
summary += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Generated thinking summary with ${summary.length} characters`);
|
||||||
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add conclusions
|
/**
|
||||||
if (conclusions.length > 0) {
|
* Reset the active thinking process
|
||||||
summary += "### Conclusions:\n";
|
*/
|
||||||
conclusions.forEach(step => {
|
resetActiveThinking(): void {
|
||||||
summary += `- ${step.content}\n`;
|
this.activeProcId = undefined;
|
||||||
});
|
|
||||||
summary += "\n";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return summary;
|
/**
|
||||||
}
|
* Generate a unique ID for a thinking process
|
||||||
|
*/
|
||||||
/**
|
private generateProcessId(): string {
|
||||||
* Reset the active thinking process
|
return `thinking_${Date.now()}_${ContextualThinkingTool.thinkingCounter++}`;
|
||||||
*/
|
|
||||||
resetActiveThinking(): void {
|
|
||||||
this.activeProcId = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a unique ID for a thinking process
|
|
||||||
*/
|
|
||||||
private generateProcessId(): string {
|
|
||||||
return `thinking_${Date.now()}_${ContextualThinkingTool.thinkingCounter++}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a unique ID for a thinking step
|
|
||||||
*/
|
|
||||||
private generateStepId(): string {
|
|
||||||
return `step_${Date.now()}_${ContextualThinkingTool.stepCounter++}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format duration between two timestamps
|
|
||||||
*/
|
|
||||||
private formatDuration(start: number, end: number): string {
|
|
||||||
const durationMs = end - start;
|
|
||||||
if (durationMs < 1000) {
|
|
||||||
return `${durationMs}ms`;
|
|
||||||
} else if (durationMs < 60000) {
|
|
||||||
return `${Math.round(durationMs / 1000)}s`;
|
|
||||||
} else {
|
|
||||||
return `${Math.round(durationMs / 60000)}m ${Math.round((durationMs % 60000) / 1000)}s`;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively render a step and its children
|
* Generate a unique ID for a thinking step
|
||||||
*/
|
*/
|
||||||
private renderStepTree(step: ThinkingStep, allSteps: ThinkingStep[]): string {
|
private generateStepId(): string {
|
||||||
const typeIcons: Record<string, string> = {
|
return `step_${Date.now()}_${ContextualThinkingTool.stepCounter++}`;
|
||||||
'observation': '🔍',
|
}
|
||||||
'hypothesis': '🤔',
|
|
||||||
'question': '❓',
|
|
||||||
'evidence': '📋',
|
|
||||||
'conclusion': '✅'
|
|
||||||
};
|
|
||||||
|
|
||||||
const icon = typeIcons[step.type] || '•';
|
/**
|
||||||
const confidenceDisplay = step.confidence !== undefined
|
* Format duration between two timestamps
|
||||||
? `<span class="confidence">${Math.round(step.confidence * 100)}%</span>`
|
*/
|
||||||
: '';
|
private formatDuration(start: number, end: number): string {
|
||||||
|
const durationMs = end - start;
|
||||||
|
if (durationMs < 1000) {
|
||||||
|
return `${durationMs}ms`;
|
||||||
|
} else if (durationMs < 60000) {
|
||||||
|
return `${Math.round(durationMs / 1000)}s`;
|
||||||
|
} else {
|
||||||
|
return `${Math.round(durationMs / 60000)}m ${Math.round((durationMs % 60000) / 1000)}s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let html = `
|
/**
|
||||||
|
* Recursively render a step and its children
|
||||||
|
*/
|
||||||
|
private renderStepTree(step: ThinkingStep, allSteps: ThinkingStep[]): string {
|
||||||
|
const typeIcons: Record<string, string> = {
|
||||||
|
'observation': '🔍',
|
||||||
|
'hypothesis': '🤔',
|
||||||
|
'question': '❓',
|
||||||
|
'evidence': '📋',
|
||||||
|
'conclusion': '✅'
|
||||||
|
};
|
||||||
|
|
||||||
|
const icon = typeIcons[step.type] || '•';
|
||||||
|
const confidenceDisplay = step.confidence !== undefined
|
||||||
|
? `<span class="confidence">${Math.round(step.confidence * 100)}%</span>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
let html = `
|
||||||
<div class="thinking-step thinking-${step.type}">
|
<div class="thinking-step thinking-${step.type}">
|
||||||
<div class="step-header">
|
<div class="step-header">
|
||||||
<span class="step-icon">${icon}</span>
|
<span class="step-icon">${icon}</span>
|
||||||
@ -411,28 +415,28 @@ export class ContextualThinkingTool {
|
|||||||
<div class="step-content">${step.content}</div>
|
<div class="step-content">${step.content}</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Add sources if available
|
// Add sources if available
|
||||||
if (step.sources && step.sources.length > 0) {
|
if (step.sources && step.sources.length > 0) {
|
||||||
html += `<div class="step-sources">Sources: ${step.sources.join(', ')}</div>`;
|
html += `<div class="step-sources">Sources: ${step.sources.join(', ')}</div>`;
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively render children
|
|
||||||
if (step.children && step.children.length > 0) {
|
|
||||||
html += `<div class="step-children">`;
|
|
||||||
|
|
||||||
for (const childId of step.children) {
|
|
||||||
const childStep = allSteps.find(s => s.id === childId);
|
|
||||||
if (childStep) {
|
|
||||||
html += this.renderStepTree(childStep, allSteps);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
html += `</div>`;
|
// Recursively render children
|
||||||
|
if (step.children && step.children.length > 0) {
|
||||||
|
html += `<div class="step-children">`;
|
||||||
|
|
||||||
|
for (const childId of step.children) {
|
||||||
|
const childStep = allSteps.find(s => s.id === childId);
|
||||||
|
if (childStep) {
|
||||||
|
html += this.renderStepTree(childStep, allSteps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
html += `</div>`;
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ContextualThinkingTool;
|
export default ContextualThinkingTool;
|
||||||
|
@ -443,11 +443,32 @@ export class NoteNavigatorTool {
|
|||||||
try {
|
try {
|
||||||
log.info(`Getting note structure for note ${noteId}`);
|
log.info(`Getting note structure for note ${noteId}`);
|
||||||
|
|
||||||
|
// Special handling for 'root' or other special notes
|
||||||
|
if (noteId === 'root' || !noteId) {
|
||||||
|
log.info('Using root as the special note for structure');
|
||||||
|
return {
|
||||||
|
noteId: 'root',
|
||||||
|
title: 'Root',
|
||||||
|
type: 'root',
|
||||||
|
childCount: 0, // We don't know how many direct children root has
|
||||||
|
attributes: [],
|
||||||
|
parentPath: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Get the note from becca
|
// Get the note from becca
|
||||||
const note = becca.notes[noteId];
|
const note = becca.notes[noteId];
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
throw new Error(`Note ${noteId} not found`);
|
log.error(`Note ${noteId} not found in becca.notes`);
|
||||||
|
return {
|
||||||
|
noteId,
|
||||||
|
title: 'Unknown',
|
||||||
|
type: 'unknown',
|
||||||
|
childCount: 0,
|
||||||
|
attributes: [],
|
||||||
|
parentPath: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get child notes count
|
// Get child notes count
|
||||||
|
@ -15,459 +15,480 @@
|
|||||||
import log from '../../log.js';
|
import log from '../../log.js';
|
||||||
|
|
||||||
export interface SubQuery {
|
export interface SubQuery {
|
||||||
id: string;
|
id: string;
|
||||||
text: string;
|
text: string;
|
||||||
reason: string;
|
reason: string;
|
||||||
isAnswered: boolean;
|
isAnswered: boolean;
|
||||||
answer?: string;
|
answer?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DecomposedQuery {
|
export interface DecomposedQuery {
|
||||||
originalQuery: string;
|
originalQuery: string;
|
||||||
subQueries: SubQuery[];
|
subQueries: SubQuery[];
|
||||||
status: 'pending' | 'in_progress' | 'completed';
|
status: 'pending' | 'in_progress' | 'completed';
|
||||||
complexity: number;
|
complexity: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryDecompositionTool {
|
export class QueryDecompositionTool {
|
||||||
private static queryCounter: number = 0;
|
private static queryCounter: number = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Break down a complex query into smaller, more manageable sub-queries
|
* Break down a complex query into smaller, more manageable sub-queries
|
||||||
*
|
*
|
||||||
* @param query The original user query
|
* @param query The original user query
|
||||||
* @param context Optional context about the current note being viewed
|
* @param context Optional context about the current note being viewed
|
||||||
* @returns A decomposed query object with sub-queries
|
* @returns A decomposed query object with sub-queries
|
||||||
*/
|
*/
|
||||||
decomposeQuery(query: string, context?: string): DecomposedQuery {
|
decomposeQuery(query: string, context?: string): DecomposedQuery {
|
||||||
try {
|
try {
|
||||||
// Log the decomposition attempt for tracking
|
// Log the decomposition attempt for tracking
|
||||||
log.info(`Decomposing query: "${query.substring(0, 100)}..."`);
|
log.info(`Decomposing query: "${query.substring(0, 100)}..."`);
|
||||||
|
|
||||||
// Assess query complexity to determine if decomposition is needed
|
if (!query || query.trim().length === 0) {
|
||||||
const complexity = this.assessQueryComplexity(query);
|
log.info("Query decomposition called with empty query");
|
||||||
log.info(`Query complexity assessment: ${complexity}/10`);
|
return {
|
||||||
|
originalQuery: query,
|
||||||
|
subQueries: [],
|
||||||
|
status: 'pending',
|
||||||
|
complexity: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// For simple queries, just return the original as a single sub-query
|
// Assess query complexity to determine if decomposition is needed
|
||||||
// Use a lower threshold (2 instead of 3) to decompose more queries
|
const complexity = this.assessQueryComplexity(query);
|
||||||
if (complexity < 2) {
|
log.info(`Query complexity assessment: ${complexity}/10`);
|
||||||
log.info(`Query is simple (complexity ${complexity}), returning as single sub-query`);
|
|
||||||
return {
|
|
||||||
originalQuery: query,
|
|
||||||
subQueries: [{
|
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text: query,
|
|
||||||
reason: 'Direct question that can be answered without decomposition',
|
|
||||||
isAnswered: false
|
|
||||||
}],
|
|
||||||
status: 'pending',
|
|
||||||
complexity
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// For complex queries, perform decomposition
|
// For simple queries, just return the original as a single sub-query
|
||||||
const subQueries = this.createSubQueries(query, context);
|
// Use a lower threshold (2 instead of 3) to decompose more queries
|
||||||
log.info(`Decomposed query into ${subQueries.length} sub-queries`);
|
if (complexity < 2) {
|
||||||
|
log.info(`Query is simple (complexity ${complexity}), returning as single sub-query`);
|
||||||
|
|
||||||
// Log the sub-queries for better visibility
|
const mainSubQuery = {
|
||||||
subQueries.forEach((sq, index) => {
|
id: this.generateSubQueryId(),
|
||||||
log.info(`Sub-query ${index + 1}: "${sq.text}" - Reason: ${sq.reason}`);
|
text: query,
|
||||||
});
|
reason: 'Direct question that can be answered without decomposition',
|
||||||
|
isAnswered: false
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
// Still add a generic exploration query to get some related content
|
||||||
originalQuery: query,
|
const genericQuery = {
|
||||||
subQueries,
|
id: this.generateSubQueryId(),
|
||||||
status: 'pending',
|
text: `Information related to ${query}`,
|
||||||
complexity
|
reason: "Generic exploration to find related content",
|
||||||
};
|
isAnswered: false
|
||||||
} catch (error: any) {
|
};
|
||||||
log.error(`Error decomposing query: ${error.message}`);
|
|
||||||
|
|
||||||
// Fallback to treating it as a simple query
|
return {
|
||||||
return {
|
originalQuery: query,
|
||||||
originalQuery: query,
|
subQueries: [mainSubQuery, genericQuery],
|
||||||
subQueries: [{
|
status: 'pending',
|
||||||
id: this.generateSubQueryId(),
|
complexity
|
||||||
text: query,
|
};
|
||||||
reason: 'Error in decomposition, treating as simple query',
|
}
|
||||||
isAnswered: false
|
|
||||||
}],
|
|
||||||
status: 'pending',
|
|
||||||
complexity: 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// For complex queries, perform decomposition
|
||||||
* Update a sub-query with its answer
|
const subQueries = this.createSubQueries(query, context);
|
||||||
*
|
log.info(`Decomposed query into ${subQueries.length} sub-queries`);
|
||||||
* @param decomposedQuery The decomposed query object
|
|
||||||
* @param subQueryId The ID of the sub-query to update
|
|
||||||
* @param answer The answer to the sub-query
|
|
||||||
* @returns The updated decomposed query
|
|
||||||
*/
|
|
||||||
updateSubQueryAnswer(
|
|
||||||
decomposedQuery: DecomposedQuery,
|
|
||||||
subQueryId: string,
|
|
||||||
answer: string
|
|
||||||
): DecomposedQuery {
|
|
||||||
const updatedSubQueries = decomposedQuery.subQueries.map(sq => {
|
|
||||||
if (sq.id === subQueryId) {
|
|
||||||
return {
|
|
||||||
...sq,
|
|
||||||
answer,
|
|
||||||
isAnswered: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return sq;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check if all sub-queries are answered
|
// Log the sub-queries for better visibility
|
||||||
const allAnswered = updatedSubQueries.every(sq => sq.isAnswered);
|
subQueries.forEach((sq, index) => {
|
||||||
|
log.info(`Sub-query ${index + 1}: "${sq.text}" - Reason: ${sq.reason}`);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...decomposedQuery,
|
originalQuery: query,
|
||||||
subQueries: updatedSubQueries,
|
subQueries,
|
||||||
status: allAnswered ? 'completed' : 'in_progress'
|
status: 'pending',
|
||||||
};
|
complexity
|
||||||
}
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
log.error(`Error decomposing query: ${error.message}`);
|
||||||
|
|
||||||
/**
|
// Fallback to treating it as a simple query
|
||||||
* Synthesize all sub-query answers into a comprehensive response
|
return {
|
||||||
*
|
originalQuery: query,
|
||||||
* @param decomposedQuery The decomposed query with all sub-queries answered
|
subQueries: [{
|
||||||
* @returns A synthesized answer to the original query
|
id: this.generateSubQueryId(),
|
||||||
*/
|
text: query,
|
||||||
synthesizeAnswer(decomposedQuery: DecomposedQuery): string {
|
reason: 'Error in decomposition, treating as simple query',
|
||||||
try {
|
isAnswered: false
|
||||||
// Ensure all sub-queries are answered
|
}],
|
||||||
if (!decomposedQuery.subQueries.every(sq => sq.isAnswered)) {
|
status: 'pending',
|
||||||
return "Cannot synthesize answer - not all sub-queries have been answered.";
|
complexity: 1
|
||||||
}
|
};
|
||||||
|
|
||||||
// For simple queries with just one sub-query, return the answer directly
|
|
||||||
if (decomposedQuery.subQueries.length === 1) {
|
|
||||||
return decomposedQuery.subQueries[0].answer || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// For complex queries, build a structured response that references each sub-answer
|
|
||||||
let synthesized = `Answer to: "${decomposedQuery.originalQuery}"\n\n`;
|
|
||||||
|
|
||||||
// Group by themes if there are many sub-queries
|
|
||||||
if (decomposedQuery.subQueries.length > 3) {
|
|
||||||
// Here we would ideally group related sub-queries, but for now we'll just present them in order
|
|
||||||
synthesized += "Based on the information gathered:\n\n";
|
|
||||||
|
|
||||||
for (const sq of decomposedQuery.subQueries) {
|
|
||||||
synthesized += `${sq.answer}\n\n`;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// For fewer sub-queries, present each one with its question
|
|
||||||
for (const sq of decomposedQuery.subQueries) {
|
|
||||||
synthesized += `${sq.answer}\n\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return synthesized.trim();
|
|
||||||
} catch (error: any) {
|
|
||||||
log.error(`Error synthesizing answer: ${error.message}`);
|
|
||||||
return "Error synthesizing the final answer.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a status report on the progress of answering a complex query
|
|
||||||
*
|
|
||||||
* @param decomposedQuery The decomposed query
|
|
||||||
* @returns A status report string
|
|
||||||
*/
|
|
||||||
getQueryStatus(decomposedQuery: DecomposedQuery): string {
|
|
||||||
const answeredCount = decomposedQuery.subQueries.filter(sq => sq.isAnswered).length;
|
|
||||||
const totalCount = decomposedQuery.subQueries.length;
|
|
||||||
|
|
||||||
let status = `Progress: ${answeredCount}/${totalCount} sub-queries answered\n\n`;
|
|
||||||
|
|
||||||
for (const sq of decomposedQuery.subQueries) {
|
|
||||||
status += `${sq.isAnswered ? '✓' : '○'} ${sq.text}\n`;
|
|
||||||
if (sq.isAnswered) {
|
|
||||||
status += ` Answer: ${this.truncateText(sq.answer || "", 100)}\n`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
/**
|
||||||
}
|
* Update a sub-query with its answer
|
||||||
|
*
|
||||||
/**
|
* @param decomposedQuery The decomposed query object
|
||||||
* Assess the complexity of a query on a scale of 1-10
|
* @param subQueryId The ID of the sub-query to update
|
||||||
* This helps determine how many sub-queries are needed
|
* @param answer The answer to the sub-query
|
||||||
*
|
* @returns The updated decomposed query
|
||||||
* @param query The query to assess
|
*/
|
||||||
* @returns A complexity score from 1-10
|
updateSubQueryAnswer(
|
||||||
*/
|
decomposedQuery: DecomposedQuery,
|
||||||
assessQueryComplexity(query: string): number {
|
subQueryId: string,
|
||||||
// Count the number of question marks as a basic indicator
|
answer: string
|
||||||
const questionMarkCount = (query.match(/\?/g) || []).length;
|
): DecomposedQuery {
|
||||||
|
const updatedSubQueries = decomposedQuery.subQueries.map(sq => {
|
||||||
// Count potential sub-questions based on question words
|
if (sq.id === subQueryId) {
|
||||||
const questionWords = ['what', 'how', 'why', 'where', 'when', 'who', 'which'];
|
return {
|
||||||
const questionWordMatches = questionWords.map(word => {
|
...sq,
|
||||||
const regex = new RegExp(`\\b${word}\\b`, 'gi');
|
answer,
|
||||||
return (query.match(regex) || []).length;
|
isAnswered: true
|
||||||
});
|
};
|
||||||
|
}
|
||||||
const questionWordCount = questionWordMatches.reduce((sum, count) => sum + count, 0);
|
return sq;
|
||||||
|
|
||||||
// Look for conjunctions which might join multiple questions
|
|
||||||
const conjunctionCount = (query.match(/\b(and|or|but|as well as)\b/gi) || []).length;
|
|
||||||
|
|
||||||
// Look for complex requirements
|
|
||||||
const comparisonCount = (query.match(/\b(compare|versus|vs|difference|similarities?)\b/gi) || []).length;
|
|
||||||
const analysisCount = (query.match(/\b(analyze|examine|investigate|explore|explain|discuss)\b/gi) || []).length;
|
|
||||||
|
|
||||||
// Calculate base complexity
|
|
||||||
let complexity = 1;
|
|
||||||
|
|
||||||
// Add for multiple questions
|
|
||||||
complexity += Math.min(2, questionMarkCount);
|
|
||||||
|
|
||||||
// Add for question words beyond the first one
|
|
||||||
complexity += Math.min(2, Math.max(0, questionWordCount - 1));
|
|
||||||
|
|
||||||
// Add for conjunctions that might join questions
|
|
||||||
complexity += Math.min(2, conjunctionCount);
|
|
||||||
|
|
||||||
// Add for comparative/analytical requirements
|
|
||||||
complexity += Math.min(2, comparisonCount + analysisCount);
|
|
||||||
|
|
||||||
// Add for overall length/complexity
|
|
||||||
if (query.length > 100) complexity += 1;
|
|
||||||
if (query.length > 200) complexity += 1;
|
|
||||||
|
|
||||||
// Ensure we stay in the 1-10 range
|
|
||||||
return Math.max(1, Math.min(10, complexity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a unique ID for a sub-query
|
|
||||||
*/
|
|
||||||
private generateSubQueryId(): string {
|
|
||||||
return `sq_${Date.now()}_${QueryDecompositionTool.queryCounter++}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create sub-queries based on the original query and optional context
|
|
||||||
*/
|
|
||||||
private createSubQueries(query: string, context?: string): SubQuery[] {
|
|
||||||
const subQueries: SubQuery[] = [];
|
|
||||||
|
|
||||||
// Use context to enhance sub-query generation if available
|
|
||||||
if (context) {
|
|
||||||
log.info(`Using context to enhance sub-query generation`);
|
|
||||||
|
|
||||||
// Add context-specific questions
|
|
||||||
subQueries.push({
|
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text: `What key information in the current note relates to: "${query}"?`,
|
|
||||||
reason: 'Identifying directly relevant information in the current context',
|
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Look for multiple question marks
|
|
||||||
const questionSplit = query.split(/\?/).filter(q => q.trim().length > 0);
|
|
||||||
|
|
||||||
if (questionSplit.length > 1) {
|
|
||||||
// Multiple distinct questions detected
|
|
||||||
for (let i = 0; i < questionSplit.length; i++) {
|
|
||||||
const text = questionSplit[i].trim() + '?';
|
|
||||||
subQueries.push({
|
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text,
|
|
||||||
reason: `Separate question ${i+1} detected in the original query`,
|
|
||||||
isAnswered: false
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Also add a synthesis question
|
// Check if all sub-queries are answered
|
||||||
subQueries.push({
|
const allAnswered = updatedSubQueries.every(sq => sq.isAnswered);
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text: `How do the answers to these questions relate to each other in the context of the original query?`,
|
|
||||||
reason: 'Synthesizing information from multiple questions',
|
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
|
|
||||||
return subQueries;
|
return {
|
||||||
|
...decomposedQuery,
|
||||||
|
subQueries: updatedSubQueries,
|
||||||
|
status: allAnswered ? 'completed' : 'in_progress'
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Look for "and", "or", etc. connecting potentially separate questions
|
/**
|
||||||
const conjunctions = [
|
* Synthesize all sub-query answers into a comprehensive response
|
||||||
{ regex: /\b(compare|versus|vs\.?|difference between|similarities between)\b/i, label: 'comparison' },
|
*
|
||||||
{ regex: /\b(list|enumerate)\b/i, label: 'listing' },
|
* @param decomposedQuery The decomposed query with all sub-queries answered
|
||||||
{ regex: /\b(analyze|examine|investigate|explore)\b/i, label: 'analysis' },
|
* @returns A synthesized answer to the original query
|
||||||
{ regex: /\b(explain|why)\b/i, label: 'explanation' },
|
*/
|
||||||
{ regex: /\b(how to|steps to|process of)\b/i, label: 'procedure' }
|
synthesizeAnswer(decomposedQuery: DecomposedQuery): string {
|
||||||
];
|
try {
|
||||||
|
// Ensure all sub-queries are answered
|
||||||
|
if (!decomposedQuery.subQueries.every(sq => sq.isAnswered)) {
|
||||||
|
return "Cannot synthesize answer - not all sub-queries have been answered.";
|
||||||
|
}
|
||||||
|
|
||||||
// Check for comparison queries - these often need multiple sub-queries
|
// For simple queries with just one sub-query, return the answer directly
|
||||||
for (const conj of conjunctions) {
|
if (decomposedQuery.subQueries.length === 1) {
|
||||||
if (conj.regex.test(query)) {
|
return decomposedQuery.subQueries[0].answer || "";
|
||||||
if (conj.label === 'comparison') {
|
}
|
||||||
// For comparisons, we need to research each item, then compare them
|
|
||||||
const comparisonMatch = query.match(/\b(compare|versus|vs\.?|difference between|similarities between)\s+(.+?)\s+(and|with|to)\s+(.+?)(\?|$)/i);
|
|
||||||
|
|
||||||
if (comparisonMatch) {
|
// For complex queries, build a structured response that references each sub-answer
|
||||||
const item1 = comparisonMatch[2].trim();
|
let synthesized = `Answer to: "${decomposedQuery.originalQuery}"\n\n`;
|
||||||
const item2 = comparisonMatch[4].trim();
|
|
||||||
|
|
||||||
|
// Group by themes if there are many sub-queries
|
||||||
|
if (decomposedQuery.subQueries.length > 3) {
|
||||||
|
// Here we would ideally group related sub-queries, but for now we'll just present them in order
|
||||||
|
synthesized += "Based on the information gathered:\n\n";
|
||||||
|
|
||||||
|
for (const sq of decomposedQuery.subQueries) {
|
||||||
|
synthesized += `${sq.answer}\n\n`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For fewer sub-queries, present each one with its question
|
||||||
|
for (const sq of decomposedQuery.subQueries) {
|
||||||
|
synthesized += `${sq.answer}\n\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return synthesized.trim();
|
||||||
|
} catch (error: any) {
|
||||||
|
log.error(`Error synthesizing answer: ${error.message}`);
|
||||||
|
return "Error synthesizing the final answer.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a status report on the progress of answering a complex query
|
||||||
|
*
|
||||||
|
* @param decomposedQuery The decomposed query
|
||||||
|
* @returns A status report string
|
||||||
|
*/
|
||||||
|
getQueryStatus(decomposedQuery: DecomposedQuery): string {
|
||||||
|
const answeredCount = decomposedQuery.subQueries.filter(sq => sq.isAnswered).length;
|
||||||
|
const totalCount = decomposedQuery.subQueries.length;
|
||||||
|
|
||||||
|
let status = `Progress: ${answeredCount}/${totalCount} sub-queries answered\n\n`;
|
||||||
|
|
||||||
|
for (const sq of decomposedQuery.subQueries) {
|
||||||
|
status += `${sq.isAnswered ? '✓' : '○'} ${sq.text}\n`;
|
||||||
|
if (sq.isAnswered) {
|
||||||
|
status += ` Answer: ${this.truncateText(sq.answer || "", 100)}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assess the complexity of a query on a scale of 1-10
|
||||||
|
* This helps determine how many sub-queries are needed
|
||||||
|
*
|
||||||
|
* @param query The query to assess
|
||||||
|
* @returns A complexity score from 1-10
|
||||||
|
*/
|
||||||
|
assessQueryComplexity(query: string): number {
|
||||||
|
// Count the number of question marks as a basic indicator
|
||||||
|
const questionMarkCount = (query.match(/\?/g) || []).length;
|
||||||
|
|
||||||
|
// Count potential sub-questions based on question words
|
||||||
|
const questionWords = ['what', 'how', 'why', 'where', 'when', 'who', 'which'];
|
||||||
|
const questionWordMatches = questionWords.map(word => {
|
||||||
|
const regex = new RegExp(`\\b${word}\\b`, 'gi');
|
||||||
|
return (query.match(regex) || []).length;
|
||||||
|
});
|
||||||
|
|
||||||
|
const questionWordCount = questionWordMatches.reduce((sum, count) => sum + count, 0);
|
||||||
|
|
||||||
|
// Look for conjunctions which might join multiple questions
|
||||||
|
const conjunctionCount = (query.match(/\b(and|or|but|as well as)\b/gi) || []).length;
|
||||||
|
|
||||||
|
// Look for complex requirements
|
||||||
|
const comparisonCount = (query.match(/\b(compare|versus|vs|difference|similarities?)\b/gi) || []).length;
|
||||||
|
const analysisCount = (query.match(/\b(analyze|examine|investigate|explore|explain|discuss)\b/gi) || []).length;
|
||||||
|
|
||||||
|
// Calculate base complexity
|
||||||
|
let complexity = 1;
|
||||||
|
|
||||||
|
// Add for multiple questions
|
||||||
|
complexity += Math.min(2, questionMarkCount);
|
||||||
|
|
||||||
|
// Add for question words beyond the first one
|
||||||
|
complexity += Math.min(2, Math.max(0, questionWordCount - 1));
|
||||||
|
|
||||||
|
// Add for conjunctions that might join questions
|
||||||
|
complexity += Math.min(2, conjunctionCount);
|
||||||
|
|
||||||
|
// Add for comparative/analytical requirements
|
||||||
|
complexity += Math.min(2, comparisonCount + analysisCount);
|
||||||
|
|
||||||
|
// Add for overall length/complexity
|
||||||
|
if (query.length > 100) complexity += 1;
|
||||||
|
if (query.length > 200) complexity += 1;
|
||||||
|
|
||||||
|
// Ensure we stay in the 1-10 range
|
||||||
|
return Math.max(1, Math.min(10, complexity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a unique ID for a sub-query
|
||||||
|
*/
|
||||||
|
private generateSubQueryId(): string {
|
||||||
|
return `sq_${Date.now()}_${QueryDecompositionTool.queryCounter++}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create sub-queries based on the original query and optional context
|
||||||
|
*/
|
||||||
|
private createSubQueries(query: string, context?: string): SubQuery[] {
|
||||||
|
const subQueries: SubQuery[] = [];
|
||||||
|
|
||||||
|
// Use context to enhance sub-query generation if available
|
||||||
|
if (context) {
|
||||||
|
log.info(`Using context to enhance sub-query generation`);
|
||||||
|
|
||||||
|
// Add context-specific questions
|
||||||
subQueries.push({
|
subQueries.push({
|
||||||
id: this.generateSubQueryId(),
|
id: this.generateSubQueryId(),
|
||||||
text: `What are the key characteristics of ${item1}?`,
|
text: `What key information in the current note relates to: "${query}"?`,
|
||||||
reason: `Need to understand ${item1} for the comparison`,
|
reason: 'Identifying directly relevant information in the current context',
|
||||||
isAnswered: false
|
isAnswered: false
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
subQueries.push({
|
// 1. Look for multiple question marks
|
||||||
id: this.generateSubQueryId(),
|
const questionSplit = query.split(/\?/).filter(q => q.trim().length > 0);
|
||||||
text: `What are the key characteristics of ${item2}?`,
|
|
||||||
reason: `Need to understand ${item2} for the comparison`,
|
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
|
|
||||||
subQueries.push({
|
if (questionSplit.length > 1) {
|
||||||
id: this.generateSubQueryId(),
|
// Multiple distinct questions detected
|
||||||
text: `What are the main differences between ${item1} and ${item2}?`,
|
for (let i = 0; i < questionSplit.length; i++) {
|
||||||
reason: 'Understanding key differences',
|
const text = questionSplit[i].trim() + '?';
|
||||||
isAnswered: false
|
subQueries.push({
|
||||||
});
|
id: this.generateSubQueryId(),
|
||||||
|
text,
|
||||||
|
reason: `Separate question ${i + 1} detected in the original query`,
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also add a synthesis question
|
||||||
subQueries.push({
|
subQueries.push({
|
||||||
id: this.generateSubQueryId(),
|
id: this.generateSubQueryId(),
|
||||||
text: `What are the main similarities between ${item1} and ${item2}?`,
|
text: `How do the answers to these questions relate to each other in the context of the original query?`,
|
||||||
reason: 'Understanding key similarities',
|
reason: 'Synthesizing information from multiple questions',
|
||||||
isAnswered: false
|
isAnswered: false
|
||||||
});
|
|
||||||
|
|
||||||
subQueries.push({
|
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text: `What practical implications do these differences and similarities have?`,
|
|
||||||
reason: 'Understanding practical significance of the comparison',
|
|
||||||
isAnswered: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return subQueries;
|
return subQueries;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. For complex questions without clear separation, create topic-based sub-queries
|
// 2. Look for "and", "or", etc. connecting potentially separate questions
|
||||||
// Lowered the threshold to process more queries this way
|
const conjunctions = [
|
||||||
if (query.length > 50) {
|
{ regex: /\b(compare|versus|vs\.?|difference between|similarities between)\b/i, label: 'comparison' },
|
||||||
// Extract potential key topics from the query
|
{ regex: /\b(list|enumerate)\b/i, label: 'listing' },
|
||||||
const words = query.toLowerCase().split(/\W+/).filter(w =>
|
{ regex: /\b(analyze|examine|investigate|explore)\b/i, label: 'analysis' },
|
||||||
w.length > 3 &&
|
{ regex: /\b(explain|why)\b/i, label: 'explanation' },
|
||||||
!['what', 'when', 'where', 'which', 'with', 'would', 'could', 'should', 'have', 'this', 'that', 'there', 'their'].includes(w)
|
{ regex: /\b(how to|steps to|process of)\b/i, label: 'procedure' }
|
||||||
);
|
];
|
||||||
|
|
||||||
// Count word frequencies
|
// Check for comparison queries - these often need multiple sub-queries
|
||||||
const wordFrequency: Record<string, number> = {};
|
for (const conj of conjunctions) {
|
||||||
for (const word of words) {
|
if (conj.regex.test(query)) {
|
||||||
wordFrequency[word] = (wordFrequency[word] || 0) + 1;
|
if (conj.label === 'comparison') {
|
||||||
}
|
// For comparisons, we need to research each item, then compare them
|
||||||
|
const comparisonMatch = query.match(/\b(compare|versus|vs\.?|difference between|similarities between)\s+(.+?)\s+(and|with|to)\s+(.+?)(\?|$)/i);
|
||||||
|
|
||||||
// Get top frequent words
|
if (comparisonMatch) {
|
||||||
const topWords = Object.entries(wordFrequency)
|
const item1 = comparisonMatch[2].trim();
|
||||||
.sort((a, b) => b[1] - a[1])
|
const item2 = comparisonMatch[4].trim();
|
||||||
.slice(0, 4) // Increased from 3 to 4
|
|
||||||
.map(entry => entry[0]);
|
|
||||||
|
|
||||||
if (topWords.length > 0) {
|
subQueries.push({
|
||||||
// Create factual sub-query
|
id: this.generateSubQueryId(),
|
||||||
subQueries.push({
|
text: `What are the key characteristics of ${item1}?`,
|
||||||
id: this.generateSubQueryId(),
|
reason: `Need to understand ${item1} for the comparison`,
|
||||||
text: `What are the key facts about ${topWords.join(' and ')} relevant to this question?`,
|
isAnswered: false
|
||||||
reason: 'Gathering basic information about main topics',
|
});
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add individual queries for each key topic
|
subQueries.push({
|
||||||
topWords.forEach(word => {
|
id: this.generateSubQueryId(),
|
||||||
subQueries.push({
|
text: `What are the key characteristics of ${item2}?`,
|
||||||
id: this.generateSubQueryId(),
|
reason: `Need to understand ${item2} for the comparison`,
|
||||||
text: `What specific details about "${word}" are most relevant to the query?`,
|
isAnswered: false
|
||||||
reason: `Detailed exploration of the "${word}" concept`,
|
});
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create relationship sub-query if multiple top words
|
subQueries.push({
|
||||||
if (topWords.length > 1) {
|
id: this.generateSubQueryId(),
|
||||||
for (let i = 0; i < topWords.length; i++) {
|
text: `What are the main differences between ${item1} and ${item2}?`,
|
||||||
for (let j = i + 1; j < topWords.length; j++) {
|
reason: 'Understanding key differences',
|
||||||
subQueries.push({
|
isAnswered: false
|
||||||
id: this.generateSubQueryId(),
|
});
|
||||||
text: `How do ${topWords[i]} and ${topWords[j]} relate to each other?`,
|
|
||||||
reason: `Understanding relationship between ${topWords[i]} and ${topWords[j]}`,
|
subQueries.push({
|
||||||
isAnswered: false
|
id: this.generateSubQueryId(),
|
||||||
});
|
text: `What are the main similarities between ${item1} and ${item2}?`,
|
||||||
|
reason: 'Understanding key similarities',
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: `What practical implications do these differences and similarities have?`,
|
||||||
|
reason: 'Understanding practical significance of the comparison',
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return subQueries;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a "what else" query to ensure comprehensive coverage
|
// 3. For complex questions without clear separation, create topic-based sub-queries
|
||||||
|
// Lowered the threshold to process more queries this way
|
||||||
|
if (query.length > 50) {
|
||||||
|
// Extract potential key topics from the query
|
||||||
|
const words = query.toLowerCase().split(/\W+/).filter(w =>
|
||||||
|
w.length > 3 &&
|
||||||
|
!['what', 'when', 'where', 'which', 'with', 'would', 'could', 'should', 'have', 'this', 'that', 'there', 'their'].includes(w)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Count word frequencies
|
||||||
|
const wordFrequency: Record<string, number> = {};
|
||||||
|
for (const word of words) {
|
||||||
|
wordFrequency[word] = (wordFrequency[word] || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get top frequent words
|
||||||
|
const topWords = Object.entries(wordFrequency)
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 4) // Increased from 3 to 4
|
||||||
|
.map(entry => entry[0]);
|
||||||
|
|
||||||
|
if (topWords.length > 0) {
|
||||||
|
// Create factual sub-query
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: `What are the key facts about ${topWords.join(' and ')} relevant to this question?`,
|
||||||
|
reason: 'Gathering basic information about main topics',
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add individual queries for each key topic
|
||||||
|
topWords.forEach(word => {
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: `What specific details about "${word}" are most relevant to the query?`,
|
||||||
|
reason: `Detailed exploration of the "${word}" concept`,
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create relationship sub-query if multiple top words
|
||||||
|
if (topWords.length > 1) {
|
||||||
|
for (let i = 0; i < topWords.length; i++) {
|
||||||
|
for (let j = i + 1; j < topWords.length; j++) {
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: `How do ${topWords[i]} and ${topWords[j]} relate to each other?`,
|
||||||
|
reason: `Understanding relationship between ${topWords[i]} and ${topWords[j]}`,
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a "what else" query to ensure comprehensive coverage
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: `What other important aspects should be considered about this topic that might not be immediately obvious?`,
|
||||||
|
reason: 'Exploring non-obvious but relevant information',
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the original query as the final synthesizing question
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: query,
|
||||||
|
reason: 'Original question to be answered after gathering information',
|
||||||
|
isAnswered: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return subQueries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: If we can't meaningfully decompose, just use the original query
|
||||||
|
// But also add some generic exploration questions
|
||||||
subQueries.push({
|
subQueries.push({
|
||||||
id: this.generateSubQueryId(),
|
id: this.generateSubQueryId(),
|
||||||
text: `What other important aspects should be considered about this topic that might not be immediately obvious?`,
|
text: query,
|
||||||
reason: 'Exploring non-obvious but relevant information',
|
reason: 'Primary question',
|
||||||
isAnswered: false
|
isAnswered: false
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add the original query as the final synthesizing question
|
// Add generic exploration questions even for "simple" queries
|
||||||
subQueries.push({
|
subQueries.push({
|
||||||
id: this.generateSubQueryId(),
|
id: this.generateSubQueryId(),
|
||||||
text: query,
|
text: `What background information is helpful to understand this query better?`,
|
||||||
reason: 'Original question to be answered after gathering information',
|
reason: 'Gathering background context',
|
||||||
isAnswered: false
|
isAnswered: false
|
||||||
|
});
|
||||||
|
|
||||||
|
subQueries.push({
|
||||||
|
id: this.generateSubQueryId(),
|
||||||
|
text: `What related concepts might be important to consider?`,
|
||||||
|
reason: 'Exploring related concepts',
|
||||||
|
isAnswered: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return subQueries;
|
return subQueries;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: If we can't meaningfully decompose, just use the original query
|
/**
|
||||||
// But also add some generic exploration questions
|
* Truncate text to a maximum length with ellipsis
|
||||||
subQueries.push({
|
*/
|
||||||
id: this.generateSubQueryId(),
|
private truncateText(text: string, maxLength: number): string {
|
||||||
text: query,
|
if (text.length <= maxLength) return text;
|
||||||
reason: 'Primary question',
|
return text.substring(0, maxLength - 3) + '...';
|
||||||
isAnswered: false
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Add generic exploration questions even for "simple" queries
|
|
||||||
subQueries.push({
|
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text: `What background information is helpful to understand this query better?`,
|
|
||||||
reason: 'Gathering background context',
|
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
|
|
||||||
subQueries.push({
|
|
||||||
id: this.generateSubQueryId(),
|
|
||||||
text: `What related concepts might be important to consider?`,
|
|
||||||
reason: 'Exploring related concepts',
|
|
||||||
isAnswered: false
|
|
||||||
});
|
|
||||||
|
|
||||||
return subQueries;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Truncate text to a maximum length with ellipsis
|
|
||||||
*/
|
|
||||||
private truncateText(text: string, maxLength: number): string {
|
|
||||||
if (text.length <= maxLength) return text;
|
|
||||||
return text.substring(0, maxLength - 3) + '...';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default QueryDecompositionTool;
|
export default QueryDecompositionTool;
|
||||||
|
@ -147,22 +147,24 @@ export class ContextService {
|
|||||||
|
|
||||||
// Step 4: Add agent tools context with thinking process if requested
|
// Step 4: Add agent tools context with thinking process if requested
|
||||||
let enhancedContext = context;
|
let enhancedContext = context;
|
||||||
if (contextNoteId) {
|
try {
|
||||||
try {
|
// Pass 'root' as the default noteId when no specific note is selected
|
||||||
const agentContext = await this.getAgentToolsContext(
|
const noteIdToUse = contextNoteId || 'root';
|
||||||
contextNoteId,
|
log.info(`Calling getAgentToolsContext with noteId=${noteIdToUse}, showThinking=${showThinking}`);
|
||||||
userQuestion,
|
|
||||||
showThinking,
|
|
||||||
relevantNotes
|
|
||||||
);
|
|
||||||
|
|
||||||
if (agentContext) {
|
const agentContext = await this.getAgentToolsContext(
|
||||||
enhancedContext = enhancedContext + "\n\n" + agentContext;
|
noteIdToUse,
|
||||||
}
|
userQuestion,
|
||||||
} catch (error) {
|
showThinking,
|
||||||
log.error(`Error getting agent tools context: ${error}`);
|
relevantNotes
|
||||||
// Continue with the basic context
|
);
|
||||||
|
|
||||||
|
if (agentContext) {
|
||||||
|
enhancedContext = enhancedContext + "\n\n" + agentContext;
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.error(`Error getting agent tools context: ${error}`);
|
||||||
|
// Continue with the basic context
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -402,8 +404,13 @@ export class ContextService {
|
|||||||
|
|
||||||
// Add thinking process if requested
|
// Add thinking process if requested
|
||||||
if (showThinking) {
|
if (showThinking) {
|
||||||
|
log.info(`Including thinking process in context (showThinking=true)`);
|
||||||
agentContext += `\n## Reasoning Process\n`;
|
agentContext += `\n## Reasoning Process\n`;
|
||||||
agentContext += contextualThinkingTool.getThinkingSummary(thinkingId);
|
const thinkingSummary = contextualThinkingTool.getThinkingSummary(thinkingId);
|
||||||
|
log.info(`Thinking summary length: ${thinkingSummary.length} characters`);
|
||||||
|
agentContext += thinkingSummary;
|
||||||
|
} else {
|
||||||
|
log.info(`Skipping thinking process in context (showThinking=false)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log stats about the context
|
// Log stats about the context
|
||||||
|
Loading…
x
Reference in New Issue
Block a user