1.2.1 • Published 6 months ago

amadaius v1.2.1

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

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?

  1. Separation of Concerns: Keep your prompt content and structure independent, making it easier to update, localize, and reuse templates.
  2. Validation and Transformation: Ensure your data is always in the correct format with Zod's powerful schema validation and enrichment features.
  3. Dynamic Templating: Use Handlebars for conditional logic, loops, and custom helpers to create flexible and adaptable prompts.
  4. Modular Template Composition: Build complex prompt templates seamlessly from smaller prompt templates.
  5. Incremental Application: Build complex prompts step-by-step with partial templates, allowing you to fill in data incrementally.
  6. 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

  1. Concepts
  2. Features
  3. Installation
  4. Basic Usage
  5. API Reference
  6. Contributing
  7. License

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

  1. Define a schema describing the data your prompt needs.
  2. Define a template string (Handlebars syntax) that references properties in the schema.
  3. Create a PromptTemplate using promptTemplate(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 what TSchema 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.

1.2.0

6 months ago

1.2.1

6 months ago

1.1.1

7 months ago

1.1.0

7 months ago

1.1.2

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago