跳至主要內容

🎥 如何建立與設定 AI 代理程式

更新於
2026年4月8日

閱讀時間 10 分鐘


單一串流、單一費用、多個目的地

您的Quicknode 串流現可透過單一管道同時傳送至多個目的地。無需重複建立串流、不會發生配置偏移,且每處目的地均不收取額外費用。每個串流可用的目的地數量因方案而異。了解更多

概覽

整合區塊鏈技術的現代 AI 代理程式正變得越來越強大且功能多樣,從獲取鏈上數據到進行代幣兌換皆能勝任。在本指南中,您將學習如何使用 JavaScript 和 OpenAI API 為 Discord 建立一個 AI 代理程式。

我們將從 Quicknode Streams 取得區塊指標資料,並將其儲存於 PostgreSQL 實例中。接著,一套 JavaScript 程式碼將負責管理 Discord 上的使用者與我們的 AI 代理程式之間的互動。

訂閱我們的 YouTube 頻道,觀看更多影片!

您需要準備的物品

您將負責的工作內容

  • 了解 AI 代理程式
  • 設定Quicknode 串流
  • 透過 Discord 設定 AI 代理程式
  • 執行代理程式,並將 Quicknode Streams 中的區塊指標資料擷取至 PostgreSQL 實例
依賴關係版本
節點23.3.0
axios^1.7.9
discord.js^14.18.0
dotenv^16.4.7
openai^4.83.0
pg^8.13.1

設定資料流

我們將設定一個Quicknode Stream來取得經過篩選的區塊資料。請參閱以下 Streams 篩選程式碼,以取得經過篩選的區塊指標。

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,
},
};
}

我們將使用 PostgreSQL 資料庫作為串流的目標位置;影片中使用的 PostgreSQL 提供者是tembo

透過 Discord 設定 AI 代理程式

我們的 AI 代理程式將以 Discord 機器人的形式運作,您可以在Discord 開發者入口網站上建立自己的 Discord 機器人。

讓我們為我們的 JavaScript 應用程式建立一個目錄,並將其設為工作目錄:

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

現在,針對我們的 JavaScript 應用程式,我們首先需要安裝一系列用於 Discord、OpenAI、PostgreSQL 以及環境變數互動的函式庫:

npm i axios discord.js openai pg dotenv

接著,請在您的根目錄中建立以下檔案:

BLOCK-METRICS-BOT/
├── .env
├── main.js
└── actions/
├── getMetrics.js
└── openaiHelper.js

讓我們開始在檔案中加入程式碼吧,您也可以在範例應用程式的單一儲存庫中找到完整的程式碼。

.env
DISCORD_TOKEN=您的 Discord 機器人代幣
OPENAI_API_KEY=openai_api_key
資料庫網址=postgres_sql_database_url

請適當替換每個環境變數的值,並觀看影片以了解如何取得各變數的值。

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.");
}
};

上述腳本將從 PostgreSQL 資料庫擷取資料。

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.";
};

此腳本將協助您與 OpenAI API 進行互動並處理請求。

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);

這是主要腳本,負責處理 Discord 上用戶的輸入,並透過位於 actions/ 目錄。

現在,要執行該代理程式,只需執行 main.js 檔案。

node main.js

一旦操作成功,您在 Discord 上的代理程式便應能為您解答有關以太坊區塊指標的問題。

我們 ❤️ 您的回饋!

如果您有任何意見回饋或對新主題的建議,請告訴我們。我們非常樂意聽取您的意見。

分享這份指南