AI驱动的Discord聊天机器人

APPLICATION Dec 2, 2024

在花费大量时间将 Amazon Bedrock 集成到应用程序中并构建基于 RAG 的聊天机器人后,我想—为什么不尝试一些更简单的东西呢?我决定创建一个由人工智能驱动的 Discord 机器人,它可以实时回答小组成员的问题。

1、设置基础

我首先在 Amazon Bedrock 上启用 Llama 3.2——我之所以做出这个选择,是因为它是一个支持文本和视觉功能的多模态模型。这意味着我们的机器人不仅可以回答问题——它实际上可以分析用户上传的图像并回答有关它们的特定问题。

Amazon Bedrock 特别吸引人的地方在于它为开发人员提供了开箱即用的推理端点。无需处理服务器配置、API 公开或负载平衡。我们可以直接进入开发阶段,这正是我想要的。

2、Discord 机器人设置

Discord 方面的事情出奇地简单。如果你正在关注:

前往 Discord 开发者平台,创建新应用程序。

在设置中的 OAuth2 部分下生成令牌:

确保将其设置为公共机器人 - 如果你想邀请它加入你的服务器,这一点至关重要

3、与 Amazon Bedrock 集成

实际的集成代码非常干净。我使用了 Bedrock Converse API,这是实现此功能的关键。基本实现如下所示:

import boto3
from botocore.exceptions import ClientError

client = boto3.client("bedrock-runtime", region_name="region_goes_here")

model_id = "model_id_goes_here"

user_message = "Describe the purpose of a 'hello world' program in one line."
conversation = [
 {
 "role": "user",
 "content": [{"text": user_message}],
 }
]

try:
 response = client.converse(
 modelId=model_id,
 messages=conversation,
 inferenceConfig={"maxTokens": 512, "temperature": 0.5, "topP": 0.9},
 )
 response_text = response["output"]["message"]["content"][0]["text"]
 print(response_text)

except (ClientError, Exception) as e:
 print(f"ERROR: Can't invoke '{model_id}'. Reason: {e}")
 exit(1)

4、处理图像处理

由于 Llama 3.2 支持多模式交互,我确保实现图像处理功能。机器人现在可以处理用户上传的图像,但有一个问题 — Llama 3.2 有特定的图像大小限制。为了解决这个问题,我在将图像发送到 Bedrock Converse API 之前实现了图像大小调整和 base64 编码。

import pathlib
from PIL import Image as PILImage
import base64
import io
from typing import Tuple, Optional, Union
import magic  # for mime type detection

class ImageUtils:
    @staticmethod
    def resize_img(b64imgstr: str, size: Tuple[int, int] = (256, 256)) -> str:
        """
        Resize a base64 encoded image to the specified size.
        
        Args:
            b64imgstr (str): Base64 encoded image string
            size (tuple): Target size as (width, height)
            
        Returns:
            str: Base64 encoded resized image
        """
        buffer = io.BytesIO()
        img = base64.b64decode(b64imgstr)
        img = PILImage.open(io.BytesIO(img))
        rimg = img.resize(size, PILImage.LANCZOS)
        rimg.save(buffer, format=img.format)
        return base64.b64encode(buffer.getvalue()).decode("utf-8")

    @staticmethod
    def img2base64(image_path: Union[str, pathlib.Path], resize: bool = False) -> str:
        """
        Convert an image file to base64 string.
        
        Args:
            image_path (str or Path): Path to the image file
            resize (bool): Whether to resize the image to 256x256
            
        Returns:
            str: Base64 encoded image
        """
        with open(image_path, "rb") as img_f:
            img_data = base64.b64encode(img_f.read())
        
        if resize:
            return ImageUtils.resize_img(img_data.decode())
        else:
            return img_data.decode()

    @staticmethod
    def get_image(image_path: Union[str, pathlib.Path]) -> bytes:
        """
        Read an image file and return its bytes.
        
        Args:
            image_path (str or Path): Path to the image file
            
        Returns:
            bytes: Raw image data
        """
        with open(image_path, "rb") as img_f:
            return img_f.read()

    @staticmethod
    def process_image_bytes(image_bytes: bytes, resize: bool = True) -> Tuple[bytes, str]:
        """
        Process image bytes - optionally resize and detect format.
        
        Args:
            image_bytes (bytes): Raw image bytes
            resize (bool): Whether to resize the image
            
        Returns:
            tuple: (processed image bytes, image format)
        """
        # Detect image format using python-magic
        mime = magic.Magic(mime=True)
        image_format = mime.from_buffer(image_bytes).split('/')[-1]

        if resize:
            # Convert to base64, resize, and back to bytes
            b64_str = base64.b64encode(image_bytes).decode()
            resized_b64 = ImageUtils.resize_img(b64_str)
            return base64.b64decode(resized_b64), image_format
        
        return image_bytes, image_format

    @staticmethod
    def validate_image(image_bytes: bytes) -> bool:
        """
        Validate if the bytes represent a valid image.
        
        Args:
            image_bytes (bytes): Raw image bytes
            
        Returns:
            bool: True if valid image, False otherwise
        """
        try:
            img = PILImage.open(io.BytesIO(image_bytes))
            img.verify()
            return True
        except Exception:
            return False

我甚至添加了一个巧妙的功能,如果有人在没有任何提示的情况下上传图像,机器人会自动使用默认提示来描述它在图像中看到的内容。

5、有趣的实验

为了好玩,我甚至启动了两个机器人,让它们互相交谈——尽管我不得不承认,它们的聊天并没有真正产生任何意义!不过,观看它们还是很有趣的。

6、展望未来

我的下一个项目更加令人兴奋——我计划使用 Amazon Bedrock 构建一个 AI 代理,该代理使用 RAG 模型连接到知识库。目标是让它使用我们自己的领域知识来回答特定问题,而不是仅仅依赖基础模型的训练数据。敬请期待!


原文链接:Building an AI-Powered Discord Bot (That Can See!) Using Amazon Bedrock

汇智网翻译整理,转载请标明出处

Tags