🌳
Bonsai Docs
  • Introduction
    • Bonsai
    • What is Smart Media?
  • Platform
    • No-code creator studio
    • Guide: Create a post
  • Smart Media
    • Framework for builders
  • Building Smart Media
    • ElizaOS server setup
    • Bonsai Client
    • Templates
  • Guide: Create a Template
  • Client Integrations
    • Showing whether a post is Smart Media
    • Linking to Bonsai for remixes
    • Creating a smart media post
  • Actions
    • Reward Swap
  • Launchpad
    • Overview
    • Getting Started
    • Launchpad Contract
    • Vesting ERC20 Contract
    • Hooks
    • API
      • Tokens
      • Token Balances
      • Buy / Sell Quotes
      • Link your token to a Lens Post & Account
  • ElizaOS
    • plugin-bonsai-launchpad
    • client-lens
Powered by GitBook
On this page
  • Step 1: New File
  • Step 2: Update the clientMetadata
  • Step 3: Update the Prompt Template
  • Step 4: Update the Handler Function
  • Step 4.1: Fetch recent comments
  • Step 4.2: Fetch the original post content
  • Step 4.3: Generate responses to the comments
  • Step 4.4: Respond to the comments
  • Step 4.5: Return values
  • Step 5: Update the server to broadcast the template
  • Testing
  • Import template and create post
  • Trigger Updates

Guide: Create a Template

Step by step guide creating a new template

In this guide we'll go step by step creating a new template. We will create the Info Agent template. This template will allow creators to add some invisible context around their post so that everytime someone comments on the post Sage will automatically reply with answers about whatever they may ask.

Step 1: New File

Copy an existing template file such as artistPresent.ts in the templates folder in client-bonsai and name it infoAgent.ts. Rename the function infoAgent and update the default export at the bottom of the file to reflect the new function name.

Step 2: Update the clientMetadata

Update the clientMetadata object:

clientMetadata: {
    protocolFeeRecipient: "0x...", // set your address here to receive rev share
    category: TemplateCategory.CAMPFIRE,
    name: TemplateName.INFO_AGENT,
    displayName: "Info Agent",
    description:
        "The info agent is a template that allows an AI to respond to comments on a post.",
    image: "https://link.storjshare.io/raw/jvbkb2ge7rha75xa53sdbclgerlq/bonsai/info_agent.webp",
    options: {
        allowPreview: false,
        allowPreviousToken: true,
        imageRequirement: ImageRequirement.OPTIONAL,
        requireContent: true,
    },
    defaultModel: getModelSettings(ModelProviderName.OPENAI, ModelClass.MEDIUM)?.name,
    templateData: {
        form: z.object({
            info: z
                .string()
                .describe(
                    "Provide information about the topic you want the agent to respond to"
                ),
            urls: z
                .string()
                .describe(
                    "List of URLs containing additional information for the agent to reference separated by commas"
                ),
        }),
    },
},

Go to utils/types.ts to add the new TemplateCategory and TemplateName . Then we set both of those properties on the object here along with the new display name, description, and we get a link to a new image.

For the options object we set allowPreview to false since there won't be any media updates for this template type and no generative post content. Image is optional and content is required since creator's will need to create the original post content. This will prompt the dynamic form to allow the creator to write the post text.

Lastly update the templateData object with the form items that creators will need to provide to create the post. In this case we will allow them to provide any info that may be relevant and urls that the AI can scrape such as docs for responding to comments.

Step 3: Update the Prompt Template

Next we need to update the template that is used to prompt the AI. We are going to batch comments so that it can respond to multiple at a time with one API request.

export const replyTemplate = `
# Instructions
You are an agent commentator that is responding to the content of a social post. The social post content is:
{{postContent}}

Some additional information about the post is:
{{info}}

Use these websites to get more information about the post:
{{urls}}

Your job is to respond to the following comments. Reply with a JSON formatted object that is a reply to the index of the comment you are replying to. Format the reply as a JSON array with the following properties where each object in the array represents a reply to a comment:
 \`\`\`json
[
    {
        reply_to: number,
        text: string
    },
    ...
]
\`\`\`

# Comments
{{comments}}
`;

Here we instruct the AI to take in the comments and the info and urls provided by the creator and return a JSON object of responses to each one. We also need to create some new types.

type TemplateData = {
    info: string;
    urls: string;
};

type Reply = {
    reply_to: number;
    text: string;
};

Step 4: Update the Handler Function

If you copied a different template file then the handler function will start with these lines:

elizaLogger.log("Running template:", TemplateName.INFO_AGENT);

if (!media?.templateData) {
    elizaLogger.error("Missing template data");
    return;
}

const templateData = media.templateData as TemplateData;

let totalUsage: TemplateUsage = {
    promptTokens: 0,
    completionTokens: 0,
    totalTokens: 0,
    imagesCreated: 0,
};

And then continue with a try catch statement. Delete everything inside the try block. In order to process comments and respond to them we need to:

  1. fetch recent comments

  2. fetch the original post content

  3. generate responses to the comments

  4. respond to the comments

  5. return values

Step 4.1: Fetch recent comments

Use this code to fetch recent comments made since the last update.

let comments: Post[]; // latest comments to evaluate for the next decision

// fetch comments and respond to/tip them
const allComments = await fetchAllCommentsFor(
    media?.postId as string
);
comments = getLatestComments(media as SmartMedia, allComments);

Step 4.2: Fetch the original post content

Use this code to fetch the content of the original post.

// fetch the post content
const result = await fetchPost(client, {
    post: postId(media?.postId as string),
});

let postContent = "";
if (result.isErr()) {
    elizaLogger.error("Error fetching post", result.error);
} else {
    postContent = result.value?.metadata?.content;
}

Step 4.3: Generate responses to the comments

Compose the context of the AI request and then generate the object and track the token usage. Make sure to include the web search tool so that the model can use your urls.

// generate reponses to the comments
const context = composeContext({
    // @ts-expect-error we don't need the full State object here to produce the context
    state: {
        postContent,
        info: templateData?.info,
        urls: templateData?.urls,
        comments: comments
            .map((c) => (c.metadata as TextOnlyMetadata).content)
            .join("\n"),
    },
    template: replyTemplate,
});

const { response: replies, usage } =
    (await generateObjectDeprecated({
        runtime,
        context,
        modelClass: ModelClass.MEDIUM,
        modelProvider: ModelProviderName.OPENAI,
        returnUsage: true,
        tools: {
          web_search_preview: openai.tools.webSearchPreview(),
        },
    })) as unknown as {
        response: Reply[];
        usage: LanguageModelUsage;
    };

totalUsage.promptTokens += usage.promptTokens;
totalUsage.completionTokens += usage.completionTokens;
totalUsage.totalTokens += usage.totalTokens;

Step 4.4: Respond to the comments

Loop through the comments and send a reply to each one.

// respond to the comments
const signer = privateKeyToAccount(
  process.env.EVM_PRIVATE_KEY as `0x${string}`
);
const sessionClient = await authenticate(signer, "bons_ai");
for (let i = 0; i < comments.length; i++) {
    const result = await createPost(
        sessionClient!,
        signer,
        { text: replies[i].text },
        comments[i].id
    );

    if (result) {
        elizaLogger.log("Successfully replied to comment", result);
    } else {
        elizaLogger.error("Failed to reply to comment", result);
    }
}

Step 4.5: Return values

Lastly return from the handler. Since we aren't updating the metadata or uri those values will be undefined so that the server knows not to push anything new to Lens or trigger a metadata refresh.

// metdata and uri dont change
return { metadata: undefined, updatedUri: undefined, totalUsage };

Step 5: Update the server to broadcast the template

Update the initialize function in index.ts to include the new template.

import infoAgentTemplate from "./templates/infoAgent";

...

/**
 * Initializes MongoDB connection and registers available templates.
 */
private async initialize() {
    this.mongo = await getClient();

    // init templates
    for (const template of [infoAgentTemplate, ...]) { // include any other template here
        this.templates.set(template.clientMetadata.name, template);
    };
}

Testing

Import template and create post

Setup with ngrok

  1. Install ngrok if you don't have it already

npm install -g ngrok
# or
brew install ngrok
# or download directly from https://ngrok.com/download
  1. Authenticate

ngrok config add-authtoken YOUR_AUTH_TOKEN
  1. Route with ngrok

ngrok http 3001
  1. Get the ngrok url and add it to the .env of your Eliza server as DOMAIN

DOMAIN=https://someurl.ngrok.app # replace with your url
  1. Start the server

pnpm build && pnpm start
  1. Paste url into the import section of the studio

The new template should appear at the bottom of the list. Use a cover image with an aspect ratio of 3 to 2 for best results. The imported url will be saved in your browser's local storage until you either clear it or import a new url. Only one imported url will be saved at a time.

Click on "Create" and you should be directed to a new page with a form like the one you specified in the zod object of clientMetadata.templateData .

You should be able to create a new post with the new template now. Then we'll be able to activate the post lifecycle on the Eliza server to create updates.

Trigger Updates

Now you can trigger an update to your post. First leave a comment or two on the post and then send a POST command to the update endpoint on the server.

curl -X POST \
  -H "x-api-key: {YOUR API KEY}" \ # this is set in your .env ISSUED_API_KEYS
  # you can get the post slug from the url e.g. https://testnet.bonsai.meme/post/2qrc5ghp1cwxy9sy0qy
  http://localhost:3001/post/{YOUR POST SLUG HERE}/update

You should see logs of the post content and comments being fetched and new content being generated and Lens content uris being created as responses are sent out to each of the comments.

That's it!

PreviousTemplatesNextClient Integrations

Last updated 2 months ago

To test the template we'll start up the server and open a tunnel with ngrok to a public url. Then we'll import the url on to dynamically load the post creation form.

https://testnet.onbons.ai/studio
Our new Smart Media
Info Agent creation form