AgentMark uses adapters to convert its normalized prompt configuration (PromptShape) into model-specific input formats.

This guide walks you through how to build a custom adapter, using the VercelAIAdapter as a reference implementation.

What is an Adapter?

An adapter implements methods to transform AgentMark prompt types (text, object, image, speech, etc.) into provider-specific input formats (like OpenAI chat completions, Google PaLM input, etc).

It uses the raw prompt configuration (TextConfig, ObjectConfig, etc.) and combines it with:

  • Model configuration (e.g., name, temperature, max tokens)
  • Tool definitions (functions to be called during inference)
  • Optional telemetry or metadata

✅ Requirements

To create an adapter, you need to implement Adapter class from @agentmark/agentmark-core that defines the following methods:

Required Methods

  • adaptText(input, options, metadata)
  • adaptObject(input, options, metadata)
  • adaptImage(input, options)
  • adaptSpeech(input, options)

These methods are called by AgentMark when formatting a prompt.

Here’s a basic example of a MyCustomAdapter:

import {
  Adapter,
  TextConfig,
  ImageConfig,
  ObjectConfig,
  PromptShape,
  SpeechConfig,
  AdaptOptions,
  PromptMetadata
} from "@agentmark/agentmark-core";

export class MyCustomAdapter<T extends PromptShape<T>> implements Adapter<T> {
  declare readonly __dict: T;

  adaptText(input: TextConfig, options: AdaptOptions, metadata: PromptMetadata): CustomReturnTypeText {
    // Your custom logic here
    // Return a value of type CustomReturnTypeText
    return "customTextOutput";
  }

  adaptObject<K extends KeysWithKind<T, "object"> & string>(input: ObjectConfig, options: AdaptOptions, metadata: PromptMetadata): CustomReturnTypeObject<T[K]["output"]> {
    // Your custom logic here
    // Return a value of type CustomReturnTypeObject<T[K]>
    return "customObjectOutput";
  }

  adaptImage(input: ImageConfig, options: AdaptOptions, metadata: PromptMetadata): CustomReturnTypeImage {
    // Your custom logic here
    // Return a value of type CustomReturnTypeImage
    return "customImageOutput";
  }

  adaptSpeech(input: SpeechConfig, options: AdaptOptions, metadata: PromptMetadata): CustomReturnTypeSpeech {
    // Your custom logic here
    // Return a value of type CustomReturnTypeSpeech
    return "customSpeechOutput";
  }
}

Each method must transform the AgentMark prompt config into the correct shape for your target provider.

The Adapter interface is defined as follows:

import { KeysWithKind, PromptMetadata, AdaptOptions } from "@agentmark/agentmark-core"; // Assuming these are also from core

export interface Adapter<D extends PromptShape<D>> {
  readonly __dict: D;

  adaptText<K extends KeysWithKind<D, "text"> & string>(
    input: TextConfig,
    options: AdaptOptions,
    metadata: PromptMetadata
  ): any;

  adaptObject<K extends KeysWithKind<D, "object"> & string>(
    input: ObjectConfig,
    options: AdaptOptions,
    metadata: PromptMetadata
  ): any;

  adaptImage<K extends KeysWithKind<D, "image"> & string>(
    input: ImageConfig,
    options: AdaptOptions,
    metadata: PromptMetadata
  ): any;

  adaptSpeech<K extends KeysWithKind<D, "speech"> & string>(
    input: SpeechConfig,
    options: AdaptOptions,
    metadata: PromptMetadata
  ): any;
}

Here D is the AgentMark type and K is the path of the prompt.

You can then create a custom AgentMark client using your adapter:

import {
  AgentMark,
  KeysWithKind,
  Loader,
  ObjectPrompt,
  PromptFormatParams,
  PromptShape,
} from "@agentmark/agentmark-core";

// Required for type safety of object
export interface CustomObjectPrompt<
  T extends PromptShape<T>,
  A extends MyCustomAdapter<T>,
  K extends KeysWithKind<T, "object"> & string
> extends ObjectPrompt<T, A, K> {
  format({
    params
  }: PromptFormatParams<T[K]["input"]>): Promise<CustomReturnTypeObject<T[K]["output"]>>;
}

// Required for type safety
export interface CustomAgentmark<T extends PromptShape<T>>
  extends AgentMark<T, MyCustomAdapter<T>> {
  loadObjectPrompt<K extends KeysWithKind<T, "object"> & string>(
    pathOrPreloaded: K,
    options?: any
  ): Promise<CustomObjectPrompt<T, MyCustomAdapter<T>, K>>;
}

export function createCustomAgentMarkClient<D extends PromptShape<D>, T extends VercelAIToolRegistry<any, any> = VercelAIToolRegistry<any, any>>(opts: {
  loader?: Loader<D>;
}): CustomAgentmark<D> {
  const adapter = new MyCustomAdapter<D>();

  return new AgentMark<D, MyCustomAdapter<D>>({
    loader: opts.loader,
    adapter,
  });
}

PromptShape is a type that represents the shape of a prompt object in AgentMark types. KeysWithKind is a type that represents the keys of a prompt object in AgentMark types with a specific kind (eg. "text", "object", "image", "speech").

For more information on AgentMark types, see the AgentMark types documentation.

Model Registry

A Model Registry allows you to define and manage different AI models that your adapter can use. This is useful for swapping models, configuring model-specific parameters, or routing prompts to different models based on certain criteria.

Creating a Model Registry

Here’s an example of how you might define a model registry and register a model:

class CustomModelRegistry {
  private models: Record<string, ModelFunctionCreator> = {};
  
  register(modelName: string, creator: ModelFunctionCreator) {
    this.models[modelName] = creator;
  }

  getModelFunction(name: string): ModelFunctionCreator {
    return this.models[name] || throw new Error("Model not found");
  }
}

Your adapter can then use this registry to look up model configurations when processing prompts.

Tool Registry

A Tool Registry enables your AI models to interact with external tools or functions. This is essential for building agents that can perform actions, retrieve data, or interact with other services.

Creating a Tool Registry

You can create a tool registery similar to VercelAIToolRegistry defined here.

Make sure the tool registry is type safe to make the tools type safe with agentmark.

Have Questions?

We’re here to help! Choose the best way to reach us: