Gemini直翻為雙子座,是Google的大型語言模型
它提供免費的API使用,並且把API包裝成一個套件,讓我們不用手動request

安裝套件

1
pip install google-genai

一樣視編譯器而更改開頭,但套件名稱就是google-genai

送出一個prompt

首先到Google AI Studio申請一個API金鑰
登入帳號後同意使用條款,然後Create API key
生成的代碼跟機器人的token一樣,請用生命保護它

送出一個prompt並拿到回應需要3個變數

  • client: 建立一個連線,告訴Gemini我等等會用你
  • prompt: 提示詞
  • response: 回應的內容,包含這次使用的模型、微調數據等,也包含回應文字本體

首先引入套件

1
from google import genai

然後建立一個用戶端Client,以及想說的prompt

1
2
3
client = genai.Client(api_key="剛剛拿到的金鑰")

prompt = input("請輸入提示詞:")

最後生成回應,並把回應的text屬性印出來
如果直接印response會得到很長一串東西,在那一串裡面塞了一小段你的回應內容
我們使用gemini-2.0-flash模型

1
2
3
4
5
6
response = client.models.generate_content(
model="gemini-2.0-flash",
contents=prompt
)

print(response.text)

執行後輸入prompt稍等就能取得回應了
回應
(孔子會知其不可而微之,知道不能微分但還是微分)

融合機器人

用指令輸入提示詞

其實這一步非常簡單
我們只需要跟使用者要求提示詞即可,最後再把回覆訊息發出去
需注意的是,AI回覆一定需要時間,所以強烈建議使用interaction.response.defer()

且Discord一則訊息上限是2000個文字,包含用來表示格式的* | - #等等,所以可以適度修改prompt
因為機器人常常會回答非常多東西,容易出現400 Bad Request (error code: 50035): Invalid Form Body In content: Must be 2000 or fewer in length.錯誤

可以先依照這個要求自己寫寫看,再來看解答

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@bot.tree.command()
async def chat(interaction:discord.Interaction, prompt:str):
await interaction.response.defer()

client = genai.Client(api_key="剛剛拿到的金鑰")

response = client.models.generate_content(
model="gemini-2.0-flash",
contents=prompt + ",在2000個字元以內回答問題"
#直接在程式內修改prompt,可以避免使用者輸入時需要顧慮到長度問題,因為不論輸入什麼都能正常運行
)

await interaction.followup.send(response.text)

角色扮演

此處想做到的事是這樣的
我們可以傳送訊息來與指定角色對話
而且只有在特定頻道才有這個效果,以免干擾到正常聊天

範例

需要做到這樣的效果需要幾步
說不定分析完你也能自行做出來

偵測訊息並判斷

這部分很簡單,就是用事件偵測即可
然後在內部新增條件來識別是不是機器人,或是不是指定頻道
可以參考事件處理

如果是機器人,或者不是指定頻道
就結束運行

1
2
3
4
@bot.event
async def on_message(msg:discord.Message):
if msg.author.bot or msg.channel.id != (頻道ID):
return

打包聊天訊息

因為每一次的問答都是獨立的,也就是AI的記憶是從0開始
所以我們需要把前面一定數量的訊息傳給他,讓他有點印象
這時候就使用我們之前學過的遍歷訊息,把訊息打包成一個清單
可以參考頻道歷史

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 接續上一段程式碼
msgs = [] # 創建一個陣列用來儲存訊息

async for msg in msg.channel.history(limit=50): # 遍歷最後50則
msgs.append((msg.author.display_name, msg.content))
"""
這裡傳進來會很像[("小明", "哈囉"), ("小華", "閉嘴啦")]
每一項陣列都儲存一組資料,是說話者和內容
其中用()來包覆資料稱為元組(tuple),你可以理解為小的陣列,但更快速
"""

msgs.reverse()
"""
由於是從最新的開始遍歷,陣列的第一項會是最新一則
所以AI讀的時候會從最新的開始讀,如
3/16 -> 3/15 -> 3/14
但你看訊息怎麼可能從最新的開始往上,當然是從前面往下看才能了解始末
所以我們把這個陣列頭尾倒轉,讓時間線符合順序
3/14 -> 3/15 -> 3/16
"""

至此,我們擁有了一個儲存了一定期間訊息的陣列,且順序正確

發送請求

這部分涉及到prompt的設計
我們的請求有以下要點

  1. 我們想請AI扮演一個角色
  2. 我們將傳給AI一串陣列
  3. AI需要知道這串陣列代表的是聊天記錄,且知道陣列的結構代表什麼
  4. 去掉寒暄詞,比如「好的,以下是接續的對話」等回應詞
  5. 請他接出下一句話

釐清後就很好寫了,可以自己寫寫看
注意,要求越清晰越好,比如說你需不需要他知道他死後的歷史(希特勒知不知道美蘇冷戰)
或是特定角色需不需要知道他世界以外的事物(異世界角色知不知道現代科技)

1
prompt = f"這些是一串聊天記錄組成的清單。每一項有一個元組,是一則訊息,包含了發言者名稱與內容,請你扮演活在現代的溥儀來聊天,假設你還沒死。你已經知道目前為止的所有歷史,不論時間。請說出下一句話,不需要包含角色身份等等,只要說話的內容就好: {msgs}"

最後發送就非常簡單了,可以參考送出一個prompt

1
2
3
4
5
6
7
client = genai.Client(api_key="金鑰")

response = client.models.generate_content(
model="gemini-2.0-flash",
contents=prompt
#由於AI大概知道聊天通常不會超過2000個字,所以要不要特別要求自行決定
)

用Webhook發送訊息

這是最簡單的一步,把response.text發出去就好了
可以參考Webhook
記得自己先創建一個Webhook並取得ID

1
2
webhook = await bot.fetch_webhook(1301252464044933191)
await webhook.send(response.text)

完整程式碼

我們的程式碼會長這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@bot.event
async def on_message(msg:discord.Message):
if msg.author.bot or msg.channel.id != 374819350173:
return

msgs = []

async for msg in msg.channel.history(limit=50):
msgs.append((msg.author.display_name, msg.content))

msgs.reverse()

prompt = f"這些是一串聊天記錄組成的清單。每一項有一個元組,是一則訊息,包含了發言者名稱與內容,請你扮演活在現代的溥儀來聊天,假設你還沒死。你已經知道目前為止的所有歷史,不論時間。請說出下一句話,不需要包含角色身份等等,只要說話的內容就好: {msgs}"

client = genai.Client(api_key="jdhf471ij58lkdhf8174hfk813g484hg4819o3h")

response = client.models.generate_content(
model="gemini-2.0-flash",
contents=prompt
)

webhook = await bot.fetch_webhook(1301252464044933191)
await webhook.send(response.text)