跳转至

会话与自动压缩

消息结构

Conversation.messages 保存的是类型化消息对象(定义在 src/xun/conversation_message.py):每个类自己负责生成 OpenAI 请求参数(completion_param())与落盘格式(to_json()),message_from_json() 是反化工厂。

类型 角色 承载内容
SystemPrompt system 提示词正文、is_compressed(正文是否为摘要)、persist_sections(压缩后仍要保留的段落,AGENTS.md 就在这里)
UserMessage user 文本、已归一化的图片 URL(data 或 http(s))、可选的 response_schema
RawOpenAIMessage assistant / tool 原样保存的 OpenAI 参数字典(模型返回的消息与工具结果)
方法 说明
completion_params() 把整个会话展开成发给模型的 ChatCompletionMessageParam 列表;msg.role 给出角色
set_system_message_content(content, is_compressed=False) 替换或插入第 0 条系统消息(agent.system() 的落点),保留已有段落
set_persistent_section(name, content) 写入或删除一个持久段落(内容留空即删除);agent.sync_project_instructions() 用它注入 AGENTS.md
add_user_message(content, images=None) 追加用户消息;有图片时 content 变成 text/image_url 分块列表,PIL 图像转 data URL
set_response_schema(schema) 把 pydantic schema 的 JSON 附到末条用户消息上(结构化输出的注入点);末条不是用户消息则 ValueError
add_agent_message(msg) 写入 assistant 消息,自动剔除空的 tool_calls: []
add_tool_result(tool_call_id, content) 写入 role="tool" 消息,内容取 content.value_str();序列化失败写成 [Error] Failed to serialize...
pop_last_message_if_user() 取消时回退最后一条用户消息
pop_from_last_user_message(inclusive=True) /retry(不含)与 /revise(含)的实现,返回被弹出的消息
compact_toolcall(keep_max=12) / compacted_toolcall_result(id) 第一级回收,以及按 id 取回被回收的原文
compact(summarize, keep_recent) 第二级摘要压缩;summarize(messages) 返回 None 表示放弃
clear() 清空但保留系统消息,并重置 token、压缩计数与被压缩工具结果的原文
to_history(truncate=False) / content_to_text(content, truncate=False) 界面用的 {role, content} 记录(truncate 截到 1000 字符)
dumps() / dump(path) / loads() / load(path) 序列化;落盘键 tokens_used 为历史兼容保留,消息按 kind(system / user / raw)区分

界面渲染不在这一层:/history 用 to_history(),/render 渲染的是显示层缓存的事件流,见 显示层与 Web 服务。

工具调用保存在 assistant 消息的 tool_calls 数组里(含参数字符串),工具结果单独作为 role="tool" 消息按 tool_call_id 关联。推理内容按 model.reasoning_field 自动探测(reasoning 或 reasoning_content)并原样回写,以便多轮保持。

旧存档读不进来

1.3.0 起的落盘格式给每条消息带上 kind 与 raw;更早版本直接存 OpenAI 参数字典,新代码载入会抛 KeyError: 'raw',只能由旧版本读取。

两种长度度量

度量 来源 用途
total_tokens 模型返回的 usage.total_tokens 自动压缩的唯一判据(压缩后重置为 None,避免立刻再触发)
estimated_message_length() 对 content / text / reasoning / reasoning_content 字段做字符数累加(递归进入嵌套 dict/list) 估算回收比例,并据此决定「继续廉价回收」还是「升级为摘要」

分级压缩

flowchart TD
  S["每轮调用前检查"] --> A["回收旧工具结果"]
  A --> B{"仍超标?"}
  B -->|否| E["本轮结束"]
  B -->|是| C["摘要压缩"]
  C --> D{"仍超标?"}
  D -->|否| E
  D -->|是| A

第一级:回收旧工具结果

超过 keep_max(默认 12)条的旧 role="tool" 消息被就地替换成占位符:

[Compacted, ID: <toolcall_id>. If this content is still needed, call extract_compacted_tool_result with this ID or re-run the tool.]

原文仍留在内存里,模型可用 extract_compacted_tool_result(toolcall_id) 取回——这是「先廉价回收、不丢信息」的关键。只在替换后确实更短时才替换。

第二级:摘要压缩

切点选择保证尾部不以孤立的工具结果开头,且必要时把最后一条用户消息强行保留在尾部(部分服务端要求请求中必须存在用户消息)。摘要么由一个子智能体产出:

Agent.inherit(agent, share_display=False, copy_toolbox=False, copy_command=False)
# 关闭 auto_compact / enable_extensions、开启 auto_confirm
# 待压缩的那批消息直接塞进它的会话,系统消息里的持久段落会被剥掉
# execute(max_iterations=1)

摘要要求以 markdown 分节输出(system_context、overview、key_facts、user_preferences、decisions、pending_tasks、open_questions、tone_context),总量控制在 1024 token 内。

成功后摘要写进 SystemPrompt.content 并把 is_compressed 置真:请求时它被包进一段「Earlier conversation history has been compacted into the summary below」模板(COMPACTED_SYSTEM_PROMPT),persist_sections(含 AGENTS.md)原样追加在后面。同时 total_tokens 置空,compaction_counter.summary_rounds 加一,工具轮次计数归零。

无内容可压缩或摘要失败时,会话保持原样,返回状态分别为 NOTHING_TO_CONDENSE / SUMMARIZE_FAILED。

参数一览

策略参数是 AutoCompactor 的字段(代码层),开关与阈值是配置(用户层):

位置 名称 默认 说明
AutoCompactor toolcall_keep_max 12 保留最近多少条工具结果
AutoCompactor summary_keep_max 16 摘要时保留的尾部消息数
AutoCompactor escalation_rounds 2 连续多少轮廉价回收后升级为摘要;回收后估算仍超过阈值 × ESCALATION_RATIO 时当轮立即升级
AutoCompactor max_retries 2 摘要后仍超标时允许的额外压缩次数;每再压一轮,toolcall_keep_max 与 summary_keep_max 各自减半
模块常量 ESCALATION_RATIO 0.95 估算值需低于阈值的 95% 才算有效进展;它是 compact 模块级常量,不能按 AutoCompactor 实例调整
配置 auto_compact.enabled true 开关
配置 auto_compact.token_threshold 192000 触发阈值

自定义策略只需继承 CompactorAbstract 并实现 auto_compact(agent),然后 Agent(compactor=MyCompactor())。

手动触发

方式 行为
/compact 只做第二级摘要压缩(等价于 compact_conversation)
/compact toolcall 只做第一级回收
agent.conversation.compact_toolcall(keep_max=12) 编程调用第一级(Conversation 的方法),返回 ToolCallCompactResult(reclaimed_count, reclaimed_fraction)
xun.compact.compact_conversation(agent, keep_recent=16) 编程调用第二级(模块函数,未在顶层导出),返回 SummaryCompactResult