资讯动态

Craft 中 `/compact` 命令的端到端设计:基于 opencode summarize 的按需上下文压缩实现指南

发布时间:2026/9/10 13:43:25 来源:尧图企业网站定制
Craft 中/compact命令的端到端设计基于 opencode summarize 的按需上下文压缩实现指南【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer本文以 compact-command.md 设计文档为骨架结合 danswerOnyx仓库中 Craft 构建会话Build Session的真实源码完整梳理/compact斜杠命令从为什么需要、opencode 协议契约、前后端实现策略、UX 到测试验证的完整链路。读者将掌握Craft 会话中上下文窗口压缩的现有机制CompactionPacket→CompactionMarker如何复用kindcompact交互式 turn 如何在既有 executor/streaming 管道中落地以及如何把一条纯 POST 的 summarize 请求改造成可流式、可持久化的真实命令。一、背景为什么需要手动触发压缩Craft 会话的上下文窗口由 opencode 负责管理其默认策略是自动压缩当上下文接近限制本文档环境下 Opus 4.8 约为 1M token时opencode 会自动生成摘要并压缩历史。这套自动压缩的能力在 danswer 中已经通过上下文窗口压缩功能完整接入——即输入栏上方的 context ring 指示器 CompactionPacket→CompactionMarker的渲染链路。但自动压缩存在明显的体验缺口用户无法决定压缩时机。当用户希望在执行大任务之前、或者 ring 已经变为琥珀色接近上限时主动回收上下文没有任何入口可用。/compact命令正是为此而生——它是一个面向用户的斜杠命令按需调用 opencode 的summarize端点并把压缩结果作为转录transcript中已有的压缩标记compaction marker呈现出来。关键设计定位这是手动触发而非新造一套压缩渲染管线。渲染路径已经建好且无需改动——本工作只负责按需触发压缩 把它做成一级 picker 命令。二、opencode 压缩协议契约v1.15.7 已验证2.1 端点与请求体/compact底层调用的 opencode 端点为POST /session/{sessionID}/summarize请求体为{ providerID: …, modelID: …, auto: false }其中auto: false明确表示这是一次手动压缩请求区别于 opencode 内部自动触发的压缩。2.2 事件流与状态机调用summarize之后opencode 会生成一条 assistant 消息其info.summary true标记该消息为摘要消息发布session.compacted {sessionID}事件进入空闲状态go idle。danswer 的翻译器translator已经对这套事件做好了处理收到session.compacted时发出CompactionPacket抑制summary:true消息的可见文本摘要正文不会作为普通消息渲染出来通过 REST 把摘要文本附加到CompactionPacket上session.idle通过_emit_terminator终止流与普通 turn 完全一致。因此consume → translate → persist消费事件 → 翻译 → 持久化整条路径可以原样复用零改动。2.3 核心细节只调用 summarize 不会显示任何东西这是整个设计中最重要的一个 subtlety单独发一个POST /summarize是fire-and-forget界面上什么都不会出现。压缩标记marker只有在session.compacted/summary:true事件流经translate_opencode_event→CompactionPacket→ 持久化/流式推送之后才会渲染。所以/compact必须作为一个真正的流式 turn运行而不是一条丢弃结果的 POST。这直接决定了后端要以kindcompact的完整交互式 turn 来实现复用 cache-turn 后台 runner attach/resume 机制获得与 send-message 相同的可靠性marker 实时流式呈现刷新后依然持久可见。三、已锁定的产品决策文档记录了几个与用户确认过的决策实现时必须遵守决策项结论命令名/compact无前导点号后端运行方式完整交互式 turnkindcompact复用 cache-turn 后台 runner attach/resume 机制Picker 分组专属Commands分组显示在 Skills / Apps 之上定位真实用户可见命令而非测试钩子同时它也是端到端演练压缩能力的按需入口3.1 Turn 的形状一个 compact turn 有以下特殊之处没有用户 prompt不创建任何用户消息行user message row只产出压缩标记compaction marker附带一个间接效果摘要消息的 token 统计会产生一个更新的ContextUsagePacket从而把 ring 刷新为压缩后的数值。3.2 模型解析summarize需要providerID/modelID。方案是直接使用会话上存储的agent_provider/agent_model——这两个字段已经贯穿到_streaming.yield_sandbox_events中。如果两者都为 null遗留行数据则命令不可用而不是去猜测模型。四、前端实现Picker 命令与触发链路4.1 现状Picker 模型与匹配逻辑前端 picker 的模型与匹配逻辑集中在 web/src/lib/skills/picker.ts目前已定义PickerSkillkind: skill、PickerAppkind: app、PickerMcpServerkind: mcp三种条目PickerEntry联合类型PickerSections结构skills/apps/mcpServers三个数组toPickerSections从 SkillsList、external apps、MCP servers 构造分组并做过滤与排序匹配与扁平化工具函数filterPickerSections、flattenSections、detectSlashTrigger、matchesQuery。4.2 新增PickerCommand条目按设计文档需要在 picker 中新增第四种条目interface PickerCommand { kind: command; slug: string; name: string; description: string; }具体步骤扩展联合类型把PickerCommand加入PickerEntry联合扩展分组给PickerSections增加commands数组过滤在filterPickerSections中纳入 commands复用现有matchesQuery排序把 commands 放在flattenSections的最前面——这样才能保证键盘导航的索引与渲染顺序一致种子数据内置一个静态的compact命令无需服务端拉取。4.3 渲染 Commands 分组在EntryPickerPopover中把 Commands 分组渲染在 Skills / Apps 之上并使用与 marker 相同的SvgFold图标见 web/src/app/craft/components/CompactionMarker.tsx 第 30 行SvgFold的使用让它在视觉上读起来是一个动作而非技能。4.4 选择即动作路由到onCompact在 web/src/app/craft/components/CraftInputBar.tsx 中picker 的onSelect需要按条目类型分支普通技能/应用走现有addEntry生成 chipentry.kind command entry.slug compact不插入任何文本直接调用从ChatPanel传入的新 proponCompact。同样需要分支的路径包括粘贴路径paste path以及可选菜单中的buildEntryMenuItems。当前的接线点包括useSlashPicker({ onSelect: addEntry })、activeEntrieschips、handleSubmit的前缀逻辑。4.5 可用性 gating命令在以下情况下不可用隐藏或禁用并带 tooltip尚无opencode_session_id首轮 turn 之前已有 turn 正在运行isRunning模型未知。这样保证命令永远不会产生令人困惑的空操作no-op。4.6 触发与附加复用useBuildStreamingChatPanel.onCompact的流程设计为onCompact → POST /build/sessions/{id}/compact # 返回与 send-message 相同的 turn 形状 → 注册 active turn → 通过既有 useBuildStreaming 附加到 GET .../turns/{turn_id}/events → marker 实时流式呈现并持久化前端不需要任何新的流式代码——web/src/app/craft/hooks/useBuildStreaming.ts 与 web/src/app/craft/hooks/useBuildSessionStore.ts 负责 active-turn 注册、appendStreamItem与CompactionMarker渲染这些全部复用。运行期间展示一个瞬态的 Compacting context… 提示复用 running/interrupt 的既有 affordance在 terminator 到来时清除。五、后端实现kindcompact的交互式 turn5.1 现状交互式 turn 的基础设施后端交互式 turn 的核心设施已经存在均位于 backend/onyx/server/features/build 下Turn 状态interactive_turns/state.py 定义了InteractiveTurndataclassturn_id、session_id、user_id、prompt、status、turn_index、attachments等、InteractiveTurnStatus枚举QUEUED / RUNNING / SUCCEEDED / FAILED / CANCELLED、create_interactive_turn、acquire_active_turn_lockRedis 锁lease 60 秒等后台执行器interactive_turns/executor.py 提供start_interactive_turn_runner与_drive_interactive_turn让 turn 脱离浏览器响应流运行刷新/重连不中断事件附加interactive_turns/api.py 提供GET .../turns/{turn_id}/events创建路由session/messages.py 的POST /build/sessions/{id}/send-message内部依次执行acquire_active_turn_lock→create_interactive_turn→start_interactive_turn_runner驱动链路session/streaming.py 的BuildStreamingState与yield_sandbox_events其中已导入并使用CompactionPacket、ContextUsagePacket等 packet 类型。5.2 步骤 1Turn 模型增加kind字段在InteractiveTurnstate.py与create_interactive_turn中新增kind: Literal[prompt, compact] prompt并同步到_save_turn/_load_turn的序列化逻辑保证 turn 在 cache 中往返后kind不丢失。5.3 步骤 2新增 compact 路由在 session/messages.py 中新增POST /build/sessions/{id}/compact镜像 send-message 的创建路径但kindcompact空 prompt使用下一个turn_indexcount_user_messages的结果与 send-message 一致返回与 send-message 相同的响应形状InteractiveTurnResponse前端可以无缝复用 attach 逻辑复用 active-turn 锁 runner 启动当opencode_session_id或模型缺失时直接拒绝。5.4 步骤 3驱动链路穿透kind把kind从executor._drive_interactive_turn穿透到SessionManager.yield_sandbox_eventssession/manager.py再到_streaming.yield_sandbox_eventssession/streaming.py。当kind compact时调用新的serve_client.compact()而非send_message跳过用户消息持久化不写 user message row。5.5 步骤 4serve_client.compact()生成器在 backend/onyx/server/features/build/sandbox/opencode/serve_client.py 中新增compact()生成器镜像现有send_message第 1329 行起的结构订阅 pod 事件总线self._event_bus.subscribe(...)等待/event流就绪_wait_for_event_stream_ready避免 POST 后错过首屏事件POST /session/{id}/summarizebody 为{providerID, modelID, auto: false}——通过新增的_post_summarize作为_post_prompt_async的兄弟函数发送通过_consume_from_bus消费事件经translate_opencode_event翻译直到 terminator。无需改动 translatorsession.compacted→CompactionPacket、summary:true文本抑制、session.idle终止这三件事已经工作正常。参考send_message的实现compact()也应正确处理浏览器断连GeneratorExit→POST /abort与超时逻辑以获得与普通 turn 一致的生命周期语义。5.6 明确无需改动的部分以下模块已构建完成且对自动/手动压缩的事件处理完全一致不需要任何修改translate_opencode_event事件翻译CompactionPacket压缩数据包CompactionMarker前端压缩标记渲染含 View summary 折叠披露持久化与重载convertMessagesToStreamItems六、用户体验设计/compact的交互体验定义如下在斜杠弹层顶部出现专属Commands分组SvgFold图标标签 Compact context描述 Summarize earlier context to free up space输入/comp…即可匹配选中后立即执行不产生 chip、不插入文本短暂显示 Compacting context… 状态然后转录中会出现低调的压缩分隔线带 View summary 披露控件ring 指示器降至压缩后的数值首轮 turn 之前、turn 运行中、模型未知时命令不可用——保证它永远不会变成令人困惑的空操作。七、测试策略7.1 后端测试serve_client.compact()验证其向/session/{id}/summarize发送正确的{providerID, modelID, auto}请求体并在构造的session.compacted事件上产出翻译后的CompactionPacket——扩展现有test_translate_opencode_event的 fixtures压缩/抑制翻译已有覆盖路由测试验证kindcompact在_streaming.yield_sandbox_events中驱动compact()而非send_message。7.2 前端单元测试web/src/lib/skills/picker.ts命令出现在 sections 中、按/comp过滤、在flattenSections中排首位CraftInputBarcommand选择路由到onCompact而非addEntry可用性 gating 逻辑。7.3 Playwright 端到端可选仅当 FE↔backend 的 attach 需要端到端覆盖时才加打开/→ Commands 分组显示 Compact → 选中 → 断言 compact turn 启动且压缩标记渲染。否则以上单测已足够。八、源码索引速查关注点仓库路径本文档docs/craft/features/compact-command.mdPicker 模型与匹配web/src/lib/skills/picker.ts输入栏接线web/src/app/craft/components/CraftInputBar.tsx压缩标记渲染web/src/app/craft/components/CompactionMarker.tsx前端流式附加web/src/app/craft/hooks/useBuildStreaming.ts、web/src/app/craft/hooks/useBuildSessionStore.tsTurn 状态模型backend/onyx/server/features/build/interactive_turns/state.py后台执行器backend/onyx/server/features/build/interactive_turns/executor.py创建/附加路由backend/onyx/server/features/build/session/messages.py、backend/onyx/server/features/build/interactive_turns/api.py流式驱动管线backend/onyx/server/features/build/session/streaming.pyopencode serve 客户端backend/onyx/server/features/build/sandbox/opencode/serve_client.py九、小结/compact是一个以小博大的功能渲染、翻译、持久化等核心链路全部复用既有上下文压缩机制新增的工作集中在触发一侧——前端新增一个PickerCommand条目并路由到onCompact后端新增一个kindcompact的交互式 turn 与POST /build/sessions/{id}/compact路由以及serve_client.compact()这一镜像send_message的生成器。整条链路的关键约束是必须以流式 turn 运行而非 fire-and-forget POST否则 marker 永远不会渲染。这一设计同时让/compact成为端到端演练压缩能力的按需入口兼具功能价值与可测试性。【免费下载链接】danswerOpen Source AI Platform - AI Chat with advanced features that works with every LLM项目地址: https://gitcode.com/GitHub_Trending/da/danswer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价