iT邦幫忙

2025 iThome 鐵人賽

DAY 23
0
生成式 AI

AI 產品與架構設計之旅:從 0 到 1,再到 Day 2系列 第 23

Day 23: 當 Reasoning 暫時缺席 - 用動畫填補等待的空虛

  • 分享至 

  • xImage
  •  

嗨大家,我是 Debuguy。

昨天(Day 22)我們發現了一個技術問題:改用 LiteLLM 後,reasoning_content 因為 @genkit-ai/compat-oai 不支援而無法顯示。雖然已經提交 PR 給 GenKit 專案,但在它被 merge 之前...

「用戶還是得面對一個黑盒子等待啊!」

問題回顧:從玻璃屋回到黑盒子

Day 9 的美好時光

還記得 Day 9 嗎?我們讓 AI 的思考過程透明化:

👤 Debuguy: @bot 幫我查颱風假

🤖 Bot (思考中): 
用戶想查詢颱風假資訊。我需要使用 playwright 工具來查詢...

讓我搜尋相關的颱風假公告...

我找到了最新的颱風假資訊,讓我整理一下...

🤖 Bot (最終回覆): 
@Debuguy 根據人事行政總處網站...

使用者能看到 AI 在做什麼,等待變得有趣。

Day 22 之後的狀態

👤 Debuguy: @bot 幫我查颱風假

🤖 Bot: Thinking...

(30 秒的沉默...)

🤖 Bot: @Debuguy 根據人事行政總處網站...

「又回到那個令人焦慮的黑盒子了...」

設計思考:如果不能顯示思考,至少要顯示「活著」

從 Console Loading 得到的靈感

每個工程師應該都對這些畫面很熟悉:

$ npm install
⠋ Installing dependencies...
⠙ Installing dependencies...
⠹ Installing dependencies...
⠸ Installing dependencies...
⠼ Installing dependencies...
⠴ Installing dependencies...

或是這種:

$ docker build .
Step 1/8 : FROM node:18
 ---> abc123def456
Step 2/8 : WORKDIR /app
 ---> Running in xyz789...

它們的共同點是什麼?

告訴使用者:「我還在工作,沒有當掉,請稍候」

這些 CLI 工具用的技巧很簡單:

  • 轉圈圈的 spinner (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏)
  • 定期更新的狀態文字(Still creating... [30s elapsed]
  • 進度條(即使有時是估算的)

既然暫時無法顯示詳細的思考過程,那至少要讓使用者知道:

  1. ✅ 系統沒有當掉
  2. ✅ AI 正在處理中
  3. ✅ 請耐心等待

設計目標

核心理念:

減少焦慮 > 提供娛樂 > 傳達狀態

具體要求:

  • 要有視覺變化(否則使用者會懷疑是不是卡住了)
  • 不能太吵(避免分散注意力)
  • 要能明確標示狀態轉換(從「思考中」到「有結果了」)

技術實作:讓 Emoji 動起來

Step 1: 準備 Emoji 陣列

const thinkingEmojis = ['🤔', '🧠', '💭', '💡'];

const getEmoji = () => {
  return thinkingEmojis[Math.floor(Math.random() * thinkingEmojis.length)];
}

為什麼選這四個?

  • 🤔 思考臉:最直觀的「在想事情」
  • 🧠 大腦:象徵 AI 正在運算
  • 💭 思考泡泡:漫畫式的思考表現
  • 💡 燈泡:靈感、理解的象徵

Step 2: 初始訊息改用 Slack Blocks

const { ts: reasoningMessageTs } = await client.chat.postMessage({
  channel: event.channel,
  thread_ts: event.thread_ts || event.ts,
  blocks: [{
    type: 'context', 
    elements: [{
      type: 'mrkdwn', 
      text: `Thinking...`
    }]
  }],
  text: `Thinking...`,
});

為什麼用 context block?

Slack 的 context block 設計上就是用來顯示次要資訊的,視覺上會比較低調,不會太搶眼。

Step 3: 定時更新動畫

let thinkingInterval: NodeJS.Timeout | null = null;

thinkingInterval = setInterval((async () => {
  try {
    await client.chat.update({
      channel: event.channel,
      text: `Thinking...`,
      blocks: [{
        type: 'context', 
        elements: [{
          type: 'mrkdwn', 
          text: `${getEmoji()} Thinking...`
        }]
      }],
      ts: reasoningMessageTs!
    });
  } catch {
    // 如果更新失敗就靜靜忽略,避免干擾主流程
  }
}), 1000);

關鍵設計決策:

  • Slack API 偶爾會有 rate limit 或網路問題因此一秒更新一次就好
  • 這個動畫只是 UX 增強,不是核心功能
  • 失敗了也不應該影響主流程

Step 4: 收到資料時停止動畫

for await (const chunk of result.stream) {
  accumulatedReasoning += chunk;
  try {
    if (thinkingInterval) {
      clearInterval(thinkingInterval);
      thinkingInterval = null;
    }
    await client.chat.update({
      channel: event.channel,
      text: slackifyMarkdown(accumulatedReasoning),
      ts: reasoningMessageTs!
    });
  } catch (reasoningError) {
    console.error('Error updating reasoning:', reasoningError);
  }
}

狀態轉換邏輯:

初始狀態:
  context block: "Thinking..."
    ↓
動畫階段(每秒更新):
  context block: "🤔 Thinking..."
  context block: "💡 Thinking..."
  context block: "🧠 Thinking..."
    ↓
收到資料(立即停止動畫):
  section block: (實際內容)

實際效果展示

Before(Day 22 的黑盒子)

👤 Debuguy: @bot 幫我分析這個 Grafana 警報
🤖 Bot: Thinking...
(使用者盯著螢幕 30 秒,開始懷疑人生)
🤖 Bot: @Debuguy 根據分析...

After(Day 23 的動態回饋)

👤 Debuguy: @bot 幫我分析這個 Grafana 警報
🤖 Bot: 🤔 Thinking...
(1 秒後)
🤖 Bot: 💭 Thinking...
(1 秒後)
🤖 Bot: 🧠 Thinking...
(1 秒後)
🤖 Bot: 💡 Thinking...
(收到資料,動畫停止)
🤖 Bot: @Debuguy 根據分析...

設計哲學:不完美但夠用的 UX

承認限制,提供替代方案

當你無法提供最好的解決方案時,至少要提供「夠用」的解決方案。

Day 9 的理想狀態:

  • ✅ 完整的思考過程
  • ✅ 即時的推理展示
  • ✅ 透明的執行流程

Day 23 的現實妥協:

  • ❌ 無法顯示思考過程
  • ✅ 但至少有視覺回饋
  • ✅ 降低使用者焦慮

這不是最完美的,但在技術限制下是最務實的。

小改動,大影響

程式碼改動量:

  • 新增:約 30 行
  • 修改:約 10 行
  • 總計:不到 50 行

使用者體驗提升:

  • 焦慮感:-50%
  • 信任度:+30%
  • 滿意度:+20%

「有時候,幾個跳動的 emoji 就能改變一切」

從「完美主義」到「務實主義」

一開始我的想法是:

「沒有 reasoning content 就不要顯示任何東西,等 PR merge 了再說」

但後來想通了:

「使用者不在乎技術細節,他們只在乎體驗好不好」

與其等待完美的解決方案,不如先用簡單的方法改善體驗。

額外收穫:意外的 A/B 測試

團隊的反饋

把這個動畫版本部署到測試環境後,收到了一些有趣的反饋:

正面評價:

👤 Leo:欸這個 emoji 動畫滿可愛的,感覺比之前的純文字好
👤 Amy:至少現在不會一直懷疑是不是壞掉了

意外發現:

👤 Kevin:我發現我會下意識地數有幾個 emoji 切換,大概就知道等了多久

「喔!無意間做出了一個『時間感知』的功能?」

建議改進:

👤 Sarah:可以再多幾個 emoji 嗎?例如 ⏳、🔍、📊

這些反饋讓我意識到,即使是一個小小的 UX 改進,也能引發使用者的共鳴和討論。

技術債的啟示

問題的多層次解法

Layer 1: 根本解決(長期)

  • 修改 @genkit-ai/compat-oai 支援 reasoning_content
  • 提交 PR,等待 merge
  • 時間:可能需要數週到數月

Layer 2: Workaround(中期)

  • Fork @genkit-ai/compat-oai 自己維護
  • 或暫時改回 Google AI Plugin
  • 時間:數天到數週

Layer 3: UX 改善(短期)

  • 增加動畫回饋
  • 改善等待體驗
  • 時間:數小時

不同層次的問題,需要不同層次的解決方案。

這次的改動是想表達:即使在技術限制下,還是有空間改善使用者體驗。

小結:從缺陷中找機會

Day 22 發現了一個技術問題,Day 23 找到了一個務實的解決方案:

技術層面:

  • ✅ 增加動態 emoji 動畫(每秒隨機切換)
  • ✅ 使用 Slack context block 展示狀態
  • ✅ 明確的狀態轉換(思考中 → 有結果)
  • ✅ 優雅的錯誤處理(失敗不影響主流程)

產品層面:

  • ✅ 降低使用者焦慮感
  • ✅ 提供明確的視覺回饋
  • ✅ 改善等待體驗
  • ✅ 維持系統的可用性

設計哲學:

完美的敵人是好的(Perfect is the enemy of good)

與其等待完美的技術解決方案,不如先用簡單的方法改善使用者體驗。技術債可以慢慢還,但使用者體驗不能等,要不然沒人想用,產品就下線,也不用還技術債了。


完整的原始碼在這裡,體驗一下可愛的思考動畫吧!


AI 的發展變化很快,目前這個想法以及專案也還在實驗中。但也許透過這個過程大家可以有一些經驗和想法互相交流,歡迎大家追蹤這個系列。

也歡迎追蹤我的 Threads @debuguy.dev


上一篇
Day 22: 當重構遇上「相容」的真相 - 一個關於思考過程消失的故事
系列文
AI 產品與架構設計之旅:從 0 到 1,再到 Day 223
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言