iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0
生成式 AI

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

Day 2: Slack ChatBot 的技術選型

  • 分享至 

  • xImage
  •  

嗨大家,我是 Debuguy。

昨天聊了為什麼要做 Slack AI ChatBot,今天來分享一個看似簡單但其實很關鍵的決定:選什麼技術來開發?

這不只是技術問題,更是一個關於「在企業環境中如何做產品」的現實課題。

語言選擇:Python 還是 JavaScript?

我的內心 OS:「Python 不是理所當然的嗎?」

一開始,我的想法很單純:

「做 AI 當然要用 Python 啊,這還用想嗎?」

腦海中的完美藍圖:

從早期的 Machine Learning 框架 TensorFlow、PyTorch,到現在各種 LLM 相關的套件,Python 在 AI 領域累積的整個生態系都相對完整。OpenAI、Anthropic、Google 的官方 SDK 都是以 Python 優先,社群資源也最豐富。

「這麼多現成的工具,應該可以很快就做出來吧?」

從技術角度看,Python 幾乎是 AI 應用開發的標準答案。我甚至已經開始想像自己優雅地 pip install 各種套件的畫面了。

現實給我的當頭一棒

但當我開始認真考慮「這個 ChatBot 要怎麼在公司內推廣」時,內心開始出現雜音:

「等等... 如果我用 Python 寫,到時候誰來維護?」
「我們的 CI/CD 都是針對 .NET 和 JS 設計的,要重新搞一套?」
「萬一我要 handover,這個專案是不是會變成大家無法維護?」

並且考量到「這個 ChatBot 的影響力要能很快在公司內發酵」時,現實的問題接踵而來:

部署與維運的挑戰

  • 現有的監控工具、日誌收集、除錯工具都是針對 .NET 和 node.js 設計
  • CI/CD Pipeline 可能需要重新設計
  • 容器化部署的 base image 和相關工具鏈需要重新建立

團隊協作的現實

  • 公司主要是 .NET 後端 + Vue.js 前端的技術棧
  • 團隊裡大部分人對 python 不熟悉
  • Code Review 有機會流於形式,很難找到真正能深度檢視的人
  • 當有 Bug 或需要緊急修復時,可能要等到真正熟悉 Python 的人才能處理

這些不是技術問題,而是組織問題。再好的技術,如果團隊駕馭不了,就不是好的選擇。

重新思考:技術選型的真正考量

不只是語言,而是整個企業內部的開發生態系統

技術選型不能只看語言本身以及語言生態系,還要看整個公司的開發生態系統

開發生態

  • 語言特性和開發體驗
  • 第三方套件的豐富程度
  • 社群支援和學習資源

企業生態

  • 現有技術棧的匹配程度
  • 團隊技能和學習成本
  • 部署和維運的複雜度

產品生態

  • 目標用戶的技術背景
  • 未來擴展的方向
  • 與其他系統整合的需求

JavaScript / TypeScript 的重新評估

當我重新評估 JavaScript / TypeScript 時,發現 AI 相關的生態系統其實已經迎頭趕上:

AI 生態系統已經足夠成熟

  • Google Gemini、Anthropic、OpenAI 都有官方 JavaScript SDK
  • Vercel 的 AI SDK 提供了很好的抽象層
  • 最近流行的 CLI 工具(Claude CLI、Codex CLI、Gemini CLI)也都是透過 npm 發佈和安裝

企業整合優勢明顯

  • 與現有的 DevOps 流程無縫整合
  • 團隊熟悉度高,維護成本低
  • 部署流程成熟,風險可控
  • 前後端可以共享一些型別定義和工具函數

Slack 整合也很完整

  • Slack 官方的 Bolt SDK 同時提供 Python 和 JavaScript 版本
  • JavaScript 版本的文檔和範例同樣完整
  • 社群活躍度也不輸 Python 版本

具體的 Tech Stack 決定

Language: TypeScript

  • 型別安全,減少 runtime 錯誤
  • 企業專案的標準選擇
  • 優秀的 IDE 支援和開發體驗

Runtime: Bun (主要) / Node.js

  • Bun: 新興的 JavaScript runtime,效能比 Node.js 快很多,內建 TypeScript 支援,既然是一個新的 project 就順便嘗試看看
  • 號稱相容 Node.js API,遷移成本低
  • 內建套件管理器,安裝速度極快
  • Node.js: 作為 fallback 選項,如果過程發現 Bun 沒那麼好用那還是有機會退回 node.js
  • 兩者都對異步 I/O 有天然支援,共享 NPM 生態系統

LLM 框架選擇:如何優雅地整合 LLM?

選定語言只是開始,接下來面臨的是更關鍵的問題:要怎麼跟 LLM 溝通?

最初的天真想法:

「反正都是 API 呼叫,直接用官方 SDK 不就好了?」

我一開始的想法很單純,既然決定以 Gemini 為主,那就直接用 Google 的官方 SDK:

import {GoogleGenAI} from '@google/genai';
const GEMINI_API_KEY = process.env.GEMINI_API_KEY;

const ai = new GoogleGenAI({apiKey: GEMINI_API_KEY});

async function main() {
  const response = await ai.models.generateContent({
    model: 'gemini-2.5-flash-lite',
    contents: 'Why is the sky blue?',
  });
  console.log(response.text);
}

main();

「這樣最直接,功能最完整,還有官方支援,完美!」

現實又來敲門了:

但當我開始思考長期規劃時,內心開始不安:

「等等,如果之後要換成 Claude 怎麼辦?整個 API 都不一樣啊...」 「更麻煩的是,如果要接 Ollama 這種自部署的模型,我是不是要自己寫抽象層?」 「自己寫抽象層?那不就變成要維護一個迷你版的 LangChain?」

想到這裡,我開始想著 AI 技術變化這麼快,今天用 Gemini,明天說不定 Claude 更好,甚至考量到資訊安全可能還要支援自行部署的 LLM 例如使用 Ollama 部署的 Gemma。如果每次都要重寫整合程式碼,這不是找自己麻煩嗎?

尋找救星的過程:

於是我開始尋找有沒有現成的解決方案。

「Vercel AI SDK 看起來不錯啊!」

import { generateText } from "ai"
import { google } from "@ai-sdk/google"

const { text } = await generateText({
	model: google("models/gemini-2.5-flash-lite"),
	prompt: "What is love?"
})

API 設計很現代化,TypeScript 支援也很好。但仔細研究後發現:

「這個主要是給前端用的吧?我要做的是後端服務啊...」

而且對於複雜的對話管理、trace 除錯這些企業級需求,支援似乎還不夠完整。

意外發現 GenKit:

就在我快要放棄,準備自己寫抽象層的時候,偶然發現了 Firebase 的 GenKit

import { genkit } from 'genkit';
import { googleAI } from '@genkit-ai/google-genai';

const ai = genkit({
	plugins: [googleAI()],
});

const response = await ai.generate({
	model: googleAI.model('gemini-2.5-flash-lite'),
	prompt: 'Tell me something interesting about Google AI.',
});

console.log(response.text());

「咦?這個看起來不錯誒...」

一開始我還有點懷疑:「Google 出的框架,會不會只支援自家的模型?」但研究後發現,它的設計思路正好解決了我的所有痛點:

1. 模型切換超簡單

const ai = genkit({
  plugins: [
    googleAI(),
    openAI(),
    ollama({
		models: [
			{
				name: 'gemma3',
				type: 'generate',
			},
		],
		serverAddress: 'http://127.0.0.1:11434',
	}),
  ],
});

「哇,這樣就能支援三種模型了?」

2. 完整的企業級工具鏈

當我看到 GenKit 提供的功能清單時,內心 OS

「這根本就是為了解決我所有問題而生的啊!」

  • Prompt 管理:可以版本控制,還能做 A/B 測試
  • Flow 設計:複雜對話流程不用自己從頭建模
  • Trace 除錯:每個 LLM 呼叫都能完整追蹤
  • Dataset & Evaluation:測試和品質保證的工具都內建了

3. 未來擴展性

最重要的是,GenKit 的 plugin 架構,既使沒有開源的人提供,需要自己撰寫 plugin 接入自己開發的模型也是保有這個彈性的。

最終拍板:就是它了!

當然,選擇新框架也不是沒有風險。我心裡也有些擔心:

「GenKit 社群還不大,遇到問題怎麼辦?」
「文檔還在完善中,會不會踩到坑?」
「學習新的概念和開發模式,會不會拖慢開發速度?」

但仔細權衡後,我覺得這個投資是值得的。畢竟:

  • 框架設計思路很先進,解決了很多實際痛點
  • 學習成本雖然存在,但換來的是長期的開發效率和維護性
  • Plugin 的設計使得需要自己造輪子的時候還是可行的

「就決定是你了,GenKit!」

Slack Integration:Socket Mode vs HTTP Mode

選擇如何與 Slack 連接是另一個關鍵決定,這會影響整個部署架構。

兩種模式比較

選項 1: HTTP Mode(傳統方式)

  • 優點:擴展性好,可以水平擴展,支援所有 Slack 功能
  • 缺點:需要公網 IP、SSL 證書、處理 webhook 驗證

選項 2: Socket Mode(我的選擇)

  • 優點:不需要公網 IP,部署簡單,安全性高
  • 缺點:連接數量有限制(每個 App Token 最多 10 個並發)

為什麼選擇 Socket Mode?

1. 企業環境的現實考量

  • 我們的 Bot 需要部署在內網環境
  • 公司防火牆政策不允許隨意開放對外的 endpoint
  • 申請公網 IP 和 SSL 證書的流程很繁瑣,而且需要維護

2. 部署簡化

  • Bot 主動連接 Slack,不需要暴露任何服務端口
  • 可以直接在內網或是容器中運行
  • 不用擔心負載均衡、SSL 憑證更新等問題

3. 安全性優勢

  • 減少攻擊面,不用擔心 webhook 被惡意請求攻擊
  • 符合企業「最小權限原則」的安全要求
  • 所有通訊都是從內網主動發起,更容易通過資安審查

Socket Mode 的限制

當然,Socket Mode 也有一些限制需要考慮:

1. 連接數量限制

  • 每個 App Token 最多支援 10 個並發連接
  • 對於大多數企業應用來說已經足夠
  • 但無法像 HTTP Mode 那樣無限水平擴展

第一個 Hello World

基於這些選擇,第一個可運行的版本長這樣:

import 'dotenv/config';
import slackBolt from '@slack/bolt';
import { genkit, z } from 'genkit';
import { googleAI } from '@genkit-ai/google-genai';
import { startFlowServer } from '@genkit-ai/express';
import { runFlow } from 'genkit/beta/client';

async function startSlackBolt() {
  const app = new slackBolt.App({
    token: process.env['SLACK_BOT_TOKEN']!,
    appToken: process.env['SLACK_APP_TOKEN']!,
    socketMode: true,
  });

  app.event('app_mention', async ({ event, say }) => {
    const response = await runFlow({
      url: 'http://127.0.0.1:3400/chatFlow',
      input: event
    });

    await say({ text: response, thread_ts: event.thread_ts || event.ts });
  });

  await app.start();
}

async function startGenkitFlow() {
  const ai = genkit({
    plugins: [
      googleAI({
        apiKey: process.env['GEMINI_API_KEY']!,
      }),
    ],
  });

  const chatFlow = ai.defineFlow(
    {
      name: 'chatFlow',
      inputSchema: z.object({
        text: z.string(),
        user: z.string(),
        ts: z.string(),
      }),
    },
    async ({ text }) => (await ai.generate({
      model: googleAI.model('gemini-2.5-flash-lite'),
      prompt: text,
    })).text
  );

  startFlowServer({
    flows: [chatFlow],
  });
}

async function main() {
  await Promise.all([
    startGenkitFlow(),
    startSlackBolt()
  ]);
  console.log('Service Started');
}

await main().catch(console.error);

在 Slack 中的成果

GenKit 帶來的即時好處:

  1. 結構化的 Flow 定義:邏輯更清晰,容易測試
  2. 自動的 Trace:每個請求都可以追蹤執行過程
  3. 型別安全:輸入輸出都有 schema 驗證
  4. 配置管理:模型參數可以統一管理和調整

這裡附上原始碼
有興趣玩玩看的朋友可依照 README.md 操作

小結

技術選型看起來是技術問題,但其實更多時候是組織和產品問題。選擇 JavaScript/TypeScript + GenKit 這個組合,不是因為它在技術上最完美,而是因為它在我們的環境中最適合:

  • 團隊能力匹配:大家都會,維護不是問題
  • 部署整合簡單:現有流程直接使用
  • 未來擴展彈性:GenKit 提供了很好的抽象層

明天我們來聊一個更有趣的話題:如何讓 LLM 知道「自己」是誰?這個看似哲學的問題,其實是 ChatBot 和普通 Bot 最根本的差異。


後記:
一開始嘗試使用官方的 Gemini SDK 套件時
用了 @google/generative-ai 結果過陣子才注意到
Deprecated !!!
後來才改用 @google/genai ... 有種被坑了的感覺


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

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


上一篇
Day 1: 從 0 開始的 Slack ChatBot
下一篇
Day 3: 讓 LLM 知道「我是誰」- Slack ChatBot 的 System Prompt 設計哲學
系列文
AI 產品與架構設計之旅:從 0 到 1,再到 Day 24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言