llm-prompt-tag
v0.1.2
Published
A lightweight utility for building clean, composable, and maintainable LLM prompts using tagged template literals
Downloads
20
Maintainers
Readme
LLMPromptTag
A lightweight utility for building clean, composable, and maintainable LLM prompts using tagged template literals.
import { prompt } from 'llm-prompt-tag';
const problems = [
"String concatenation",
"Conditional logic mess",
"Formatting inconsistencies",
"Hard to maintain"
];
const solutions = [
"Tagged templates",
"Conditional sections",
"Automatic formatting",
"Composable structure"
];
const includeDebugInfo = false;
const msg = prompt()`
You are a helpful AI assistant. It can be really annoying to put all the conditional data in your prompts.
${prompt('Problems with creating prompts')`${problems}`}
${prompt('Solutions from llm-prompt-tag')`${solutions}`}
${prompt('Debug Info', includeDebugInfo)`This section only appears when debugging`}
Now I can make complex, conditional, formatted prompts I can still read thanks to llm-prompt-tag!
`;
console.log(msg);Output:
You are a helpful AI assistant. It can be really annoying to put all the conditional data in your prompts.
==== Problems with creating prompts ====
String concatenation
Conditional logic mess
Formatting inconsistencies
Hard to maintain
==== End of Problems with creating prompts ====
==== Solutions from llm-prompt-tag ====
Tagged templates
Conditional sections
Automatic formatting
Composable structure
==== End of Solutions from llm-prompt-tag ====
Now I can make complex, conditional, formatted prompts I can still read thanks to llm-prompt-tag!✨ Features
- ✅ Native JavaScript/TypeScript tagged template syntax
- ✅ Automatic whitespace and formatting cleanup
- ✅ Optional section headers
- ✅ Conditional rendering
- ✅
makePromptgenerator for type-safe extensibility - ✅ Smart array formatting with customizable separators
- ✅ No dependencies
📦 Installation
npm install llm-prompt-tag🧠 Basic Usage
import { prompt } from 'llm-prompt-tag';
const result = prompt('User Instructions')`
Write a short, direct note using the user's own words.
`;
console.log(result);Output:
==== User Instructions ====
Write a short, direct note using the user's own words.
==== End of User Instructions ====🔁 Conditional Sections
const showHelp = false;
const result = prompt('Help Section', showHelp)`
If you get stuck, refer to the user's original notes.
`;
console.log(result);Output:
(empty string - no output because condition is false)With condition true:
const showHelp = true;
const result = prompt('Help Section', showHelp)`
If you get stuck, refer to the user's original notes.
`;
console.log(result);Output:
==== Help Section ====
If you get stuck, refer to the user's original notes.
==== End of Help Section ====🏷️ Semantic Naming Patterns
Some people may prefer to rename the function for two distinct purposes: a simpler tag p for the overall template, and a separate one for sections.
This approach can be more readable and semantic:
import { prompt } from 'llm-prompt-tag';
// Use 'section' for structured sections
const section = prompt;
// Use a plain wrapper for the main content
const p = prompt();
const someVariable = "User prefers dark mode";
const comments = "Remember to save work frequently";
const result = p`You are a helpful AI assistant.
${section('User Settings')`${someVariable}`}
${section('Comments')`The user's comments are: ${comments}`}
Whatever approach feels cleaner to you!`;
console.log(result);Output:
You are a helpful AI assistant.
==== User Settings ====
User prefers dark mode
==== End of User Settings ====
==== Notes ====
The user's notes are: Remember to save work frequently
==== End of Notes ====
Whatever approach feels cleaner to you!🧱 Extending with makePrompt
If you have custom objects you're putting into your prompts you can make that easier/cleaner by using the makePrompt generator with type guards and their formatters.
Example:
import { makePrompt } from 'llm-prompt-tag';
type Note = {
title: string;
content: string;
}
// Type guard function to check if an object is a Note
const isNote = (obj: any): obj is Note =>
obj && typeof obj.title === 'string' && typeof obj.content === 'string';
const promptWithNotes = makePrompt([
[isNote, (note: Note) => `• ${note.title}\n${note.content}`]
]);
const note: Note = { title: "LLM Summary", content: "LLMs are transforming software development." };
const result = promptWithNotes()`
Here's a user note:
${note}
`;
console.log(result);Output:
Here's a user note:
• LLM Summary
LLMs are transforming software development.Fallback to toString():
Objects that don't match any registered type guard will automatically use their toString() method, making it easy to mix custom formatted objects with other types:
const customObj = {
value: "important data",
toString() { return `CustomObject: ${this.value}`; }
};
const result = promptWithNotes()`
${note}
${customObj}
${"plain string"}
${42}
`;Output:
• LLM Summary
LLMs are transforming software development.
CustomObject: important data
plain string
42📝 Smart Array Formatting
To keep templates simpler, arrays of registered types are automatically formatted. By default, items are separated with double newlines for clean readability:
type Note = {
title: string;
content: string;
}
const isNote = (obj: any): obj is Note =>
obj && typeof obj.title === 'string' && typeof obj.content === 'string';
const promptWithNotes = makePrompt([
[isNote, (note: Note) => `• ${note.title}\n ${note.content}`]
]);
const notes: Note[] = [
{ title: "Meeting", content: "Discuss project timeline" },
{ title: "Task", content: "Review pull request" },
{ title: "Reminder", content: "Update documentation" }
];
const result = promptWithNotes()`${notes}`;
console.log(result);Output:
• Meeting
Discuss project timeline
• Task
Review pull request
• Reminder
Update documentationCustom Array Formatting:
You can customize how arrays are formatted (default is two line seperation: '\n\n') by providing an arrayFormatter option:
const commaPrompt = makePrompt(
[[isNote, (note: Note) => note.title]],
{ arrayFormatter: (items: any[], formatter: any) => items.map(formatter).join(', ') }
);
const result = commaPrompt()`
Meeting topics: ${notes}
`;
console.log(result);Output:
Meeting topics: Meeting, Task, ReminderMixed-Type Arrays:
Arrays can contain different types, and each item will be formatted according to its type:
type Task = {
name: string;
completed: boolean;
}
const isTask = (obj: any): obj is Task =>
obj && typeof obj.name === 'string' && typeof obj.completed === 'boolean';
const mixedPrompt = makePrompt([
[isNote, (note: Note) => `📝 ${note.title}`],
[isTask, (task: Task) => `${task.completed ? '✅' : '⏳'} ${task.name}`]
]);
const note: Note = { title: "Meeting", content: "Important discussion" };
const task: Task = { name: "Review code", completed: false };
const customObj = { toString() { return "Custom item"; } };
const mixedArray = [note, task, customObj, "plain string"];
const result = mixedPrompt()`${mixedArray}`;
console.log(result);Output:
📝 Meeting
⏳ Review code
Custom item
plain string🧪 Multiple formatters example
type Task = {
name: string;
completed: boolean;
}
const isTask = (obj: any): obj is Task =>
obj && typeof obj.name === 'string' && typeof obj.completed === 'boolean';
const customPrompt = makePrompt([
[isNote, (note: Note) => `📝 ${note.title}: ${note.content}`],
[isTask, (task: Task) => `${task.completed ? '✅' : '⏳'} ${task.name}`]
]);
const note: Note = { title: "Meeting", content: "Discuss project timeline" };
const task: Task = { name: "Review code", completed: false };
const result = customPrompt()`
Current items:
${note}
${task}
`;
console.log(result);Output:
Current items:
📝 Meeting: Discuss project timeline
⏳ Review code🔧 Custom Object Formatting
You can customize how unregistered objects are formatted using the objectFormatter option. This is useful for JSON output, custom serialization, or specialized object representations:
JSON Formatting:
const jsonPrompt = makePrompt([
[isNote, (note: Note) => `• ${note.title}`]
], {
objectFormatter: (obj) => JSON.stringify(obj, null, 2)
});
const unknownObj = { type: "unknown", data: [1, 2, 3] };
const note: Note = { title: "Known object", content: "Has custom formatter" };
const result = jsonPrompt()`
${note}
${unknownObj}
`;
console.log(result);Output:
• Known object
{
"type": "unknown",
"data": [
1,
2,
3
]
}Using toJSON():
const toJSONPrompt = makePrompt([], {
objectFormatter: (obj: any) => obj.toJSON ? obj.toJSON() : obj.toString()
});
const customObj = {
value: "secret",
toJSON() { return `JSON: ${this.value}`; }
};
const result = toJSONPrompt()`${customObj}`;
// Output: "JSON: secret"🔗 Composable Prompts
Prompts can be nested and composed together:
const systemPrompt = prompt('System')`
You are a helpful AI assistant.
`;
const userContext = prompt('User Context')`
User: Alice
Task: Help prioritize work
`;
const fullPrompt = prompt()`
${systemPrompt}
${userContext}
Please provide helpful guidance.
`;
console.log(fullPrompt);Output:
==== System ====
You are a helpful AI assistant.
==== End of System ====
==== User Context ====
User: Alice
Task: Help prioritize work
==== End of User Context ====
Please provide helpful guidance.🧪 Example Unit Tests
The library includes comprehensive tests that demonstrate usage patterns:
import { prompt, makePrompt } from 'llm-prompt-tag';
// Basic usage
const result = prompt('Intro')`
Hello world.
`;
// Output: "==== Intro ====\nHello world.\n==== End of Intro ===="
// With custom formatters using array of tuples
type Note = {
title: string;
content: string;
}
const isNote = (obj: any): obj is Note =>
obj && typeof obj.title === 'string' && typeof obj.content === 'string';
const noteFormatter = (note: Note) => `• ${note.title}\n${note.content}`;
const customPrompt = makePrompt([
[isNote, noteFormatter]
]);
const note: Note = { title: "Meeting", content: "Discuss project timeline" };
const formatted = customPrompt()`${note}`;
// Output: "• Meeting\nDiscuss project timeline"📝 API Reference
prompt(header?, condition?)
Creates a tagged template literal function for building prompts.
Parameters:
header(optional): String to use as section headercondition(optional): Boolean to conditionally render the section (default:true)
Returns: A tagged template function
makePrompt(formatters, options?)
Creates a customizable prompt function with type-specific formatters.
Parameters:
formatters: Array of tuples[typeGuard, formatter]wheretypeGuardis a function that checks types andformatteris a function that formats valuesoptions(optional): Configuration object with optional properties:arrayFormatter: Custom function for formatting arrays (default: join with'\n\n')objectFormatter: Custom function for formatting objects (default: usetoString())
Returns: A prompt function with custom formatting capabilities
Example:
type Note = {
title: string;
content: string;
}
const isNote = (obj: any): obj is Note =>
obj && typeof obj.title === 'string' && typeof obj.content === 'string';
const customPrompt = makePrompt([
[isNote, (note: Note) => `• ${note.title}\n${note.content}`]
], {
arrayFormatter: (items, formatter) => items.map(formatter).join(' | '),
objectFormatter: (obj) => JSON.stringify(obj, null, 2)
});🏗 Development
# Install dependencies
npm install
# Build the library
npm run build
# Run tests
npm test
# Development mode with watch
npm run dev🧩 Core Implementation
The library consists of two main modules:
prompt.ts
Core tagged template implementation with whitespace cleanup and conditional rendering.
makePrompt.ts
Extension mechanism for registering custom object formatters.
🧾 License
MIT
📢 Author
Dave Fowler
GitHub | X / Twitter | ThingsILearned
