DAY 18|讓 LINE Bot「聽你說」:語音訊息 → 轉文字 → AI 回覆(FastAPI + LINE SDK + Whisper)
今天把主題換成「語音互動」:使用者在 LINE 傳錄音訊息(語音訊息 / AudioMessage),Bot 自動下載音檔 → 語音轉文字(STT,Speech-to-Text) → 把逐字稿丟給對話模型 → 產生回覆。整條鏈做完,Her 就真的會「聽你說」啦。
⸻
你會得到什麼
• 支援 LINE 語音訊息(m4a)的一鍵流程
• 兩段式 AI:Whisper 語音轉文字 + 對話模型產生回覆
• 鐵人賽可複用的錯誤保護(即使 AI Key 缺失也能穩定啟動)
• 範例程式片段可直接貼進你現有的 app_fastapi.py
⸻
成果示意(文字流程)
1. 使用者在 LINE 傳一段錄音(按住麥克風講話)。
2. Bot 收到 AudioMessage → 下載音檔(m4a)到暫存目錄。
3. 呼叫 STT(OpenAI Whisper 或 Groq Whisper-large-v3)拿到逐字稿。
4. 把逐字稿丟進聊天模型(Groq / OpenAI),回一段暖心的文字。
5. (選配)你也能把文字丟進 TTS 產生語音回傳,但本文先做文字回覆。
⸻
必備環境變數
變數 說明
BASE_URL 你的公開網址(Render 主 URL)
CHANNEL_ACCESS_TOKEN LINE Messaging API token
CHANNEL_SECRET LINE Channel Secret
GROQ_API_KEY (選)Groq 金鑰:用於聊天與/或 Whisper
OPENAI_API_KEY (選)OpenAI 金鑰:Whisper 與聊天皆可用
兩家金鑰任選其一即可。沒有就降級為純規則訊息(服務仍會啟動)。
⸻
requirements.txt(語音版與你目前相容)
fastapi
uvicorn
line-bot-sdk>=3.0.0
groq
openai
requests
pandas
beautifulsoup4
lxml
html5lib
taiwanlottery
yfinance
不強制安裝 pydub / ffmpeg。Whisper API 可直接吃 m4a,上雲端最省事。
⸻
關鍵程式片段
把下面三段貼入你的 app_fastapi.py(都與你現有架構相容):
import tempfile
from linebot.models import AudioMessage
def _has(value: str) -> bool:
return bool(value and value.strip())
def stt_transcribe(file_path: str) -> str:
"""
依序嘗試:
1) OpenAI Whisper (audio.transcriptions.create)
2) Groq Whisper-large-v3
任一成功就回逐字稿;都失敗回空字串。
"""
# OpenAI Whisper
if openai_client:
try:
with open(file_path, "rb") as f:
tx = openai_client.audio.transcriptions.create(
model="whisper-1",
file=f,
response_format="text", # 直接回純文字
temperature=0
)
if _has(tx):
return tx.strip()
except Exception as e:
logger.warning(f"OpenAI Whisper 失敗:{e}")
# Groq Whisper-large-v3
if sync_groq_client:
try:
# Groq 的 Python SDK 也支援音訊轉錄端點
with open(file_path, "rb") as f:
tx = sync_groq_client.audio.transcriptions.create(
model="whisper-large-v3",
file=f,
response_format="text",
temperature=0
)
if _has(tx):
return tx.strip()
except Exception as e:
logger.warning(f"Groq Whisper 失敗:{e}")
return ""
在你的 handle_message_async 最前面解析完 event 與 msg_raw 後,加入這段判斷(放在「命令 & 功能觸發」前也行):
if isinstance(event.message, AudioMessage):
try:
# 下載語音
content = await run_in_threadpool(line_bot_api.get_message_content, event.message.id)
with tempfile.NamedTemporaryFile(suffix=".m4a", delete=False) as tmp:
for chunk in content.iter_content(1024):
if chunk:
tmp.write(chunk)
tmp_path = tmp.name
# STT
transcript = await run_in_threadpool(stt_transcribe, tmp_path)
if not _has(transcript):
return reply_with_quick_bar(
reply_token,
"我有收到你的語音,但現在聽不太清楚(或轉錄服務忙碌)。能再傳一次嗎?🙏"
)
# 丟給聊天模型(用你現有的 groq_chat_async)
sys_prompt = build_persona_prompt(chat_id, await analyze_sentiment(transcript))
messages = [
{"role": "system", "content": sys_prompt},
{"role": "user", "content": f"(以下是使用者語音逐字稿)\n{transcript}"}
]
ai_reply = await groq_chat_async(messages)
return reply_with_quick_bar(
reply_token,
f"🗣️ 你說:\n{transcript}\n\n——\n{ai_reply}"
)
except Exception as e:
logger.error(f"處理語音訊息失敗:{e}", exc_info=True)
return reply_with_quick_bar(reply_token, "我剛剛耳機斷訊了 😅 你可以再試一次音訊嗎?")
這段會把語音逐字稿也一起回給使用者,方便「聽寫確認」。
若 OPENAI_API_KEY / GROQ_API_KEY 缺失,服務依舊啟動、其他功能不受影響。
建議保留我們前一次提供的 環境變數不毀損啟動、/diagz 診斷端點與 get_analysis_reply() 降級邏輯。
⸻
本地/雲端測試
1. LINE Developer Console → Messaging API → Webhook → 指到 {BASE_URL}/callback
2. 手機對 Bot 按住麥克風錄一段話
3. 期待 Bot 回覆:
• 第一段:逐字稿(你剛剛說的文字)
• 第二段:AI 回覆(走你設定的人設口吻)
⸻
常見錯誤與排除
• Cause of failure could not be determined
多半是匯入期就 raise 導致 Uvicorn沒綁定 $PORT。用我們的「延後檢查+/diagz」能避免。
• InvalidSignatureError
請確認 CHANNEL_SECRET 正確、/callback 路由存在、Render URL 有更新到 LINE 後台。
• 轉錄空白 / 失敗
可能是音檔太短、背景噪音大、API 一時超時。建議回覆「請再說一次」並做重試(上面已加溫和提示)。
• 沒有 ffmpeg
本教學路線直接把 m4a 丟進 API,不需要 ffmpeg;除非你要做離線轉檔。
⸻
進階延伸
• 回語音(TTS):把 AI 文字丟到 TTS,輸出 mp3/m4a,使用 LINE 的 AudioSendMessage 回傳。
• 語音指令:以關鍵詞觸發「查股價 / 金價 / 匯率 / 翻譯」。
• 多語辨識:Whisper 支援自動偵測語言;偵測後自動切換回覆語系。
• 長語音摘要:超過 30 秒先做摘要,再追問。
⸻
文末小結
今天完成「語音 → 文字 → AI 回覆」的閉環,Her 不只會看文字、也開始聽得懂你。把這套接回你的股票 / 金價 / 匯率模組,用一句話就能查資料,體驗會更貼近真實助理。明天我們可以把TTS 回語音也串上,讓整個對話完全「說話就好」。