Email / tutorial /01-foundation /04-context /solutions /15-config-inheritance-solution.js
lenzcom's picture
Upload folder using huggingface_hub
e706de2 verified
/**
* Exercise 15: Config Merging and Child Configs
*
* Goal: Understand how configs inherit and merge
*
* In this exercise, you'll:
* 1. Create parent and child configs
* 2. See how child configs inherit from parents
* 3. Understand callback accumulation
* 4. Learn when to use config.merge() vs config.child()
*
* This is crucial for nested Runnable calls!
*/
import {RunnableConfig} from '../../../../src/core/context.js';
import {Runnable} from '../../../../src/index.js';
import {BaseCallback} from '../../../../src/utils/callbacks.js';
// Simple callback to show when it's called
class TagLoggerCallback extends BaseCallback {
constructor(name) {
super();
this.name = name;
}
async onStart(runnable, input, config) {
console.log(`[${this.name}] Starting ${runnable.constructor.name}`);
console.log(` Tags: [${config.tags.join(', ')}]`);
console.log(` Callback count: ${config.callbacks.length}`);
}
async onEnd(runnable, output, config) {
console.log(`[${this.name}] Completed ${runnable.constructor.name}`);
}
}
// Test Runnables that create child configs
class Step1Runnable extends Runnable {
async _call(input, config) {
console.log('\n--- Inside Step1 ---');
const childConfig = config.child({ tags: ['step1'] });
console.log(`Step1 child has ${childConfig.callbacks.length} callbacks`);
// Simulate nested work
return `Step1(${input})`;
}
}
class Step2Runnable extends Runnable {
async _call(input, config) {
console.log('\n--- Inside Step2 ---');
const childConfig = config.child({ tags: ['step2'] });
console.log(`Step2 child has ${childConfig.callbacks.length} callbacks`);
return `Step2(${input})`;
}
}
class Step3Runnable extends Runnable {
async _call(input, config) {
console.log('\n--- Inside Step3 ---');
const childConfig = config.child({
tags: ['step3'],
metadata: { nested: true }
});
console.log(`Step3 tags: [${childConfig.tags.join(', ')}]`);
console.log(`Step3 metadata:`, childConfig.metadata);
return `Step3(${input})`;
}
}
async function exercise() {
console.log('=== Exercise 15: Config Merging and Child Configs ===\n');
// Part 1: Basic config inheritance
console.log('--- Part 1: Basic Inheritance ---\n');
const parentConfig = new RunnableConfig({
callbacks: [new TagLoggerCallback('Parent')],
tags: ['base']
});
const childConfig = parentConfig.child({
callbacks: [new TagLoggerCallback('Child')],
tags: ['child']
});
console.log('Parent callbacks:', parentConfig.callbacks.length);
console.log('Child callbacks:', childConfig.callbacks.length);
console.log('Parent tags:', parentConfig.tags);
console.log('Child tags:', childConfig.tags);
// Part 2: Config in pipelines
console.log('\n--- Part 2: Config in Pipelines ---\n');
const pipelineConfig = new RunnableConfig({
callbacks: [new TagLoggerCallback('Pipeline')],
tags: ['base']
});
const step1 = new Step1Runnable();
const step2 = new Step2Runnable();
const pipeline = step1.pipe(step2);
await pipeline.invoke("test", pipelineConfig);
// Part 3: Multiple levels of nesting
console.log('\n--- Part 3: Multiple Nesting Levels ---\n');
const level1Config = new RunnableConfig({
callbacks: [new TagLoggerCallback('Level1')],
tags: ['level1'],
metadata: {level: 1}
});
const level2Config = level1Config.child({
callbacks: [new TagLoggerCallback('Level2')],
tags: ['level2'],
metadata: {level: 2}
});
const level3Config = level2Config.child({
callbacks: [new TagLoggerCallback('Level3')],
tags: ['level3'],
metadata: {level: 3}
});
console.log('Level 1 - Callbacks:', level1Config.callbacks.length, 'Tags:', level1Config.tags);
console.log('Level 2 - Callbacks:', level2Config.callbacks.length, 'Tags:', level2Config.tags);
console.log('Level 3 - Callbacks:', level3Config.callbacks.length, 'Tags:', level3Config.tags);
// Part 4: merge() vs child()
console.log('\n--- Part 4: merge() vs child() ---\n');
const configA = new RunnableConfig({
tags: ['a'],
metadata: {source: 'A'}
});
const configB = new RunnableConfig({
tags: ['b'],
metadata: {source: 'B', extra: 'data'}
});
const merged = configA.merge(configB);
const child = configA.child({
tags: ['b'],
metadata: { extra: 'data' }
});
console.log('Merged metadata:', merged.metadata);
console.log('Child metadata:', child.metadata);
console.log('\n✓ Exercise 15 complete!');
}
// Run the exercise
exercise().catch(console.error);
/**
* Expected Output Snippets:
*
* --- Part 1: Basic Inheritance ---
* Parent callbacks: 1
* Child callbacks: 2
* Parent tags: ['base']
* Child tags: ['base', 'child']
*
* --- Part 2: Config in Pipelines ---
* [Pipeline] Starting Step1Runnable
* Tags: [base]
* Callback count: 1
*
* --- Inside Step1 ---
* Step1 child has 1 callbacks
* [Pipeline] Completed Step1Runnable
*
* --- Part 3: Multiple Nesting Levels ---
* Level 1 - Callbacks: 1, Tags: ['level1']
* Level 2 - Callbacks: 2, Tags: ['level1', 'level2']
* Level 3 - Callbacks: 3, Tags: ['level1', 'level2', 'level3']
*
* --- Part 4: merge() vs child() ---
* Merged metadata: { source: 'B', extra: 'data' }
* Child metadata: { source: 'A', extra: 'data' }
*
* Learning Points:
* 1. child() creates a new config inheriting from parent
* 2. Callbacks accumulate (child has parent's + its own)
* 3. Tags accumulate (arrays concatenate)
* 4. Metadata merges (child overrides parent keys)
* 5. merge() treats both configs equally
* 6. child() treats parent as base, child as override
*/