Ian Chou's Blog

用 Bun + Vercel 打造免費、有記憶的 Serverless Telegram AI Bot

想做一個 AI Telegram Bot,但不想租伺服器?
這篇文章教你用 Bun + Vercel Functions 打造一個完全免費反應極快擁有對話記憶的 AI 機器人。

我們將解決以下關鍵挑戰:

  1. Serverless Timeout:如何讓 Webhook 在 10秒內回應,同時處理 AI 長時間運算?
  2. AI 對話記憶:在無狀態的 Serverless 環境,如何讓 AI 記得你的名字?
  3. SDK 相容性地雷:Vercel AI SDK 與 OpenRouter 目前的 Bug 及解決方案。

為什麼選擇 Bun + Vercel?


步驟 1:初始化專案

使用 Bun 快速建立專案:

mkdir telegram-ai-bot
cd telegram-ai-bot
bun init
bun add @vercel/functions @vercel/kv

建立 vercel.json 設定檔,告訴 Vercel 使用 Bun runtime:

{
  "bunVersion": "1.x", // 關鍵:使用 Bun 1.x 版本
  "crons": [
    {
      "path": "/api/cron-notify",
      "schedule": "0 4 * * *"
    }
  ]
}

步驟 2:實作 Webhook (關鍵:waitUntil)

Telegram 要求 Webhook 必須在短時間內回應 200 OK,否則會重複發送訊息。但 AI 生成通常需要幾秒鐘。

解決方案:使用 Vercel 的 waitUntil,先回應 200,再於背景處理 AI。

建立 api/telegram-webhook.ts

import { waitUntil } from '@vercel/functions';

export async function POST(request: Request) {
  let update;
  try {
    update = await request.json();
  } catch (e) {
    return new Response('Invalid JSON', { status: 400 });
  }

  // 1. 立即回應 Telegram "收到!"
  const response = new Response('OK', { status: 200 });
  
  // 2. 背景執行繁重的 AI 任務
  waitUntil(handleTelegramUpdate(update));

  return response;
}

async function handleTelegramUpdate(update: any) {
  const chatId = update.message?.chat?.id;
  const text = update.message?.text;
  
  if (!chatId || !text) return;

  // 呼叫 AI 並回傳訊息的邏輯寫在這裡...
}

步驟 3:整合 AI 與記憶 (Sliding Window)

要讓 AI 記得對話,我們不能依賴 Server 的記憶體 (因為 Serverless 隨時會回收)。我們必須將對話存入資料庫 (Upstash KV)。

⚠️ 特別注意:Vercel AI SDK vs OpenRouter

原本想用 Vercel AI SDK (ai 套件) 來簡化開發,但發現目前 (2026.01) Vercel AI SDK 與 OpenRouter 存在相容性問題 (會報錯 APICallError)。

因此,我們回歸最穩定的做法:使用原生 fetch + 手動 Sliding Window

實作記憶邏輯

import { kv } from '@vercel/kv';

// ... Inside handleTelegramUpdate ...

const conversationKey = `chat:${chatId}`;

// 1. 從 KV 讀取歷史訊息 (預設空陣列)
const history = await kv.get<Array<{role: string, content: string}>>(conversationKey) || [];

// 2. 組合 API Payload (System + History + Current User Message)
const messages = [
  {
    role: 'system',
    content: '你是一個友善的 Telegram 助理,請用 HTML 格式回覆 (<b>粗體</b>, <i>斜體</i>)...'
  },
  ...history, // 帶入舊記憶
  { role: 'user', content: text }
];

// 3. 呼叫 OpenRouter API (使用 fetch)
const aiRes = await fetch('https://openrouter.ai/api/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'google/gemini-2.0-flash-exp:free', // 免費又快!
    messages: messages,
  }),
});

const aiData = await aiRes.json();
const replyText = aiData.choices?.[0]?.message?.content || '思考中斷...';

// 4. 更新記憶 (Sliding Window:只留最近 10 則)
const updatedHistory = [
  ...history,
  { role: 'user', content: text },
  { role: 'assistant', content: replyText }
].slice(-10); // 關鍵:避免 Token 爆炸,只留最後 5 輪對話

await kv.set(conversationKey, updatedHistory);

// 5. 回傳給 Telegram
await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`, {
  method: 'POST',
  body: JSON.stringify({
    chat_id: chatId,
    text: replyText,
    parse_mode: 'HTML' // 支援格式化輸出
  })
});

步驟 4:設定環境變數與部署

  1. 在 Vercel 建立新專案。
  2. Storage 頁籤新增 Upstash KV (Vercel 會自動幫你設定 KV 相關變數)。
  3. Settings > Environment Variables 加入:
    • TELEGRAM_TOKEN: 從 BotFather 取得。
    • OPENROUTER_API_KEY: OpenRouter 金鑰。
  4. 部署:
bunx vercel --prod

取得部署網址,例如:https://my-bot.vercel.app


步驟 5:綁定 Webhook

最後一步,告訴 Telegram 把訊息往哪裡送。使用 curl 設定:

curl -F "url=https://my-bot.vercel.app/api/telegram-webhook" \
     https://api.telegram.org/bot<YOUR_TELEGRAM_TOKEN>/setWebhook

如果看到 Webhook was set,恭喜你!你的 AI 機器人已經上線了 🎉


總結

我們完成了一個強大的架構:

  1. Bun + Vercel:免費、快速、自動擴展。
  2. WaitUntil:優雅解決 Webhook Timeout 問題。
  3. Upstash KV + Sliding Window:極低成本實現 AI 記憶。
  4. OpenRouter Free Model:享受 Google Gemini 2.0 的強大能力而不花一分錢。

雖然我們遇到了 Vercel AI SDK 的小坑,但回歸 fetch 反而讓我們更清楚底層的運作原理,系統也更輕量穩定了!