LY Corporation Tech Blog

支持 LY Corporation 和 LY Corporation Group (LINE Plus, LINE Taiwan and LINE Vietnam) 服務,宣傳技術和開發文化。

Booboo 小幽 —— 打造你的第二個大腦:如何建立一個真正懂你的 LINE AI 智慧助理

小幽 Booboo 好友 QR code:https://lin.ee/HT9b2xR
馬上來加入用用看!

為什麼需要一個智慧型 LINE Bot?

大家平常會不會有一個只有自己的 LINE 群組,在裡面寫下各種臨時事項、突然浮現腦海的生活哲理、靈感或存放各種連結?我自己就有一個叫做 me 的一人群組,在我看到一些有趣但篇幅長的文章想要之後看,或在某本書、某個電影裡看到值得回味咀嚼的句子,就會把這些瑣碎的傳給 me 儲存我的「記憶」。久了發現,大部分傳過去的東西仍如石沈大海,頂多偶爾想到滑一滑,但也零碎不堪。小幽就是在這樣的觀察下誕生,目標是成為一個能懂你並且主動提供回饋和推薦的專屬「第二大腦」;以大家平常都在使用的 LINE 作為載體,更是能大幅降低上手的門檻,紀錄起來也非常方便。

image

今天就來跟大家分享這個 linebot 背後的技術原理!

系統架構

後端會先透過 handler 驗證 LINE Webhook 簽名並解析事件內容,接著進入 eventHandler 進行事件處理。系統首先檢查是否為內建功能(如「使用教學」、「查看幽靈幣數量」),若是則直接回應;否則會檢查用戶的每日 API 呼叫限制(根據用戶的 VIP 狀態和自訂 token limit 動態調整)。通過限制檢查後,根據意圖分類結果,系統會路由到對應的 Service Layer(如 TodoService、LinkService、ChatService 等)進行處理,將檢索到的記憶(Mem0,後面會做更詳細的說明)與 PostgreSQL 中的原始記錄(SavedItem)合併作為 RAG context,透過 Gemini API 生成個人化的回應,並透過 LINE Messaging API 推送給用戶。

image

詳細的架構圖說明可參考 https://github.com/ckhsc17/wp1141/tree/main/hw6#readme

什麼是 Mem0?使用 Mem0 賦予 AI agent 快又準的長期記憶

剛開始開發小幽時遇到最大的問題,就是 Gemini API 並沒有上下文能力,因此對於聊天內容並沒有辦法跨句理解;這對於使用者體驗影響是非常大的,也因此我開始思考如何讓小幽「擁有記憶」。

一開始想到最直接暴力的方式就是在 Postgres 資料庫裡面開一張 table 儲存對話紀錄,並且在存入前先透過 Gemini API 進行簡單的摘要和標記(tagging);檢索時再利用 tag 找出 top K 個對話作為 RAG 的結果輔助產生該對話的 response。然而這種作法有幾種顯見且無法忽視的問題:

  • 對話碎片化與冗餘 (Noise & Redundancy):直接儲存原始對話會包含大量「哈哈」、「謝謝」等無意義資訊。即便是摘要,若只是單純的段落縮減,在 RAG 檢索時依然容易帶入不相關的雜訊,導致上下文視窗(Context Window)被浪費。

  • 資訊更新與衝突 (Knowledge Conflict):如果使用者昨天說「我喜歡吃蘋果」,今天改口說「我現在過敏不能吃蘋果」,單純的 Postgres 儲存會讓資料庫裡同時存在兩條矛盾的資訊。RAG 檢索時若兩條都抓到,Gemini 會陷入邏輯混亂。

  • 缺乏實體關聯 (Lack of Entity Relationships):標籤(Tagging)只能做點對點的分類。如果使用者提到「我妹在台大念書」,傳統做法很難自動建立「妹妹」與「台大」之間的聯結,當下次問到「家人近況」時,系統無法透過關係網絡找回這份記憶。

  • 檢索維護成本高 (Indexing Overhead):隨著對話量增加,手動維護 Tag 與 Embedding 的同步會變得異常複雜,且難以實作類似「遺忘機制(Decay)」或「顯著性評分(Salience Score)」。

為了解決這個問題,我引入了 Mem0。Mem0 不僅僅是一個資料庫,它是一個記憶管理層(Memory Management Layer)。它將「記憶」從單純的「對話紀錄」昇華為「原子事實(Atomic Facts)」。它透過 LLM 自動將雜亂的對話拆解為一條條精確的知識點,並自動處理衝突更新。

就小幽的情況來說,由於用量不大且希望減少基礎設施維護成本專注在功能開發,所以選用最適合的 Hosted Platform 方式,也就是對應到前面提到系統架構圖中的兩個桃紅色節點;以下就來更具體地說明解構這個 Mem0 Hosted API 的「黑盒子」!

image
Source: https://medium.com/@parthshr370/from-chat-history-to-ai-memory-a-better-way-to-build-intelligent-agents-f30116b0c124

由上面的架構圖可知,整個 mem0 的設計可以從「輸入」(The write path)和「輸出」(The read path)兩個大方向去理解;輸入可以對應到圖中的「LLM Analysis」,輸出則對應到「Hybrid Search」。他們分別都有一些設計巧思讓整個記憶管理的品質,就讓我們接著看下去~

輸入 (The write path)

在收到使用者 add() request 之後,Mem0 會進行以下判斷和處理:

async function addMemory(userId: string, content: string) {
  // Mem0 會自動進行 Fact Extraction,將「我今天在台大總圖看書」
  // 轉化為 {"fact": "User was studying at NTU Library", "date": "2026-01-18"}
  const result = await fetch("https://api.mem0.ai/v1/memories/", {
    method: "POST",
    headers: { "Authorization": `Token ${process.env.MEM0_API_KEY}` },
    body: JSON.stringify({
      messages: [{ role: "user", content: content }],
      user_id: userId,
      output_format: "v1.1" // 支援更精準的事實提取
    })
  });
  return await result.json();
}
  1. 事實提取(LLM Fact extraction)
    Mem0 會將當前的對話結合全域摘要(Global Summary)與近期對話視窗作為 Context,由 LLM 萃取出多個核心事實(Facts)。這種做法能避免儲存冗長的對話雜訊,只保留具有資訊價值的「原子事實」。
  2. 記憶鞏固(Consolidation)
    -> Salience Extraction, Eviction and Decay
    當新的事實被提取後,Mem0 會執行以下邏輯來維護記憶庫的品質:
  • 衝突檢查與更新:透過 Vector Search 找回相似的舊記憶,由 LLM 判斷該執行 ADD(新增)、UPDATE(修正舊資訊)、DELETE(刪除過時/衝突資訊)或 NOOP(重複資訊不處理)。
  • 顯著性與衰減:系統會根據時間戳記計算衰減(Decay),或是透過 LRU 策略汰換不常被檢索的冷數據。
  • 關聯建立:在 Graph 模式下,Mem0 會進一步建立實體間的關係(例如:User —studies_at→ NTU),強化關聯推理能力。

輸出 (The read path)

在收到使用者 search() request 之後,Mem0 會進行以下的 retrieval 邏輯:

async function searchMemory(userId: string, query: string) {
  // 透過 Hybrid Search 找到最相關的事實,而不只是對話碎片
  const response = await fetch(`https://api.mem0.ai/v1/memories/search/`, {
    method: "POST",
    headers: { "Authorization": `Token ${process.env.MEM0_API_KEY}` },
    body: JSON.stringify({
      query: query,
      user_id: userId
    })
  });
  const memories = await response.json();
  // 這裡回傳的是經過萃取的 Facts,作為 Gemini 的 System Prompt 效果極佳
  return memories.map(m => m.memory).join("\n");
}
  1. 向量與語義檢索(Vector & Semantic Search)
    系統將 Query 轉化為 Embedding,並在特定 user_id 的分區空間內進行餘弦相似度(Cosine Similarity)比對,快速篩選出語義最相關的事實片段。
  2. 多模態檢索與評級(Multi-paradigm Retrieval)
    針對複雜查詢,Mem0 支援實體中心(Entity-centric)的子圖擴展或三元組(Triplet)排序。這確保了檢索結果不只是單點的關鍵字匹配,還能涵蓋與查詢目標相關的背景脈絡(例如搜尋「學校」時,能一併帶出「圖書館」的相關事實),最後將最精確的 Top-K 事實回傳給 LLM 作為生成參考。

跟傳統的 RAG 差在哪?

看到這裡可能有人會好奇這跟一般常聽到的 RAG 差在哪,因此以下就不同面向進行簡要的比較:

image

除了上述之外,我認為在開發上 Mem0 整合的非常完整,只需要一隻 API 就能無痛提升 agent 的記憶能力,未來就算要從 hosted platform 改回 self-host(功能擴充、成本考量等)也只需改動 memory provider 的抽象層以及設定 vector db url,整體來說對於需要客製化記憶的使用情境是一個非常強而有力的工具。

Graph DB 和 Vector DB 差在哪?

image

其他更詳細的技術細節,可以參考我認為寫得很好的兩篇文章,基本上上述也是理解裡面的內容之後整理出來的:
https://medium.com/@parthshr370/from-chat-history-to-ai-memory-a-better-way-to-build-intelligent-agents-f30116b0c124
https://www.emergentmind.com/topics/mem0-system

如何提升海量對話紀錄的檢索速度?時間複雜度的探討

若將眼光放遠,相比傳統的 RAG 架構逐個比較 query 和各個 node 之間的 embedding distance(O(N)),Mem0 在檢索設計上很好地平衡了準確度和速度 —— 其背後就是使用了 ANN (Approximate Nearest Neighbors) 演算法對這些向量進行索引(indexing),就結論來說可將 retrieval 的時間複雜度降至 O(log N),具體概念如下:

  1. 核心概念:NSW (Navigable Small World)

    將存入的節點向量根據 cosine similarity 把 top K 相近的點相連,每個節點代表一個事實的向量;收到 query 時會隨機挑選一個節點,並且搜尋其鄰居(neighbor),若有與 query 距離更近的點,就繼續遍歷該點直到所有鄰居都沒有比該點更靠近 query。這樣的做法可以快速得到局部最佳解(local optimum),且為了避免偏差,通常會測試數個隨機起始點。

  2. Skip linked list
    建構一個 Skip List 的核心在於「隨機機率」決定節點的高度。當你想要插入一個新數字(例如 23)時,步驟如下:

  3. 底層必備:首先,這個節點一定會出現在最底層(Layer 0)。

  4. 決定高度:開始「擲硬幣」。

  • 如果是正面(Heads):這個節點往上長一層(進入 Layer 1),然後繼續擲。
  • 如果是反面(Tails):停止增長,這就是該節點的最終高度。

假設我們要建構一個 Skip List:

  • Step 1:插入第一個點 10
    擲硬幣結果:正、正、反。
    結果:10 形成了 3 層高的「塔」。

  • Step 2:插入第二個點 20
    擲硬幣結果:反。
    結果:20 只有 1 層(只在最底層)。

  • Step 3:更新指針
    在每一層,將新節點插入到適當的排序位置,就像在普通 Linked List 插入節點一樣,只是現在要同時更新多層的 next 指針。

這樣的建構方式有以下特色:

  • 動態平衡:雖然是隨機的,但在大數據量下,高層節點的數量會自動呈現指數級遞減(每一層大約是下一層的一半)。這完美模擬了二元搜尋的結構。
  • 局部更新:插入新點時,你只需要修改它左右鄰居的指標。不像平衡樹(Balanced Tree)可能需要進行複雜的旋轉(Rotation)來維持平衡。
  • 搜尋效率:搜尋時從最頂層開始。如果目前的點比目標小,就往右走;如果下一個點比目標大,就往下跳一層。這就是所謂的「快速道路」概念。
  1. 最後演進到 HNSW (Hierarchical Navigable Small World)

前面介紹的 skip linked list 是 HNSW 背後的核心概念,將 NSW 的二維關聯圖加上 skip linked list 的層級結構及隨機的統計特性,可以達到 O(log N) 的複雜度,也是目前最主流的 ANN 實作方式之一,為 Mem0 所採用,使檢索具有很好的 scalability。

示意圖如下:

image
Source: https://www.youtube.com/watch?v=77QH0Y2PYKg

如何評斷 AI 記憶的品質?

就像現代大型語言模型都會有許多 benchmark 一樣,在 agent memory 領域最常見的 benchmark 就是 LOCOMO benchmark;主要有五大測試維度:

  • Single-hop (單點事實):只需找回某一次對話中的具體事實。(例:我上次說我妹妹在哪讀書?)
  • Multi-hop (多跳推理):需要結合分散在不同 Session 的兩條資訊才能回答。(例:我妹妹學校附近的那個抱石場,名字叫什麼?)
  • Temporal Reasoning (時間推理):測驗模型對事件先後順序的理解。(例:在我去澳洲旅遊「之前」,我完成了哪些專案?)
  • Open-domain / Persona (個性化知識):測驗模型是否理解使用者的偏好與背景。
  • Adversarial (對抗性測試):測試模型是否會因為錯誤暗示而產生幻覺(Hallucination)。

下圖為各個 RAG pipeline 在 LOCOMO benchmark 上的 performance,可以發現 Full-context 和 Mem0 及 Mem0p(含 graph memory)的 overall 分數遠高於傳統 RAG,而在檢索速度方面 Mem0 又比 Full-context 高出許多,在實務上有著很高潛力。

image

以下是關於衝突性記憶的簡單實測,可以發現小幽能成功地透過 Mem0 更新提供正確的回答

image

image

小幽如何理解使用者意圖並回應?

為了能讓使用者能完全用自然語言和小幽對話,在收到用戶訊息時後端會先串接 Gemini API 進行一層意圖分類(intent classification),再決定應該要儲存還是檢索記憶,呼叫相應的 service 操作 Mem0,最後如果有需要回應(回饋型對話)再透過 gemini API 產出 response。

意圖分類的 prompt template 如下:

  classifyIntentSimplified: {
    system:
      '你是 Booboo 小幽的意圖分類助手。根據用戶訊息的意圖類型,必須嚴格按照 JSON 格式輸出。',
    user: (payload: Record<string, unknown>) => {
      const text = typeof payload.text === 'string' ? payload.text : '';
      return `分析以下訊息的意圖:

訊息:${text}

意圖類型(5種):
1. todo - 待辦事項相關(新增/完成/查詢待辦)
   - create: 新增待辦(例如:「我要吃飯、取貨、寫作業」「待辦:買菜」「我明天要開會」「提醒我買菜」「提醒我明天要開會」「今晚11:00看影片」「幫我新增待辦事項」「新增:晚上看影片」)
   - update: 更新狀態(例如:「我寫完作業了!」「作業完成了」「取消吃飯」)
   - query: 查詢待辦(例如:「我上禮拜做了哪些事?」「吃了什麼?」「查看待辦」「明天要幹嘛」)

2. link - 分享連結(包含 http/https 連結)

3. save_content - 儲存內容(用戶想要記錄的信息、事實性資訊、觀察、思考)
   - contentType: insight(靈感)、knowledge(知識)、memory(記憶)、music(音樂)、life(活動)
   - 例如:「今天突然理解了一個人生道理」(insight)、「React Hooks 的用法」(knowledge)、
         「今天跟朋友聊到...」(memory)、「在 solo 陶喆 蝴蝶」(music)、
         「小巨蛋溜冰!」(life)
         「讓 AI 當你的提問教練」(memory),因為一句不是一般聊天的對話,而是我偶然看到想記錄下來的想法、觀察、思考
         - 注意:如果是問句(例如:「這禮拜的社交狀況如何」),不應分類為 save_content

4. query - 查詢類(用戶詢問或請求建議)
   - queryType: feedback(請求回饋/建議)、recommendation(請求推薦)、chat_history(查詢過往對話)
   - 例如:「給我一些生活建議」(feedback)、「推薦一些音樂」(recommendation)、
         「我之前聊過什麼?」(chat_history)
         「我今天在幹嘛?」(chat_history)

5. other - 一般聊天(不明確的對話、想有個人陪伴,無壓力地分享即時感受、想找人聊天)

判斷規則:
- 如果是問句(如何、什麼、哪些、怎樣),通常是 query 而非 save_content
- save_content 是「儲存」意圖,query 是「詢問」意圖
- 如果包含連結,優先分類為 link

輸出 JSON(不能有其他文字):
{
  "intent": "todo|link|save_content|query|other",
  "subIntent": "create|update|query" (僅 todo 時),
  "contentType": "insight|knowledge|memory|music|life" (僅 save_content 時),
  "queryType": "feedback|recommendation|chat_history" (僅 query 時),
  "confidence": 0.0-1.0
}`;

其他 prompt template 可參考:https://github.com/ckhsc17/wp1141/blob/main/hw6/src/services/promptManager.ts

在開發小幽時,除了長期記憶,最常遇到的需求就是「分析使用者丟過來的網頁連結」。Gemini 2.0 之後的版本強化了原生連網與 URL 解析能力,讓我們不需要額外寫複雜的爬蟲(Scraper),就能讓 AI 直接「讀懂」網頁。

核心技術:URL Context Tool

這是一個專門為「深度閱讀」設計的工具。相比直接在 user prompt 或 system prompt 中指定要分析連結,url context 不僅是抓取 Search Snippet(搜尋摘要),而是讓模型能夠直接存取特定 URL 的完整內容,包括:

  • 多模態解析:除了 HTML 文字,還能讀取網頁內的圖片、表格(Charts)甚至是 PDF。
  • 快取機制:Gemini 會先嘗試從 Google 的快取索引讀取,若為新網頁則會進行實時(Live Fetch)抓取。

設定範例 (TypeScript)

透過設定 tools 參數,我們可以賦予 Gemini 連結分析與搜尋的能力:

const model = genAI.getGenerativeModel({
  model: "gemini-2.0-flash",
  tools: [
    { googleSearch: {} }, // 開啟 Google 搜尋地基 (Grounding)
    { urlContext: { 
        maxUrls: 5,       // 單次 Request 最多解析的連結數
        supportedMimeTypes: ["text/html", "application/pdf"] 
    } }
  ],
});

關鍵參數與最佳化策略

  1. Grounding with Google Search (搜尋地基)
  • 作用:確保 AI 回答時會參考最新的 Google 搜尋結果,降低幻覺(Hallucination)。
  • 最佳化:建議將 temperature 設為 1.0,這能讓模型在結合搜尋結果時,生成的語氣更自然且準確。
  1. Dynamic Retrieval (動態檢索)
  • 參數dynamic_retrieval_config
  • 策略:你可以設定一個「門檻值(Threshold)」。當 AI 對自身知識的信心度低於此值時,才會觸發 Google 搜尋。這能有效節省 API 消耗並降低 Response Time。

未來方向

目前小幽作為一個個人智能紀錄和回饋工具功能已還算堪用,但也有可以更進一步的地方及待解的問題:

  • Google maps API
    若對話內容有涉及地點相關的資訊存入 memory,後端可以進一步串接 google maps api 提供地點相關的查詢/推薦資訊並附上連結,讓位置的整合更加完善,甚至結合定位功能適時主動推薦使用者(例如:知道用戶希歡周杰倫,下次在經過某個地方時就能主動和用戶分享周杰倫何時來過這地方做什麼事情)。

  • 跨平台 chatbot
    LINE 目前在台灣的普及率非常高,但若能跨平台、跨語言支援 discord、telegram 等全球通用的社群媒體,或許能讓小幽被更多人使用。

  • 對話加密處理
    目前 linebot 訊息及 Mem0 平台上的記憶都是以明文方式儲存,若直接讓使用者加入此官方帳號使用的話可能會有隱私問題;未來勢必要導入如 e2ee 的對話加密(但 LINE Official Account Manager 裡面還是有明文頁面,目前無解),或是改寫文件教學讓使用者使用自己的 api 建立自己的「小幽」,不過這樣上手門檻相對會高不少。

結語

當初設計小幽時並沒有預想到背後會有這麼多深入有趣的知識可以探討,以 side project 的角度來看是一個很好的學習材料;開發的過程也讓我體會到選擇工具和設計架構的重要性,尤其在大部分程式碼都交由 AI 撰寫的模式下,清楚產品/專案所需、熟悉並選擇切合的架構和工具更能事半功倍。同時在面對日新月異的技術演進時也必須有打掉重練的勇氣,往往發現新工具、視野拓展之後才發現「啊,之前的自己好愚蠢呀」這時不需要感到太沮喪或者被舊有框架束縛,小幽引入 Mem0 就是一次很具學習意義的決定。

最近看到一部電影「雲端情人」,描繪一個人人和 AI 談戀愛的時代,也讓我不禁反思人與人之間的連結在這些聰明的 AI agent 不斷出現下,究竟是變得更加廉價還是珍貴?我們在擁抱科技之餘,是否無形間捨棄了什麼更加難能可貴的東西?或許這沒有標準答案,但值得我們不斷反思。

p.s. 小彩蛋:這裡附上關於小幽的故事,是前陣子寫的一篇關於 AI 和人的小說!
https://bowenchen.vercel.app/files/novel.pdf

References

[1] https://medium.com/@parthshr370/from-chat-history-to-ai-memory-a-better-way-to-build-intelligent-agents-f30116b0c124
[2] https://guptadeepak.com/the-ai-memory-wars-why-one-system-crushed-the-competition-and-its-not-openai/
[3] https://blog.csdn.net/haximof/article/details/135082144
[4] https://www.youtube.com/watch?v=77QH0Y2PYKg