🎥 如何创建和设置AI代理

  • QuickNode
  • 发布于 2024-06-26 15:33
  • 阅读 38

本文介绍了如何创建一个集成了区块链技术的AI代理,用于在Discord上获取和处理以太坊区块数据。文章详细介绍了如何使用QuickNode Streams获取区块数据,并将其存储到PostgreSQL数据库中,同时通过JavaScript代码实现Discord与AI代理的交互。

概述

现代集成区块链技术的 AI 代理正变得越来越强大和多功能,从获取链上数据到进行代币交换。在本指南中,学习如何使用 JavaScript 和 OpenAI API 为 Discord 创建一个 AI 代理。

我们将从 QuickNode Streams 获取区块指标数据,并将其存储在 PostgreSQL 实例中。然后,一个 JavaScript 代码库将管理 Discord 上用户与我们的 AI 代理之间的交互。

如何创建和设置 AI 代理? - YouTube

QuickNode

131K 订阅者

如何创建和设置 AI 代理?

QuickNode

搜索

信息

购物

点击取消静音

如果播放没有很快开始,请尝试重新启动设备。

你已退出登录

你观看的视频可能会被添加到电视的观看历史记录中,并影响电视推荐。为避免这种情况,请取消并在电脑上登录 YouTube。

取消确认

分享

包含播放列表

检索共享信息时出错。请稍后再试。

稍后观看

分享

复制链接

在 YouTube 上观看

0:00

/ •直播

在 YouTube 上观看

订阅我们的 YouTube 频道以获取更多视频!订阅

你需要什么

你将做什么

  • 了解 AI 代理
  • 设置 QuickNode Stream
  • 使用 Discord 设置 AI 代理
  • 运行代理并从 QuickNode Streams 检索区块指标数据到 PostgreSQL 实例
依赖项 版本
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

使用 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

让我们开始为我们的文件添加代码,你也可以在 示例应用程序 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。