Menu

How to Make an AI Agent: Developing Applied Artificial Intelligence with Saafir

English
This guide will walk you through creating an AI agent using Saafir. Dive into the world of applied artificial intelligence development with practical examples.

How to Build Your Own AI Agent: A Step-by-Step Guide with the Saafir Framework

Today, artificial intelligence (AI) is fundamentally changing software development practices. So, how can you create your own AI-powered assistants or "agents"? In this article, we'll learn step-by-step how to develop intelligent AI agents that can understand your commands and perform tasks on your behalf using the TypeScript-based Saafir framework.

What is an AI Agent? A Simple Overview

In their simplest definition, AI agents are intelligent software components that automate specific tasks for you, understand and interpret your natural language commands, and can even perform various actions based on these commands (such as calling a function or making an API request). You can think of them as your personal assistants in the digital world.

Why Choose the Saafir Framework?

While many tools are available, Saafir offers some significant advantages, especially for TypeScript developers:

  • Smart Type Conversion: AI models often return text-based responses. Saafir saves you a lot of trouble by automatically converting these texts (e.g., "July 15, 2025" or "true") into the correct JavaScript types (Date, boolean, number, etc.).
  • Type Safety with Zod: Thanks to integrated Zod schemas, you can ensure that data coming from or sent to the AI is in the expected format. This significantly reduces runtime errors.
  • Flexible Integration: It can be easily integrated with popular Node.js frameworks like Express.js, Fastify, tRPC, and even WebSocket servers.
  • Multi-language Support: You can enable your agent to interact with users in English or other supported languages.
  • Developer-Friendly: Speeds up the development process with its debug mode and understandable structure.

Quick Start with Saafir

Incorporating Saafir into your project is quite easy. First, let's install the necessary packages:

npm install saafir zod openai chalk
# or
bun add saafir zod openai chalk

Creating Your First AI Assistant

Now, let's write our first AI agent that will perform a simple user creation task.

1. Defining Action Schemas and Functions

First, we need to define a Zod schema for the "action" our agent will perform and a function that will execute this action. The schema determines what parameters the AI should collect and in what types.

import { Saafir } from 'saafir';
import { z } from 'zod';

// Zod schema for the user creation action
const createUserSchema = z.object({
  name: z.string().describe("The user's full name"),
  email: z.string().email().describe("The user's email address"),
  age: z.number().min(18).describe("The user's age, must be at least 18"),
  isActive: z.boolean().optional().default(true).describe("Whether the user account is active (default: active)")
});

// Function to perform the user creation action
const createUser = async (input: z.infer<typeof createUserSchema>) => {
  console.log('Creating new user:', input);
  // Database record or other operations can be done here
  return { 
    message: `User ${input.name} created successfully. Email: ${input.email}, Age: ${input.age}, Active: ${input.isActive}`,
    userId: `user_${Math.random().toString(36).substr(2, 9)}` 
  };
};

Note: The .describe() methods in the schemas help the AI understand the parameters better.

2. Initializing and Using the Agent

After defining our schema and function, we can initialize the Saafir agent with this information.

// ... previous imports and definitions

// Configure the Saafir agent
const userManagerAgent = new Saafir({
  name: "UserManagerAgent", // Give your agent a name
  description: "An AI assistant used to manage user accounts.", // General description of the agent
  apiKey: process.env.OPENROUTER_API_KEY!, // Your OpenRouter API key
  model: "anthropic/claude-3.5-sonnet", // AI model to be used
  language: "English", // Agent's communication language
  debug: true, // To see detailed logs during development
  actions: {
    createUser: { // Give the action a unique name
      call: createUser, // Function to run when this action is called
      schema: createUserSchema, // Parameter schema for this action
      description: "Creates a new user account with the given information." // Description of the action to be understood by the AI
    }
  }
});

// Run the agent by giving a command in natural language
async function main() {
  const userInput = "Create a user named John Doe, with email john@example.com, aged 25.";
  try {
    const response = await userManagerAgent.run(userInput);
    console.log("AI Response:", response);
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

main();
// Expected Output (example):
// AI Response: {
//   message: 'User John Doe created successfully. Email: john@example.com, Age: 25, Active: true',
//   userId: 'user_xxxxxxxxx'
// }

It's that simple! The natural language command you give to the userManagerAgent.run() function is processed by Saafir, the AI model selects the createUser action, extracts the necessary parameters (name, email, age) from your sentence, validates them with createUserSchema, and finally calls your createUser function with these parameters.

Running Your Saafir Agent in Different Environments

Saafir can be easily integrated with various Node.js backend frameworks. Here are a few popular examples:

E-commerce Assistant with Express.js

Imagine an AI assistant for an e-commerce site with capabilities to add products and create orders.

import express from 'express';
// ... Saafir and Zod imports ...
// ... productSchema, orderSchema, addProduct, createOrder functions ...
// ... ecommerceAgent definition ...

const app = express();
app.use(express.json());

app.post('/chat', async (req, res) => {
  try {
    const { message } = req.body;
    if (!message) {
      return res.status(400).json({ error: 'Message cannot be empty' });
    }
    // Assuming ecommerceAgent is defined earlier
    // const response = await ecommerceAgent.run(message); 
    // res.json({ success: true, response });
    // Placeholder for example:
    res.json({ success: true, response: "AI response for Express integration will be here." });
  } catch (error) {
    console.error("Express chat error:", error);
    res.status(500).json({ 
      success: false, 
      error: error instanceof Error ? error.message : 'An unknown server error occurred.' 
    });
  }
});

app.listen(3000, () => {
  console.log('E-commerce AI assistant is waiting for POST requests at http://localhost:3000/chat.');
});
// Usage: With a POST request {"message": "Add a new laptop, price 25000 TL, category electronics"}

Note: The Express.js example above is shortened for the fluency of the blog post. You may need to adapt the full ecommerceAgent definition and action functions according to previous sections or your project structure.

Finance Manager with Fastify

An AI that can perform operations like adding expenses or setting budgets for personal finance tracking.

import Fastify from 'fastify';
// ... Saafir and Zod imports ...
// ... transactionSchema, budgetSchema, addTransaction, setBudget functions ...
// ... financeAgent definition ...

const fastify = Fastify({ logger: true });

fastify.post<{ Body: { message: string } }>('/finance-chat', async (request, reply) => {
  try {
    const { message } = request.body;
    if (!message) {
      reply.status(400);
      return { error: 'Message cannot be empty' };
    }
    // Assuming financeAgent is defined earlier
    // const response = await financeAgent.run(message);
    // return { success: true, response };
    // Placeholder for example:
    return { success: true, response: "AI response for Fastify integration will be here." };
  } catch (error) {
    fastify.log.error(error);
    reply.status(500);
    return { 
      success: false, 
      error: error instanceof Error ? error.message : 'An unknown server error occurred.' 
    };
  }
});

const startFastify = async () => {
  try {
    await fastify.listen({ port: 3001 });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};
startFastify();
// Usage: With a POST request {"message": "I spent 500 TL at the market today, category food"}

Note: The Fastify example is also similarly shortened.

Task Manager with tRPC

Interacting with AI via tRPC endpoints for project and task management.

import { initTRPC } from '@trpc/server';
import { createHTTPServer } from '@trpc/server/adapters/standalone';
// ... Saafir and Zod imports ...
// ... taskSchema, projectSchema, createTask, createProject functions ...
// ... taskAgent definition ...

const t = initTRPC.create();
const appRouter = t.router({
  taskChat: t.procedure
    .input(z.object({ message: z.string() }))
    .mutation(async ({ input }) => {
      if (!input.message) {
        throw new Error('Message cannot be empty');
      }
      // Assuming taskAgent is defined earlier
      // const response = await taskAgent.run(input.message);
      // return { success: true, response };
      // Placeholder for example:
      return { success: true, response: "AI response for tRPC integration will be here." };
    }),
});

const server = createHTTPServer({ router: appRouter });
server.listen(3002);
console.log('Task management AI assistant tRPC endpoint is running on http://localhost:3002.');
// Usage: By calling the taskChat mutation with a tRPC client {"message": "Create a task named 'Write Saafir article' for tomorrow"}

Note: The tRPC example is also similarly shortened.

Real-time Chatbot with WebSocket

Adding AI capabilities to a real-time chat application using uWebSockets.js (or a similar WebSocket library).

import { App, TemplatedApp } from 'uWebSockets.js'; // uWebSockets.js import corrected
// ... Saafir and Zod imports ...
// ... messageSchema, roomSchema, sendMessage, createRoom functions ...
// ... chatAgent definition ...

const uwsApp: TemplatedApp = App({}); // TemplatedApp type used
uwsApp.ws('/*', {
  message: async (ws, messageBuffer, isBinary) => {
    try {
      const message = Buffer.from(messageBuffer).toString();
      const data = JSON.parse(message);
      if (data.type === 'ai_command' && data.message) {
        // Assuming chatAgent is defined earlier
        // const response = await chatAgent.run(data.message);
        // ws.send(JSON.stringify({ type: 'ai_response', response }));
        // Placeholder for example:
        ws.send(JSON.stringify({ type: 'ai_response', response: "AI response for WebSocket integration will be here." }));
      }
    } catch (error) {
      console.error("WebSocket error:", error);
      ws.send(JSON.stringify({ type: 'error', message: 'Error processing message' }));
    }
  },
});

uwsApp.listen(3003, (token) => {
  if (token) {
    console.log('Chat AI assistant is waiting for WebSocket connections at ws://localhost:3003.');
  } else {
    console.log('Port 3003 could not be opened.');
  }
});
// Usage: By sending a JSON message {"type": "ai_command", "message": "Create general chat room"} via WebSocket.

Note: The WebSocket example is also similarly shortened, and uWebSockets.js usage is simplified.

Advanced Capabilities of Saafir

Saafir is not just about basic action calls. It also comes with some powerful features to ease your development process.

Automatic Type Conversions: Seamless Communication with AI

One of Saafir's biggest conveniences is its ability to automatically convert text-based data from AI into the correct JavaScript types.

const advancedSchema = z.object({
  createdAt: z.date().describe("Creation date (e.g., 'today', 'tomorrow at 2 PM')"),
  isActive: z.boolean().describe("Activity status (e.g., 'yes', 'active')"),
  price: z.number().describe("Price information (e.g., '150 USD')"),
  tags: z.array(z.string()).describe("Tags (comma-separated, e.g., 'technology, artificial intelligence')"),
  metadata: z.object({
    version: z.string(),
    author: z.string()
  }).describe("Metadata in JSON format (e.g., '{\'version\': \'1.0\', \'author\': \'John\'}')"),
  categories: z.set(z.string()).describe("Categories (comma-separated, unique)"),
  attributes: z.map(z.string(), z.string()).describe("Attributes in JSON format (key-value pairs)"),
  bigNumberValue: z.bigint().describe("A large number value (e.g., '12345678901234567890')"),
  description: z.string().optional().describe("Description (optional)"),
  priority: z.number().optional().describe("Priority level (optional)")
});

These automatic conversions make the data flow between AI and your application extremely smooth.

Debugging (Debug Mode)

Understanding what's happening during the development phase is crucial. Saafir's debug: true option helps you by printing every step of the agent's decision-making process to the console:

const agent = new Saafir({
  // ... other settings
  debug: true, // Enable debug mode
});

// Example Console Output:
// [SaafirAgent][DEBUG][2025-06-09T10:30:25.123Z] User input: "Create a new user..."
// [SaafirAgent][DEBUG][2025-06-09T10:30:26.456Z] AI Request: {...}
// [SaafirAgent][DEBUG][2025-06-09T10:30:27.789Z] AI Response: { actionName: 'createUser', parameters: {...} }
// [SaafirAgent][DEBUG][2025-06-09T10:30:27.795Z] Validating parameters for action 'createUser'...
// [SaafirAgent][DEBUG][2025-06-09T10:30:27.800Z] Executing action 'createUser'...
// [SaafirAgent][DEBUG][2025-06-09T10:30:27.850Z] Action 'createUser' executed. Result: {...}

Multi-language Support and Personalization

You might want your agent to speak different languages with users or adopt a specific personality.

const multilingualAgent = new Saafir({
  // ... other settings
  language: "English", // Or "Turkish", "Spanish", "French", etc.
  context: "You are a witty and friendly assistant helping customers on an e-commerce site. You can provide detailed information about products and make recommendations.",
  // Additional information like title, referer can also be sent to the AI model.
});

// When the user says "Yeni bir kullanıcı oluştur adı John",
// if set to English, you might get a response like "New user named John created."

The context parameter gives the AI model important clues about the agent's role and behavior.

Conclusion and Next Steps

The Saafir framework significantly simplifies creating AI-powered agents with TypeScript. Managing complex AI interactions becomes easier thanks to automatic type conversions, Zod-integrated schema validation, and its flexible structure.

In this guide, we've covered Saafir's capabilities, from basic setup to different framework integrations and some advanced features. You now have the necessary knowledge to bring your own AI agents to life. Start leveraging the power of AI by trying Saafir in your projects!

Remember, the AI world is constantly evolving. Tools like Saafir will help you keep up with this development. Good luck!


Take your AI-powered applications to the next level with Saafir! 🚀

1

Never miss a campaign!

Subscribe to our newsletter and don't miss our exciting offers.