Skip to main content

πŸŽ₯ How to Create and Set Up AI Agents

Updated on
Feb 11, 2025

10 min read

Overview​

Modern AI agents integrating blockchain tech are becoming very powerful and versatile, from getting onchain data to making token swaps. In this guide, learn how to create an AI agent for Discord using JavaScript and OpenAI API.

We will get block metrics data from QuickNode Streams and store it in a PostgreSQL instance. Then, a JavaScript codebase will manage interactions between users on Discord and our AI agent.

Subscribe to our YouTube channel for more videos!

What You Will Need​

What You Will Do​

  • Learn about AI Agents
  • Set up a QuickNode Stream
  • Set up the AI Agent with Discord
  • Run the Agent and retrieve block metrics data from QuickNode Streams to a PostgreSQL instance
DependencyVersion
node23.3.0
axios^1.7.9
discord.js^14.18.0
dotenv^16.4.7
openai^4.83.0
pg^8.13.1

Setting up the Data Stream​

We will set up a QuickNode Stream to get filtered block data. Check out the following Streams filter code to get filtered block metrics.

function main(stream) {
const data = stream.data ? stream.data : stream;
const block = Array.isArray(data) ? data[0] : data;

if (!block || !block.transactions) {
throw new Error('Invalid block data structure');
}

const metadata = stream.metadata || {};
const dataset = metadata.dataset;
const network = metadata.network;
const transactions = block.transactions;
const blockNumber = parseInt(block.number, 16);
const blockTimestampHex = block.timestamp;
const blockTimestamp = new Date(parseInt(blockTimestampHex, 16) * 1000).toISOString();

let totalGasPrice = BigInt(0);
let totalEthTransferred = BigInt(0);
let contractCreations = 0;
let largestTxValue = BigInt(0);
let largestTxHash = null;
const activeAddresses = new Set();
const uniqueTokens = new Set();

for (const tx of transactions) {
const gasPriceInWei = BigInt(tx.gasPrice);
totalGasPrice += gasPriceInWei;

const valueInWei = BigInt(tx.value);
totalEthTransferred += valueInWei;

if (valueInWei > largestTxValue) {
largestTxValue = valueInWei;
largestTxHash = tx.hash;
}

if (tx.from) activeAddresses.add(tx.from.toLowerCase());
if (tx.to) activeAddresses.add(tx.to.toLowerCase());

if (!tx.to) contractCreations++;

if (tx.to) uniqueTokens.add(tx.to.toLowerCase());
}

const averageGasPriceInWei = totalGasPrice / BigInt(transactions.length);
const averageGasPriceInGwei = Number(averageGasPriceInWei) / 1e9;

return {
blockNumber,
blockTimestamp,
dataset,
network,
transactionCount: transactions.length,
averageGasPriceInWei: averageGasPriceInWei.toString(),
averageGasPriceInGwei,
totalEthTransferred: (Number(totalEthTransferred) / 1e18).toString(),
activeAddressCount: activeAddresses.size,
contractCreations,
uniqueTokensInteracted: uniqueTokens.size,
largestTx: {
value: (Number(largestTxValue) / 1e18).toString(),
hash: largestTxHash,
},
};
}

We will have a PostgreSQL database as the destination for our Stream, tembo is a PostgreSQL provider used in the video.

Setting the AI Agent with Discord​

Our AI agent will work as a Discord bot, you can create your Discord bot on Discord developer portal.

Let's create a directory for our JavaScript app and make it our working directory:

mkdir BLOCK-METRICS-BOT
cd BLOCK-METRICS-BOT

Now, for our JavaScript app, we will first need to install a bunch of libraries for Discord, OpenAI, PostgreSQL, and environment variable interactions:

npm i axios discord.js openai pg dotenv

Then create the following files in your root directory:

BLOCK-METRICS-BOT/
β”œβ”€β”€ .env
β”œβ”€β”€ main.js
└── actions/
β”œβ”€β”€ getMetrics.js
└── openaiHelper.js

Let's start on adding code to our files, you can also find the entire code in sample apps monorepo.

.env
DISCORD_TOKEN=your_discord_bot_token
OPENAI_API_KEY=openai_api_key
DATABASE_URL=postgres_sql_database_url

Replace the values of each environment variable appropriately, check out the video to learn how to get each value.

actions/getMetrics.js
const { Pool } = require('pg');

// Initialize PostgreSQL connection
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false, // Allow self-signed certificates
},
});

module.exports = async (blockNumber) => {
const query = `
SELECT data
FROM "block-metrics"
WHERE (data->>'blockNumber')::BIGINT = $1
`;

try {
const result = await pool.query(query, [blockNumber]);

if (result.rows.length > 0) {
return result.rows[0].data; // Data is already parsed JSON
} else {
return null;
}
} catch (err) {
console.error("Database query error:", err.message);
throw new Error("Failed to fetch block metrics.");
}
};

The above script will get data from PostgreSQL database.

actions/openaiHelper.js
const axios = require('axios');

const openaiEndpoint = 'https://api.openai.com/v1/chat/completions';

module.exports = async (prompt) => {
const MAX_RETRIES = 5; // Maximum retries for handling rate limits
let retryDelay = 1000; // Initial retry delay (in milliseconds)

for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const response = await axios.post(
openaiEndpoint,
{
model: "gpt-4", // Use GPT-4 for higher quality responses
messages: [{ role: "user", content: prompt }],
max_tokens: 200, // Limit the response length to 200 tokens
},
{
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
}
);

// Log only rate limit details
console.log("Rate Limit Details:", {
remainingRequests: response.headers['x-ratelimit-remaining-requests'],
resetTime: response.headers['x-ratelimit-reset-requests'],
});

return response.data.choices[0].message.content.trim();
} catch (err) {
if (err.response?.status === 429) {
console.warn(`Rate limit hit. Retrying in ${retryDelay / 1000}s... (Attempt ${attempt}/${MAX_RETRIES})`);
await new Promise((resolve) => setTimeout(resolve, retryDelay));
retryDelay *= 2; // Increase delay exponentially
} else {
console.error("OpenAI API Error:", err.response?.data || err.message);
throw new Error("Failed to generate response.");
}
}
}

return "I'm currently experiencing high demand and cannot process your request. Please try again later.";
};

This script will help in interacting with OpenAI API and process requests.

main.js
require('dotenv').config();
const { Client, GatewayIntentBits } = require('discord.js');
const getMetrics = require('./actions/getMetrics');
const openaiHelper = require('./actions/openaiHelper');

const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, // Required to read message content
],
});

// Thread-specific context storage
const THREAD_CONTEXT = new Map();

client.once('ready', () => {
console.log(`Logged in as ${client.user.tag}`);
});

client.on('messageCreate', async (message) => {
if (message.author.bot) return; // Ignore bot messages

// Handle follow-up queries in threads
if (message.channel.isThread()) {
const context = THREAD_CONTEXT.get(message.channel.id);

if (!context) {
message.channel.send("This thread has no active context. Please start a new query.");
return;
}

const includeTimestamp = /time|date|when|confirmed|timestamp/i.test(message.content);
const prompt = `You are Michael Scott, the quirky and often inappropriate boss from The Office.
You are answering questions about Ethereum block ${context.blockNumber}.
Here is the known data for block ${context.blockNumber}:
${JSON.stringify(context.blockData, null, 2)}
User's query: "${message.content}"
Respond as Michael Scott would: provide an accurate answer first, and then add a humorous remark in Michael's style.
${includeTimestamp ? `Mention the block timestamp (${context.blockData.blockTimestamp}) as part of the response.` : 'Do not mention the block timestamp unless explicitly asked.'}
Keep your response under 150 tokens.`;

try {
const response = await openaiHelper(prompt);
await message.reply(response);
} catch (error) {
console.error("OpenAI Error:", error.message);
message.reply("Uh-oh, looks like something went wrong. Classic Michael mistake!");
}
return;
}

const blockNumberMatch = message.content.match(/block(?:\s*number)?\s*(\d+)/i);

if (blockNumberMatch) {
const blockNumber = parseInt(blockNumberMatch[1], 10);

try {
const blockData = await getMetrics(blockNumber);

if (!blockData) {
message.channel.send(`No data found for block ${blockNumber}. That's what she said!`);
return;
}

const thread = await message.startThread({
name: `Block ${blockNumber} Query`,
autoArchiveDuration: 60,
});

THREAD_CONTEXT.set(thread.id, { blockNumber, blockData });

const includeTimestamp = /time|date|when|confirmed|timestamp/i.test(message.content);
const prompt = `You are Michael Scott, the quirky and often inappropriate boss from The Office.
You are answering questions about Ethereum block ${blockNumber}.
Here is the known data for block ${blockNumber}:
${JSON.stringify(blockData, null, 2)}
User's query: "${message.content}"
Respond as Michael Scott would: provide an accurate answer first, and then add a humorous remark in Michael's style.
${includeTimestamp ? `Mention the block timestamp (${blockData.blockTimestamp}) as part of the response.` : 'Do not mention the block timestamp unless explicitly asked.'}
Keep your response under 150 tokens.`;

const response = await openaiHelper(prompt);
await thread.send(response);
} catch (error) {
console.error("Error:", error.message);
message.channel.send(`I couldn't process your query for block ${blockNumber}.`);
}
} else {
const funnyResponses = [
"I'm sorry, I can't read your mind. Do you know how many times I've been asked to do that in the office? Just give me a block number!",
"This feels like a setup for 'that's what she said.' Anyway, I need a block number to work with.",
"No block number? That’s okay, I’ll just sit here awkwardly until you give me one.",
"Imagine I'm your assistant... but I need details. Which block are we talking about?",
"You’re lucky I’m not Dwight, or I’d make you fill out a block request form. Just give me the number!"
];
const followUp = "Could you please specify the block number you'd like to know about?";

message.channel.send(funnyResponses[Math.floor(Math.random() * funnyResponses.length)]);
setTimeout(() => {
message.channel.send(followUp);
}, 2000);
}
});

// Login to Discord
client.login(process.env.DISCORD_TOKEN);

This is the main script that handles the takes input from user on Discord and process the output using the scripts in actions/ directory.

Now, to run the agent just run the main.js file.

node main.js

Once this is successful, your agent on Discord should be ready to answer your questions about Ethereum block metrics.

We ❀️ Feedback!

Let us know if you have any feedback or requests for new topics. We'd love to hear from you.

Share this guide