File size: 7,040 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
224
225
226
227
228
229
230
231
232
233
234
235
236
/**

 * Exercise 12 Solution: Composition and Pipelines

 */

import {HumanMessage, SystemMessage, LlamaCppLLM, Runnable} from '../../../../src/index.js';

// Part 1: PromptFormatter Runnable
class PromptFormatter extends Runnable {
    constructor(systemPrompt = "You are a helpful assistant. Be concise.") {
        super();
        this.systemPrompt = systemPrompt;
    }

    async _call(input, config) {
        return [
            new SystemMessage(this.systemPrompt),
            new HumanMessage(input)
        ];
    }
}

// Part 2: ResponseParser Runnable
class ResponseParser extends Runnable {
    async _call(input, config) {
        // Extract content from AIMessage
        if (input.content) {
            return input.content.trim();
        }
        return String(input).trim();
    }
}

// Part 3: AnswerValidator Runnable
class AnswerValidator extends Runnable {
    constructor(minLength = 10) {
        super();
        this.minLength = minLength;
    }

    async _call(input, config) {
        if (input.length < this.minLength) {
            return `Error: Response too short (${input.length} chars, min ${this.minLength})`;
        }
        return input;
    }
}

async function exercise4() {
    console.log('=== Exercise 4: Composition and Pipelines ===\n');

    const llm = new LlamaCppLLM({
        modelPath: './models/Meta-Llama-3.1-8B-Instruct-Q5_K_S.gguf',
        temperature: 0.7,
        maxTokens: 100
    });

    try {
        // Part 1: Test individual components
        console.log('Part 1: Testing individual components');

        const formatter = new PromptFormatter();
        const parser = new ResponseParser();
        const validator = new AnswerValidator();

        console.log('Testing formatter:');
        const formatted = await formatter.invoke("What is AI?");
        console.log(formatted);
        console.log();

        console.log('Testing LLM + parser:');
        const llmResponse = await llm.invoke(formatted);
        const parsed = await parser.invoke(llmResponse);
        console.log('Parsed:', parsed);
        console.log();

        console.log('Testing validator with short input:');
        const shortResult = await validator.invoke("Hi");
        console.log(shortResult);
        console.log();

        // Part 2: Build a complete pipeline
        console.log('Part 2: Complete pipeline');

        // Chain all components together
        const pipeline = formatter
            .pipe(llm)
            .pipe(parser)
            .pipe(validator);

        console.log('Pipeline structure:', pipeline.toString());

        const result1 = await pipeline.invoke("What is machine learning?");
        console.log('Result:', result1);
        console.log();

        // Part 3: Reusable agent pipelines
        console.log('Part 3: Reusable agent pipeline');

        // Creative pipeline: high temperature, no validator
        const creativeLLM = new LlamaCppLLM({
            modelPath: './models/Meta-Llama-3.1-8B-Instruct-Q5_K_S.gguf',
            temperature: 0.9,
            maxTokens: 100
        });

        const creativeFormatter = new PromptFormatter(
            "You are a creative writer. Use vivid imagery."
        );

        const creativePipeline = creativeFormatter
            .pipe(creativeLLM)
            .pipe(parser);

        // Factual pipeline: low temperature, with validator
        const factualLLM = new LlamaCppLLM({
            modelPath: './models/Meta-Llama-3.1-8B-Instruct-Q5_K_S.gguf',
            temperature: 0.1,
            maxTokens: 100
        });

        const factualFormatter = new PromptFormatter(
            "You are a factual encyclopedia. Be precise and accurate."
        );

        const factualPipeline = factualFormatter
            .pipe(factualLLM)
            .pipe(parser)
            .pipe(validator);

        console.log('Creative (temp=0.9):');
        const creative = await creativePipeline.invoke("Describe a sunset");
        console.log(creative);
        console.log();

        console.log('Factual (temp=0.1):');
        const factual = await factualPipeline.invoke("What is the capital of France?");
        console.log(factual);
        console.log();

        // Part 4: Batch processing with pipelines
        console.log('Part 4: Batch processing with pipeline');

        const questions = [
            "What is Python?",
            "What is JavaScript?",
            "What is Rust?"
        ];

        const answers = await pipeline.batch(questions);

        questions.forEach((q, i) => {
            console.log(`Q: ${q}`);
            console.log(`A: ${answers[i]}`);
            console.log();
        });

        // Cleanup additional LLMs
        await creativeLLM.dispose();
        await factualLLM.dispose();

    } finally {
        await llm.dispose();
    }

    console.log('\n✓ Exercise 4 complete!');
}

// Run the solution
exercise4().catch(console.error);

/**

 * Key Takeaways:

 *

 * 1. Building Runnables:

 *    - Extend Runnable base class

 *    - Override _call() method

 *    - Each Runnable does one thing well

 *    - Configure via constructor parameters

 *

 * 2. Composition with .pipe():

 *    - a.pipe(b).pipe(c) chains operations

 *    - Output of one becomes input of next

 *    - Creates a new RunnableSequence

 *    - Result is itself a Runnable

 *

 * 3. Pipeline benefits:

 *    - Reusable across different inputs

 *    - Testable components in isolation

 *    - Easy to modify (swap components)

 *    - Clear data flow

 *

 * 4. Specialized pipelines:

 *    - Create different LLM instances with different configs

 *    - Mix and match components

 *    - Same interface, different behavior

 *

 * 5. Batch compatibility:

 *    - Pipelines work with batch() automatically

 *    - Each input goes through entire pipeline

 *    - No extra code needed

 *

 * 6. Real-world pattern:

 *    - This is exactly how LangChain works

 *    - Prompt -> Model -> Parser is common pattern

 *    - You now understand what frameworks do

 *

 * 7. Design principles:

 *    - Single Responsibility: Each Runnable does one thing

 *    - Composition over Inheritance: Build complex from simple

 *    - Open/Closed: Easy to extend, no need to modify

 *

 * Example advanced pipeline:

 *

 * ```javascript

 * const agent = inputValidator

 *   .pipe(promptFormatter)

 *   .pipe(llm)

 *   .pipe(responseParser)

 *   .pipe(errorChecker)

 *   .pipe(outputFormatter);

 *

 * // Use it

 * const result = await agent.invoke(userInput);

 *

 * // Test a component

 * const parsed = await responseParser.invoke(testData);

 *

 * // Swap LLM

 * const newAgent = inputValidator

 *   .pipe(promptFormatter)

 *   .pipe(differentLLM)  // Just changed this!

 *   .pipe(responseParser)

 *   .pipe(errorChecker)

 *   .pipe(outputFormatter);

 * ```

 */