File size: 7,315 Bytes
e706de2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
/**
* Exercise 16: Runtime Configuration Override
*
* Goal: Learn to override settings at runtime using config.configurable
*
* In this exercise, you'll:
* 1. Build a Runnable that reads from config.configurable
* 2. Override default settings at runtime
* 3. Test different configurations without changing code
* 4. Understand A/B testing with configs
*
* This is how you change LLM temperature, max tokens, etc. at runtime!
*/
import { RunnableConfig } from '../../../../src//core/context.js';
import {Runnable} from '../../../../src/index.js';
import {BaseCallback} from '../../../../src/utils/callbacks.js';
class TextProcessorRunnable extends Runnable {
constructor(options = {}) {
super();
// Set defaults
this.defaultMaxLength = options.maxLength ?? 50;
this.defaultUppercase = options.uppercase ?? false;
this.defaultPrefix = options.prefix ?? '';
}
async _call(input, config) {
const maxLength = config.configurable?.maxLength ?? this.defaultMaxLength;
const uppercase = config.configurable?.uppercase ?? this.defaultUppercase;
const prefix = config.configurable?.prefix ?? this.defaultPrefix;
// Process text
let result = input;
// Apply prefix
if (prefix) {
result = prefix + result;
}
// Apply uppercase
if (uppercase) {
result = result.toUpperCase();
}
// Apply truncation
if (result.length > maxLength) {
result = result.substring(0, maxLength) + '...';
}
return result;
}
}
// Callback to show what config was used
class ConfigLoggerCallback extends BaseCallback {
async onStart(runnable, input, config) {
if (config.configurable && Object.keys(config.configurable).length > 0) {
console.log(`π Runtime config:`, config.configurable);
}
}
}
async function exercise() {
console.log('=== Exercise 16: Runtime Configuration Override ===\n');
const processor = new TextProcessorRunnable({ maxLength: 50 });
const logger = new ConfigLoggerCallback();
const longText = "The quick brown fox jumps over the lazy dog. This is a longer sentence to test truncation and various configuration options.";
// Test 1: Use defaults
console.log('--- Test 1: Using Defaults ---');
const result1 = await processor.invoke(longText, { callbacks: [logger] });
console.log('Result:', result1);
console.log();
// Test 2: Override maxLength
console.log('--- Test 2: Override maxLength ---');
const result2 = await processor.invoke(longText, {
callbacks: [logger],
configurable: { maxLength: 20 }
});
console.log('Result:', result2);
console.log();
// Test 3: Override multiple settings
console.log('--- Test 3: Override Multiple Settings ---');
const result3 = await processor.invoke(longText, {
callbacks: [logger],
configurable: { uppercase: true, maxLength: 30 }
});
console.log('Result:', result3);
console.log();
// Test 4: Add prefix at runtime
console.log('--- Test 4: Add Prefix at Runtime ---');
const result4 = await processor.invoke(longText, {
callbacks: [logger],
configurable: { prefix: '[PREFIX] ', maxLength: 40 }
});
console.log('Result:', result4);
console.log();
// Test 5: A/B Testing scenario
console.log('--- Test 5: A/B Testing Different Configs ---');
const configA = new RunnableConfig({
callbacks: [logger],
configurable: { maxLength: 25, uppercase: false },
metadata: { variant: 'A', experiment: 'text-processing' }
});
const configB = new RunnableConfig({
callbacks: [logger],
configurable: { maxLength: 40, uppercase: true },
metadata: { variant: 'B', experiment: 'text-processing' }
});
const testText = "Testing A/B configuration variants";
const resultA = await processor.invoke(testText, configA);
const resultB = await processor.invoke(testText, configB);
console.log('Variant A:', resultA);
console.log('Variant B:', resultB);
console.log();
// Test 6: Simulating LLM-style configuration
console.log('--- Test 6: LLM-Style Temperature Override ---');
// Create a mock LLM runnable
class MockLLMRunnable extends Runnable {
constructor(defaultTemp = 0.7) {
super();
this.defaultTemperature = defaultTemp;
}
async _call(input, config) {
const temperature = config.configurable?.temperature ?? this.defaultTemperature;
// Simulate different outputs based on temperature
if (temperature < 0.3) {
return `[temp=${temperature}] Deterministic response: ${input}`;
} else if (temperature > 0.8) {
return `[temp=${temperature}] Creative response about ${input}!!!`;
} else {
return `[temp=${temperature}] Balanced response: ${input}.`;
}
}
}
const llm = new MockLLMRunnable();
console.log('Low temp (0.1):');
const low = await llm.invoke("AI", { configurable: { temperature: 0.1 } });
console.log(low);
console.log('\nMedium temp (0.7):');
const med = await llm.invoke("AI", { configurable: { temperature: 0.7 } });
console.log(med);
console.log('\nHigh temp (1.0):');
const high = await llm.invoke("AI", { configurable: { temperature: 1.0 } });
console.log(high);
console.log('\nβ Exercise 16 complete!');
}
// Run the exercise
exercise().catch(console.error);
/**
* Expected Output:
*
* --- Test 1: Using Defaults ---
* Result: The quick brown fox jumps over the lazy dog. Thi...
*
* --- Test 2: Override maxLength ---
* π Runtime config: { maxLength: 20 }
* Result: The quick brown fox ...
*
* --- Test 3: Override Multiple Settings ---
* π Runtime config: { uppercase: true, maxLength: 30 }
* Result: THE QUICK BROWN FOX JUMPS O...
*
* --- Test 4: Add Prefix at Runtime ---
* π Runtime config: { prefix: '[PREFIX] ', maxLength: 40 }
* Result: [PREFIX] The quick brown fox jumps ove...
*
* --- Test 5: A/B Testing Different Configs ---
* π Runtime config: { maxLength: 25, uppercase: false }
* Variant A: Testing A/B configurati...
* π Runtime config: { maxLength: 40, uppercase: true }
* Variant B: TESTING A/B CONFIGURATION VARIANTS
*
* --- Test 6: LLM-Style Temperature Override ---
* Low temp (0.1):
* [temp=0.1] Deterministic response: AI
*
* Medium temp (0.7):
* [temp=0.7] Balanced response: AI.
*
* High temp (1.0):
* [temp=1.0] Creative response about AI!!!
*
* Learning Points:
* 1. config.configurable holds runtime overrides
* 2. Use ?? operator for defaults: config.configurable?.key ?? default
* 3. Don't modify instance defaults - just use config value
* 4. Perfect for A/B testing different settings
* 5. This is how LLMs change temperature/maxTokens at runtime
* 6. Combine with metadata to track which config was used
*/ |