Compare commits

...

1 Commits
1.4.5 ... 1.4.6

Author SHA1 Message Date
04f038ba65 Memory prompt refinement
All checks were successful
Publish Library / Build NPM Project (push) Successful in 38s
Publish Library / Tag Version (push) Successful in 19s
2026-08-05 13:14:21 -04:00
3 changed files with 20 additions and 24 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@ztimson/ai-utils", "name": "@ztimson/ai-utils",
"version": "1.4.5", "version": "1.4.6",
"description": "AI Utility library", "description": "AI Utility library",
"author": "Zak Timson", "author": "Zak Timson",
"license": "MIT", "license": "MIT",

View File

@@ -1,4 +1,4 @@
import {clean, snakeCase} from '@ztimson/utils'; import {clean, makeUnique, snakeCase} from '@ztimson/utils';
import {AbortablePromise, Ai} from './ai.ts'; import {AbortablePromise, Ai} from './ai.ts';
import {Anthropic} from './antrhopic.ts'; import {Anthropic} from './antrhopic.ts';
import {OpenAi} from './open-ai.ts'; import {OpenAi} from './open-ai.ts';
@@ -7,7 +7,7 @@ import {AiTool, AiToolArg} from './tools.ts';
import {fileURLToPath} from 'url'; import {fileURLToPath} from 'url';
import {dirname, join} from 'path'; import {dirname, join} from 'path';
import {spawn} from 'node:child_process'; import {spawn} from 'node:child_process';
import {Memory, MemoryCache, MemoryManager, MemoryOptions} from './memory.ts'; import {Memory, MemoryCache, MemoryManager, MemoryOptions, stripHeader} from './memory.ts';
const MAX_AGENT_DEPTH = 5; const MAX_AGENT_DEPTH = 5;
@@ -207,7 +207,7 @@ ${a.system}`,
const list = allTools.map(t => `- ${t.name}: ${t.description}`).join('\n'); const list = allTools.map(t => `- ${t.name}: ${t.description}`).join('\n');
return { return {
prompt: `You have access to the following MCP tools:\n${list}`, prompt: `## MCP\nYou have access to the following MCP tools:\n${list}`,
tools: allTools tools: allTools
}; };
} }
@@ -216,7 +216,7 @@ ${a.system}`,
if(!skills?.length) return {prompt: '', tools: []}; if(!skills?.length) return {prompt: '', tools: []};
const list = skills.map(s => `- ${s.name}: ${s.description}`).join('\n'); const list = skills.map(s => `- ${s.name}: ${s.description}`).join('\n');
return { return {
prompt: `You have access to the following skill documents, whenever there is overlap between a question and a skill file, use \`skill_read\` to get instructions and background knowledge:\n${list}`, prompt: `## Skills\nYou have access to the following skill documents, whenever there is overlap between a question and a skill file, use \`skill_read\` to get instructions and background knowledge:\n${list}`,
tools: [{ tools: [{
name: 'skill_read', name: 'skill_read',
description: 'Read the full content of a skill/knowledge document', description: 'Read the full content of a skill/knowledge document',
@@ -316,30 +316,26 @@ ${a.system}`,
} else listed.push(r); } else listed.push(r);
} }
prompts.unshift(`You have a background memory process which has prefetched relevant information${mem.update ? ' and will create new memories from this conversation' : ''} for you prompts.unshift(`## Memory
You have a background memory process which has prefetched relevant information${mem.update ? ' and will create new memories from this conversation' : ''} for you
Assume it is perfect and never mention this process to anyone ever Assume it is perfect and never mention this process to anyone ever
Always use your memories to craft a personalized response, they contain links / [[wiki links]] which you use navigate between them Always use your memories to craft a personalized response, they contain links / [[wiki links]] which you use navigate between them
${mem.tool ? `You can access memory files via the \`memory_search\` and \`memory_recall\` tools ${mem.tool ? `You can access memory files via the \`memory_search\` and \`memory_recall\` tools
When you need information about the user, \`memory_recall\` \`People/User\` before asking (fetch if not included bellow) When you need information about the user, \`memory_recall\` \`People/User\` before asking (fetch if not included bellow)
When you need information not provided, attempt 1-3 \`memory_search\` calls with unique queries before asking` : ''} When you need information not provided, attempt 1-3 \`memory_search\` calls with distinct queries before asking` : ''}
${preloaded.length ? ` ${preloaded.length ? `### Prefetched Memories (Most relevant first):
Prefetched Memories (Most relevant first):
${preloaded.map(r => `Memory: ${r.name} ${preloaded.map(r => `Memory: ${r.name}
Description: ${r.description} Description: ${r.description}
Linked: ${[r.links, ...r.backlinks].join(', ')} Linked: ${makeUnique([...r.links, ...r.backlinks]).join(', ')}
\`\`\` \`\`\`
${r.content} ${stripHeader(r.content)}
\`\`\``).join('\n\n')}` : ''} \`\`\``).join('\n\n')}` : ''}
${mem.tool && listed.length ? '\n' + listed.map(r => `Memory: ${r.name}
${mem.tool && listed.length ? listed.map(r => `Memory: ${r.name}
Description: ${r.description} Description: ${r.description}
Linked: ${[r.links, ...r.backlinks].join(', ')} Linked: ${makeUnique([...r.links, ...r.backlinks]).join(', ')}
<!-- Truncated -->`).join('\n\n') : ''} <!-- Truncated -->`).join('\n\n') : ''}`.trim())
${mem.tool ? `Full memory list:
${mems.map(m => `- ${m.name}: ${m.description}`).join('\n')}` : ''}`.trim())
} }
if(mem.tool) tools.push(this.memoryManager.tools.read(mem.memory)); if(mem.tool) tools.push(this.memoryManager.tools.read(mem.memory));
} }

View File

@@ -61,6 +61,10 @@ function cosineSearch(query: number[], memories: Memory[], limit: number): Memor
.map(s => s.ref); .map(s => s.ref);
} }
export function stripHeader(content: string): string {
return content.replace(/^---[\s\S]*?\n---\n?/, '').trimStart();
}
export class MemoryCache { export class MemoryCache {
private tree!: KDTree<MemoryRef>; private tree!: KDTree<MemoryRef>;
public memories: Memory[]; public memories: Memory[];
@@ -249,7 +253,7 @@ ${m.content}
private appendFacts(node: Memory, facts: string[]): void { private appendFacts(node: Memory, facts: string[]): void {
this.ensureDoc(node); this.ensureDoc(node);
const body = this.stripHeader(node.content); const body = stripHeader(node.content);
const bullets = facts.map(f => `- ${f}`).join('\n'); const bullets = facts.map(f => `- ${f}`).join('\n');
const idx = body.indexOf(FACTS_HEADING); const idx = body.indexOf(FACTS_HEADING);
const newBody = idx === -1 const newBody = idx === -1
@@ -355,7 +359,7 @@ ${ghosts.length ? `${ghosts.map(g => `- ${g}: (Ghost)`).join('\n')}` : ''}`,
} }
private async docAgent(node: Memory, memories: Memory[], options: LLMRequest, entry: {request: {abort?: () => void} | null}): Promise<void> { private async docAgent(node: Memory, memories: Memory[], options: LLMRequest, entry: {request: {abort?: () => void} | null}): Promise<void> {
const currentBody = this.stripHeader(node.content); const currentBody = stripHeader(node.content);
let update; let update;
try { try {
for (let i = 0; i < 2 && !update?.content; i++) { for (let i = 0; i < 2 && !update?.content; i++) {
@@ -420,10 +424,6 @@ ${currentBody}
return {fm, body: match[2]}; return {fm, body: match[2]};
} }
private stripHeader(content: string): string {
return content.replace(/^---[\s\S]*?\n---\n?/, '').trimStart();
}
private touchHeader(node: Memory, body: string): string { private touchHeader(node: Memory, body: string): string {
const {fm} = this.parseFrontmatter(node.content); const {fm} = this.parseFrontmatter(node.content);
fm.set('name', node.name); fm.set('name', node.name);