This guide explains when and how to use FlatFormatter vs RequestFormatterService for formatting data for LLM consumption.
Ollama Middleware provides two main tools for formatting data:
| Tool | Purpose | Best For |
|---|---|---|
| FlatFormatter | Object-to-string formatting | Simple, flat data structures |
| RequestFormatterService | Complex prompt handling | Nested prompts with context + instruction |
Purpose: Convert flat or structured objects into LLM-readable strings.
import { FlatFormatter } from 'llm-middleware';
const data = { name: 'Alice', age: 30, role: 'Engineer' };
const formatted = FlatFormatter.flatten(data, {
format: 'numbered',
keyValueSeparator: ': '
});
// Output:
// 1. name: Alice
// 2. age: 30
// 3. role: Engineer- Presets for common entities (Character, Chapter, Genre, etc.)
- Computed fields for dynamic values
- Array slicing for large datasets
- Custom formatting options
Full documentation:
Purpose: Handle complex, nested prompt structures with context and instruction separation.
import { RequestFormatterService } from 'llm-middleware';
const prompt = {
context: {
genre: 'sci-fi',
tone: 'dark',
setting: 'dystopian future'
},
instruction: 'Write the opening paragraph'
};
const formatted = RequestFormatterService.formatUserMessage(
prompt,
(s) => s, // template function
'MyUseCase'
);Output:
## CONTEXT:
genre: sci-fi
tone: dark
setting: dystopian future
## INSTRUCTION:
Write the opening paragraph
RequestFormatterService handles multiple input formats:
const prompt = "Write a story about dragons";const prompt = {
context: { genre: 'fantasy', tone: 'epic' },
instruction: 'Write the battle scene'
};const prompt = {
prompt: {
context: { /* ... */ },
instruction: 'Write something'
}
};Main formatting method - handles all prompt types.
RequestFormatterService.formatUserMessage(
prompt, // string | object
templateFn, // (formatted: string) => string
'MyUseCase' // use case name for debugging
);Extract context from various prompt formats.
const ctx = RequestFormatterService.extractContext(prompt);
// Returns: { genre: 'fantasy', tone: 'epic' } or nullExtract user instruction from prompt.
const instruction = RequestFormatterService.extractInstruction(prompt);
// Returns: "Write the battle scene"Validate that a prompt is not empty.
if (!RequestFormatterService.isValidPrompt(prompt)) {
throw new Error('Invalid prompt');
}✅ You have simple, flat data structures
const character = { name: 'Alice', age: 30, role: 'Hero' };✅ You need preset formatting for entities
// Create your own preset (see examples/flat-formatter-demo/)
import { BasePreset } from 'llm-middleware';
class MyEntityPreset extends BasePreset<MyEntity, ProcessedMyEntity> { /* ... */ }
const formatted = myEntityPreset.formatForLLM(entity);✅ You're building custom context piece by piece
const context = FlatFormatter.flatten(setting) + '\n\n' +
FlatFormatter.flatten(genre);✅ You need fine-grained control over formatting
FlatFormatter.flatten(data, {
format: 'bulleted',
indent: 4,
ignoredKeys: ['id', 'internal'],
computedFields: { fullName: (d) => `${d.first} ${d.last}` }
});✅ You have nested prompt structures from API requests
// API receives this complex structure
{ prompt: { context: {...}, instruction: '...' } }✅ You need automatic context/instruction separation
// Automatically formatted into sections:
// ## CONTEXT:
// ...
// ## INSTRUCTION:
// ...✅ You want flexible prompt format support
// Handles string, object, nested - all in one
RequestFormatterService.formatUserMessage(anyPrompt, templateFn, 'UseCase');✅ You need to extract metadata from prompts
const context = RequestFormatterService.extractContext(prompt);
const instruction = RequestFormatterService.extractInstruction(prompt);import { FlatFormatter } from 'llm-middleware';
class DataFormatterUseCase extends BaseAIUseCase {
protected formatUserMessage(prompt: any): string {
const { userData, preferences, constraints } = prompt;
// Use FlatFormatter for any structured data
const contextSections = [
`## USER INFO:\n${FlatFormatter.flatten(userData, { format: 'separator' })}`,
// Use FlatFormatter for custom structures
`## PREFERENCES:\n${FlatFormatter.flatten(preferences, {
format: 'bulleted',
keyValueSeparator: ': '
})}`,
`## CONSTRAINTS:\n${FlatFormatter.flatten(
constraints.map(c => ({ constraint: c, priority: "MUST FOLLOW" })),
{ format: 'numbered', ignoredKeys: ['constraint'] }
)}`
];
return contextSections.join('\n\n');
}
}import { RequestFormatterService } from 'llm-middleware';
class StoryGeneratorUseCase extends BaseAIUseCase {
protected formatUserMessage(prompt: any): string {
// RequestFormatterService handles all formats automatically
return RequestFormatterService.formatUserMessage(
prompt,
this.getUserTemplate(),
'StoryGeneratorUseCase'
);
}
protected createResult(content: string, usedPrompt: string): StoryResult {
// Extract metadata for result
const context = RequestFormatterService.extractContext(this.currentRequest?.prompt);
const instruction = RequestFormatterService.extractInstruction(this.currentRequest?.prompt);
return {
generatedContent: content,
story: content,
extractedContext: context,
extractedInstruction: instruction,
// ...
};
}
}Don't use RequestFormatterService for simple formatting:
// ❌ Overkill
RequestFormatterService.formatUserMessage({ name: 'Alice' }, t => t, 'X');
// ✅ Better
FlatFormatter.flatten({ name: 'Alice' });Create your own presets for your domain entities:
import { BasePreset, ProcessedEntity } from 'llm-middleware';
// Define your entity and processed types
interface MyEntity { /* ... */ }
interface ProcessedMyEntity extends ProcessedEntity {
[key: string]: string | number | boolean;
/* ... normalized fields */
}
// Create preset class
class MyEntityPreset extends BasePreset<MyEntity, ProcessedMyEntity> {
protected preprocessEntity(entity: MyEntity): ProcessedMyEntity {
// Transform and normalize your data
return { /* ... */ };
}
}
// See src/examples/flat-formatter-demo/ for complete examplesif (!RequestFormatterService.isValidPrompt(prompt)) {
throw new Error('Prompt cannot be empty');
}Use extraction methods to get structured data from prompts:
const context = RequestFormatterService.extractContext(prompt);
const instruction = RequestFormatterService.extractInstruction(prompt);
// Use in results or logging
logger.info('Processing request', {
context,
instruction,
useCaseName: this.constructor.name
});You can use both together:
protected formatUserMessage(prompt: any): string {
// Use RequestFormatterService for overall structure
const extracted = RequestFormatterService.extractPromptData(prompt);
// Use FlatFormatter for specific nested objects
const formattedContext = FlatFormatter.flatten(extracted.context, {
format: 'numbered',
ignoreEmptyValues: true
});
return `## CONTEXT:\n${formattedContext}\n\n## INSTRUCTION:\n${extracted.instruction}`;
}See FlatFormatter README for complete API.
| Method | Parameters | Returns | Purpose |
|---|---|---|---|
formatUserMessage() |
prompt, templateFn, useCaseName |
string |
Main formatting method |
extractContext() |
prompt |
any | null |
Extract context object |
extractInstruction() |
prompt |
string |
Extract instruction string |
isValidPrompt() |
prompt |
boolean |
Check if prompt is valid |
getPromptStats() |
prompt |
PromptStats |
Get prompt metadata |
mergePromptComponents() |
components[] |
string |
Merge multiple parts |
sanitizePrompt() |
prompt |
string |
Remove control chars |