昨天已經讓 Python 可以呼叫 Ollama,送出 Prompt 後也能取得 qwen3.5:4b 的回答。
不過,現在還是透過 manual_test.py 在終端機測試。今天要把這段功能接回 Discord,建立一個簡單的 !ask 指令。
完成後,使用者只要在 Discord 頻道輸入問題,Bot 就會把問題交給本機的 Qwen,再將回答傳回原本的頻道。
!ask 文字指令LLMService
目前 Discord Bot 與 Ollama 已經可以分別執行,但兩邊還沒有連在一起。
今天要完成的流程如下:
Discord 使用者
↓ !ask 問題
bot.py
↓ 呼叫 chat()
LLMService
↓
Ollama 與 qwen3.5:4b
↓
Discord 顯示回答
bot.py 的責任是接收 Discord 指令與顯示結果;呼叫 Ollama、解析回應和處理連線錯誤,仍然由昨天建立的 LLMService 負責。
這樣可以避免所有功能都塞進 bot.py。未來就算把 !ask 改成 Slash Command,模型呼叫的程式也不需要重寫。
先在 bot.py 加入以下匯入:
from services.llm_service import LLMService, LLMServiceError
接著建立一個共用的 Service:
llm_service = LLMService(timeout=300)
這裡沒有在每次收到指令時重新建立 LLMService,而是在 Bot 啟動時建立一次,後續的問題共用同一個 Service。
timeout=300 代表最多等待 300 秒。第一次呼叫模型時可能還包含載入時間,因此先保留較寬鬆的等待範圍。只要模型提早完成,程式就會立刻繼續執行,不是每次都要等滿 300 秒。
在原本的 !hello 指令下方加入:
@bot.command()
async def ask(ctx, *, question: str = ""):
question = question.strip()
if not question:
await ctx.send("請在 `!ask` 後面輸入問題。")
return
status = await ctx.send("Qwen 正在思考中……")
try:
result = await llm_service.chat(question[:1000])
await status.edit(content=result.content[:1900])
except LLMServiceError as error:
await status.edit(content=f"暫時無法取得回答:{error}")
使用方式如下:
!ask 請用繁體中文簡單說明什麼是 Discord Bot
函式中的 * 會讓 question 接收指令後方剩下的全部文字。因此問題中即使包含空格,也不需要額外加上引號。
例如:
!ask Python 的 async 和 await 有什麼用途?
整句都會成為 question,再交給 LLMService.chat()。
使用
!開頭的文字指令時,除了程式中的intents.message_content = True,也要確認 Discord Developer Portal 已啟用 Message Content Intent。
本機模型不像 !hello 可以立刻回覆。第一次使用時,Ollama 可能還需要把模型載入記憶體,產生回答也會受到問題長度和電腦硬體影響。
如果 Bot 在等待期間完全沒有反應,使用者可能會以為指令沒有成功送出。因此程式會先傳送一則狀態訊息:
status = await ctx.send("Qwen 正在思考中……")
模型完成回答後,再編輯同一則訊息:
await status.edit(content=result.content[:1900])
這樣頻道中不會同時出現一則「處理中」和另一則正式回答,畫面也比較乾淨。


程式目前將問題限制為前 1000 個字元:
question[:1000]
這是專案第一版自行設定的保護,不是 Ollama 的固定限制。它可以避免使用者一次貼入過長內容,讓本機模型占用太多推論時間。
回答則先保留前 1900 個字元:
result.content[:1900]
一般 Discord 訊息最多可包含 2000 個字元。這裡保留一些空間,避免日後在回答前後加入提示文字時超過限制。
直接截斷不是最完整的做法,但適合先確認串接流程。之後可以再將長回答切成多則訊息傳送。
昨天的 LLMService 已經將常見問題轉換成 LLMServiceError,例如:
message.content
因此 !ask 不需要理解 HTTPX 或 Ollama 套件的所有例外,只要捕捉專案自己的錯誤:
except LLMServiceError as error:
await status.edit(content=f"暫時無法取得回答:{error}")
例如 Ollama 沒有啟動時,使用者預期會看到:
暫時無法取得回答:無法連線到 Ollama,請確認服務是否已啟動。
錯誤訊息會留在 Discord,方便知道問題出在本機模型,而不是讓 Bot 一直停在「思考中」。
先確認 Ollama 正在執行,而且本機已有模型:
ollama ls
接著啟用專案的虛擬環境並啟動 Bot:
source .venv/bin/activate
python bot.py
在 Discord 頻道測試:
!ask 請只回答:Discord 與 Qwen 串接成功
預期流程是:

這次把 Discord 和 Qwen 接在一起,但仍讓 bot.py 處理指令,LLMService 專心呼叫模型。現在只做單輪問答,回答太長會直接截斷,也還沒有保存對話;實際畫面等測試完成後再補。下一篇先整理專案結構與測試。