kbot cache implementations

This commit is contained in:
lovebird 2025-04-07 19:40:44 +02:00
parent 4c40bcdbac
commit e2ba560720
14 changed files with 433 additions and 103 deletions

View File

@ -0,0 +1,16 @@
export interface CacheProvider {
get(key: object, namespace: string): Promise<any | null>;
set(key: object, namespace: string, value: any, options?: {
expiration?: number;
}): Promise<void>;
delete(key: object, namespace: string): Promise<void>;
}
export interface CacheConfig {
enabled?: boolean;
provider?: 'default' | 'redis';
namespace?: string;
expiration?: number;
redisUrl?: string;
}
export declare const DEFAULT_CACHE_CONFIG: Required<Omit<CacheConfig, 'redisUrl'>>;
export declare function createCacheProvider(config?: CacheConfig): CacheProvider;

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,6 @@
import { IKBotTask } from '@polymech/ai-tools';
import { AsyncTransformer, ErrorCallback, FilterCallback, OnTransformCallback, OnTransformedCallback, INetworkOptions } from './async-iterator.js';
import { CacheConfig } from './iterator-cache.js';
/**
* Notes for LLM modifications
*
@ -22,11 +23,6 @@ export interface IteratorFactory {
transform: (mappings: FieldMapping[]) => Promise<void>;
createTransformer: (options: IKBotTask) => AsyncTransformer;
}
export interface CacheConfig {
enabled?: boolean;
namespace?: string;
expiration?: number;
}
export interface IOptions {
network?: INetworkOptions;
errorCallback?: ErrorCallback;
@ -38,6 +34,7 @@ export interface IOptions {
onTransformed?: OnTransformedCallback;
}
export { INetworkOptions };
export { CacheConfig };
export declare function createLLMTransformer(options: IKBotTask, logger?: ILogger, cacheConfig?: CacheConfig): AsyncTransformer;
export declare function createIterator(obj: Record<string, any>, optionsMixin: Partial<IKBotTask>, globalOptions?: IOptions): IteratorFactory;
export declare function transformWithMappings(obj: Record<string, any>, createTransformer: (options: IKBotTask) => AsyncTransformer, mappings: FieldMapping[], globalOptions?: IOptions): Promise<void>;

File diff suppressed because one or more lines are too long

View File

@ -17,12 +17,15 @@ export const preferences = async (opts) => {
let preferencesPath = path.resolve(path.join(process.cwd(), PREFERENCES_FILE_NAME));
if (!exists(preferencesPath)) {
// Fall back to specified preferences path if local file doesn't exist
preferencesPath = path.resolve(resolve(opts.preferences, false, env_vars()));
// Only resolve if opts.preferences is actually defined
preferencesPath = opts.preferences ? path.resolve(resolve(opts.preferences, false, env_vars())) : '';
}
const preferences = read(preferencesPath, 'string');
// Only read if preferencesPath is valid and exists
const preferencesContent = preferencesPath && exists(preferencesPath) ? read(preferencesPath, 'string') : '';
return {
role: "user",
content: `USER Preferences : ${preferences}` || ''
// Only include content if preferences were actually loaded
content: preferencesContent ? `USER Preferences : ${preferencesContent}` : ''
};
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvbXB0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3Byb21wdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxPQUFPLEVBQUUsSUFBSSxJQUFJLElBQUksRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2hELE9BQU8sRUFBRSxJQUFJLElBQUksTUFBTSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFDcEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQzNDLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFBO0FBRWpDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQTtBQUMvQyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFDekMsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFFdEQsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLEtBQUssRUFBRSxJQUFlLEVBQW1ELEVBQUU7SUFDN0YsTUFBTSxLQUFLLEdBQUcsTUFBTSxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDdEMsT0FBTztRQUNILElBQUksRUFBRSxNQUFNO1FBQ1osT0FBTyxFQUFFLEtBQUssSUFBSSxFQUFFO0tBQ3ZCLENBQUE7QUFDTCxDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsS0FBSyxFQUFFLElBQWUsRUFBbUQsRUFBRTtJQUNsRyw4QkFBOEI7SUFDOUIsSUFBSSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxxQkFBcUIsQ0FBQyxDQUFDLENBQUE7SUFDbkYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsRUFBRSxDQUFDO1FBQzNCLHNFQUFzRTtRQUN0RSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFBO0lBQ2hGLENBQUM7SUFDRCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsZUFBZSxFQUFFLFFBQVEsQ0FBVyxDQUFBO0lBQzdELE9BQU87UUFDSCxJQUFJLEVBQUUsTUFBTTtRQUNaLE9BQU8sRUFBRSxzQkFBc0IsV0FBVyxFQUFFLElBQUksRUFBRTtLQUNyRCxDQUFBO0FBQ0wsQ0FBQyxDQUFBIn0=
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvbXB0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3Byb21wdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFJQSxPQUFPLEVBQUUsSUFBSSxJQUFJLElBQUksRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2hELE9BQU8sRUFBRSxJQUFJLElBQUksTUFBTSxFQUFFLE1BQU0scUJBQXFCLENBQUE7QUFDcEQsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQzNDLE9BQU8sS0FBSyxJQUFJLE1BQU0sV0FBVyxDQUFBO0FBRWpDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQTtBQUMvQyxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFDekMsT0FBTyxFQUFFLHFCQUFxQixFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFFdEQsTUFBTSxDQUFDLE1BQU0sTUFBTSxHQUFHLEtBQUssRUFBRSxJQUFlLEVBQW1ELEVBQUU7SUFDN0YsTUFBTSxLQUFLLEdBQUcsTUFBTSxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDdEMsT0FBTztRQUNILElBQUksRUFBRSxNQUFNO1FBQ1osT0FBTyxFQUFFLEtBQUssSUFBSSxFQUFFO0tBQ3ZCLENBQUE7QUFDTCxDQUFDLENBQUE7QUFFRCxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsS0FBSyxFQUFFLElBQWUsRUFBbUQsRUFBRTtJQUNsRyw4QkFBOEI7SUFDOUIsSUFBSSxlQUFlLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUUsRUFBRSxxQkFBcUIsQ0FBQyxDQUFDLENBQUE7SUFDbkYsSUFBSSxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsRUFBRSxDQUFDO1FBQzNCLHNFQUFzRTtRQUN0RSx1REFBdUQ7UUFDdkQsZUFBZSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsS0FBSyxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFBO0lBQ3hHLENBQUM7SUFDRCxtREFBbUQ7SUFDbkQsTUFBTSxrQkFBa0IsR0FBRyxlQUFlLElBQUksTUFBTSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsZUFBZSxFQUFFLFFBQVEsQ0FBVyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUE7SUFDdEgsT0FBTztRQUNILElBQUksRUFBRSxNQUFNO1FBQ1osMkRBQTJEO1FBQzNELE9BQU8sRUFBRSxrQkFBa0IsQ0FBQyxDQUFDLENBQUMsc0JBQXNCLGtCQUFrQixFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUU7S0FDaEYsQ0FBQTtBQUNMLENBQUMsQ0FBQSJ9

View File

@ -1,13 +1,13 @@
{
"model": "mistralai/mistral-tiny",
"model": "openai/chatgpt-4o-latest",
"messages": [
{
"role": "user",
"content": "Generate a random number"
"content": "Analyze this product review and extract key information\n\nText to transform: \"Great selection of fruits with good prices and quality. Some items were out of stock.\""
},
{
"role": "user",
"content": "USER Preferences : undefined"
"content": ""
}
],
"tools": []

View File

@ -7,6 +7,7 @@
"": {
"name": "@polymech/kbot-d",
"version": "0.3.5",
"license": "MIT",
"dependencies": {
"@polymech/ai-tools": "file:../ai-tools",
"@polymech/cache": "file:../cache",
@ -22,6 +23,7 @@
"emojilib": "4.0.1",
"env-var": "7.5.0",
"glob": "11.0.1",
"ioredis": "^5.4.1",
"json-schema-to-zod": "2.6.0",
"jsonpath-plus": "10.3.0",
"marked": "14.1.4",
@ -1085,6 +1087,12 @@
}
}
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@ -3128,6 +3136,15 @@
"node": ">=6"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -3287,6 +3304,15 @@
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@ -4435,6 +4461,30 @@
"node": ">=10.13.0"
}
},
"node_modules/ioredis": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz",
"integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==",
"license": "MIT",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
@ -4817,6 +4867,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
"license": "MIT"
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -6347,6 +6409,27 @@
"node": ">= 10.13.0"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"license": "MIT",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/remark-parse": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
@ -6766,6 +6849,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View File

@ -63,6 +63,7 @@
"emojilib": "4.0.1",
"env-var": "7.5.0",
"glob": "11.0.1",
"ioredis": "^5.4.1",
"json-schema-to-zod": "2.6.0",
"jsonpath-plus": "10.3.0",
"marked": "14.1.4",

View File

@ -0,0 +1,139 @@
import { get_cached_object, set_cached_object, rm_cached_object } from "@polymech/cache";
import { Redis } from 'ioredis';
export interface CacheProvider {
get(key: object, namespace: string): Promise<any | null>;
set(key: object, namespace: string, value: any, options?: { expiration?: number }): Promise<void>;
delete(key: object, namespace: string): Promise<void>;
}
export interface CacheConfig {
enabled?: boolean;
provider?: 'default' | 'redis';
namespace?: string;
expiration?: number; // in seconds
redisUrl?: string; // e.g., 'redis://localhost:6379'
}
export const DEFAULT_CACHE_CONFIG: Required<Omit<CacheConfig, 'redisUrl'>> = {
enabled: true,
provider: 'default',
namespace: 'default-cache',
expiration: 7 * 24 * 60 * 60 // 7 days in seconds
};
// --- Default Cache Provider (using @polymech/cache) ---
class DefaultCacheProvider implements CacheProvider {
async get(key: object, namespace: string): Promise<any | null> {
try {
return await get_cached_object({ ca_options: key }, namespace);
} catch (error) {
console.error(`DefaultCache: Error getting cache for key ${JSON.stringify(key)} in namespace ${namespace}:`, error);
return null;
}
}
async set(key: object, namespace: string, value: any, options?: { expiration?: number }): Promise<void> {
try {
await set_cached_object({ ca_options: key }, namespace, value, options);
} catch (error) {
console.error(`DefaultCache: Error setting cache for key ${JSON.stringify(key)} in namespace ${namespace}:`, error);
}
}
async delete(key: object, namespace: string): Promise<void> {
try {
await rm_cached_object({ ca_options: key }, namespace);
} catch (error) {
console.error(`DefaultCache: Error deleting cache for key ${JSON.stringify(key)} in namespace ${namespace}:`, error);
}
}
}
// --- Redis Cache Provider ---
class RedisCacheProvider implements CacheProvider {
private redis: Redis;
constructor(redisUrl?: string) {
// Default to local instance if no URL is provided
this.redis = new Redis(redisUrl || 'redis://localhost:6379');
this.redis.on('error', (err) => console.error('Redis Client Error', err));
}
private generateKey(key: object, namespace: string): string {
// Simple serialization; consider a more robust hashing function for complex keys
return `${namespace}:${JSON.stringify(key)}`;
}
async get(key: object, namespace: string): Promise<any | null> {
const redisKey = this.generateKey(key, namespace);
try {
const data = await this.redis.get(redisKey);
return data ? JSON.parse(data) : null;
} catch (error) {
console.error(`RedisCache: Error getting cache for key ${redisKey}:`, error);
return null;
}
}
async set(key: object, namespace: string, value: any, options?: { expiration?: number }): Promise<void> {
const redisKey = this.generateKey(key, namespace);
try {
const stringValue = JSON.stringify(value);
if (options?.expiration) {
await this.redis.set(redisKey, stringValue, 'EX', options.expiration);
} else {
await this.redis.set(redisKey, stringValue);
}
} catch (error) {
console.error(`RedisCache: Error setting cache for key ${redisKey}:`, error);
}
}
async delete(key: object, namespace: string): Promise<void> {
const redisKey = this.generateKey(key, namespace);
try {
await this.redis.del(redisKey);
} catch (error) {
console.error(`RedisCache: Error deleting cache for key ${redisKey}:`, error);
}
}
async disconnect(): Promise<void> {
await this.redis.quit();
}
}
// --- Factory Function ---
let defaultCacheProviderInstance: CacheProvider | null = null;
let redisCacheProviderInstance: CacheProvider | null = null;
export function createCacheProvider(config?: CacheConfig): CacheProvider {
const mergedConfig = { ...DEFAULT_CACHE_CONFIG, ...config };
if (!mergedConfig.enabled) {
// Return a dummy provider if caching is disabled
return {
get: async () => null,
set: async () => {},
delete: async () => {},
};
}
if (mergedConfig.provider === 'redis') {
if (!redisCacheProviderInstance) {
// Pass redisUrl if provided in config
redisCacheProviderInstance = new RedisCacheProvider(config?.redisUrl);
}
return redisCacheProviderInstance;
}
// Default provider
if (!defaultCacheProviderInstance) {
defaultCacheProviderInstance = new DefaultCacheProvider();
}
return defaultCacheProviderInstance;
}

View File

@ -13,8 +13,8 @@ import {
DEFAULT_NETWORK_OPTIONS
} from './async-iterator.js'
import { run } from './commands/run.js'
import { get_cached_object, set_cached_object, rm_cached_object } from "@polymech/cache"
import { deepClone } from "@polymech/core/objects"
import { CacheConfig, createCacheProvider, CacheProvider, DEFAULT_CACHE_CONFIG } from './iterator-cache.js'
/**
* Notes for LLM modifications
@ -64,12 +64,6 @@ export interface IteratorFactory {
createTransformer: (options: IKBotTask) => AsyncTransformer
}
export interface CacheConfig {
enabled?: boolean;
namespace?: string;
expiration?: number; // in seconds
}
export interface IOptions {
network?: INetworkOptions;
errorCallback?: ErrorCallback;
@ -81,21 +75,18 @@ export interface IOptions {
onTransformed?: OnTransformedCallback;
}
const DEFAULT_CACHE_CONFIG: Required<CacheConfig> = {
enabled: true,
namespace: 'llm-responses',
expiration: 7 * 24 * 60 * 60 // 7 days in seconds
};
// Re-export INetworkOptions for other modules to use
export { INetworkOptions };
// Re-export CacheConfig as well
export { CacheConfig };
export function createLLMTransformer(
options: IKBotTask,
logger: ILogger = dummyLogger,
cacheConfig?: CacheConfig
): AsyncTransformer {
const config: Required<CacheConfig> = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
const mergedCacheConfig = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
const cacheProvider = createCacheProvider(mergedCacheConfig);
return async (input: string, jsonPath: string): Promise<string> => {
logger.info(`Transforming field at path: ${jsonPath}`);
@ -117,8 +108,8 @@ export function createLLMTransformer(
});
try {
if (config.enabled) {
const cachedResponse = await get_cached_object({ ca_options: cacheKeyObj }, config.namespace) as { content: string };
if (mergedCacheConfig.enabled) {
const cachedResponse = await cacheProvider.get(cacheKeyObj, 'llm-responses') as { content: string };
if (cachedResponse?.content) {
logger.info(`Using cached LLM response for prompt: ${kbotTask.prompt.substring(0, 100)}...`);
return cachedResponse.content;
@ -129,12 +120,12 @@ export function createLLMTransformer(
if (results && results.length > 0 && typeof results[0] === 'string') {
const result = results[0].trim();
if (config.enabled) {
await set_cached_object(
{ ca_options: cacheKeyObj },
config.namespace,
if (mergedCacheConfig.enabled) {
await cacheProvider.set(
cacheKeyObj,
'llm-responses',
{ content: result },
{ expiration: config.expiration }
{ expiration: mergedCacheConfig.expiration }
);
logger.info(`Cached LLM response for prompt: ${kbotTask.prompt.substring(0, 100)}...`);
}
@ -173,7 +164,9 @@ export function createIterator(
...network
};
const config: Required<CacheConfig> = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
const mergedCacheConfig = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
const cacheProvider = createCacheProvider(mergedCacheConfig);
const objCacheNamespace = 'transformed-objects';
const defaultTransformerFactory = (options: IKBotTask): AsyncTransformer => {
return async (input: string): Promise<string> => input;
@ -213,16 +206,15 @@ export function createIterator(
transform: async (mappings: FieldMapping[]): Promise<void> => {
// *** Object Cache Check (Start) ***
let objectCacheKey: any;
if (config.enabled) {
objectCacheKey = createObjectCacheKey(obj, mappings); // Key based on initial state
const cachedObject = await get_cached_object(
{ ca_options: objectCacheKey },
'transformed-objects'
if (mergedCacheConfig.enabled) {
objectCacheKey = createObjectCacheKey(obj, mappings);
const cachedObject = await cacheProvider.get(
objectCacheKey,
objCacheNamespace
) as { content: Record<string, any> };
if (cachedObject?.content) {
logger.info('Using cached transformed object');
// Clear the original object before merging cache to avoid partial states
Object.keys(obj).forEach(key => delete obj[key]);
deepMerge(obj, cachedObject.content);
return;
@ -230,16 +222,13 @@ export function createIterator(
}
// *** Object Cache Check (End) ***
// If no cache hit or caching disabled, perform the transformations directly on 'obj'
// REMOVED: const transformedObj = JSON.parse(JSON.stringify(obj));
for (const mapping of mappings) {
const mergedOptions = { ...optionsMixin, ...mapping.options } as IKBotTask;
const { jsonPath, targetPath = null } = mapping;
const transformer = createTransformer(mergedOptions);
// Call transformObjectWithOptions directly on the original 'obj'
await transformObjectWithOptions(
obj, // <<< Operate directly on obj
obj,
transformer,
{
jsonPath,
@ -255,19 +244,16 @@ export function createIterator(
}
// *** Object Cache Setting (Start) ***
// Cache the final state of the modified 'obj'
if (config.enabled && objectCacheKey) { // Ensure key was generated
await set_cached_object(
{ ca_options: objectCacheKey },
'transformed-objects',
{ content: obj }, // <<< Cache the final obj
{ expiration: config.expiration }
if (mergedCacheConfig.enabled && objectCacheKey) {
await cacheProvider.set(
objectCacheKey,
objCacheNamespace,
{ content: obj },
{ expiration: mergedCacheConfig.expiration }
);
logger.info('Cached transformed object');
}
// *** Object Cache Setting (End) ***
// REMOVED: deepMerge(obj, transformedObj);
}
};
}
@ -278,7 +264,6 @@ export async function transformWithMappings(
mappings: FieldMapping[],
globalOptions: IOptions = {}
): Promise<void> {
// Pass the provided createTransformer as the transformerFactory in options
const optionsWithTransformer: IOptions = {
...globalOptions,
transformerFactory: createTransformer

View File

@ -24,11 +24,14 @@ export const preferences = async (opts: IKBotTask): Promise<ChatCompletionMessag
let preferencesPath = path.resolve(path.join(process.cwd(), PREFERENCES_FILE_NAME))
if (!exists(preferencesPath)) {
// Fall back to specified preferences path if local file doesn't exist
preferencesPath = path.resolve(resolve(opts.preferences, false, env_vars()))
// Only resolve if opts.preferences is actually defined
preferencesPath = opts.preferences ? path.resolve(resolve(opts.preferences, false, env_vars())) : ''
}
const preferences = read(preferencesPath, 'string') as string
// Only read if preferencesPath is valid and exists
const preferencesContent = preferencesPath && exists(preferencesPath) ? read(preferencesPath, 'string') as string : ''
return {
role: "user",
content: `USER Preferences : ${preferences}` || ''
// Only include content if preferences were actually loaded
content: preferencesContent ? `USER Preferences : ${preferencesContent}` : ''
}
}

View File

@ -4,24 +4,24 @@
{
"id": "f1",
"name": "apple",
"description": "A deliciously sweet, crisp, and refreshing fruit packed with flavor.",
"description": "A lusciously sweet, vibrantly colored fruit with irresistible crunch.",
"details": {
"color": "red",
"origin": "Worldwide",
"nutrition": "Rich in fiber and vitamin D, this food supports healthy digestion, helps regulate blood sugar levels, and promotes strong bones by enhancing calcium absorption and supporting the immune system. [HEALTH FOCUS]"
"nutrition": "Rich in fiber and vitamin D, this food supports healthy digestion and promotes regular bowel movements. Vitamin D also strengthens bones, boosts immune function, and may help improve mood and energy levels. [HEALTH FOCUS]"
},
"marketingName": "Sure! Here are a few appealing marketing name options for \"apple\":\n\n1. Crimson Bliss\n2. Orchard Jewel\n3. Scarlet Crunch\n4. Natures Candy\n5. Ruby Crisp\n6. SweetHarvest\n7. Eden Bite\n8. Golden Orchard (if its a yellow variety)\n9. PurePom\n10. FreshMuse\n\nLet me know if you'd like names tailored to a specific apple variety or target audience!"
"marketingName": "Sure! Here are a few more appealing marketing name ideas for the product \"apple\":\n\n1. **Crimson Orchard Delight**\n2. **Edens Bite**\n3. **Ruby Crisp**\n4. **Natures Jewel**\n5. **SweetHarvest**\n6. **BlushBloom**\n7. **Golden Grove**\n8. **Scarlet Whisper**\n9. **FreshMuse**\n10. **Core Essence**\n\nLet me know the specific tone or target audience you're going for, and I can tailor the names further!"
},
{
"id": "f2",
"name": "banana",
"description": "Vibrant, sun-kissed yellow tropical fruit bursting with sweet flavor",
"description": "A vibrant, sun-kissed tropical fruit bursting with sweet, exotic flavor.",
"details": {
"color": "yellow",
"origin": "Southeast Asia",
"nutrition": "High in potassium, which helps regulate blood pressure, supports proper muscle function, and maintains fluid balance in the body, contributing to overall cardiovascular and muscular health. [HEALTH FOCUS]"
"nutrition": "High in potassium, which helps regulate blood pressure, supports proper muscle function, and maintains fluid balance. It also plays a key role in nerve signaling and can reduce the risk of stroke. [HEALTH FOCUS]"
},
"marketingName": "Golden Delight"
"marketingName": "Sure! Here are several appealing marketing name ideas for \"banana,\" tailored to different branding styles:\n\n1. **Golden Bliss** Emphasizes natural sweetness and luxury.\n2. **SunBurst Banana** Suggests freshness and energy.\n3. **TropiSweet** A fun, tropical twist with a focus on sweetness.\n4. **Natures Smile** Highlights natural origins and positive feelings.\n5. **Bananza** A playful and memorable take on \"banana.\"\n6. **Yellow Zest** A zippy, vibrant name suggesting flavor and fun.\n7. **Velvet Peel** Evokes a smooth, premium experience.\n8. **Bananique** A more exotic, alluring twist on the original name.\n9. **Sunshine Fruit** Warm and inviting, focuses on natural appeal.\n10. **GoGo Banana** Youthful and energetic, ideal for snacking products.\n\nLet me know if you'd like names tailored to a specific use case or audience!"
}
]
},
@ -47,12 +47,12 @@
30,
60
],
"pricingAnalysis": "Prices show moderate volatility with a peak at 12 and a low at 3, indicating fluctuating demand. Overall trend is slightly upward with intermittent dips, suggesting cautious market optimism.",
"ratingsSummary": "Average rating is 4.3, indicating generally positive user feedback with minor variations in satisfaction.",
"inventoryStatus": "Stock levels are moderately balanced, though the lowest item at 30 units may need restocking soon."
"pricingAnalysis": "The market shows moderate volatility with prices fluctuating between 3 and 12. A peak at 12 suggests potential upward momentum, but inconsistent trends indicate uncertainty.",
"ratingsSummary": "Consistently high ratings averaging around 4.3 indicate strong overall customer satisfaction with the product.",
"inventoryStatus": "Stock levels are adequate overall, but the lowest item may need restocking soon to avoid shortages."
},
"productReview": {
"reviewText": "Great selection of fruits with good prices and quality. Some items were out of stock.",
"analysis": "Key Information Extracted:\n\n- Positive Aspects:\n - Good selection of fruits\n - Competitive prices\n - High quality\n\n- Negative Aspects:\n - Some items were out of stock"
"analysis": "Here is the extracted key information from the product review:\n\n- Positive Aspects:\n - Wide/great selection of fruits\n - Good prices\n - High quality of products\n\n- Negative Aspects:\n - Some items were out of stock\n\nSummary:\nThe review praises the variety, affordability, and quality of the fruit selection but notes limited availability due to out-of-stock items."
}
}

View File

@ -633,14 +633,7 @@
"offset": 1193
}
},
"manualAnalysisResult": {
"keywords": [
"sunny weather",
"ideal",
"park walk"
],
"sentiment": "positive"
}
"tempId": "analyze-me"
},
{
"type": "heading",

View File

@ -5,6 +5,7 @@
"rootDir": "src",
"baseUrl": ".",
"allowJs": true,
"esModuleInterop": true,
"composite": false,
"importHelpers": false,
"inlineSourceMap": true,