The Create stage is about defining a new AI as a structured, version-able asset in your codebase. The goal is to move away from scattered, hard-coded string prompts and toward a more disciplined and organized approach to prompt engineering.

Defining a capability as a prompt object

In Rudder, every capability is represented by a Prompt object. This object serves as the single source of truth for the capability’s logic, including its messages, metadata, and the schema for its arguments.

For now, these Prompt objects can be defined and managed as TypeScript files within your own project repository.

A typical Prompt object looks like this:

// src/prompts/email-summarizer.prompt.ts

import { Type, type Prompt } from '@axiomhq/ai';

export const emailSummarizerPrompt = {
  id: 'prompt_123',
  name: 'Email Summarizer',
  slug: 'email-summarizer',
  version: '1.0.0',
  environment: 'production',
  messages: [
    {
      role: 'system',
      content: 'Summarize emails concisely, highlighting action items. The user is named {{ username }}.',
    },
    {
      role: 'user',
      content: 'Please summarize this email: {{ email_content }}',
    },
  ],
  arguments: {
    username: Type.String(),
    email_content: Type.String(),
  },
} satisfies Prompt;

Strongly-typed arguments with Template

To ensure that prompts are used correctly, the @axiomhq/ai package includes a Template type system (exported as Type) for defining the schema of a prompt’s arguments. This provides type safety, autocompletion, and a clear, self-documenting definition of what data the prompt expects.

The arguments object uses Template helpers to define the shape of the context:

// src/prompts/report-generator.prompt.ts

import { Type, type Prompt } from '@axiomhq/ai';

export const reportGeneratorPrompt = {
  // ... other properties
  arguments: {
    company: Type.Object({
      name: Type.String(),
      isActive: Type.Boolean(),
      departments: Type.Array(
        Type.Object({
          name: Type.String(),
          budget: Type.Number(),
        }),
      ),
    }),
    priority: Type.Union([
      Type.Literal('high'),
      Type.Literal('medium'),
      Type.Literal('low')
    ]),
  },
} satisfies Prompt;

You can even infer the exact TypeScript type for a prompt’s context using the InferContext utility.

Prototyping and local testing

Before using a prompt in your application, you can test it locally using the parse function. This function takes a Prompt object and a context object, rendering the templated messages to verify the output. This is a quick way to ensure your templating logic is correct.

import { parse } from '@axiomhq/ai';
import { reportGeneratorPrompt } from './prompts/report-generator.prompt';

const context = {
  company: {
    name: 'Axiom',
    isActive: true,
    departments: [
      { name: 'Engineering', budget: 500000 },
      { name: 'Marketing', budget: 150000 },
    ],
  },
  priority: 'high' as const,
};

// Render the prompt with the given context
const parsedPrompt = await parse(reportGeneratorPrompt, { context });

console.log(parsedPrompt.messages);
// [
//   {
//     role: 'system',
//     content: 'Generate a report for Axiom.\nCompany Status: Active...'
//   }
// ]

Managing prompts with Axiom

To enable more advanced workflows and collaboration, Axiom is building tools to manage your prompt assets centrally.

  • Coming soon
    The axiom CLI will allow you to push, pull, and list prompt versions directly from your terminal, synchronizing your local files with the Axiom platform.
  • Coming soon
    The SDK will include methods like axiom.prompts.create() and axiom.prompts.load() for programmatic access to your managed prompts. This will be the foundation for A/B testing, version comparison, and deploying new prompts without changing your application code.

What’s next?

Now that you’ve created and structured your capability, the next step is to measure its quality against a set of known good examples.

Learn more about this step of the Rudder workflow in the Measure docs.