使用 Golang + Google Gemini API 打造 Discord AI 聊天機器人
本文將使用 Golang 語言,搭配 Google Gemini API,實作一個 Discord 聊天機器人。
🛠️ 前置需求 (Prerequisites)
在開始之前,請確保你已經準備好以下開發環境與 API Key:
- Golang 環境: 安裝 Go 1.25 或更高版本。
- Discord Bot Token:
- 前往 Discord Developer Portal。
- 建立一個新的 Application,並新增一個 Bot。
- 取得 Bot Token (請妥善保存,不要外洩)。
- 開啟
Message Content Intent權限,這樣機器人才讀得到使用者的訊息內容。
- 透過 OAuth2 將 bot 邀請到你的 Discord 伺服器。
- 選擇 scope
- 選擇 permission
- 產生 url 並邀請 bot
⚠️ 注意:此時的 bot 應該為離線狀態
- 選擇 scope
- Google Gemini API Key:
- 前往 Google AI Studio。
- 建立一個新的 API Key。
🚀 專案初始化
首先,建立一個新的專案資料夾並初始化 Go 模組:
mkdir discord-chatbot
cd discord-chatbot
go mod init discord-chatbot
接著,我們需要安裝兩個主要的套件:
github.com/bwmarrin/discordgo: Discord 的 Go 語言 SDK。google.golang.org/genai: Google GenAI 的 Go 語言 SDK。github.com/joho/godotenv: 用來讀取.env環境變數檔。
go get github.com/bwmarrin/discordgo
go get google.golang.org/genai
go get github.com/joho/godotenv
💻 程式實作
專案結構非常簡單,主要分為 main.go (處理 Discord 邏輯) 與 gemini.go (處理 AI 邏輯)。
1. Gemini Client (gemini.go)
為了讓機器人能夠像真人一樣對話,我們需要實作一個機制來儲存對話歷史。這裡我們建立一個 GeminiClient 結構,並使用 map 來儲存不同頻道的對話紀錄。
你可以將
model替換成你需要的模型,例如gemini-2.5-flash-lite。
package main
import (
"context"
"fmt"
"sync"
"google.golang.org/genai"
)
// GeminiClient 包裝 Google GenAI 客戶端並管理對話歷史
type GeminiClient struct {
client *genai.Client
sessions map[string][]*genai.Content // Key: Channel ID, Value: 對話歷史
mu sync.Mutex // Mutex 用於保護 map 的並發存取
}
func NewGeminiClient(apiKey string) (*GeminiClient, error) {
ctx := context.Background()
client, err := genai.NewClient(ctx, &genai.ClientConfig{
APIKey: apiKey,
})
if err != nil {
return nil, fmt.Errorf("failed to create genai client: %w", err)
}
return &GeminiClient{
client: client,
sessions: make(map[string][]*genai.Content),
}, nil
}
// Chat 發送訊息給 Gemini 並維護對話歷史
func (gc *GeminiClient) Chat(channelID, userMessage string) (string, error) {
gc.mu.Lock()
defer gc.mu.Unlock()
// 初始化該頻道的歷史紀錄
if _, ok := gc.sessions[channelID]; !ok {
gc.sessions[channelID] = []*genai.Content{}
}
// 新增使用者訊息到歷史紀錄
gc.sessions[channelID] = append(gc.sessions[channelID], &genai.Content{
Role: "user",
Parts: []*genai.Part{
{Text: userMessage},
},
})
// 優化:只保留最近 20 則訊息,避免 Token 超出限制或消耗過多資源
const maxHistory = 20
if len(gc.sessions[channelID]) > maxHistory {
gc.sessions[channelID] = gc.sessions[channelID][len(gc.sessions[channelID])-maxHistory:]
}
// 指定使用 gemini 模型
model := "gemini-2.5-flash"
// 呼叫 Gemini API
resp, err := gc.client.Models.GenerateContent(context.Background(), model, gc.sessions[channelID], nil)
if err != nil {
return "", fmt.Errorf("failed to generate content: %w", err)
}
// 解析回應
if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 {
return "", fmt.Errorf("no response candidates returned")
}
var aiResponse string
for _, part := range resp.Candidates[0].Content.Parts {
if part.Text != "" {
aiResponse += part.Text
}
}
// 將 AI 的回應也加入歷史紀錄
gc.sessions[channelID] = append(gc.sessions[channelID], &genai.Content{
Role: "model",
Parts: []*genai.Part{
{Text: aiResponse},
},
})
return aiResponse, nil
}
// Reset 清除指定頻道的對話紀錄
func (gc *GeminiClient) Reset(channelID string) {
gc.mu.Lock()
defer gc.mu.Unlock()
delete(gc.sessions, channelID)
}
2. Main Entry (main.go)
在主程式中,我們負責連線 Discord Gateway,並監聽 MessageCreate 事件。當收到 !ai 指令時,就呼叫 GeminiClient 取得回應。
package main
import (
"fmt"
"log"
"os"
"os/signal"
"strings"
"syscall"
"github.com/bwmarrin/discordgo"
"github.com/joho/godotenv"
)
var geminiClient *GeminiClient
func main() {
// 讀取 .env
godotenv.Load()
botToken := os.Getenv("DISCORD_BOT_TOKEN")
geminiKey := os.Getenv("GEMINI_API_KEY")
if botToken == "" || geminiKey == "" {
log.Fatal("請在 .env 檔案中設定 DISCORD_BOT_TOKEN 與 GEMINI_API_KEY")
}
// 初始化 Gemini Client
var err error
geminiClient, err = NewGeminiClient(geminiKey)
if err != nil {
log.Fatal("Error creating Gemini client:", err)
}
// 初始化 Discord Session
dg, err := discordgo.New("Bot " + botToken)
if err != nil {
log.Fatal("error creating Discord session,", err)
}
// 註冊訊息事件處理器
dg.AddHandler(messageCreate)
// 設定 Intent
dg.Identify.Intents = discordgo.IntentsGuildMessages
// 連線
err = dg.Open()
if err != nil {
log.Fatal("error opening connection,", err)
}
defer dg.Close()
fmt.Println("Bot is now running. Press CTRL-C to exit.")
// 等待中斷訊號
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
<-sc
}
func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
if m.Author.ID == s.State.User.ID {
return
}
// 指令: !reset (清除記憶)
if m.Content == "!reset" {
if geminiClient != nil {
geminiClient.Reset(m.ChannelID)
s.ChannelMessageSend(m.ChannelID, "🧹 已清除本頻道的對話記憶,我們可以重新開始了!")
}
return
}
// 指令: !ai <訊息>
if strings.HasPrefix(m.Content, "!ai ") {
userMessage := strings.TrimPrefix(m.Content, "!ai ")
if userMessage == "" {
s.ChannelMessageSend(m.ChannelID, "請輸入訊息,例如:`!ai 你好`")
return
}
// 顯示「正在輸入...」狀態
s.ChannelTyping(m.ChannelID)
if geminiClient != nil {
response, err := geminiClient.Chat(m.ChannelID, userMessage)
if err != nil {
log.Println("Gemini Error:", err)
s.ChannelMessageSend(m.ChannelID, "❌ AI 發生錯誤,請稍後再試。")
return
}
// Discord 訊息長度限制 2000 字,這裡做個簡單截斷處理
if len(response) > 2000 {
s.ChannelMessageSend(m.ChannelID, response[:1990]+"...\n(回應過長已截斷)")
} else {
s.ChannelMessageSend(m.ChannelID, response)
}
}
}
}
🏃♂️ 執行與測試
1. 設定環境變數
在專案根目錄建立 .env 檔案:
DISCORD_BOT_TOKEN=你的Token
GEMINI_API_KEY=你的APIKey
2. 啟動
go run .
3. 指令測試
回到你的 Discord 伺服器 (Bot 需在伺服器內),試試看以下指令:
!ai 嗨,請用一句話從我介紹 Golang!ai 承上題,那 Python 呢?(測試上下文記憶)!reset(清除記憶)
🔮 What’s Next?
目前我們的 Bot 只能在本地電腦執行,一關機就沒了。為了讓它能夠 24 小時運作,有時間再研究看看怎麼部署到雲端~