File size: 7,820 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
216
217
218
219
220
221
222
223
/**

 * 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';

// TODO: Create a configurable text processor
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) {
        // TODO: Get maxLength from config.configurable or use default
        const maxLength = 0; // config.configurable?.maxLength ?? this.defaultMaxLength

        // TODO: Get uppercase setting from config
        const uppercase = false; // config.configurable?.uppercase ?? this.defaultUppercase

        // TODO: Get prefix setting from config
        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);
        }
    }
}

// TODO: Test with different runtime configs
async function exercise() {
    console.log('=== Exercise 16: Runtime Configuration Override ===\n');

    // TODO: Create processor with defaults
    const processor = null; // 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 ---');
    // TODO: Invoke with just callbacks, no configurable
    const result1 = null;
    console.log('Result:', result1);
    console.log();

    // Test 2: Override maxLength
    console.log('--- Test 2: Override maxLength ---');
    // TODO: Invoke with configurable: { maxLength: 20 }
    const result2 = null;
    console.log('Result:', result2);
    console.log();

    // Test 3: Override multiple settings
    console.log('--- Test 3: Override Multiple Settings ---');
    // TODO: Invoke with configurable: { uppercase: true, maxLength: 30 }
    const result3 = null;
    console.log('Result:', result3);
    console.log();

    // Test 4: Add prefix at runtime
    console.log('--- Test 4: Add Prefix at Runtime ---');
    // TODO: Invoke with configurable: { prefix: '[PREFIX] ', maxLength: 40 }
    const result4 = null;
    console.log('Result:', result4);
    console.log();

    // Test 5: A/B Testing scenario
    console.log('--- Test 5: A/B Testing Different Configs ---');

    // TODO: Create config variant A
    const configA = null; // new RunnableConfig({
    //   callbacks: [logger],
    //   configurable: { maxLength: 25, uppercase: false },
    //   metadata: { variant: 'A', experiment: 'text-processing' }
    // })

    // TODO: Create config variant B
    const configB = null; // new RunnableConfig({
    //   callbacks: [logger],
    //   configurable: { maxLength: 40, uppercase: true },
    //   metadata: { variant: 'B', experiment: 'text-processing' }
    // })

    const testText = "Testing A/B configuration variants";

    // TODO: Test both variants
    // const resultA = await processor.invoke(testText, configA);
    // const resultB = await processor.invoke(testText, configB);

    console.log('Variant A:', 'result here');
    console.log('Variant B:', 'result here');
    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) {
            // TODO: Get temperature from config.configurable
            const temperature = 0.7; // 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}.`;
            }
        }
    }

    // TODO: Test the mock LLM with different temperatures
    const llm = null; // 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 4 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

 */