kbot cache object level
This commit is contained in:
parent
ad216c888d
commit
243d8c2fad
File diff suppressed because one or more lines are too long
20
packages/kbot/dist-in/iterator.d.ts
vendored
20
packages/kbot/dist-in/iterator.d.ts
vendored
@ -1,5 +1,10 @@
|
||||
import { IKBotTask } from '@polymech/ai-tools';
|
||||
import { AsyncTransformer, ErrorCallback, FilterCallback } from './async-iterator.js';
|
||||
export interface ILogger {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string, error?: any) => void;
|
||||
}
|
||||
export declare const removeEmptyObjects: (obj: any) => any;
|
||||
export interface FieldMapping {
|
||||
jsonPath: string;
|
||||
@ -17,12 +22,7 @@ export interface CacheConfig {
|
||||
namespace?: string;
|
||||
expiration?: number;
|
||||
}
|
||||
export declare function createLLMTransformer(options: IKBotTask, logger?: {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string, error?: any) => void;
|
||||
}, cacheConfig?: CacheConfig): AsyncTransformer;
|
||||
export declare function createIterator(obj: Record<string, any>, optionsMixin: Partial<IKBotTask>, globalOptions?: {
|
||||
export interface IOptions {
|
||||
throttleDelay?: number;
|
||||
concurrentTasks?: number;
|
||||
errorCallback?: ErrorCallback;
|
||||
@ -30,11 +30,11 @@ export declare function createIterator(obj: Record<string, any>, optionsMixin: P
|
||||
transformerFactory?: (options: IKBotTask) => AsyncTransformer;
|
||||
maxRetries?: number;
|
||||
retryDelay?: number;
|
||||
logger?: {
|
||||
error: (message: string, error?: any) => void;
|
||||
};
|
||||
logger?: ILogger;
|
||||
cacheConfig?: CacheConfig;
|
||||
}): IteratorFactory;
|
||||
}
|
||||
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?: {
|
||||
throttleDelay?: number;
|
||||
concurrentTasks?: number;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -49,21 +49,21 @@ const exampleData = {
|
||||
// Field mappings definition
|
||||
const fieldMappings: FieldMapping[] = [
|
||||
{
|
||||
jsonPath: '$..description',
|
||||
jsonPath: '$.products.fruits[*].description',
|
||||
targetPath: null,
|
||||
options: {
|
||||
prompt: 'Make this description more engaging and detailed, around 20-30 words'
|
||||
}
|
||||
},
|
||||
{
|
||||
jsonPath: '$..nutrition',
|
||||
jsonPath: '$.products.fruits[*].details.nutrition',
|
||||
targetPath: null,
|
||||
options: {
|
||||
prompt: 'Expand this nutrition information with 2-3 specific health benefits, around 25-35 words'
|
||||
}
|
||||
},
|
||||
{
|
||||
jsonPath: '$..name',
|
||||
jsonPath: '$.products.fruits[*].name',
|
||||
targetPath: 'marketingName',
|
||||
options: {
|
||||
prompt: 'Generate a more appealing marketing name for this product'
|
||||
@ -89,17 +89,6 @@ export async function factoryExample() {
|
||||
console.log("========================================");
|
||||
|
||||
try {
|
||||
// Clear the test-data folder
|
||||
const outputPath = path.resolve('./tests/test-data/core/iterator-factory-data.json');
|
||||
const outputDir = path.dirname(outputPath);
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
// Make sure the file is empty to start with a clean slate
|
||||
if (fs.existsSync(outputPath)) {
|
||||
fs.unlinkSync(outputPath);
|
||||
}
|
||||
|
||||
const data = JSON.parse(JSON.stringify(exampleData));
|
||||
|
||||
// Global options for all transformations
|
||||
@ -120,7 +109,7 @@ export async function factoryExample() {
|
||||
errorCallback,
|
||||
filterCallback: async () => true,
|
||||
transformerFactory: (options) => createLLMTransformer(options, logger, cacheConfig),
|
||||
logger: logger,
|
||||
logger,
|
||||
cacheConfig
|
||||
}
|
||||
);
|
||||
@ -129,15 +118,25 @@ export async function factoryExample() {
|
||||
console.log("First run - should transform and cache results:");
|
||||
await iterator.transform(fieldMappings);
|
||||
|
||||
const outputPath = path.resolve('./tests/test-data/core/iterator-factory-data.json');
|
||||
const outputDir = path.dirname(outputPath);
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
// Make sure the file is empty to start with a clean slate
|
||||
if (fs.existsSync(outputPath)) {
|
||||
fs.unlinkSync(outputPath);
|
||||
}
|
||||
|
||||
write(outputPath, JSON.stringify(data, null, 2));
|
||||
console.log("Transformation complete. Results saved to", outputPath);
|
||||
|
||||
// Print the transformed data structure
|
||||
console.log("\nTransformed data structure:");
|
||||
console.log(JSON.stringify(data.products.fruits[0], null, 2).substring(0, 200) + "...");
|
||||
console.log(JSON.stringify(data.products.fruits[0], null, 2));
|
||||
|
||||
// Create a second instance with the same data to test cache
|
||||
console.log("\n\n========================================");
|
||||
console.log("\n========================================");
|
||||
console.log("Second run - should use cached results:");
|
||||
console.log("========================================");
|
||||
|
||||
@ -152,7 +151,7 @@ export async function factoryExample() {
|
||||
errorCallback,
|
||||
filterCallback: async () => true,
|
||||
transformerFactory: (options) => createLLMTransformer(options, logger, cacheConfig),
|
||||
logger: logger,
|
||||
logger,
|
||||
cacheConfig
|
||||
}
|
||||
);
|
||||
@ -162,13 +161,9 @@ export async function factoryExample() {
|
||||
|
||||
console.log("\nBefore/After Comparison Example:");
|
||||
console.log(`Original description: ${exampleData.products.fruits[0].description}`);
|
||||
console.log(`Transformed description: ${data.products.fruits[0].description}`);
|
||||
console.log(`Transformed description: ${data2.products.fruits[0].description}`);
|
||||
console.log(`Original name: ${exampleData.products.fruits[0].name}`);
|
||||
|
||||
// Check if name is an object with marketingName property
|
||||
const name = data.products.fruits[0].name;
|
||||
const marketingName = typeof name === 'object' && name !== null ? name.marketingName : 'Not available';
|
||||
console.log(`New marketing name: ${marketingName}`);
|
||||
console.log(`Marketing name: ${data2.products.fruits[0].marketingName || 'Not available'}`);
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
|
||||
@ -2,6 +2,19 @@ import { IKBotTask } from '@polymech/ai-tools'
|
||||
import { AsyncTransformer, ErrorCallback, FilterCallback, defaultError, defaultFilters, testFilters, transformObjectWithOptions } 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"
|
||||
|
||||
export interface ILogger {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string, error?: any) => void;
|
||||
}
|
||||
|
||||
const dummyLogger: ILogger = {
|
||||
info: () => {},
|
||||
warn: () => {},
|
||||
error: () => {}
|
||||
};
|
||||
|
||||
export const removeEmptyObjects = (obj: any): any => {
|
||||
if (obj === null || obj === undefined) return obj;
|
||||
@ -39,6 +52,18 @@ export interface CacheConfig {
|
||||
expiration?: number; // in seconds
|
||||
}
|
||||
|
||||
export interface IOptions {
|
||||
throttleDelay?: number;
|
||||
concurrentTasks?: number;
|
||||
errorCallback?: ErrorCallback;
|
||||
filterCallback?: FilterCallback;
|
||||
transformerFactory?: (options: IKBotTask) => AsyncTransformer;
|
||||
maxRetries?: number;
|
||||
retryDelay?: number;
|
||||
logger?: ILogger;
|
||||
cacheConfig?: CacheConfig;
|
||||
}
|
||||
|
||||
const DEFAULT_CACHE_CONFIG: Required<CacheConfig> = {
|
||||
enabled: true,
|
||||
namespace: 'llm-responses',
|
||||
@ -47,21 +72,15 @@ const DEFAULT_CACHE_CONFIG: Required<CacheConfig> = {
|
||||
|
||||
export function createLLMTransformer(
|
||||
options: IKBotTask,
|
||||
logger?: {
|
||||
info: (message: string) => void;
|
||||
warn: (message: string) => void;
|
||||
error: (message: string, error?: any) => void;
|
||||
},
|
||||
logger: ILogger = dummyLogger,
|
||||
cacheConfig?: CacheConfig
|
||||
): AsyncTransformer {
|
||||
const config: Required<CacheConfig> = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
|
||||
|
||||
return async (input: string, jsonPath: string): Promise<string> => {
|
||||
if (logger) {
|
||||
logger.info(`Transforming field at path: ${jsonPath}`);
|
||||
logger.info(`Input: ${input}`);
|
||||
logger.info(`Using prompt: ${options.prompt}`);
|
||||
}
|
||||
logger.info(`Transforming field at path: ${jsonPath}`);
|
||||
logger.info(`Input: ${input}`);
|
||||
logger.info(`Using prompt: ${options.prompt}`);
|
||||
|
||||
const kbotTask: IKBotTask = {
|
||||
...options,
|
||||
@ -81,7 +100,7 @@ export function createLLMTransformer(
|
||||
if (config.enabled) {
|
||||
const cachedResponse = await get_cached_object({ ca_options: cacheKeyObj }, config.namespace) as { content: string };
|
||||
if (cachedResponse?.content) {
|
||||
if (logger) logger.info(`Using cached LLM response for prompt: ${kbotTask.prompt.substring(0, 100)}...`);
|
||||
logger.info(`Using cached LLM response for prompt: ${kbotTask.prompt.substring(0, 100)}...`);
|
||||
return cachedResponse.content;
|
||||
}
|
||||
}
|
||||
@ -97,21 +116,17 @@ export function createLLMTransformer(
|
||||
{ content: result },
|
||||
{ expiration: config.expiration }
|
||||
);
|
||||
if (logger) logger.info(`Cached LLM response for prompt: ${kbotTask.prompt.substring(0, 100)}...`);
|
||||
logger.info(`Cached LLM response for prompt: ${kbotTask.prompt.substring(0, 100)}...`);
|
||||
}
|
||||
|
||||
if (logger) logger.info(`Result: ${result}`);
|
||||
logger.info(`Result: ${result}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (logger) logger.warn(`No valid result received for ${jsonPath}, returning original`);
|
||||
logger.warn(`No valid result received for ${jsonPath}, returning original`);
|
||||
return input;
|
||||
} catch (error) {
|
||||
if (logger) {
|
||||
logger.error(`Error calling LLM API: ${error.message}`, error);
|
||||
} else {
|
||||
console.error(`Error calling LLM API: ${error.message}`, error);
|
||||
}
|
||||
logger.error(`Error calling LLM API: ${error.message}`, error);
|
||||
return input;
|
||||
}
|
||||
};
|
||||
@ -120,17 +135,7 @@ export function createLLMTransformer(
|
||||
export function createIterator(
|
||||
obj: Record<string, any>,
|
||||
optionsMixin: Partial<IKBotTask>,
|
||||
globalOptions: {
|
||||
throttleDelay?: number
|
||||
concurrentTasks?: number
|
||||
errorCallback?: ErrorCallback
|
||||
filterCallback?: FilterCallback
|
||||
transformerFactory?: (options: IKBotTask) => AsyncTransformer
|
||||
maxRetries?: number
|
||||
retryDelay?: number
|
||||
logger?: { error: (message: string, error?: any) => void }
|
||||
cacheConfig?: CacheConfig
|
||||
} = {}
|
||||
globalOptions: IOptions = {}
|
||||
): IteratorFactory {
|
||||
const {
|
||||
throttleDelay = 1000,
|
||||
@ -140,26 +145,71 @@ export function createIterator(
|
||||
transformerFactory,
|
||||
maxRetries = 3,
|
||||
retryDelay = 2000,
|
||||
logger,
|
||||
logger = dummyLogger,
|
||||
cacheConfig
|
||||
} = globalOptions;
|
||||
|
||||
const config: Required<CacheConfig> = { ...DEFAULT_CACHE_CONFIG, ...cacheConfig };
|
||||
|
||||
const defaultTransformerFactory = (options: IKBotTask): AsyncTransformer => {
|
||||
return async (input: string): Promise<string> => input;
|
||||
};
|
||||
|
||||
const createTransformer = transformerFactory || defaultTransformerFactory;
|
||||
|
||||
const createObjectCacheKey = (data: Record<string, any>, mappings: FieldMapping[]) => {
|
||||
return removeEmptyObjects({
|
||||
data: JSON.stringify(data),
|
||||
mappings: mappings.map(m => ({
|
||||
jsonPath: m.jsonPath,
|
||||
targetPath: m.targetPath,
|
||||
options: {
|
||||
model: optionsMixin.model,
|
||||
router: optionsMixin.router,
|
||||
mode: optionsMixin.mode,
|
||||
prompt: m.options?.prompt
|
||||
}
|
||||
}))
|
||||
});
|
||||
};
|
||||
|
||||
const deepMerge = (target: Record<string, any>, source: Record<string, any>) => {
|
||||
for (const key in source) {
|
||||
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
||||
if (!target[key]) target[key] = {};
|
||||
deepMerge(target[key], source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
createTransformer,
|
||||
transform: async (mappings: FieldMapping[]): Promise<void> => {
|
||||
if (config.enabled) {
|
||||
const objectCacheKey = createObjectCacheKey(obj, mappings);
|
||||
const cachedObject = await get_cached_object(
|
||||
{ ca_options: objectCacheKey },
|
||||
'transformed-objects'
|
||||
) as { content: Record<string, any> };
|
||||
|
||||
if (cachedObject?.content) {
|
||||
logger.info('Using cached transformed object');
|
||||
deepMerge(obj, cachedObject.content);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If no cache hit or caching disabled, perform the transformations
|
||||
const transformedObj = JSON.parse(JSON.stringify(obj));
|
||||
for (const mapping of mappings) {
|
||||
const mergedOptions = { ...optionsMixin, ...mapping.options } as IKBotTask;
|
||||
const { jsonPath, targetPath = null, maxRetries: mappingRetries, retryDelay: mappingRetryDelay } = mapping;
|
||||
const transformer = createTransformer(mergedOptions);
|
||||
|
||||
await transformObjectWithOptions(
|
||||
obj,
|
||||
transformedObj,
|
||||
transformer,
|
||||
{
|
||||
jsonPath,
|
||||
@ -173,6 +223,21 @@ export function createIterator(
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Cache the transformed object
|
||||
if (config.enabled) {
|
||||
const objectCacheKey = createObjectCacheKey(obj, mappings);
|
||||
await set_cached_object(
|
||||
{ ca_options: objectCacheKey },
|
||||
'transformed-objects',
|
||||
{ content: transformedObj },
|
||||
{ expiration: config.expiration }
|
||||
);
|
||||
logger.info('Cached transformed object');
|
||||
}
|
||||
|
||||
// Apply the transformations to the original object
|
||||
deepMerge(obj, transformedObj);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user