Compare commits

...

6 Commits

Author SHA1 Message Date
b814ea8b28 Improved memory recall results
All checks were successful
Publish Library / Build NPM Project (push) Successful in 42s
Publish Library / Tag Version (push) Successful in 10s
2026-03-03 00:26:00 -05:00
06dda88dbc Removed log statements
All checks were successful
Publish Library / Build NPM Project (push) Successful in 36s
Publish Library / Tag Version (push) Successful in 10s
2026-03-02 14:00:58 -05:00
5d34652d46 Fixed CLI tool
All checks were successful
Publish Library / Build NPM Project (push) Successful in 39s
Publish Library / Tag Version (push) Successful in 10s
2026-03-01 18:11:25 -05:00
6454548364 Fixed CLI tool
All checks were successful
Publish Library / Build NPM Project (push) Successful in 41s
Publish Library / Tag Version (push) Successful in 9s
2026-03-01 17:18:30 -05:00
936317f2f2 Better memory de-duplication
All checks were successful
Publish Library / Build NPM Project (push) Successful in 37s
Publish Library / Tag Version (push) Successful in 10s
2026-03-01 00:11:17 -05:00
cfde2ac4d3 Fixed open AI tool call streaming!
All checks were successful
Publish Library / Build NPM Project (push) Successful in 42s
Publish Library / Tag Version (push) Successful in 8s
2026-02-27 13:11:41 -05:00
4 changed files with 46 additions and 16 deletions

View File

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

View File

@@ -117,12 +117,13 @@ class LLM {
const score = (o ? this.cosineSimilarity(m.embeddings[0], o[0].embedding) : 0) const score = (o ? this.cosineSimilarity(m.embeddings[0], o[0].embedding) : 0)
+ (q ? this.cosineSimilarity(m.embeddings[1], q[0].embedding) : 0); + (q ? this.cosineSimilarity(m.embeddings[1], q[0].embedding) : 0);
return {...m, score}; return {...m, score};
}).toSorted((a: any, b: any) => a.score - b.score).slice(0, limit); }).toSorted((a: any, b: any) => a.score - b.score).slice(0, limit)
.map(m => `- ${m.owner}: ${m.fact}`).join('\n');
} }
options.system += '\nYou have RAG memory and will be given the top_k closest memories regarding the users query. Save anything new you have learned worth remembering from the user message using the remember tool and feel free to recall memories manually.\n'; options.system += '\nYou have RAG memory and will be given the top_k closest memories regarding the users query. Save anything new you have learned worth remembering from the user message using the remember tool and feel free to recall memories manually.\n';
const relevant = await search(message); const relevant = await search(message);
if(relevant.length) options.history.push({role: 'tool', name: 'recall', id: 'auto_recall_' + Math.random().toString(), args: {}, content: 'Things I remembered:\n' + relevant.map(m => `${m.owner}: ${m.fact}`).join('\n')}); if(relevant.length) options.history.push({role: 'tool', name: 'recall', id: 'auto_recall_' + Math.random().toString(), args: {}, content: `Things I remembered:\n${relevant}`});
options.tools = [{ options.tools = [{
name: 'recall', name: 'recall',
description: 'Recall the closest memories you have regarding a query using RAG', description: 'Recall the closest memories you have regarding a query using RAG',
@@ -151,7 +152,7 @@ class LLM {
const newMem = {owner: args.owner, fact: args.fact, embeddings: <any>[e[0][0].embedding, e[1][0].embedding]}; const newMem = {owner: args.owner, fact: args.fact, embeddings: <any>[e[0][0].embedding, e[1][0].embedding]};
options.memory.splice(0, options.memory.length, ...[ options.memory.splice(0, options.memory.length, ...[
...options.memory.filter(m => { ...options.memory.filter(m => {
return this.cosineSimilarity(newMem.embeddings[0], m.embeddings[0]) < 0.9 && this.cosineSimilarity(newMem.embeddings[1], m.embeddings[1]) < 0.8; return !(this.cosineSimilarity(newMem.embeddings[0], m.embeddings[0]) >= 0.9 && this.cosineSimilarity(newMem.embeddings[1], m.embeddings[1]) >= 0.8);
}), }),
newMem newMem
]); ]);

View File

@@ -103,15 +103,37 @@ export class OpenAi extends LLMProvider {
if(options.stream) { if(options.stream) {
if(!isFirstMessage) options.stream({text: '\n\n'}); if(!isFirstMessage) options.stream({text: '\n\n'});
else isFirstMessage = false; else isFirstMessage = false;
resp.choices = [{message: {content: '', tool_calls: []}}]; resp.choices = [{message: {role: 'assistant', content: '', tool_calls: []}}];
for await (const chunk of resp) { for await (const chunk of resp) {
if(controller.signal.aborted) break; if(controller.signal.aborted) break;
if(chunk.choices[0].delta.content) { if(chunk.choices[0].delta.content) {
resp.choices[0].message.content += chunk.choices[0].delta.content; resp.choices[0].message.content += chunk.choices[0].delta.content;
options.stream({text: chunk.choices[0].delta.content}); options.stream({text: chunk.choices[0].delta.content});
} }
if(chunk.choices[0].delta.tool_calls) { if(chunk.choices[0].delta.tool_calls) {
resp.choices[0].message.tool_calls = chunk.choices[0].delta.tool_calls; for(const deltaTC of chunk.choices[0].delta.tool_calls) {
const existing = resp.choices[0].message.tool_calls.find(tc => tc.index === deltaTC.index);
if(existing) {
if(deltaTC.id) existing.id = deltaTC.id;
if(deltaTC.type) existing.type = deltaTC.type;
if(deltaTC.function) {
if(!existing.function) existing.function = {};
if(deltaTC.function.name) existing.function.name = deltaTC.function.name;
if(deltaTC.function.arguments) existing.function.arguments = (existing.function.arguments || '') + deltaTC.function.arguments;
}
} else {
resp.choices[0].message.tool_calls.push({
index: deltaTC.index,
id: deltaTC.id || '',
type: deltaTC.type || 'function',
function: {
name: deltaTC.function?.name || '',
arguments: deltaTC.function?.arguments || ''
}
});
}
}
} }
} }
} }

View File

@@ -1,9 +1,15 @@
import * as cheerio from 'cheerio'; import * as cheerio from 'cheerio';
import {$, $Sync} from '@ztimson/node-utils'; import {$Sync} from '@ztimson/node-utils';
import {ASet, consoleInterceptor, Http, fn as Fn} from '@ztimson/utils'; import {ASet, consoleInterceptor, Http, fn as Fn} from '@ztimson/utils';
import * as os from 'node:os';
import {Ai} from './ai.ts'; import {Ai} from './ai.ts';
import {LLMRequest} from './llm.ts'; import {LLMRequest} from './llm.ts';
const getShell = () => {
if(os.platform() == 'win32') return 'cmd';
return $Sync`echo $SHELL`?.split('/').pop() || 'bash';
}
export type AiToolArg = {[key: string]: { export type AiToolArg = {[key: string]: {
/** Argument type */ /** Argument type */
type: 'array' | 'boolean' | 'number' | 'object' | 'string', type: 'array' | 'boolean' | 'number' | 'object' | 'string',
@@ -40,7 +46,7 @@ export const CliTool: AiTool = {
name: 'cli', name: 'cli',
description: 'Use the command line interface, returns any output', description: 'Use the command line interface, returns any output',
args: {command: {type: 'string', description: 'Command to run', required: true}}, args: {command: {type: 'string', description: 'Command to run', required: true}},
fn: (args: {command: string}) => $`${args.command}` fn: (args: {command: string}) => $Sync`${args.command}`
} }
export const DateTimeTool: AiTool = { export const DateTimeTool: AiTool = {
@@ -54,19 +60,20 @@ export const ExecTool: AiTool = {
name: 'exec', name: 'exec',
description: 'Run code/scripts', description: 'Run code/scripts',
args: { args: {
language: {type: 'string', description: 'Execution language', enum: ['cli', 'node', 'python'], required: true}, language: {type: 'string', description: `Execution language (CLI: ${getShell()})`, enum: ['cli', 'node', 'python'], required: true},
code: {type: 'string', description: 'Code to execute', required: true} code: {type: 'string', description: 'Code to execute', required: true}
}, },
fn: async (args, stream, ai) => { fn: async (args, stream, ai) => {
try { try {
switch(args.type) { switch(args.language) {
case 'bash': case 'cli':
return await CliTool.fn({command: args.code}, stream, ai); return await CliTool.fn({command: args.code}, stream, ai);
case 'node': case 'node':
return await JSTool.fn({code: args.code}, stream, ai); return await JSTool.fn({code: args.code}, stream, ai);
case 'python': { case 'python':
return await PythonTool.fn({code: args.code}, stream, ai); return await PythonTool.fn({code: args.code}, stream, ai);
} default:
throw new Error(`Unsupported language: ${args.language}`);
} }
} catch(err: any) { } catch(err: any) {
return {error: err?.message || err.toString()}; return {error: err?.message || err.toString()};
@@ -98,9 +105,9 @@ export const JSTool: AiTool = {
code: {type: 'string', description: 'CommonJS javascript', required: true} code: {type: 'string', description: 'CommonJS javascript', required: true}
}, },
fn: async (args: {code: string}) => { fn: async (args: {code: string}) => {
const console = consoleInterceptor(null); const c = consoleInterceptor(null);
const resp = await Fn<any>({console}, args.code, true).catch((err: any) => console.output.error.push(err)); const resp = await Fn<any>({console: c}, args.code, true).catch((err: any) => c.output.error.push(err));
return {...console.output, return: resp, stdout: undefined, stderr: undefined}; return {...c.output, return: resp, stdout: undefined, stderr: undefined};
} }
} }