Day 10 我先把 context 的來源分開,Day 11 再把惡意 SOP 擋在模型外面。做到這裡,我原本很自然地想:既然不可信內容已經過濾,那多放一點歷史訊息,Agent 應該會知道得更多。
問題是,安全的資料也可能過期、重複或與眼前任務無關。對話拖得越長,VPN、Wi-Fi、印表機和先前的工具結果全部堆在一起,真正重要的一句話反而更難被找到。裡面如果還夾著帳號或備用碼,風險也跟著進了模型。
今天我會沿用 Day 11 接好的 Live LLM,但不會拿單次回答宣稱模型「變笨幾%」。真正要固定的是送進模型前的 context contract:哪些保留原文、哪些壓成摘要、哪些直接省略,以及敏感資料有沒有先被移除。
Day 12 的 Live experiment 會先執行 prepare_history_context(),再呼叫 ChatOpenAI。目前輸入框提供最新一則 Prompt,另外五則本機測試歷史則用來穩定重現相關性、摘要與敏感資料移除。整份 chat log 不會直接塞進 messages。
raw history
→ 移除已知敏感資料
→ 保留最新要求
→ 補入較舊但相關的紀錄
→ 壓縮可安全摘要的穩定資料
→ 組成 model-visible blocks
→ Live LLM
現在這五個步驟已經接進 Day 12 Live 實驗。頁面會用六則本機測試歷史建立 context,其中目前 Prompt 由我親自輸入;敏感內容先被移除,剩下的三個 blocks 才會交給真實模型。
長 context 最直接的好處是能放更多資料,但容量不是注意力保證。2023 年的《Lost in the Middle》在多文件問答和 key-value retrieval 實驗中發現,相關資訊位於長 context 中間時,受測模型的表現常比資訊放在開頭或結尾差。Lost in the Middle
這份研究不能直接代表每個新模型,也不能拿來預測這個 Helpdesk Agent 的正確率。不過它提醒我一件很實際的事:不能把「沒有超過 context window」當成「模型一定找得到」。
Anthropic 將 context 變長後,模型回想與使用資訊的能力逐漸下降稱為 context rot,並建議保留能支撐目前任務的高訊號內容,而不是把所有歷史原封不動放進去。Effective context engineering for AI agents
Day 12 的 mock history 有六則訊息:
| 訊息 | 內容 | 最後怎麼處理 |
|---|---|---|
message-01 |
測試裝置是 Windows 11 | 壓成穩定資料摘要。 |
message-02 |
先前的 Wi-Fi 問題已結束 | 與本次問題無關,省略。 |
message-03 |
VPN 曾出現 E401 | 雖然較舊,但和目前問題相關,保留原文。 |
message-04 |
一組假的測試備用碼 | 標成 secret,在排序與摘要前移除。 |
message-05 |
印表機缺紙,已自行解決 | 比 VPN 紀錄新,仍然省略。 |
message-06 |
VPN 又出現 E401,只要排障,不要開單 | 最新要求,保留原文。 |
如果我只取最後三則,message-03 會被排除,模型反而看不到前一次 E401 的處理結果。只看 recency 不夠,只看 relevance 也不行;最新一句「不要開單」同樣不能丟。
這次的 prepare_history_context() 先找出最新的安全訊息,再從較早歷史中選出和 VPN、E401 最相關的一則。最後才用剩餘空間放摘要:
prepared = prepare_history_context(
history,
query_terms=("VPN", "E401"),
max_blocks=3,
)
整理後只有三個 model-visible blocks:
history-summary 裝置是測試用 Windows 11 筆電
message-03 VPN 曾出現 E401,重新登入後暫時恢復
message-06 現在 VPN 又出現 E401,只要排障步驟,不要開工單
message-05 雖然比較新,卻是已經結束的印表機問題,因此不會擠掉較舊的 VPN 紀錄。這個 demo 用固定關鍵字計算相關性,不是完整的 semantic retrieval;換成真實系統後,仍要測試同義詞、否定句和多主題對話。
刪除最舊訊息很簡單,但舊資料可能仍然重要。這次我只把「測試裝置是 Windows 11」保留成摘要;已結束的 Wi-Fi 和印表機問題沒有摘要價值,直接省略。
ModelContextBlock(
block_id="history-summary",
reason="summary",
content="較早紀錄:" + safe_summary,
)
這裡的 safe_summary 是預先寫好的 mock 欄位,不是讓另一個模型即時摘要,所以畫面每次都會相同。正式系統若用模型壓縮歷史,摘要本身也要測:它可能漏掉否定詞、把暫時狀態寫成永久偏好,甚至保留本來應該刪除的敏感資訊。原始歷史的存取與保留期限則是另一個資料治理問題,不能因為 prompt 變短就當作已刪除。
我不希望備用碼先被摘要模型讀過,再從最後的 prompt 遮掉。Day 12 的處理順序是先依程式端 metadata 找出 sensitivity="secret",整則排除後,才進行 relevance 排序與歷史壓縮。
Trace 只記錄「1 則 secret 已移除」,不記錄它的內容。OWASP 對 Sensitive Information Disclosure 的建議也包含資料清理、輸入驗證與限制外部資料來源;只在 system prompt 要求模型不要洩漏,仍可能被繞過。OWASP LLM02:2025 Sensitive Information Disclosure
這個 demo 的敏感標記是固定測試資料,不是通用的 PII detector。真實系統還需要資料分類、使用者隔離、存取控制與刪除政策。Day 12 先守一條較小的界線:已知不需要給模型的資料,就不要把它放進 context。


固定資料區的「Context 精簡」仍保留,用來重現六則歷史如何變成三個 context blocks;文章主畫面則使用上面的 Live 實驗:
context history_input 6_messages
guardrail sensitive_data_minimization dropped
context relevance_selection kept
context history_compaction summarized
context irrelevant_history dropped
context recency_selection kept
context model_context 3_blocks
單元測試會確認最新要求仍在、較舊的 E401 紀錄勝過較新的印表機訊息、mock secret 沒有出現在 context 或摘要。Promptfoo 也新增 context_is_minimized,檢查輸出與 trace 不含那段敏感測試字串。
python -m unittest discover -s tests -v
npx --yes promptfoo@latest eval -c evals/promptfooconfig.yaml --no-progress-bar --no-cache
目前共有 79 個 unit tests 通過,Promptfoo 是 10 passed、0 failed、0 errors。這證明固定的 selection policy 沒有退化,沒有證明任何模型在長 context 下都會答對。
Day 13 會把單一惡意 SOP 擴成 Promptfoo 攻擊案例,測試不同措辭和 retrieval payload 是否會讓既有防線失守。