amadaius v1.2.1
Amadaius
Amadaius is a TypeScript/JavaScript library designed to simplify and streamline the process of creating text-based prompts for AI applications. By separating data validation and transformation and prompt structure, Amadaius ensures your prompts are robust, reusable, and easy to manage.
Amadaius leverages:
- Zod for data validation and transformations.
- Handlebars for templating.
- Optional custom helpers that can be easily plugged in.
Why Use Amadaius?
- Separation of Concerns: Keep your prompt content and structure independent, making it easier to update, localize, and reuse templates.
- Validation and Transformation: Ensure your data is always in the correct format with Zod's powerful schema validation and enrichment features.
- Dynamic Templating: Use Handlebars for conditional logic, loops, and custom helpers to create flexible and adaptable prompts.
- Modular Template Composition: Build complex prompt templates seamlessly from smaller prompt templates.
- Incremental Application: Build complex prompts step-by-step with partial templates, allowing you to fill in data incrementally.
- Async Support: Handle asynchronous data fetching and transformations effortlessly.
TL;DR
Amadaius enables you to create prompts that are validated, enriched, and dynamically generated with minimal effort. It's ideal for building AI applications that require structured and reusable prompts.
Table of Contents
Concepts
Prompt Structure and Content
- Prompt Structure: How the content of the prompt is laid out, defined using a Handlebars template string.
- Prompt Content: The validated and enriched data provided to the template to populate the structure.
Validation and Enrichment with Zod
- Validation: Ensures the data adheres to the expected shape and constraints.
- Enrichment: Transforms or adds data using Zod's
transform
method.
Handlebars Helpers
Custom functions injected into templates to add dynamic behavior.
Separation of Concerns
Amadaius emphasizes keeping content (data) and structure (template) independent, enabling easier reuse and localization.
Modular Template Composition
Use the asSchema
method to compose smaller prompt templates into larger, more complex templates, promoting modularity and reusability.
Features
- Zod Validation: Ensure your template data is correct.
- Handlebars-based Templating: Use Handlebars features like conditionals, loops, helpers, and comments.
- Partial Prompt Templates: Build complex prompts incrementally, adding or overriding data fields at each step.
- Asynchronous Support: Seamlessly handle asynchronous data transformations (
buildAsync(data)
). - Modular Composition: Combine smaller prompt templates into larger ones using
asSchema()
.
Installation
# Using npm
npm install amadaius
Basic Usage
Creating a Prompt Template
- Define a schema describing the data your prompt needs.
- Define a template string (Handlebars syntax) that references properties in the schema.
- Create a
PromptTemplate
usingpromptTemplate(schema, templateStr, options)
.
import { promptTemplate } from "amadaius";
import { z } from "zod";
// Template references `{{topic}}`
const myPrompt = promptTemplate(
z.object({ topic: z.string() }),
"Write a story about {{topic}}!",
);
// Provide data matching the schema
const result = myPrompt.build({ topic: "dragons" });
console.log(result);
// -> "Write a story about dragons!"
Validating and Transforming Data
Zod can do more than just type-check. You can refine, transform, and set default values. If data fails validation, an error is thrown.
import { promptTemplate } from "amadaius";
import { z } from "zod";
// Example of refining schema
const userPrompt = promptTemplate(
z
.object({
id: z.string(),
name: z.string(),
})
.refine((data) => data.id.length === 10, {
message: "User ID must be exactly 10 characters long",
}),
"Hello, {{name}}! Your ID is {{id}}.",
);
try {
const output = userPrompt.build({ id: "0123456789", name: "Alice" });
console.log(output);
// -> "Hello, Alice! Your ID is 0123456789."
} catch (err) {
console.error(err);
// If you provide invalid data, e.g. ID is not length 10,
// you'll get a Zod validation error
}
You can also transform data using Zod's transform
method.
const transformSchema = z.string().transform((topic) => ({ topic })); // transforms a string into { topic }
const transformPrompt = promptTemplate(
transformSchema,
"Write a story about {{topic}}!",
);
// We can pass just a string; the schema transforms it into { topic }
const transformResult = transformPrompt.build("dinosaurs");
console.log(transformResult);
// -> "Write a story about dinosaurs!"
Composing Prompt Templates
You can convert a PromptTemplate
into a Zod schema using asSchema()
. This allows you to compose prompt templates together.
import { promptTemplate } from "amadaius";
import { z } from "zod";
// Define smaller prompt templates
const pt1 = promptTemplate(z.object({ name: z.string() }), "Hello, {{name}}!");
const pt2 = promptTemplate(z.object({ question: z.string() }), "{{question}}");
// Compose them into a single prompt
const result = promptTemplate(
z.object({ greeting: pt1.asSchema(), request: pt2.asSchema() }),
"{{greeting}} {{request}}",
).build({
greeting: { name: "Alice" },
request: { question: "What is your favorite color?" },
});
console.log(result);
// -> "Hello, Alice! What is your favorite color?"
Partial Templates
Sometimes you need to partially apply data to a template and fill in the rest later. You can convert a PromptTemplate
into a PartialPromptTemplate
using asPartial()
and fill in data incrementally with partial(data)
.
import { promptTemplate } from "amadaius";
import { z } from "zod";
const questionPrompt = promptTemplate(
z.object({
persona: z.string(),
message: z.string(),
}),
"You are {{persona}}. Respond to: {{message}}",
);
// Convert to partial template
const partialPrompt = questionPrompt.asPartial();
// Fill data in multiple steps
partialPrompt.partial({ persona: "a knowledgeable AI librarian" });
partialPrompt.partial({ message: "What are the best science fiction books?" });
// When you're ready, build the final string
const partialResult = partialPrompt.build();
console.log(partialResult);
// -> "You are a knowledgeable AI librarian. Respond to: What are the best science fiction books?"
You can also copy a PartialPromptTemplate
to create a new instance with the same data.
const partialPromptCopy = partialPrompt.copy();
// partialPromptCopy shares the same partial data initially, then you can branch out
Async Data Transformations
Custom Handlebars Helpers
You can add custom Handlebars helpers to your templates by passing them in the helpers
option.
import { promptTemplate } from "amadaius";
const pt = promptTemplate(
z.object({
persona: z.string(),
user_message: z.string(),
tone: z.enum(["formal", "casual", "enthusiastic"]),
}),
`
You are a helpful AI assistant who always follows the persona and tone specified below.
Persona: {{persona}}
User said: "{{transformTone user_message tone}}"
Please respond to the user's message in a manner consistent with the persona and tone above.
`,
{
helpers: {
transformTone: (
message: string,
tone: "formal" | "casual" | "enthusiastic",
) => {
switch (tone) {
case "formal":
return `Good day. I would like to bring to your attention: ${
message.charAt(0).toUpperCase() + message.slice(1)
}.`;
case "casual":
return `Hey! So basically: ${message}`;
case "enthusiastic":
return `Wow, check this out: ${message}!!!`;
default:
return message;
}
},
},
},
);
pt.build({
persona: "A knowledgeable librarian",
user_message: "could you help me find a good science fiction book?",
tone: "enthusiastic",
});
console.log(pt);
// -> `You are a helpful AI assistant who always follows the persona and tone specified below.
// Persona: A knowledgeable librarian
//
// User said: "Wow, check this out: could you help me find a good science fiction book?!!!"
//
// Please respond to the user's message in a manner consistent with the persona and tone above.
// `
Amadaius supports asynchronous data transformations using buildAsync(data)
.
import { promptTemplate } from "amadaius";
const asyncPrompt = promptTemplate(
z
.object({
productNumber: z.number(),
})
.transform(async ({ productNumber }) => {
await getProductData(productNumber);
return {
productNumber,
productData: JSON.stringify(productData, null, 2),
};
}),
"Act as an expert in creating product descriptions.\n\nProduct {{productNumber}}:\n\n{{productData}}\n\nCreate a product description based on the data provided.",
);
const prompt = await asyncPrompt.buildAsync({ productNumber: 1234 });
console.log(prompt);
// -> "Act as an expert in creating product descriptions.\n\nProduct 1234:\n\n{ ... }\n\nCreate a product description based on the data provided."
API Reference
promptTemplate(schema, templateStr, options?)
Creates a new PromptTemplate
instance.
Signature
function promptTemplate<TSchema extends ZodType<any, any>>(
schema: TSchema,
templateStr: string,
options?: PromptTemplateOptions,
): PromptTemplate<TSchema>;
Parameters
schema
: A Zod schema describing the shape of the data needed by your template.templateStr
: A Handlebars template string.options?
: Optional configuration:helpers?: Record<string, (...args: any) => any>
: A key-value map of custom Handlebars helpers.
Returns
- A new
PromptTemplate
instance.
Class: PromptTemplate<TSchema>
A fully specified prompt template. You create an instance of this class using promptTemplate
.
build(data)
Builds the prompt string using the provided data.
Signature
build(data: z.input<TSchema>): string;
Parameters
data
: Data matching the schema.
Returns
- The prompt string.
Throws
- Zod validation errors if
data
doesn't match the schema.
buildAsync(data)
Builds the prompt string asynchronously using the provided data. This enables asynchronous data transformations (e.g., when using z.transform(async ...)
in Zod).
Signature
async buildAsync(data: z.input<TSchema>): Promise<string>;
Parameters
data
: Data matching the schema.
Returns
- A promise that resolves to the prompt string.
Throws
- Zod validation errors if
data
doesn't match the schema.
asSchema()
Enables prompt template composition by converting the PromptTemplate
into a zod schema with a built-in transform
.
Signature
asSchema(): ZodType<z.input<TSchema>, string, unknown>;
Returns
- A new Zod schema with a built-in
transform
method that converts the data into a built prompt.
asPartial()
Converts the PromptTemplate
into a PartialPromptTemplate
, allowing you to partially apply data over multiple steps.
Signature
asPartial(): PartialPromptTemplate<TSchema>;
Returns
- A new
PartialPromptTemplate
instance.
Class: PartialPromptTemplate<TSchema>
A template that can be progressively filled with data before finalising. It has the same underlying schema as the original PromptTemplate
.
partial(data)
Partially applies data to the template.
Signature
partial(data: DeepPartial<z.input<TSchema>>): PartialPromptTemplate<TSchema>;
Parameters
data
: A partial version of whatTSchema
expects. Each call merges with existing partial data.
Returns
- The
PartialPromptTemplate
instance.
build()
Finalises the partial data, validates it with the original schema, and compiles the template into a string. Throws Zod validation error if the partial data is not sufficient or invalid.
Signature
build(): string;
Returns
- The prompt string.
Throws
- Zod validation errors if the partial data doesn't match the schema.
buildAsync()
Finalises the partial data, validates it with the original schema, and compiles the template into a string asynchronously.
Signature
async buildAsync(): Promise<string>;
Returns
- A promise that resolves to the prompt string.
Throws
- Zod validation errors if the partial data doesn't match the schema.
copy()
Creates a new PartialPromptTemplate
instance with the same partial data. Useful for creating branches from a partially-applied template without interfering with each other's data.
Contributing
Contributions are welcome! Please read the contribution guidelines first.
License
This project is licensed under the MIT License. See the LICENSE file for details.