摘要式上下文压缩(designs/runtime-borrow.md §1;借 Keelson
Advisors/CompactionAdvisor.cs / CompactionBudget.cs,判据又照 Reactor / Codex)。
长会话迟早撞窗。本 filter 按预算比例触发:
挂法::chat filter,注册在 memory filter 之后(看到的是展开后的完整历史)。
与 ChatMemory 的关系(缺省不写回):store 仍是完整历史的唯一真相——AG-UI 的
消息快照、timeline、审计都读它。压缩只改这一次发给模型的视图;为了不每轮重摘,
filter 按会话记一份摘要缓存 {:prefix-n :prefix-hash :summary}:历史前 n 条没变
就直接换成缓存的摘要(零 LLM 调用)。再超阈值时在视图上继续压(旧摘要进新摘要的
素材,逐次合并)。要「有界存储」就给 :write-back-store:摘要后用压缩结果改写 store
(mem-clear + mem-add,消息 id 保留;前端看到的历史随之变短,代价自负)。
两个可替换挂点(2026-11-08 用户拍板:缺省不改写、不落盘,但逻辑要能整个换掉,
同 Keelson Agui.Bitcask 那样由外部库提供实现):
:store:CompactionStore 协议,存每会话的可持久部分 {:cache :failures}
(摘要缓存 + 摘要器连续失败数)。缺省 in-memory-store(LRU,进程停即丢)。
外部实现可落盘,重启后不必重摘。缓存对不上(前缀 hash 变了)只会触发重摘,不会出错。:write-back:(fn [conversation-id compacted original]),摘要成功后调用,决定怎么
处置历史(改写 / 归档原文再改写 / 只记审计…)。缺省 nil = 不写回;
:write-back-store 是它的语法糖(= (store-write-back store))。摘要式上下文压缩(designs/runtime-borrow.md §1;借 Keelson
`Advisors/CompactionAdvisor.cs` / `CompactionBudget.cs`,判据又照 Reactor / Codex)。
长会话迟早撞窗。本 filter 按预算比例触发:
1. **微压缩**:更早的工具结果正文换成占位符、保留的截到上限(不调模型);
2. 仍超 → 在**安全切点**(不劈开 assistant 调用与它的结果)把前缀换成一条**摘要**;
3. 没有摘要器 / 无可摘前缀 / 摘要失败 → **丢最老**(按安全段丢);
4. provider 报**上下文超长** → 强压后重发一次。
**挂法**:`:chat` filter,注册在 memory filter **之后**(看到的是展开后的完整历史)。
**与 ChatMemory 的关系(缺省不写回)**:store 仍是完整历史的唯一真相——AG-UI 的
消息快照、timeline、审计都读它。压缩只改**这一次发给模型的视图**;为了不每轮重摘,
filter 按会话记一份**摘要缓存** `{:prefix-n :prefix-hash :summary}`:历史前 n 条没变
就直接换成缓存的摘要(零 LLM 调用)。再超阈值时在视图上继续压(旧摘要进新摘要的
素材,逐次合并)。要「有界存储」就给 `:write-back-store`:摘要后用压缩结果改写 store
(`mem-clear` + `mem-add`,消息 id 保留;前端看到的历史随之变短,代价自负)。
**两个可替换挂点**(2026-11-08 用户拍板:缺省不改写、不落盘,但逻辑要能整个换掉,
同 Keelson `Agui.Bitcask` 那样由外部库提供实现):
- `:store`:`CompactionStore` 协议,存每会话的**可持久部分** `{:cache :failures}`
(摘要缓存 + 摘要器连续失败数)。缺省 `in-memory-store`(LRU,进程停即丢)。
外部实现可落盘,重启后不必重摘。缓存对不上(前缀 hash 变了)只会触发重摘,不会出错。
- `:write-back`:`(fn [conversation-id compacted original])`,摘要成功后调用,决定怎么
处置历史(改写 / 归档原文再改写 / 只记审计…)。缺省 nil = 不写回;
`:write-back-store` 是它的语法糖(= `(store-write-back store)`)。(chat-model-summarizer chat-model
&
{:keys [prompt options]
:or {prompt default-summary-prompt
options {:temperature 0.2}}})用一个 ChatModel 生成摘要:(fn [transcript] -> 文本 | nil)。
opts: :prompt 系统提示(缺省 default-summary-prompt)、:options 额外调用选项
(如 {:temperature 0.2 :max-tokens 2000})。
用一个 ChatModel 生成摘要:`(fn [transcript] -> 文本 | nil)`。
opts: :prompt 系统提示(缺省 `default-summary-prompt`)、:options 额外调用选项
(如 {:temperature 0.2 :max-tokens 2000})。(compact msgs thr {:keys [budget est-opts summarizer]})需要就压:微压缩 → 摘要替换前缀 → 失败 / 没得摘退回丢最老。
参数:msgs、threshold、opts {:budget :est-opts :summarizer(nil = 不摘要)} 返回:{:kind :none|:micro|:summary|:drop :messages :before :after :cut(:summary 时,msgs 里的下标):summary-msg :summary-failed?}
需要就压:微压缩 → 摘要替换前缀 → 失败 / 没得摘退回丢最老。
参数:msgs、threshold、opts {:budget :est-opts :summarizer(nil = 不摘要)}
返回:{:kind :none|:micro|:summary|:drop :messages :before :after
:cut(:summary 时,msgs 里的下标):summary-msg :summary-failed?}(compaction-filter
&
{:keys [budget summarizer overflow? recover-overflow? use-reported-usage?
write-back-store write-back store on-compaction max-conversations]
:or
{recover-overflow? true use-reported-usage? true max-conversations 1024}})上下文压缩 :chat filter。注册在 memory filter 之后。
opts:
default-budget 的键(:context-window 最常用)chat-model-summarizer 现成。
不给 → 只做微压缩 + 丢最老default-overflow?:write-back (store-write-back s))CompactionStore:摘要缓存 / 失败数存哪(可替换挂点,缺省
(in-memory-store {:max-conversations ..}))上下文压缩 `:chat` filter。注册在 memory filter **之后**。
opts:
- :budget 覆盖 `default-budget` 的键(:context-window 最常用)
- :summarizer (fn [transcript] -> 摘要文本 | nil);`chat-model-summarizer` 现成。
不给 → 只做微压缩 + 丢最老
- :overflow? (fn [throwable-或-响应] -> bool),缺省 `default-overflow?`
- :recover-overflow? 超长时压缩重发一次(缺省 true)
- :use-reported-usage? 用 provider 报的 input token 校准(缺省 true)
- :write-back-store ChatMemory:摘要后改写 store(缺省不写回;= `:write-back (store-write-back s)`)
- :write-back (fn [conversation-id compacted original]):摘要后处置历史的策略
(可替换挂点,优先于 :write-back-store)
- :store `CompactionStore`:摘要缓存 / 失败数存哪(可替换挂点,缺省
`(in-memory-store {:max-conversations ..})`)
- :on-compaction (fn [{:keys [conversation-id kind before after]}]) 观察
kind ∈ :micro :summary :drop :overflow-recovery
- :max-conversations 摘要缓存最多记几个会话(缺省 1024,按最近使用淘汰;只对缺省 store 生效)每会话压缩状态里可持久的那部分:{:cache {:prefix-n :prefix-hash :summary-msg :head} :failures n}(EDN 可往返)。进程内的瞬时量(上次送出的视图、provider 报的 token)
不经过它。实现方:调用发生在请求路径上,要快;抛异常 filter 会吞掉并当作没有缓存。
每会话压缩状态里**可持久**的那部分:`{:cache {:prefix-n :prefix-hash :summary-msg :head}
:failures n}`(EDN 可往返)。进程内的瞬时量(上次送出的视图、provider 报的 token)
不经过它。实现方:调用发生在请求路径上,要快;抛异常 filter 会吞掉并当作没有缓存。(cs-delete! s conversation-id)删掉该会话的状态。
删掉该会话的状态。
(cs-get s conversation-id)状态 map 或 nil。
状态 map 或 nil。
(cs-put! s conversation-id state)整体替换。
整体替换。
(default-overflow? x)缺省超长判定:异常(含 cause 链)的 ex-data :type 为 :context-overflow-error
(provider 侧契约),或响应 finish-reason 为 :context-window-exceeded(GLM 以响应
而非异常报告)。x 是 Throwable 或 ChatClientResponse / ChatResponse。
缺省超长判定:异常(含 cause 链)的 ex-data `:type` 为 `:context-overflow-error` (provider 侧契约),或响应 finish-reason 为 `:context-window-exceeded`(GLM 以响应 而非异常报告)。x 是 Throwable 或 ChatClientResponse / ChatResponse。
(drop-oldest msgs threshold est-opts)丢最老(摘要失败 / 没得摘时的退路):从地板(system + 摘要)之后按整条安全段往前丢, 直到整包 < threshold;至少留最后一条。
丢最老(摘要失败 / 没得摘时的退路):从地板(system + 摘要)之后按整条安全段往前丢, 直到整包 < threshold;至少留最后一条。
(find-safe-cut msgs
min-cut
{:keys [keep-min-tokens keep-min-text keep-max-tokens]})尾部起始下标(保留 msgs[cut..],msgs[min-cut..cut) 换成摘要):从尾部按整条往前攒, 到 ≥ keep-min-tokens 且 ≥ keep-min-text 条文本消息就收;攒过 keep-max-tokens 还没凑够 ⇒ 停在上一个安全位。没有安全位 ⇒ nil。
尾部起始下标(保留 msgs[cut..],msgs[min-cut..cut) 换成摘要):从尾部按整条往前攒, 到 ≥ keep-min-tokens 且 ≥ keep-min-text 条文本消息就收;攒过 keep-max-tokens 还没凑够 ⇒ 停在上一个安全位。没有安全位 ⇒ nil。
(forbidden-cuts msgs)拆对表:(nth v c) 为真 = 在 c 处下刀(尾部 = msgs[c..])会把某个调用与它的结果劈开。
孤儿(有调用没结果 / 有结果没调用)不设限。返回长度 n+1 的向量。
拆对表:`(nth v c)` 为真 = 在 c 处下刀(尾部 = msgs[c..])会把某个调用与它的结果劈开。 孤儿(有调用没结果 / 有结果没调用)不设限。返回长度 n+1 的向量。
(head-end msgs)开头 system 消息的条数(永远留在头部,不进摘要素材)。
开头 system 消息的条数(永远留在头部,不进摘要素材)。
(in-memory-store)(in-memory-store {:keys [max-conversations] :or {max-conversations 1024}})缺省的压缩状态存储:进程内,最多记 :max-conversations 个会话(缺省 1024,按最近
写入淘汰),进程停即丢。
缺省的压缩状态存储:进程内,最多记 `:max-conversations` 个会话(缺省 1024,按最近 写入淘汰),进程停即丢。
(micro-compact msgs keep-recent max-chars)只动 tool 结果:更早的(不含最近 keep-recent 条)正文换成 cleared-marker(id 与配对不动),
保留下来的超长正文头尾保留截到 max-chars。位置不变;幂等。
只动 tool 结果:更早的(不含最近 keep-recent 条)正文换成 `cleared-marker`(id 与配对不动), 保留下来的超长正文头尾保留截到 max-chars。位置不变;幂等。
(render-transcript msgs)一段消息 → 「角色: 内容」纯文字(摘要素材):调用带名字与参数,结果长文掐头去尾。
一段消息 → 「角色: 内容」纯文字(摘要素材):调用带名字与参数,结果长文掐头去尾。
(safe-cut? msgs c)c 处下刀是否不拆对(0 与 n 不算:那是整条都留 / 都走)。
c 处下刀是否不拆对(0 与 n 不算:那是整条都留 / 都走)。
(store-write-back memory-store)写回策略:用压缩结果改写 ChatMemory(mem-clear + mem-add,消息 id 保留)。
:write-back-store 就是它。
写回策略:用压缩结果改写 ChatMemory(`mem-clear` + `mem-add`,消息 id 保留)。 `:write-back-store` 就是它。
(summary-message summary)摘要消息:user 角色(Anthropic 不收中途 system),带 :compaction/summary 标记。
摘要消息:user 角色(Anthropic 不收中途 system),带 `:compaction/summary` 标记。
(threshold {:keys [context-window trigger-ratio]})触发阈值(token):窗口 × 比例;窗口 < 512k 时比例至少 0.75;地板 64k;地板把阈值 顶出窗口时取窗口的 0.85(仍不许 ≥ 窗口)。窗口 ≤ 0 → 1(永远超、压到最小)。
触发阈值(token):窗口 × 比例;窗口 < 512k 时比例至少 0.75;地板 64k;地板把阈值 顶出窗口时取窗口的 0.85(仍不许 ≥ 窗口)。窗口 ≤ 0 → 1(永远超、压到最小)。
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |