Building Custom Functions with GAME SDK: A TypeScript Guide

Introduction

Hey developers! 👋 Recently, we released the GAME Typescript SDK, which allows you to develop your agents powered by the GAME architecture in its most fullest and most flexible form. One can read more about the GAME framework in our whitepaper.

Since then, some of you have requested for more examples of custom functions built with the SDK.

In this article, we demonstrate how a custom function could be defined and used, by defining a receipe AI agent that uses a custom function to get recipes. We will use a simple recipe helper to demonstrate the power and flexibility of custom functions.

Key Components of a Custom Function 🛠

A custom function is typically made up of 4 main components. This is outlined below:

  1. Function Definition

    new GameFunction({
      name: string,
      description: string,
      args: Array<{name: string, description: string}>,
      executable: async (args) => ExecutableGameFunctionResponse
    })
  2. Arguments Structure

    args: [
      {
        name: "parameter_name",
        description: "What this parameter does"
      }
    ]
  3. Function Logic

  4. Response Handling

    // Success Response
    return new ExecutableGameFunctionResponse(
      ExecutableGameFunctionStatus.Done,
      JSON.stringify({
        your: "data",
        goes: "here"
      })
    );
    
    // Error Response
    return new ExecutableGameFunctionResponse(
      ExecutableGameFunctionStatus.Failed,
      "Error message here"
    );

Anatomy of a Custom Function 🔍

Putting it all together, this is how a custom function could look like:

import { 
  ExecutableGameFunctionResponse, 
  ExecutableGameFunctionStatus, 
  GameFunction 
} from "@virtuals-protocol/game";

export const getRecipe = new GameFunction({
  // 1. Function Identity
  name: "get_recipe",
  description: "Get cooking instructions for a dish",

  // 2. Function Parameters
  args: [
    { 
      name: "dish", 
      description: "Name of the dish to cook" 
    }
  ],

  // 3. Function Logic
  executable: async (args) => {
    try {
      // Your custom logic here
      const result = {
        ingredients: ["pasta", "sauce"],
        time: "20 mins"
      };
      // <insert more custom logic here>

      // 4a. Return Success
      return new ExecutableGameFunctionResponse(
        ExecutableGameFunctionStatus.Done,
        JSON.stringify(result)
      );

    } catch (e) {
      // 4b. Handle Errors
      return new ExecutableGameFunctionResponse(
        ExecutableGameFunctionStatus.Failed,
        "Failed to get recipe"
      );
    }
  }
});

Best Practices

In addition, when building your custom function, we recommend the following best practices to ensure a maintainable and stable codebase for your AI agent:

  1. Error Handling

    • Always use try/catch blocks

    • Return meaningful error messages

    • Log errors for debugging

    executable: async (args) => {
      try {
        // Validate input
        if (!args.dish) {
          throw new Error("Dish name is required");
        }
    
        // Process data
        const result = await processData(args);
    
        // Return success
        return new ExecutableGameFunctionResponse(
          ExecutableGameFunctionStatus.Done,
          JSON.stringify(result)
        );
    
      } catch (e) {
        // Detailed error response
        return new ExecutableGameFunctionResponse(
          ExecutableGameFunctionStatus.Failed,
          `Error: ${e.message}`
        );
      }
    }
  2. Async operations

    executable: async (args) => {
      try {
        // API calls
        const apiData = await fetchFromAPI(args.dish);
        
        // Database operations
        const dbResult = await saveToDatabase(apiData);
        
        // Return combined results
        return new ExecutableGameFunctionResponse(
          ExecutableGameFunctionStatus.Done,
          JSON.stringify({ api: apiData, db: dbResult })
        );
      } catch (e) {
        // Handle async errors
      }
    }

A Practical Example - Building a Recipe AI Agent with GAME Framework

Now, we put the concepts together to build a real AI agent with a custom function!

We'll show you how to create an AI-powered recipe assistant using the GAME framework and Spoonacular API. Let us break down each component and see how they work together.

The recipeFunction.ts file contains these 2 functions:

  1. searchRecipeFunction:

    1. Takes a food name (or generates one if not provided)

    2. Queries the Spoonacular API

    3. Returns detailed recipe information

  2. getRecipeInstructionsFunction:

    1. Takes recipe instructions as input

    2. Formats them for easy reading

    3. Returns structured cooking steps

Here is the implementation of the recipeFunction.ts file:

import {
    ExecutableGameFunctionResponse,
    ExecutableGameFunctionStatus,
    GameFunction,
  } from "@virtuals-protocol/game";
  import axios from 'axios';
 // Set up API key with fallback and validation
const SPOONACULAR_API_KEY = process.env.SPOONACULAR_API_KEY ?? '';
if (!SPOONACULAR_API_KEY) {
  throw new Error('SPOONACULAR_API_KEY is required');
}

// Function 1: API Call - Search for recipes
export const searchRecipeFunction = new GameFunction({
  name: "search_recipe",
  description: "Search for recipes using Spoonacular API to get recipe details",
  args: [{ 
    name: "food_name", 
    description: "name of the food to search for , if non is given just use a random food name you can come up with" 
  }],
  executable: async (args) => {
    try {
      const name = args.food_name || '';  // Provide default empty string
      console.log('Searching for recipe:', name);
      
      const response = await axios.get('https://api.spoonacular.com/recipes/complexSearch', {
        params: {
          apiKey: SPOONACULAR_API_KEY,
          query: name,
          addRecipeInformation: true,
          number: 1
        }
      });

      return new ExecutableGameFunctionResponse(
        ExecutableGameFunctionStatus.Done,
        JSON.stringify(
        //   value: `take this JSON recipe ${response.data.results} and give me the extracted cooking instructions` 
        {value: `${JSON.stringify(response.data.results)}`
        })
      );
    } catch (e: unknown) {
      const errorMessage = e instanceof Error ? e.message : 'Unknown error occurred';
      return new ExecutableGameFunctionResponse(
        ExecutableGameFunctionStatus.Failed,
        `Failed to search recipes: ${errorMessage}`
      );
    }
  }
});

export const getRecipeInstructionsFunction = new GameFunction({
  name: "get_recipe_instructions",
  description: "Get recipe instructions",
  args: [{ name: "recipe_instructions", description: "instructions of the recipe" }],
  executable: async (args) => {
    console.log('Getting recipe instructions for:', args);
    return new ExecutableGameFunctionResponse(
      ExecutableGameFunctionStatus.Done,
      JSON.stringify({ value: JSON.stringify({
        instructions: "recipe instructions"
      })})
    );
  }
});

simpleRecipeWorker.ts:

import { GameWorker } from "@virtuals-protocol/game";
import { searchRecipeFunction, getRecipeInstructionsFunction } from "../functions/simpleRecipeFunction";

export const simpleRecipeWorker = new GameWorker({
  id: "simple_recipe_worker",
  name: "Simple Recipe Worker",
  description: "A worker that provides detailed recipe instructions and prints out a log",
  functions: [searchRecipeFunction, getRecipeInstructionsFunction
  ]
}); 

simpleRecipeAgent.ts:

import { GameAgent } from "@virtuals-protocol/game";
import { simpleRecipeWorker } from "../workers/simpleRecipeWorker";

export const simpleRecipeAgent = new GameAgent(API_KEY, {
  name: "Simple Recipe Agent",
  goal: "Help users find basic cooking instructions of a recipe, if a recipe is not provided, just use a use a recipe you know",
  description: "A simple AI chef that provides detailed recipe instructions of a random recipe",
  workers: [simpleRecipeWorker]
}); 

This is the result we get from the terminal:

Initializing Recipe Culture Guide...
-----[Simple Recipe Agent]-----
Environment State: {}


-----[Simple Recipe Agent]-----
Agent State: {}


-----[Simple Recipe Agent]-----
Action State: {"hlp":{"plan_id":"3629731f-2cf2-44ef-b930-4f258f06c9eb","observation_reflection":"No recent task to reflect on.","plan":["Select a recipe","Provide recipe instructions"],"todo":[],"plan_reasoning":"Given the goal of helping users find basic cooking instructions of a recipe, I should first select a recipe to provide instructions for.","current_state_of_execution":"No activity so far.","state_of_mind":"I believe I can provide detailed recipe instructions. I'm interested in cooking and helping users. I have no fears.","change_indicator":"next_step","log":[]},"current_task":{"task_id":"c1af7f01-78fc-440d-8a86-c10d62568650","task":"Select a simple recipe","location_id":"simple_recipe_worker","task_reasoning":"I need to select a recipe to provide instructions for. Since no specific recipe is provided, I will choose a simple recipe I know.","llp":{"plan_id":"f6fdcdde-9ccd-4160-95b0-3b8e630e28b0","plan_reasoning":"Need to select a simple recipe, should start by searching for one.","situation_analysis":"","plan":["Search for a simple recipe"],"reflection":"No steps taken yet, starting from scratch.","change_indicator":"next_step"}}}.


-----[Simple Recipe Agent]-----
Performing function search_recipe with args {"food_name":{"value":"Grilled Chicken"}}.


Searching for recipe: Grilled Chicken
-----[Simple Recipe Agent]-----
Function status [done]: {"value":"[{\"vegetarian\":false,\"vegan\":false,\"glutenFree\":true,\"dairyFree\":true,\"veryHealthy\":false,\"cheap\":false,\"veryPopular\":false,\"sustainable\":false,\"lowFodmap\":false,\"weightWatcherSmartPoints\":7,\"gaps\":\"no\",\"preparationMinutes\":null,\"cookingMinutes\":null,\"aggregateLikes\":1,\"healthScore\":26,\"creditsText\":\"Foodista.com – The Cooking Encyclopedia Everyone Can Edit\",\"license\":\"CC BY 3.0\",\"sourceName\":\"Foodista\",\"pricePerServing\":92.53,\"id\":645621,\"title\":\"Grilled Chicken & Corn Red Potato Salad\",\"readyInMinutes\":45,\"servings\":8,\"sourceUrl\":\"http://www.foodista.com/recipe/8PSQYHCM/grilled-chicken-corn-red-potato-salad-with-jalapeno-vinaigrette\",\"image\":\"https://img.spoonacular.com/recipes/645621-312x231.jpg\",\"imageType\":\"jpg\",\"summary\":\"Grilled Chicken & Corn Red Potato Salad is a <b>gluten free and dairy free</b> side dish. One portion of this dish contains approximately <b>13g of protein</b>, <b>7g of fat</b>, and a total of <b>248 calories</b>. This recipe serves 8. For <b>93 cents per serving</b>, this recipe <b>covers 12%</b> of your daily requirements of vitamins and minerals. 1 person found this recipe to be scrumptious and satisfying. A mixture of red wine vinegar, dijon mustard, ear corn, and a handful of other ingredients are all it takes to make this recipe so scrumptious. It will be a hit at your <b>The Fourth Of July</b> event. From preparation to the plate, this recipe takes around <b>45 minutes</b>. It is brought to you by Foodista. With a spoonacular <b>score of 40%</b>, this dish is solid. Try <a href=\\\"https://spoonacular.com/recipes/grilled-potato-corn-red-onion-salad-over-arugula-7439\\\">Grilled Potato, Corn & Red Onion Salad Over Arugula</a>, <a href=\\\"https://spoonacular.com/recipes/grilled-corn-red-pepper-salad-544514\\\">Grilled Corn & Red Pepper Salad</a>, and <a href=\\\"https://spoonacular.com/recipes/warm-red-potato-and-corn-salad-96074\\\">Warm Red Potato and Corn Salad</a> for similar recipes.\",\"cuisines\":[],\"dishTypes\":[\"side dish\"],\"diets\":[\"gluten free\",\"dairy free\"],\"occasions\":[\"father's day\",\"4th of july\",\"summer\"],\"spoonacularScore\":74.22531127929688,\"spoonacularSourceUrl\":\"https://spoonacular.com/grilled-chicken-corn-red-potato-salad-645621\"}]"}.

Simple interpretation of the result:

When we run the agent, we see this workflow:

  1. Agent initializes and plans its actions:

    {
      "plan": ["Select a recipe", "Provide recipe instructions"],
      "state_of_mind": "Ready to provide detailed recipe instructions"
    }
  2. Agent executes the search:

    "Performing function search_recipe with args {"food_name": "Grilled Chicken"}"
  3. Returns detailed recipe information:

    • Cooking time: 45 minutes

    • Cost per serving: $0.93

    • Health metrics: 26/100 health score

    • Dietary info: Gluten-free and dairy-free

This example demonstrates

  • How to create small, modular functions that interact with real-world APIs to retrieve information about recipes.

    • Each function uses the GameFunction class to define its purpose, input arguments, and execution logic.

    • The logic involves making HTTP requests to the Spoonacular API to fetch dynamic data, such as recipe details, search results, or related recipes, based on user input.

  • How the GAME SDK can seamlessly integrate with external APIs to build practical, feature-rich applications - therefore creating intelligent AI agents that work autonomously.

    • In our example, the agent doesn't just blindly fetch data - it actively makes decisions about which recipes to choose, transforms complex API responses into meaningful information, and plans its next actions based on the results.

    • Even with a simple implementation, we can see how the GAME framework enables sophisticated behaviors like decision-making and structured planning, demonstrating that even basic AI agents can provide significant value when properly architected.

Conclusion 🎉

In this article, we built a recipe AI agent using custom functions as our building blocks. Our journey from simple API calls to an autonomous recipe assistant demonstrates how the one can leverage the GAME framework to create intelligent AI agents even without deep AI skills.

From this example, we learn that to build a good custom function, we need to remember to:

  • Keep functions focused and single-purpose

  • Handle errors gracefully

  • Use TypeScript's type system

  • Document your code well

  • Test thoroughly

These principles will help you build your own AI agents that are robust, maintainable, and effective at their tasks.

We look forward to seeing what kind of cool AI agents you guys could build with the help of AI agents! Happy coding! 🚀

Last updated