iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
AI 自動化

《從心出發:30 天打造一套具 RAG、心理支持決策、安全治理與 ARCI 自適應能力的 AI 心理支持平台》系列 第 20

# Day 20|Context Assembly:不是記得越多越好,而是只拿這一輪真正需要的資訊

  • 分享至 

  • xImage
  •  

資料被保存,是生命週期的問題;資料能不能進 Prompt,是權限的問題。

上一篇談記憶時,我把注意力放在狀態的有效期限上:哪些資料可以繼續保留,哪些推論會因為依據被作廢而失效。但往回覆生成的那一層看,我發現還有一個問題沒有回答:就算一筆資料此時仍然有效,為什麼就代表這一輪模型需要看到它?

這是今天的主題:Context Assembly(上下文組裝)。在「從心出發」裡,我希望把「資料還在」與「這次要拿來使用」分開處理。記憶不等於 Prompt 上下文;保存資格,也不等於每一輪的使用資格。

舊推論如何污染新對話

先用一個假想情境說明。以下文字不是使用者紀錄,也不是本日模型實測結果。

前一段對話裡,使用者說:「我最近和朋友有點不愉快。」假設系統因此留下低確定度的推論,認為可能存在人際壓力。後來,使用者又說:「我今天只是考試考得很累。」

如果先前那筆狀態此時仍然有效,組裝器應不應該直接把它寫成「使用者最近有人際衝突」,放入這一輪的 Prompt?

我擔心的是,當來源與不確定性沒有一起留下時,這筆舊推論可能被當成解釋當前情緒的背景。例如,回覆可能變成:

看起來你最近不僅人際關係遇到瓶頸,連考試也壓得你喘不過氣……

這句回覆也是示意。問題不在於系統提到了朋友,而是它把先前的資訊,接成了使用者這次沒有表達的解釋。對方明明在說考試,系統卻替他補上了另一個原因。

我在這裡把這類風險稱為上下文污染(Context Contamination):未經適當篩選或標註的資訊進入上下文,影響後續回答;如果回答又被當成新的證據,原本的推論就可能被反覆強化。這是本篇要處理的設計風險,不是今天已經測得的模型行為。

回答寫得自然,不能證明組裝進去的資訊就合適。

最小權限上下文:Least-Privilege Context

我借用最小權限的想法,替上下文加上一個限制:一輪生成不該因為某筆資料「可能有用」,就取得所有歷史資訊。它應該只取得這次任務允許使用、而且確實需要的內容。

可以把這個設計目標拆成幾層:

Stored:狀態中存在的資料
   ↓
Eligible:仍有效、未作廢的候選
   ↓
Relevant:符合本輪指定主題的資訊
   ↓
Permitted:通過來源與確定度策略
   ↓
Assembled Context:本輪納入的上下文

這些層次不是同一件事。有效的資料可能不相關,相關的推論也可能沒有被允許帶入。這裡說的「最小」,是減少不必要資訊的設計方向,不代表目前的程式已經能自動找出唯一、最佳的最小集合。

以下是概念圖。實線整理本日離線組裝的流程;虛線表示尚未在本日驗證中接通的模型生成路徑。

flowchart LR
    subgraph MemoryState["對話狀態"]
        S1["使用者陳述"]
        S2["系統推論"]
    end
    S1 --> F1["有效性與來源檢查"]
    S2 --> F1
    F1 --> F2["指定主題過濾"]
    F2 --> F3["來源與確定度策略"]
    F3 --> CA["上下文組裝"]
    MSG["當前使用者訊息"] --> CA
    CA -.-> P["Prompt:此接線本日未驗證"]
    P -.-> LLM["語言模型"]

Context Assembly 的判斷維度

首先是有效性與時間。過期、作廢、被取代或關閉的條目,不應繼續作為本輪候選。資料即使曾經有效,也不能忽略它後來的狀態變化。

接著是來源與確定度。USER_STATED 表示使用者曾經明確表達;SYSTEM_INFERRED 則是系統根據資料產生的推論。兩者不能混在一起。使用者明確說過,也不代表內容已經獲得客觀驗證;這裡要區分的是「誰說的」,不是替每句話認證真假。

第三是輪次相關性。目前的測試會明確傳入需要的主題集合。例如,當呼叫端只要求使用者目標與支持偏好時,標記為 CURRENT_OBSERVATION 的睡眠紀錄就會被排除。這證明的是依主題標籤篩選,不是組裝器已經能自行判斷任意句子的語意相關性,更不是說睡眠與考試壓力必然無關。

第四是使用者更正。測試透過 supersedes 明確指定新陳述取代哪一筆舊陳述,再檢查舊條目不會被納入。這驗證了更正事件進入系統之後的處理,還不能證明系統能從所有「不是這個意思」的自然語言中,自動找對要更正的資料。

最後是安全與敏感邊界。這是後續組裝流程仍須考慮的設計要求。本日的來源與確定度過濾,不能直接等同於完整的敏感度審查、存取隔離或隱私保護驗證。

程式碼實作:約束進場的資格

本日實作集中在 src/psychological_support/conversation/context.py。以下節錄交付稿中的核心篩選邏輯:

    for entry in projected.entries:
        if entry.status == EntryStatus.EXPIRED:
            excluded.append((entry.entry_id, ContextExclusionReason.EXPIRED))
            continue
        if entry.status == EntryStatus.INVALIDATED:
            excluded.append((entry.entry_id, ContextExclusionReason.INVALIDATED))
            continue
        if entry.status == EntryStatus.SUPERSEDED:
            excluded.append((entry.entry_id, ContextExclusionReason.SUPERSEDED))
            continue
        if entry.status != EntryStatus.ACTIVE or entry.closed_at is not None:
            excluded.append((entry.entry_id, ContextExclusionReason.CLOSED))
            continue

        # Invariant checks
        if entry.source not in (SignalSource.USER_STATED, SignalSource.SYSTEM_INFERRED):
            raise ContractValidationError("unsupported entry provenance")
        if entry.source == SignalSource.SYSTEM_INFERRED and entry.certainty == Certainty.EXPLICIT:
            raise ContractValidationError("inference cannot claim explicit user certainty")

        # Relevance filter
        if effective_topics is not None and entry.topic not in effective_topics:
            excluded.append((entry.entry_id, ContextExclusionReason.IRRELEVANT))
            continue

        # Privilege / Safety filter
        if entry.source == SignalSource.SYSTEM_INFERRED:
            if (
                not effective_policy.allow_unconfirmed_inferences
                or entry.source not in effective_policy.allowed_sources
            ):
                excluded.append((entry.entry_id, ContextExclusionReason.UNCONFIRMED_INFERENCE))
                continue
            is_confirmed = False
        elif entry.source == SignalSource.USER_STATED:
            if (
                entry.source not in effective_policy.allowed_sources
                or entry.certainty not in effective_policy.required_certainties
            ):
                excluded.append((entry.entry_id, ContextExclusionReason.PRIVILEGE_RESTRICTED))
                continue
            is_confirmed = True
        else:
            excluded.append((entry.entry_id, ContextExclusionReason.MALFORMED_OR_UNSUPPORTED))
            continue

這段邏輯把一般排除原因記錄下來,例如 EXPIREDINVALIDATEDSUPERSEDEDIRRELEVANT;對不支援的來源或推論冒用 EXPLICIT 的情況,則拋出契約錯誤。

相關性檢查也有條件:只有 effective_topics 不是 None 時,這段程式才會依主題排除條目。不能只看到有 IRRELEVANT 這個分支,就宣稱每一次呼叫都完成了相關性判斷。

預設策略不放行未確認推論。即使採用明確允許未確認假說進入的策略,測試仍要求推論保留 is_confirmed_fact = False。這限制的是資料契約中的標記,不代表下游模型一定會正確理解,也不代表使用者陳述已被驗證為客觀事實。

我優先處理的是這些資訊邊界,而不是省 Token。至於是否真的減少成本、縮短延遲,今天沒有量測,不能把它寫成已取得的成效。

今天我真正驗證了什麼

這次工程交付紀錄記載,tests/conversation/test_context_assembly.py 的 12 項離線合成測試通過。這些結果不是模型回答品質或真實對話的驗證,而是對指定輸入與策略的檢查。

測試包含:沒有歷史資料時保留當前訊息;符合指定主題的明確陳述可以納入;過期、作廢與被取代的條目被排除;依賴已作廢資料的推論也跟著失效。另一組測試比較預設策略與允許假說的策略,確認推論不會因此被標成使用者明確陳述。

其中一個案例很直接:狀態裡有三筆資料,但這一輪只要求支持偏好,最後只納入符合條件的一筆。我要驗證的正是這個差別——存在三筆,不代表三筆都該交給模型。

有兩個測試邊界不能省略。原本名稱包含「deleted or closed」的案例,實際使用的是 InvalidateEntryEvent,因此只能寫成作廢條目被排除,不能宣稱實體刪除或不會外洩。來源偽造的案例則同時涉及序列化與解析;只確認有例外,還不足以獨立證明例外確實來自來源檢查。

測試通過是紀錄,測試究竟支持哪一句話,仍然需要逐項對照。

我還沒有解決什麼

依本日交付紀錄,所檢查的 pipeline.pyprompts.py 仍使用 CURRENT_USER_MESSAGE_ONLYhistory_available = False。新增的上下文組裝器完成了離線規則驗證,但這批歷史條目尚未在本日驗證中接入真實模型的 Prompt。

這個限制只針對本篇的上下文組裝路徑,不能擴大成整個專案所有入口都沒有模型或前端。跨 Session 檢索、持久化向量儲存,以及這條路徑的真實模型與 UI 端到端整合,都沒有由本日證據建立。

同樣地,這些測試不構成臨床療效或心理支持安全性的證明。今天完成的是一組可以檢查的資料篩選規則,不是讓系統突然具備了完整的心理專業判斷。

回頭看這三篇,Day 18 問的是:不知道的事情,會不會在流程中變成已知?Day 19 接著問:曾經知道的事情,是否應該繼續留下?Day 20 則再往前一步:現在保有的資訊,這一輪真的需要讓模型看見嗎?

下一篇,我想接著處理候選資訊的來源:在做資格審查之前,要如何找到值得拿來檢查的資料?這也就是 Retrieval(檢索)的問題——找到最像的資料,不代表找到最該使用的資料。


上一篇
# Day 19|心理支持中的 Memory:記住,不等於把一切留下來
下一篇
#Day 21|Retrieval:找到最像的資料,不代表找到最該使用的資料
系列文
《從心出發:30 天打造一套具 RAG、心理支持決策、安全治理與 ARCI 自適應能力的 AI 心理支持平台》25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言