本文介绍了如何创建一个集成了区块链技术的AI代理,用于在Discord上获取和处理以太坊区块数据。文章详细介绍了如何使用QuickNode Streams获取区块数据,并将其存储到PostgreSQL数据库中,同时通过JavaScript代码实现Discord与AI代理的交互。
现代集成区块链技术的 AI 代理正变得越来越强大和多功能,从获取链上数据到进行代币交换。在本指南中,学习如何使用 JavaScript 和 OpenAI API 为 Discord 创建一个 AI 代理。
我们将从 QuickNode Streams 获取区块指标数据,并将其存储在 PostgreSQL 实例中。然后,一个 JavaScript 代码库将管理 Discord 上用户与我们的 AI 代理之间的交互。
如何创建和设置 AI 代理? - YouTube
QuickNode
131K 订阅者
QuickNode
搜索
信息
购物
点击取消静音
如果播放没有很快开始,请尝试重新启动设备。
你已退出登录
你观看的视频可能会被添加到电视的观看历史记录中,并影响电视推荐。为避免这种情况,请取消并在电脑上登录 YouTube。
取消确认
分享
包含播放列表
检索共享信息时出错。请稍后再试。
稍后观看
分享
复制链接
在 YouTube 上观看
0:00
/ •直播
•
订阅我们的 YouTube 频道以获取更多视频!订阅
依赖项 | 版本 |
---|---|
node | 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 数据库作为 Stream 的目标,视频中使用了 PostgreSQL 提供商 tembo。
我们的 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
让我们开始为我们的文件添加代码,你也可以在 示例应用程序 monorepo 中找到完整代码。
.env
DISCORD_TOKEN=your_discord_bot_token
OPENAI_API_KEY=openai_api_key
DATABASE_URL=postgres_sql_database_url
替换每个环境变量的值,查看视频以了解如何获取每个值。
actions/getMetrics.js
const { Pool } = require('pg');
// 初始化 PostgreSQL 连接
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false, // 允许自签名证书
},
});
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; // 数据已经是解析后的 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; // 最大重试次数以处理速率限制
let retryDelay = 1000; // 初始重试延迟(以毫秒为单位)
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const response = await axios.post(
openaiEndpoint,
{
model: "gpt-4", // 使用 GPT-4 以获得更高质量的响应
messages: [{ role: "user", content: prompt }],
max_tokens: 200, // 将响应长度限制为 200 个 token
},
{
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
}
);
// 仅记录速率限制详细信息
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; // 指数增加延迟
} 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, // 需要读取消息内容\
],
});
// 线程特定的上下文存储
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; // 忽略机器人消息
// 在线程中处理后续查询
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);
}
});
// 登录 Discord
client.login(process.env.DISCORD_TOKEN);
这是主要的脚本,它处理从 Discord 用户获取输入并使用 actions/
目录中的脚本处理输出。
现在,要运行代理,只需运行 main.js
文件。
node main.js
成功后,你在 Discord 上的代理应该可以回答有关以太坊区块指标的问题。
让我们知道 如果你有任何反馈或对新主题的请求。我们很乐意听取你的意见。
- 原文链接: quicknode.com/guides/ai/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!