try using XML tags in sending to LLM, so it can more easily pick out information

This commit is contained in:
perf3ct 2025-04-02 18:57:04 +00:00
parent 6e8ab373d8
commit caada309ec
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
5 changed files with 119 additions and 55 deletions

View File

@ -631,8 +631,8 @@ function buildContextFromNotes(sources: NoteSource[], query: string): string {
const noteContexts = sources
.filter(source => source.content) // Only include sources with content
.map((source) => {
// Format each note with its title as a natural heading
return `### ${source.title}\n${source.content || 'No content available'}`;
// Format each note with its title as a natural heading and wrap in <note> tags
return `<note>\n### ${source.title}\n${source.content || 'No content available'}\n</note>`;
})
.join('\n\n');

View File

@ -71,11 +71,13 @@ Example: ["exact topic mentioned", "related concept 1", "related concept 2"]`,
CONTEXT_NOTES_WRAPPER:
`I'll provide you with relevant information from my notes to help answer your question.
<notes>
{noteContexts}
</notes>
When referring to information from these notes in your response, please cite them by their titles (e.g., "According to your note on [Title]...") rather than using labels like "Note 1" or "Note 2".
Now, based on the above information, please answer: {query}`,
Now, based on the above information, please answer: <query>{query}</query>`,
// Default fallback when no notes are found
NO_NOTES_CONTEXT:
@ -90,17 +92,17 @@ Now, based on the above information, please answer: {query}`,
// Headers for context (by provider)
CONTEXT_HEADERS: {
ANTHROPIC: (query: string) =>
`I'm your AI assistant helping with your Trilium notes database. For your query: "${query}", I found these relevant notes:\n\n`,
`I'm your AI assistant helping with your Trilium notes database. For your query: "<query>${query}</query>", I found these relevant <notes>`,
DEFAULT: (query: string) =>
`I've found some relevant information in your notes that may help answer: "${query}"\n\n`
`I've found some relevant information in your notes that may help answer: "<query>${query}</query>"\n\n<notes>`
},
// Closings for context (by provider)
CONTEXT_CLOSINGS: {
ANTHROPIC:
"\n\nPlease use this information to answer the user's query. If the notes don't contain enough information, you can use your general knowledge as well.",
"</notes>\n\nPlease use this information to answer the user's query. If the notes don't contain enough information, you can use your general knowledge as well.",
DEFAULT:
"\n\nBased on this information from the user's notes, please provide a helpful response."
"</notes>\n\nBased on this information from the user's notes, please provide a helpful response."
},
// Context for index service
@ -110,21 +112,27 @@ Now, based on the above information, please answer: {query}`,
// Prompt for adding note context to chat
NOTE_CONTEXT_PROMPT: `Here is the content of the note I want to discuss:
<note_content>
{context}
</note_content>
Please help me with this information.`,
// Prompt for adding semantic note context to chat
SEMANTIC_NOTE_CONTEXT_PROMPT: `Here is the relevant information from my notes based on my query "{query}":
SEMANTIC_NOTE_CONTEXT_PROMPT: `Here is the relevant information from my notes based on my query "<query>{query}</query>":
<notes_context>
{context}
</notes_context>
Please help me understand this information in relation to my query.`,
// System message prompt for context-aware chat
CONTEXT_AWARE_SYSTEM_PROMPT: `You are an AI assistant helping with Trilium Notes. Use this context to answer the user's question:
{enhancedContext}`,
<enhanced_context>
{enhancedContext}
</enhanced_context>`,
// Error messages
ERROR_MESSAGES: {
@ -134,25 +142,25 @@ Please help me understand this information in relation to my query.`,
// Merged from JS file
AGENT_TOOLS_CONTEXT_PROMPT:
"You have access to the following tools to help answer the user's question: {tools}"
"You have access to the following tools to help answer the user's question: <tools>{tools}</tools>"
};
// Agent tool prompts
export const AGENT_TOOL_PROMPTS = {
// Prompts for query decomposition
QUERY_DECOMPOSITION: {
SUB_QUERY_DIRECT: 'Direct question that can be answered without decomposition',
SUB_QUERY_GENERIC: 'Generic exploration to find related content',
SUB_QUERY_ERROR: 'Error in decomposition, treating as simple query',
SUB_QUERY_DIRECT_ANALYSIS: 'Direct analysis of note details',
ORIGINAL_QUERY: 'Original query'
SUB_QUERY_DIRECT: '<query_type>Direct question that can be answered without decomposition</query_type>',
SUB_QUERY_GENERIC: '<query_type>Generic exploration to find related content</query_type>',
SUB_QUERY_ERROR: '<query_type>Error in decomposition, treating as simple query</query_type>',
SUB_QUERY_DIRECT_ANALYSIS: '<query_type>Direct analysis of note details</query_type>',
ORIGINAL_QUERY: '<query_type>Original query</query_type>'
},
// Prompts for contextual thinking tool
CONTEXTUAL_THINKING: {
STARTING_ANALYSIS: (query: string) => `Starting analysis of the query: "${query}"`,
KEY_COMPONENTS: 'What are the key components of this query that need to be addressed?',
BREAKING_DOWN: 'Breaking down the query to understand its requirements and context.'
STARTING_ANALYSIS: (query: string) => `Starting analysis of the query: "<query>${query}</query>"`,
KEY_COMPONENTS: '<analysis>What are the key components of this query that need to be addressed?</analysis>',
BREAKING_DOWN: '<analysis>Breaking down the query to understand its requirements and context.</analysis>'
}
};
@ -188,13 +196,17 @@ When responding:
OPENAI: {
// OpenAI-specific prompt formatting
SYSTEM_WITH_CONTEXT: (context: string) =>
`You are an AI assistant integrated into TriliumNext Notes.
`<system_prompt>
You are an AI assistant integrated into TriliumNext Notes.
Use the following information from the user's notes to answer their questions:
<user_notes>
${context}
</user_notes>
Focus on relevant information from these notes when answering.
Be concise and informative in your responses.`
Be concise and informative in your responses.
</system_prompt>`
},
OLLAMA: {
@ -204,12 +216,12 @@ Be concise and informative in your responses.`
${context}
Based on this information, please answer: ${query}`
Based on this information, please answer: <query>${query}</query>`
},
// Common prompts across providers
COMMON: {
DEFAULT_ASSISTANT_INTRO: "You are an AI assistant integrated into TriliumNext Notes. Focus on helping users find information in their notes and answering questions based on their knowledge base. Be concise, informative, and direct when responding to queries."
DEFAULT_ASSISTANT_INTRO: "<assistant_role>You are an AI assistant integrated into TriliumNext Notes. Focus on helping users find information in their notes and answering questions based on their knowledge base. Be concise, informative, and direct when responding to queries.</assistant_role>"
}
};
@ -217,14 +229,14 @@ Based on this information, please answer: ${query}`
export const FORMATTING_PROMPTS = {
// Headers for context formatting
CONTEXT_HEADERS: {
SIMPLE: (query: string) => `I'm searching for information about: ${query}\n\nHere are the most relevant notes from my knowledge base:`,
DETAILED: (query: string) => `I'm searching for information about: "${query}"\n\nHere are the most relevant notes from my personal knowledge base:`
SIMPLE: (query: string) => `I'm searching for information about: <query>${query}</query>\n\n<notes>Here are the most relevant notes from my knowledge base:`,
DETAILED: (query: string) => `I'm searching for information about: "<query>${query}</query>"\n\n<notes>Here are the most relevant notes from my personal knowledge base:`
},
// Closing text for context formatting
CONTEXT_CLOSERS: {
SIMPLE: `End of notes. Please use this information to answer my question comprehensively.`,
DETAILED: `End of context information. Please use only the above notes to answer my question as comprehensively as possible.`
SIMPLE: `</notes>\nEnd of notes. Please use this information to answer my question comprehensively.`,
DETAILED: `</notes>\nEnd of context information. Please use only the above notes to answer my question as comprehensively as possible.`
},
// Dividers used in context formatting
@ -242,14 +254,14 @@ export const FORMATTING_PROMPTS = {
export const CHAT_PROMPTS = {
// Introduction messages for new chats
INTRODUCTIONS: {
NEW_CHAT: "Welcome to TriliumNext AI Assistant. How can I help you with your notes today?",
SEMANTIC_SEARCH: "I'll search through your notes for relevant information. What would you like to know?"
NEW_CHAT: "<greeting>Welcome to TriliumNext AI Assistant. How can I help you with your notes today?</greeting>",
SEMANTIC_SEARCH: "<instruction>I'll search through your notes for relevant information. What would you like to know?</instruction>"
},
// Placeholders for various chat scenarios
PLACEHOLDERS: {
NO_CONTEXT: "I don't have any specific note context yet. Would you like me to search your notes for something specific?",
WAITING_FOR_QUERY: "Awaiting your question..."
NO_CONTEXT: "<status>I don't have any specific note context yet. Would you like me to search your notes for something specific?</status>",
WAITING_FOR_QUERY: "<prompt>Awaiting your question...</prompt>"
}
};

View File

@ -131,9 +131,9 @@ export class ContextFormatter implements IContextFormatter {
let formattedSource = '';
if (providerId === 'ollama') {
// For Ollama, use a simpler format and plain ASCII
formattedSource = `${FORMATTING_PROMPTS.DIVIDERS.NOTE_START}${title}\n${content}\n\n`;
formattedSource = `<note>\n${FORMATTING_PROMPTS.DIVIDERS.NOTE_START}${title}\n${content}\n</note>\n\n`;
} else {
formattedSource = `### ${title}\n${content}\n\n`;
formattedSource = `<note>\n### ${title}\n${content}\n</note>\n\n`;
}
// Check if adding this would exceed our size limit
@ -144,8 +144,8 @@ export class ContextFormatter implements IContextFormatter {
const availableSpace = maxTotalLength - totalSize - 100; // Buffer for closing text
if (availableSpace > 200) { // Only if we have reasonable space
const truncatedContent = providerId === 'ollama' ?
`## ${title}\n${content.substring(0, availableSpace)}...\n\n` :
`### ${title}\n${content.substring(0, availableSpace)}...\n\n`;
`<note>\n## ${title}\n${content.substring(0, availableSpace)}...\n</note>\n\n` :
`<note>\n### ${title}\n${content.substring(0, availableSpace)}...\n</note>\n\n`;
formattedSources.push(truncatedContent);
totalSize += truncatedContent.length;
sourcesIncluded++;
@ -185,6 +185,10 @@ export class ContextFormatter implements IContextFormatter {
// Log final context size
log.info(`Final context: ${formattedContext.length} chars, ${formattedSources.length} sources included`);
// DEBUG: Log a sample of the formatted context to verify <note> tags are present
log.info(`Context sample (first 500 chars): ${formattedContext.substring(0, 500).replace(/\n/g, '\\n')}`);
log.info(`Context sample (last 500 chars): ${formattedContext.substring(Math.max(0, formattedContext.length - 500)).replace(/\n/g, '\\n')}`);
return formattedContext;
} catch (error) {
log.error(`Error building context from notes: ${error}`);

View File

@ -58,11 +58,19 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
if (msg.role === 'user' && !injectedContext) {
// Simple context injection directly in the user's message
const cleanedContext = this.cleanContextContent(context);
// DEBUG: Log the context before and after cleaning
console.log(`[OllamaFormatter] Context (first 500 chars): ${context.substring(0, 500).replace(/\n/g, '\\n')}...`);
console.log(`[OllamaFormatter] Cleaned context (first 500 chars): ${cleanedContext.substring(0, 500).replace(/\n/g, '\\n')}...`);
const formattedContext = PROVIDER_PROMPTS.OLLAMA.CONTEXT_INJECTION(
cleanedContext,
msg.content
);
// DEBUG: Log the final formatted context
console.log(`[OllamaFormatter] Formatted context (first 500 chars): ${formattedContext.substring(0, 500).replace(/\n/g, '\\n')}...`);
formattedMessages.push({
role: 'user',
content: formattedContext
@ -87,17 +95,50 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
/**
* Clean up HTML and other problematic content before sending to Ollama
* Ollama needs a more aggressive cleaning than other models
* Ollama needs a more aggressive cleaning than other models,
* but we want to preserve our XML tags for context
*/
override cleanContextContent(content: string): string {
if (!content) return '';
try {
// Store our XML tags so we can restore them after cleaning
const noteTagsRegex = /<\/?note>/g;
const notesTagsRegex = /<\/?notes>/g;
const queryTagsRegex = /<\/?query>[^<]*<\/query>/g;
// Capture tags to restore later
const noteTags = content.match(noteTagsRegex) || [];
const noteTagPositions: number[] = [];
let match;
const regex = /<\/?note>/g;
while ((match = regex.exec(content)) !== null) {
noteTagPositions.push(match.index);
}
// Remember the notes tags
const notesTagsMatch = content.match(notesTagsRegex) || [];
const notesTagPositions: number[] = [];
while ((match = notesTagsRegex.exec(content)) !== null) {
notesTagPositions.push(match.index);
}
// Remember the query tags
const queryTagsMatch = content.match(queryTagsRegex) || [];
// Temporarily replace XML tags with markers that won't be affected by sanitization
let modified = content
.replace(/<note>/g, '[NOTE_START]')
.replace(/<\/note>/g, '[NOTE_END]')
.replace(/<notes>/g, '[NOTES_START]')
.replace(/<\/notes>/g, '[NOTES_END]')
.replace(/<query>(.*?)<\/query>/g, '[QUERY]$1[/QUERY]');
// First use the parent class to do standard cleaning
let sanitized = super.cleanContextContent(content);
let sanitized = super.cleanContextContent(modified);
// Then apply Ollama-specific aggressive cleaning
// Remove any remaining HTML using sanitizeHtml
// Remove any remaining HTML using sanitizeHtml while keeping our markers
let plaintext = sanitizeHtml(sanitized, {
allowedTags: HTML_ALLOWED_TAGS.NONE,
allowedAttributes: HTML_ALLOWED_ATTRIBUTES.NONE,
@ -110,6 +151,14 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
plaintext = plaintext.replace(pattern.pattern, pattern.replacement);
}
// Restore our XML tags
plaintext = plaintext
.replace(/\[NOTE_START\]/g, '<note>')
.replace(/\[NOTE_END\]/g, '</note>')
.replace(/\[NOTES_START\]/g, '<notes>')
.replace(/\[NOTES_END\]/g, '</notes>')
.replace(/\[QUERY\](.*?)\[\/QUERY\]/g, '<query>$1</query>');
return plaintext.trim();
} catch (error) {
console.error(FORMATTER_LOGS.ERROR.CONTEXT_CLEANING("Ollama"), error);

View File

@ -13,21 +13,20 @@ You are an AI assistant integrated into TriliumNext Notes, a powerful note-takin
Your primary goal is to help users find information in their notes, answer questions based on their knowledge base, and provide assistance with using TriliumNext Notes features. Be sure to summarize the notes and include the title of the notes when providing a summary.
When responding to queries:
1. For complex queries, decompose them into simpler parts and address each one
2. When citing information from the user's notes, mention the note title (e.g., "According to your note titled 'Project Ideas'...")
3. Focus on the user's personal knowledge base first, then supplement with general knowledge if needed
4. Keep responses concise and directly relevant to the query
5. For general questions about the user's notes, provide a summary of all relevant notes found, including brief summaries of individual notes
6. For specific questions, provide detailed information from the user's notes that directly addresses the question
7. Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
8. For search requests, prioritize precision over recall - it's better to return the most relevant few notes than many marginally related ones
9. For organizational questions, offer concrete suggestions with examples rather than general advice
10. For analytical queries, structure your response to show relationships between notes and concepts
11. When you detect that a user's query relates to note organization, content structure, or knowledge management, proactively suggest relevant TriliumNext features they might not be aware of
12. If a query seems incomplete or ambiguous, ask clarifying questions rather than making assumptions
13. Respect privacy by focusing solely on the content explicitly shared - never speculate about other notes or information not directly referenced
14. When suggesting improvements to a user's note organization or structure, present these as optional enhancements rather than corrections
15. Maintain a helpful, knowledgeable tone focused on enhancing the user's knowledge management experience
16. Frame responses as collaborative assistance rather than authoritative instruction
17. Instead of telling a user on what Notes they have, summarize the notes and include the title of the notes when providing a summary.
18.
- For complex queries, decompose them into simpler parts and address each one
- When citing information from the user's notes, mention the note title (e.g., "According to your note titled 'Project Ideas'...")
- Focus on the user's personal knowledge base first, then supplement with general knowledge if needed
- Keep responses concise and directly relevant to the query
- For general questions about the user's notes, provide a summary of all relevant notes found, including brief summaries of individual notes
- For specific questions, provide detailed information from the user's notes that directly addresses the question
- Always prioritize information from the user's notes over your own knowledge, as the user's notes are likely more up-to-date and personally relevant
- For search requests, prioritize precision over recall - it's better to return the most relevant few notes than many marginally related ones
- For organizational questions, offer concrete suggestions with examples rather than general advice
- For analytical queries, structure your response to show relationships between notes and concepts
- When you detect that a user's query relates to note organization, content structure, or knowledge management, proactively suggest relevant TriliumNext features they might not be aware of
- If a query seems incomplete or ambiguous, ask clarifying questions rather than making assumptions
- Respect privacy by focusing solely on the content explicitly shared - never speculate about other notes or information not directly referenced
- When suggesting improvements to a user's note organization or structure, present these as optional enhancements rather than corrections
- Maintain a helpful, knowledgeable tone focused on enhancing the user's knowledge management experience
- Frame responses as collaborative assistance rather than authoritative instruction
- Instead of telling a user on what Notes they have, summarize the notes and include the title of the notes when providing a summary.