`;
if (showLanguage) {
enhancement += ``;
}
if (addCopyButton) {
enhancement += `
`;
}
enhancement += '
';
yield enhancement;
// Update buffer and state
buffer = buffer.substring(startMatch.index + startMatch[0].length);
inCodeBlock = true;
}
// Detect code block end
const endMatch = buffer.match(/```/);
if (endMatch && inCodeBlock) {
const codeContent = buffer.substring(0, endMatch.index);
const afterCode = buffer.substring(endMatch.index + 3);
// Yield code content
if (codeContent) {
yield escapeHtml(codeContent);
}
// Close enhanced code block
yield '
';
// Yield text after code block
if (afterCode) {
yield afterCode;
}
// Reset state
buffer = '';
inCodeBlock = false;
codeLanguage = '';
} else if (!inCodeBlock && buffer.length > 500) {
// Yield non-code content when buffer gets large
yield buffer;
buffer = '';
}
}
// Handle remaining buffer
if (buffer) {
if (inCodeBlock) {
yield escapeHtml(buffer) + '';
} else {
yield buffer;
}
}
}
function escapeHtml(text) {
return text.replace(/[&<>"']/g, (char) => {
const escape = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
return escape[char];
});
}
export const stepName = 'codeBlockEnhancer';
```
### Example: Response Quality Analyzer
```javascript
// src/pipeline/steps/responseQualityAnalyzer.js
export async function* responseQualityAnalyzer(input, config = {}) {
const {
addQualityScore = true,
highlightKeyPoints = true,
detectConfidence = true
} = config;
let completeResponse = '';
let chunks = [];
for await (const chunk of input) {
chunks.push(chunk);
completeResponse += chunk;
// Stream chunks normally while building complete response
yield chunk;
// Analyze every 10 chunks for real-time feedback
if (chunks.length % 10 === 0) {
const partialAnalysis = analyzePartialResponse(completeResponse);
if (partialAnalysis.confidence === 'low') {
yield '\n\n*[Assistant note: This response may require verification]*\n\n';
}
}
}
// Final analysis
if (addQualityScore) {
const analysis = analyzeCompleteResponse(completeResponse);
yield `\n\n---\n**Response Quality**: ${analysis.score}/10 | **Confidence**: ${analysis.confidence}`;
if (analysis.keyPoints.length > 0) {
yield `\n**Key Points**: ${analysis.keyPoints.join(', ')}`;
}
}
}
function analyzePartialResponse(text) {
// Simple confidence detection based on language patterns
const uncertainPhrases = ['might be', 'possibly', 'I think', 'maybe', 'not sure'];
const uncertaintyCount = uncertainPhrases.reduce((count, phrase) =>
count + (text.toLowerCase().includes(phrase) ? 1 : 0), 0);
return {
confidence: uncertaintyCount > 2 ? 'low' : 'medium'
};
}
function analyzeCompleteResponse(text) {
const wordCount = text.split(/\s+/).length;
const hasCode = text.includes('```');
const hasLists = /[\*\-\d]\s/.test(text);
const hasHeadings = text.includes('#');
// Simple scoring algorithm
let score = 5; // Base score
if (wordCount > 50) score += 1;
if (hasCode) score += 1;
if (hasLists) score += 1;
if (hasHeadings) score += 1;
if (wordCount > 200) score += 1;
return {
score: Math.min(score, 10),
confidence: score > 7 ? 'high' : score > 5 ? 'medium' : 'low',
keyPoints: extractKeyPoints(text)
};
}
function extractKeyPoints(text) {
// Extract sentences that might be key points
const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 20);
return sentences
.filter(s => /^[A-Z]/.test(s.trim())) // Starts with capital
.slice(0, 3) // Max 3 key points
.map(s => s.trim().substring(0, 50) + (s.length > 50 ? '...' : ''));
}
export const stepName = 'responseQualityAnalyzer';
```
## Step Module Architecture
Every pipeline step is an **async generator function** that:
- Accepts an async iterable input stream
- Processes chunks one by one
- Yields transformed output chunks
- Maintains state across chunks if needed
```javascript
// Basic step module template
export async function* myCustomStep(input, config = {}) {
// Initialize any state/configuration
const { option1 = 'default', option2 = false } = config;
for await (const chunk of input) {
// Process the chunk
const processedChunk = transform(chunk, option1, option2);
// Yield the result
yield processedChunk;
}
// Optional: flush any remaining state
// yield finalData;
}
// Export step name for registry
export const stepName = 'myCustomStep';
```
### Real Example: Simple Text Transformation
```javascript
// src/pipeline/steps/uppercaseStep.js
export async function* uppercaseStep(input, config = {}) {
const { preserveCodeBlocks = true } = config;
let inCodeBlock = false;
for await (const chunk of input) {
// Handle code block detection
if (preserveCodeBlocks && chunk.includes('```')) {
inCodeBlock = !inCodeBlock;
}
// Transform based on context
const transformed = inCodeBlock ? chunk : chunk.toUpperCase();
yield transformed;
}
}
export const stepName = 'uppercaseStep';
```
## Step Module Types
### 1. **Stateless Steps**
Simple transformations that don't need to remember previous chunks.
```javascript
// Character replacement example
export async function* replaceCharsStep(input, config = {}) {
const { from = '', to = '' } = config;
for await (const chunk of input) {
yield chunk.replace(new RegExp(from, 'g'), to);
}
}
```
### 2. **Stateful Steps**
More complex transformations that accumulate state across chunks.
```javascript
// Buffer-based processing example
export async function* bufferLinesStep(input, config = {}) {
const { bufferSize = 3 } = config;
const buffer = [];
for await (const chunk of input) {
buffer.push(chunk);
// Process when buffer is full
if (buffer.length >= bufferSize) {
const processed = processLines(buffer);
yield processed;
buffer.length = 0; // Clear buffer
}
}
// Flush remaining buffer
if (buffer.length > 0) {
yield processLines(buffer);
}
}
```
### 3. **Complex Pattern Matching Steps**
Advanced steps that understand content structure.
```javascript
// Markdown link processing example
export async function* linkProcessorStep(input, config = {}) {
const { baseUrl = '', target = '_blank' } = config;
let textBuffer = '';
for await (const chunk of input) {
textBuffer += chunk;
// Look for complete markdown links
const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
let lastIndex = 0;
let match;
while ((match = linkPattern.exec(textBuffer)) !== null) {
// Yield text before the link
yield textBuffer.slice(lastIndex, match.index);
// Process and yield the link
const [fullMatch, text, url] = match;
const fullUrl = url.startsWith('http') ? url : baseUrl + url;
yield `