lenzcom's picture
Upload folder using huggingface_hub
e706de2 verified
/**
* 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);
* ```
*/