iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

從零打造情感感知 Agentic System:FSM 狀態機與 RAG 的整合實作系列 第 14 篇

Day 14:【RAG 實戰篇2】讓記憶發揮作用!將 ChromaDB 動態注入 Gemini 對話閉環

  • 分享至 

  • xImage
  •  

前言

「如果連記憶都無法 shared,那這跟未簽約的 NPC 有什麼兩樣?!」

昨天雖然成功在 ChromaDB 裡存了莉莉 Agent 的 Lore(設定集)和我討厭香菜、要考期末考的紀錄,但那些資料如果只是躺在 Vector DB 裡冷卻,Gemini 依然只是個「初次見面,請多指教」的金魚腦 AI。這就跟好感度明明滿了,結果劇情打回第一話一樣令人崩潰!

今天的任務就是把它們「串起來」!打造出一套專屬於陪伴型 Agent 的記憶檢索迴路:

  • 背景檢索:先用使用者的這句話,去 ChromaDB 抓出「相關的角色設定」與「相關的使用者記憶」。
  • Prompt 組合:把抓出來的記憶包成 Context(背景資訊),動態插入到給 Gemini 的 system_instruction 中!

第一關:設計 RAG Prompt 組合模組(rag_builder.py)

建立一個 rag_builder.py,專門處理「檢索 ⇒ 組裝 Context」的邏輯。這樣做的好處是程式碼很乾淨,未來不論切換什麼角色(莉莉或其它 Agent),這套 Prompt 組裝器都能通用!

import rag_memory

def build_rag_context(user_input: str) -> str:
    """根據使用者的輸入,自動檢索角色設定與使用者記憶,並組裝成 Prompt 補充包"""

    # 向 ChromaDB 檢索最相關的 1-2 條資料
    retrieved_lores = rag_memory.query_character_lore(user_input, n_results=1)
    retrieved_memories = rag_memory.query_user_memory(user_input, n_results=1)

    context_parts = []

    # 組合角色設定片段
    if retrieved_lores:
        lore_text = "\n".join([f"- {lore}" for lore in retrieved_lores])
        context_parts.append(f"【檢索到的角色相關設定】:\n{lore_text}")

    # 組合對使用者的記憶片段
    if retrieved_memories:
        memory_text = "\n".join([f"- {mem}" for mem in retrieved_memories])
        context_parts.append(f"【你關於該使用者的長期記憶】:\n{memory_text}")

    if not context_parts:
        return ""

    # 拼裝成乾淨的 Context 區塊
    full_context = "\n\n".join(context_parts)
    return f"--- [RAG 檢索輔助記憶] ---\n{full_context}\n請結合上述記憶資訊,以符合人設的方式自然回應。"

第二關:在 app.py 中整合 RAG 對話閉環

接著回到 Streamlit 主程式 app.py,把這套 RAG 邏輯插進發送訊息的生命週期裡。這就跟在戰鬥中開啟「動態記憶注入」的主動技能一樣:

try:
    intent = router.classify_intent(client, user_input)

    if intent == "CHAT":
        delta = evaluator.evaluate_emotion(client, user_input)
        st.session_state.fsm.update_affection(delta)

        # 取得當前 FSM 狀態對應的動態 System Instruction
        base_instruction = st.session_state.fsm.get_system_instruction()

        # --- RAG 記憶檢索與 System Instruction 疊加 ---
        # 觸發 RAG 檢索:自動尋找相關記憶(查不到相關內容時會回空字串)
        rag_context = rag_builder.build_rag_context(user_input)

        # RAG 疊在 system_instruction 上,而不是塞進訊息本文。
        # 因為 send_message 送出去的內容會永久留在 chat history 裡,
        # 塞進本文的話那些「系統輔助訊息」會跟著每一輪重送、
        # 也會被存進 Firestore 汙染對話紀錄;
        # system_instruction 則是每輪獨立覆寫,不進 history
        final_instruction = base_instruction + ("\n\n" + rag_context if rag_context else "")

        response = st.session_state.chat.send_message(
            user_input,
            config=types.GenerateContentConfig(system_instruction=final_instruction)
        )
        ai_reply = response.text

第三步:實際測試!見證記憶開花結果

測試情境 A:觸發「使用者記憶」

  • 我輸入:「我今天晚上補習完好累喔…」

  • 背景運作:RAG 自動檢索出 使用者在下周二 有資工系的期末考,最近壓力很大。

  • 莉莉回應:

    「哼,笨蛋嗎你?既然累了就快去休息啊!下週二不是還要應付那個什麼資工系的期末考嗎?要是現在就累倒了,到時候考砸了我可不管你喔!……喏,這杯紅茶拿去啦。先說好,這只是我剛好泡多了順便給你的,才不是特地為你準備的,少自作多情了!快點喝完去睡覺!」

    https://ithelp.ithome.com.tw/upload/images/20260928/20183877OTjts13mIV.png

測試情境 B:觸發「角色設定 Lore」

  • 我輸入:「下午有點餓,有什麼推薦的食物嗎?」

  • 背景運作:RAG 自動檢索出 莉莉非常擅長製作各種甜點與手工烘焙,特別是烤薄餅。

  • 莉莉回應:

    「哼,肚子餓了才想到我,你這笨蛋是沒別的事好做了嗎?……真拿你沒辦法。我剛剛「剛好」多烤了些薄餅,就分你一點好了。你下週二不是還要應付那個什麼資工系的期末考?看你最近壓力大成那樣,吃點甜的腦袋才會轉啦!拿去,這才不是特地為你準備的,別自作多情了!」

    https://ithelp.ithome.com.tw/upload/images/20260928/20183877pTMhNejd7n.png

踩坑紀錄:RAG 閉環設計的兩大陷阱

今天在調試 RAG 閉環時,我踩到了兩個極度隱蔽但傷害力極強的「天坑」:

坑 1:RAG 算完就丟掉(變數寫好看的)

辛苦算好了 final_instruction 與 prompt_with_rag,結果呼叫 send_message 時卻一個都沒帶到,訊息送的是原始 user_input,config 帶的還是舊的 current_instruction(而且 base_instruction 還重複呼叫了相同的 function)。

每輪默默花了 2 次 Embedding API 額度去查向量庫,AI 依舊失憶且完全不會報錯,查 bug 查到懷疑人生!

坑 2:RAG 該放 system_instruction,別往訊息本文塞

千萬別把 RAG 記憶用字串接在 f"{user_input}\n\n(記憶:...)"後面直接送出去。send_message 送出的字串會永久留存在 SDK 記憶體的 chat.history 中。

如果塞進本文,聊到第 5 輪時,模型還會看到前 4 輪拼接的舊記憶,導致 Context 長度與 Token 數量暴漲,且回答容易被過期檢索干擾。RAG 本來就是每輪動態查的,放進每輪獨立覆蓋、不進 history 的 system_instruction 才是正確姿勢!


上一篇
Day 13:【RAG 實戰篇1】手把手用 ChromaDB 幫陪伴 Agent 打造外掛大腦
下一篇
# Day 15:【RAG 時間陷阱】Agent 怎麼知道那是多久以前的事?實作 Time-Aware 動態時間檢索 ## 前言
系列文
從零打造情感感知 Agentic System:FSM 狀態機與 RAG 的整合實作 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言