Email / tutorial /01-foundation /04-context /solutions /13-simple-logger-solution.js
lenzcom's picture
Upload folder using huggingface_hub
e706de2 verified
/**
* Solution 13: Build a Simple Logging Callback
*
* Goal: Understand the basic callback lifecycle
*
* This is the foundation of observability in your framework!
*/
import {Runnable} from '../../../../src/index.js';
import {BaseCallback} from '../../../../src/utils/callbacks.js';
class SimpleLoggerCallback extends BaseCallback {
constructor(options = {}) {
super();
this.showTimestamp = options.showTimestamp ?? false;
}
async onStart(runnable, input, config) {
// Your code here
console.log(`▶️ Starting: ${runnable.name}`)
console.log(` Input: ${input}`)
}
async onEnd(runnable, output, config) {
// Your code here
console.log(`✔️ Completed: ${runnable.name}`)
console.log(` Output: ${output}`)
}
async onError(runnable, error, config) {
// Your code here
console.log(` ❌ ${runnable.name}: ${error.message}`)
}
}
// Test Runnables
class GreeterRunnable extends Runnable {
async _call(input, config) {
return `Hello, ${input}!`;
}
}
class UpperCaseRunnable extends Runnable {
async _call(input, config) {
if (typeof input !== 'string') {
throw new Error('Input must be a string');
}
return input.toUpperCase();
}
}
class ErrorRunnable extends Runnable {
async _call(input, config) {
throw new Error('Intentional error for testing');
}
}
async function exercise1() {
console.log('=== Exercise 1: Simple Logging Callback ===\n');
const logger = new SimpleLoggerCallback();
const config = {
callbacks: [logger]
};
// Test 1: Normal execution
console.log('--- Test 1: Normal Execution ---');
const greeter = new GreeterRunnable();
const result1 = await greeter.invoke("World", config);
console.log('Final result:', result1);
console.log();
// Test 2: Pipeline
console.log('--- Test 2: Pipeline ---');
const upper = new UpperCaseRunnable();
const pipeline = greeter.pipe(upper);
const result2 = await pipeline.invoke("claude", config);
console.log('Final result:', result2);
console.log();
// Test 3: Error handling
console.log('--- Test 3: Error Handling ---');
const errorRunnable = new ErrorRunnable();
try {
await errorRunnable.invoke("test", config);
} catch (error) {
console.log('Caught error (expected):', error.message);
}
console.log('\n✓ Exercise 1 complete!');
}
// Run the exercise
exercise1().catch(console.error);
/**
* Expected Output:
*
* --- Test 1: Normal Execution ---
* ▶️ Starting: GreeterRunnable
* Input: World
* ✔️ Completed: GreeterRunnable
* Output: Hello, World!
* Final result: Hello, World!
*
* --- Test 2: Pipeline ---
* ▶️ Starting: RunnableSequence
* Input: claude
* ▶️ Starting: GreeterRunnable
* Input: claude
* ✔️ Completed: GreeterRunnable
* Output: Hello, claude!
* ▶️ Starting: UpperCaseRunnable
* Input: Hello, claude!
* ✔️ Completed: UpperCaseRunnable
* Output: HELLO, CLAUDE!
* ✔️ Completed: RunnableSequence
* Output: HELLO, CLAUDE!
* Final result: HELLO, CLAUDE!
*
* --- Test 3: Error Handling ---
* ▶️ Starting: ErrorRunnable
* Input: test
* ❌ ErrorRunnable: Intentional error for testing
* Caught error (expected): Intentional error for testing
*
* Learning Points:
* 1. Callbacks see every step in execution
* 2. onStart fires before _call()
* 3. onEnd fires after successful _call()
* 4. onError fires when _call() throws error
* 5. Callbacks don't change the output - they just observe
*/