Pi 插件解剖|custom-compaction.ts:117 行,自定义上下文压缩

一句话定位

替换 pi 默认的「保留最近 20k tokens」压缩策略,改为用 Gemini Flash 对整个对话做完整总结------117 行,行为替换型事件的范例。

作用(为什么存在)

前三期拆过拦截(permission-gate)、生命周期(git-checkpoint)、完整插件(todo),这期是行为替换 :拦截 session_before_compact 事件,接管压缩行为本身。

默认压缩是「保留最近 20k tokens 的对话」,会丢掉早前上下文;这个插件改成「总结全部消息 → 只留摘要」。更重要的是它展示了两个工程实践:

  1. 换模型做脏活:总结用 Gemini Flash(便宜/快),主对话模型不被浪费
  2. 返回值决定行为 :返回 {compaction} 用自定义结果,返回 undefined 退回默认------压缩永远不会因为插件故障而中断

关键信息

内容
源码位置 examples/extensions/custom-compaction.ts(117 行)
核心 API on("session_before_compact") + ctx.modelRegistry.find/complete + serializeConversation + convertToLlm
插件类型 行为替换型(拦截型事件)

触发流程 / 数据流

plaintext 复制代码
上下文水位线 / 用户 /compact / 溢出恢复
  → session_before_compact 事件(reason: manual | threshold | overflow)
  → 从 preparation 取消息、token 数、保留点、上次摘要
  → modelRegistry.find("google", "gemini-2.5-flash")
      ├─ 无 → return undefined(默认压缩)
      └─ 有 → 序列化全部消息 → complete() 让 Gemini 写摘要
              ├─ 非空 → return { compaction: {...} }(自定义生效)
              ├─ 空 → return undefined(默认)
              └─ 异常 → return undefined(默认)

架构 / 流程

关键代码解读

typescript 复制代码
pi.on("session_before_compact", async (event, ctx) => {
  // ① 取压缩准备数据
  const { messagesToSummarize, turnPrefixMessages, tokensBefore, firstKeptEntryId, previousSummary } = event.preparation;

  // ② 找便宜模型做总结(registerProvider 是前置条件)
  const model = ctx.modelRegistry.find("google", "gemini-2.5-flash");
  if (!model) return;  // ③ 没注册 → 静默退回默认压缩

  // ④ 全部消息序列化成可读文本
  const conversationText = serializeConversation(convertToLlm([...messagesToSummarize, ...turnPrefixMessages]));

  // ⑤ 带 signal 调 LLM:用户取消压缩时中止,不白烧 token
  const response = await ctx.modelRegistry.complete(model, { messages: summaryMessages }, {
    maxTokens: 8192, signal, cacheRetention: "none", sessionId: uuidv7(),
  });

  const summary = /* 提取 text 内容 */;
  if (!summary.trim()) {
    if (!signal.aborted) ctx.ui.notify("summary was empty, using default", "warning");
    return;  // ⑥ 空摘要 → 默认压缩
  }

  // ⑦ 返回 compaction 对象 = 用自定义结果替换默认行为
  return { compaction: { summary, firstKeptEntryId, tokensBefore, usage: response.usage } };
});

亮点 / 踩坑

亮点 1:摘要提示词的设计。 提示词里刻意强调 "The summary will replace the ENTIRE conversation history"------让模型知道写不全 = 永久丢失,逼它覆盖 6 个维度(目标/决策/代码变更/进行中/阻塞/下一步)。压缩场景下,「写全」比「写精」重要。

亮点 2:三条降级链全静默。 模型没注册、摘要为空、LLM 异常------全部 return undefined 退回默认压缩,永不中断会话。「扩展可以增强,但不能破坏」。

踩坑提示:模型必须预先注册。 modelRegistry.find 找不到 Gemini Flash 时整个自定义压缩不生效------这是强依赖,跑 demo 前要先确认模型可用。

边界(Limitations)

边界 表现
模型依赖 gemini-2.5-flash 必须已注册,否则全程走默认
降级链 三条路(没注册/摘要空/异常)都静默退回默认,不中断会话
取消语义 signal.aborted 时空摘要不算错误,不弹 warning
返回语义 {compaction} 替换 / undefined 默认------靠返回值类型决定行为

场景(Scenarios)

  • 能生效:模型已注册 + 压缩触发(threshold/manual/overflow)+ 摘要非空
  • 退回默认:模型未注册 / 摘要为空 / LLM 异常(三类都静默)
  • 用户取消:压缩中 abort → LLM 中止 → 空摘要被 signal 识别为已取消,静默处理

可借鉴的模式

  1. 行为替换型事件 :返回 {compaction} 用自定义、undefined 用默认------与前几期 permission-gate 的 {block}、todo 的 details 同族:返回值决定行为
  2. 降级链兜底:所有失败路径退回默认,绝不中断主流程。
  3. 换模型做脏活:便宜/快模型处理总结类任务,主模型专注对话。
  4. signal 传播:耗时调用必须传 AbortSignal,用户取消时中止,避免白烧 token。
  5. previousSummary 上下文:前一轮摘要带进新摘要,连续压缩不丢历史脉络。

一句话总结

117 行告诉你如何安全地「替换」框架行为:返回值决定替换、降级链保证不破坏、换模型省钱、传 signal 尊重取消。

相关推荐
甲维斯2 分钟前
你们要测的啊!GPT5.6版3D台球
人工智能
海盗12342 分钟前
微软技术周报 · 2026-08-11~2026-08-18
人工智能·microsoft·.net
Ricardo-Yang7 分钟前
无人机单目深度估计测试:ZipDepth 与 AerialMetric
人工智能·算法·机器学习·计算机视觉·无人机
Henry-SAP8 分钟前
具身智能新突破:开源生态与IPO热潮
人工智能·云原生·sap·erp
其实防守也摸鱼11 分钟前
红队技能总结导图:从入门到精通的完整知识体系
开发语言·人工智能·学习·安全·web安全
饼干哥哥26 分钟前
重生之我是导演:爆改成「牛来版」黑客帝国?附3D预演台保姆级教程!
人工智能·后端·深度学习
世界哪有真情41 分钟前
AI 写代码两年多,我发现自己越来越"看不进去"了
前端·后端·ai编程
Bruce_Liuxiaowei42 分钟前
自动化渗透测试——用AI构建自动化渗透测试流程
运维·人工智能·自动化
星栈43 分钟前
被 Rust async 纠正的三个异步认知
前端·后端·rust
AI导出鸭1 小时前
怎么让Claude做表格?AI导出鸭苹果版将Claude输出的Markdown表格或结构化列表智能解析为二维数据,一键导出Excel/Word标准表格。
人工智能·chatgpt·word·excel·ai导出鸭