kbot iterator callbacks

This commit is contained in:
lovebird 2025-04-07 16:12:51 +02:00
parent b2ef96e786
commit 1991695594
6 changed files with 112 additions and 89 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@
"messages": [
{
"role": "user",
"content": "Make this description more engaging and detailed, around 10 words\n\nText to transform: \"[PRODUCT INFO] A yellow tropical fruit\""
"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",

View File

@ -8,7 +8,6 @@ export type AsyncTransformer = (input: string, path: string) => Promise<string>
export type ErrorCallback = (path: string, value: string, error: unknown) => void
export type FilterCallback = (input: string, path: string) => Promise<boolean>
export type Filter = (input: string) => Promise<boolean>
// Define the new callback types, passing IKBotTask options might be useful context
export type OnTransformCallback = (jsonPath: string, value: string, options?: Partial<IKBotTask>) => Promise<string>;
export type OnTransformedCallback = (jsonPath: string, transformedValue: string, options?: Partial<IKBotTask>) => Promise<string>;
@ -126,14 +125,22 @@ export async function transformPath(
interval: networkOptions.throttleDelay,
})
if (typeof lastKey === 'string' && lastKey !== '') {
if (typeof current[lastKey] === 'string' && current[lastKey] !== '') {
if (await testCallback(current[lastKey], `${currentPath}/${lastKey}`)) {
// Get the value pointed to by the keys
const value = current[lastKey];
// Check if the value exists before proceeding
if (value !== undefined && value !== null) {
const fullJsonPath = `${currentPath}/${lastKey}`; // Construct full path
// Check if the filter callback allows transformation
// Note: The default filter blocks numbers/booleans. Arrays/Objects depend on the filter implementation.
// The example uses `async () => true`, so arrays should pass.
if (await testCallback(value, fullJsonPath)) {
// Add retry mechanism with exponential backoff
let attempts = 0;
let success = false;
let lastError: unknown;
let valueToTransform = current[lastKey];
const fullJsonPath = `${currentPath}/${lastKey}`; // Construct full path
let valueToTransform = value;
// Call onTransform before transformation
try {
@ -173,18 +180,6 @@ export async function transformPath(
errorCallback(currentPath, lastKey, lastError); // Use currentPath (logical path)
}
}
} else if (typeof current[lastKey] === 'object' && current[lastKey] !== null) {
await transformObject(
current[lastKey] as Record<string, any>,
transform,
'$.*', // Recurse on all properties
networkOptions,
errorCallback,
testCallback,
onTransform, // Pass callbacks down
onTransformed, // Pass callbacks down
options // Pass options context down
)
}
}
}

View File

@ -59,13 +59,13 @@ export async function simpleTransformExample() {
console.log(`Original description: ${exampleData.products.fruits[0].description}`);
console.log(`Transformed description: ${data.products.fruits[0].description}`);
console.log(`\nOriginal prices: ${exampleData.numberArrays.prices}`);
console.log(`\nOriginal prices: ${JSON.stringify(exampleData.numberArrays.prices)}`);
console.log(`Pricing Analysis: ${data.numberArrays.pricingAnalysis}`);
console.log(`\nOriginal ratings: ${exampleData.numberArrays.ratings}`);
console.log(`\nOriginal ratings: ${JSON.stringify(exampleData.numberArrays.ratings)}`);
console.log(`Ratings Summary: ${data.numberArrays.ratingsSummary}`);
console.log(`\nOriginal quantities: ${exampleData.numberArrays.quantities}`);
console.log(`\nOriginal quantities: ${JSON.stringify(exampleData.numberArrays.quantities)}`);
console.log(`Inventory Status: ${data.numberArrays.inventoryStatus}`);
return data;
@ -112,9 +112,9 @@ const exampleData = {
]
},
numberArrays: {
prices: JSON.stringify([5, 3, 8, 12, 7]),
ratings: JSON.stringify([4.5, 3.8, 4.2, 5.0, 4.1]),
quantities: JSON.stringify([100, 50, 75, 30, 60])
prices: [5, 3, 8, 12, 7],
ratings: [4.5, 3.8, 4.2, 5.0, 4.1],
quantities: [100, 50, 75, 30, 60]
},
productReview: {
reviewText: 'Great selection of fruits with good prices and quality. Some items were out of stock.'
@ -170,19 +170,7 @@ const fieldMappings: FieldMapping[] = [
targetPath: 'analysis',
options: {
// Clear and explicit prompt that includes the schema format details
prompt: `Analyze this product review and extract key information using EXACTLY the schema specified below.
The review: "Great selection of fruits with good prices and quality. Some items were out of stock."
Your response MUST be a valid JSON object following this exact schema:
{
"sentiment": "positive" | "neutral" | "negative",
"pros": ["string", "string"...], // 1-3 items
"cons": ["string"...] // 0-3 items
}
Do not add any extra fields not in the schema, and make sure to use the exact field names as specified.`,
// Schema validation ensures structured output format
prompt: `Analyze this product review and extract key information`,
format: {
type: "object",
properties: {
@ -218,17 +206,26 @@ Do not add any extra fields not in the schema, and make sure to use the exact fi
// Example onTransform callback
const exampleOnTransform: OnTransformCallback = async (jsonPath, value, options) => {
console.log(` -> onTransform: Path='${jsonPath}', Original Value='${value.substring(0, 30)}...', Options Model='${options?.model}'`);
let valueToTransform: string;
// Check if the value is not a string (e.g., our number arrays)
if (typeof value !== 'string') {
valueToTransform = JSON.stringify(value);
console.log(` -> onTransform: Path='${jsonPath}', Original Value (Array)=${valueToTransform}, Options Model='${options?.model}'`);
} else {
valueToTransform = value;
console.log(` -> onTransform: Path='${jsonPath}', Original Value='${valueToTransform.substring(0, 30)}...', Options Model='${options?.model}'`);
}
// Example: Prefix value before sending to LLM
if (jsonPath.includes('description')) {
return `[PRODUCT INFO] ${value}`;
return `[PRODUCT INFO] ${valueToTransform}`;
}
return value; // Return original value if no modification needed
return valueToTransform; // Return stringified or original string value
};
// Example onTransformed callback
const exampleOnTransformed: OnTransformedCallback = async (jsonPath, transformedValue, options) => {
console.log(` <- onTransformed: Path='${jsonPath}', Transformed Value='${transformedValue.substring(0, 30)}...', Options Model='${options?.model}'`);
console.log(` <- onTransformed: Path='${jsonPath}', Transformed Value='${transformedValue.substring(0, 100)}...', Options Model='${options?.model}'`);
// Example: Post-process the LLM response
if (jsonPath.includes('nutrition')) {
return `${transformedValue} [HEALTH FOCUS]`;
@ -238,8 +235,16 @@ const exampleOnTransformed: OnTransformedCallback = async (jsonPath, transformed
// Simpler callbacks for the second example
const simpleOnTransform: OnTransformCallback = async (jsonPath, value) => {
console.log(` -> simpleOnTransform: Path='${jsonPath}'`);
return value;
let valueToTransform: string;
// Check if the value is not a string (e.g., our number arrays)
if (typeof value !== 'string') {
valueToTransform = JSON.stringify(value);
console.log(` -> simpleOnTransform: Path='${jsonPath}' (Array stringified)`);
} else {
valueToTransform = value;
console.log(` -> simpleOnTransform: Path='${jsonPath}'`);
}
return valueToTransform;
};
const simpleOnTransformed: OnTransformedCallback = async (jsonPath, transformedValue) => {
@ -388,9 +393,9 @@ export async function factoryExample() {
console.log(`Marketing name: ${data2.products.fruits[0].marketingName || 'Not available'}`);
console.log("\nNumber Arrays Transformation Examples:");
console.log(`Original prices: ${exampleData.numberArrays.prices}`);
console.log(`Original prices: ${JSON.stringify(exampleData.numberArrays.prices)}`);
console.log(`Pricing analysis: ${data2.numberArrays.pricingAnalysis || 'Not available'}`);
console.log(`Original ratings: ${exampleData.numberArrays.ratings}`);
console.log(`Original ratings: ${JSON.stringify(exampleData.numberArrays.ratings)}`);
console.log(`Ratings summary: ${data2.numberArrays.ratingsSummary || 'Not available'}`);
if (data2.productReview && data2.productReview.analysis) {

View File

@ -26,15 +26,33 @@
]
},
"numberArrays": {
"prices": "[5,3,8,12,7]",
"ratings": "[4.5,3.8,4.2,5,4.1]",
"quantities": "[100,50,75,30,60]",
"prices": [
5,
3,
8,
12,
7
],
"ratings": [
4.5,
3.8,
4.2,
5,
4.1
],
"quantities": [
100,
50,
75,
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."
},
"productReview": {
"reviewText": "Great selection of fruits with good prices and quality. Some items were out of stock.",
"analysis": "{\n \"sentiment\": \"positive\",\n \"pros\": [\"great selection of fruits\", \"good prices\", \"good quality\"],\n \"cons\": [\"some items were out of stock\"]\n}"
"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"
}
}