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:
Putting it all together, this is how a custom function could look like:
import { ExecutableGameFunctionResponse, ExecutableGameFunctionStatus, GameFunction } from"@virtuals-protocol/game";exportconstgetRecipe=newGameFunction({// 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 Logicexecutable:async (args) => {try {// Your custom logic hereconstresult= { ingredients: ["pasta","sauce"], time:"20 mins" };// <insert more custom logic here>// 4a. Return SuccessreturnnewExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Done,JSON.stringify(result) ); } catch (e) {// 4b. Handle ErrorsreturnnewExecutableGameFunctionResponse(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:
Error Handling
Always use try/catch blocks
Return meaningful error messages
Log errors for debugging
executable:async (args) => {try {// Validate inputif (!args.dish) {thrownewError("Dish name is required"); }// Process dataconstresult=awaitprocessData(args);// Return successreturnnewExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Done,JSON.stringify(result) ); } catch (e) {// Detailed error responsereturnnewExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Failed,`Error: ${e.message}` ); }}
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:
searchRecipeFunction:
Takes a food name (or generates one if not provided)
Queries the Spoonacular API
Returns detailed recipe information
getRecipeInstructionsFunction:
Takes recipe instructions as input
Formats them for easy reading
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 validationconstSPOONACULAR_API_KEY=process.env.SPOONACULAR_API_KEY??'';if (!SPOONACULAR_API_KEY) {thrownewError('SPOONACULAR_API_KEY is required');}// Function 1: API Call - Search for recipesexportconstsearchRecipeFunction=newGameFunction({ 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 {constname=args.food_name ||''; // Provide default empty stringconsole.log('Searching for recipe:', name);constresponse=awaitaxios.get('https://api.spoonacular.com/recipes/complexSearch', { params: { apiKey:SPOONACULAR_API_KEY, query: name, addRecipeInformation:true, number:1 } });returnnewExecutableGameFunctionResponse(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) {consterrorMessage= e instanceofError?e.message :'Unknown error occurred';returnnewExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Failed,`Failed to search recipes: ${errorMessage}` ); } }});exportconstgetRecipeInstructionsFunction=newGameFunction({ 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);returnnewExecutableGameFunctionResponse(ExecutableGameFunctionStatus.Done,JSON.stringify({ value:JSON.stringify({ instructions:"recipe instructions" })}) ); }});
simpleRecipeWorker.ts:
import { GameWorker } from"@virtuals-protocol/game";import { searchRecipeFunction, getRecipeInstructionsFunction } from"../functions/simpleRecipeFunction";exportconstsimpleRecipeWorker=newGameWorker({ 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";exportconstsimpleRecipeAgent=newGameAgent(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:
Agent initializes and plans its actions:
{"plan": ["Select a recipe","Provide recipe instructions"],"state_of_mind":"Ready to provide detailed recipe instructions"}
Agent executes the search:
"Performing function search_recipe with args {"food_name": "Grilled Chicken"}"
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! 🚀