28ca43ccb2
将 note、pdd、prd、roadmap 四类文档分别归入 `design/` 下对应子目录中,并新增 `.gitkeep` 占位文件
472 lines
20 KiB
Markdown
472 lines
20 KiB
Markdown
# Phase 16 — 摘要自动生成
|
||
|
||
## 背景与目标
|
||
|
||
### 问题
|
||
|
||
长对话场景中,用户与 Agent 交互 30+ 轮后,消息历史长度远超模型上下文窗口,导致:
|
||
|
||
- LLM 被迫丢弃早期上下文,对话丧失连贯性
|
||
- 开发者需要手动管理摘要逻辑(调 LLM → 写 SessionMemory → 注入 FocusedConfig)
|
||
- v0.2 的 `FocusedConfig.summary_override` 消费端已就绪,但生产端是空的——用户只能手动设字符串
|
||
|
||
### 目标
|
||
|
||
闭环长对话的"上下文压缩"链路:
|
||
|
||
```
|
||
[消费端 v0.2 已就绪] FocusedConfig.summary_override → filter_focused() 注入摘要
|
||
[生产端 v0.3 补齐] token 水位检测 → LLM 摘要生成 → 自动写入 summary_override
|
||
```
|
||
|
||
### 成功标准
|
||
|
||
1. 开发者只需在 `AgentBuilder` 中链式调用 `.summary_config(cfg)` 即可启用
|
||
2. 长对话(如 30+ 轮或 token 水位超过 `max_context_tokens * trigger_token_ratio`)自动触发摘要,下轮 `load_messages()` 返回值包含 `[上下文摘要] {summary}`
|
||
3. 摘要生成不改变 `submit_turn` 行为(opt-in、静默失败、不阻断主流程)
|
||
4. 零新外部依赖
|
||
|
||
---
|
||
|
||
## 需求分析
|
||
|
||
### 功能需求
|
||
|
||
| # | 需求 | 优先级 | 说明 |
|
||
|---|------|--------|------|
|
||
| F1 | `SummaryConfig` 配置结构体 | P0 | `trigger_token_ratio` / `max_context_tokens` / `summary_prompt` / `debounce_turns` / `summary_model` / `max_tool_result_chars` |
|
||
| F2 | Token 水位自动检测 | P0 | 每轮 OnTurnEnd 之后检查 `cost_so_far` 是否超过 `max * ratio` |
|
||
| F3 | LLM 摘要生成 | P0 | 复用 `self.bundle.provider`,单次无工具 LLM 调用 |
|
||
| F4 | 摘要写入 FocusedConfig | P0 | 更新 `summary_override` + `slot.save()` 持久化 |
|
||
| F5 | 摘要全局快照 | P0 | 同步写入 `SessionMemory::set("conversation_summary", summary)` |
|
||
| F6 | 防抖机制 | P0 | 两次摘要之间至少间隔 `debounce_turns` 轮(默认 3) |
|
||
| F7 | 流式路径对称支持 | P0 | `finalize_turn` 中插入相同检查点 |
|
||
| F8 | 公开 API:`get_conversation_summary()` | P1 | 读取 SessionMemory 中最新的摘要 |
|
||
|
||
### 非功能需求
|
||
|
||
| # | 需求 | 指标 |
|
||
|---|------|------|
|
||
| N1 | 零外部依赖 | 不修改 `Cargo.toml` |
|
||
| N2 | 向后兼容 | 未设置 `SummaryConfig` 时行为零变化 |
|
||
| N3 | 静默失败 | 摘要 LLM 调用失败不阻断 `submit_turn` |
|
||
| N4 | 摘要延迟 | 首次摘要 LLM 调用 ≤ 3s(依赖 provider 响应速度) |
|
||
|
||
---
|
||
|
||
## 当前状态分析
|
||
|
||
### 消费端已就绪
|
||
|
||
`FocusedConfig.summary_override`(`src/agent/context.rs`)已在 Phase 10 实现,当前消费逻辑:
|
||
|
||
```
|
||
filter_focused() → 若 cfg.summary_override = Some(text) → 在消息列表末尾插入
|
||
Message::system("[上下文摘要] {text}")
|
||
```
|
||
|
||
文档注释明确标注:`// v0.3 将支持 Hook 驱动的自动摘要生成`
|
||
|
||
### 代码上下文
|
||
|
||
| 模块 | 文件 | 状态 | 与 Phase 16 的关系 |
|
||
|------|------|------|-------------------|
|
||
| FocusedConfig | `agent/context.rs` | ✅ 消费端 | 摘要写入 `summary_override` 即生效 |
|
||
| OnTurnEnd | `agent/session.rs:345` | ✅ 触发点 | 摘要检查点插在此之后 |
|
||
| CostTracker | `llm/cycle/usage.rs` | ✅ 累计 token | 水位检测的数据源 |
|
||
| SessionMemory | `agent/session_memory.rs` | ✅ set/get | 摘要全局快照存储 |
|
||
| AgentBuilder | `agent/builder.rs` | ✅ 链式构造 | 新增 `.summary_config()` |
|
||
| AgentConfig | `agent/runtime.rs` | ✅ 配置结构 | 新增 `summary_config` 字段 |
|
||
| LlmProvider | `llm/provider.rs` | ✅ Trait | 摘要 LLM 调用复用 provider |
|
||
| ContextSlot.save | `agent/context.rs:251` | ✅ 持久化 | 更新 config 后写回 |
|
||
|
||
---
|
||
|
||
## 可选方案推演
|
||
|
||
### 方案 A(推荐):内联检查点
|
||
|
||
**做法**:在 `submit_turn` 和 `finalize_turn` 中,OnTurnEnd 触发之后、`turn_index` 递增之前,插入以下逻辑:
|
||
|
||
```rust
|
||
if let Some(ref sc) = self.bundle.config.summary_config
|
||
&& self.should_summarize(sc)
|
||
{
|
||
// clone 所需数据(释放 &self 借用)
|
||
let provider = Arc::clone(&self.bundle.provider);
|
||
let messages = self.slots.get(&self.current_slot_id)
|
||
.map(|s| s.messages.clone()).unwrap_or_default();
|
||
let prompt = sc.summary_prompt.clone();
|
||
let model = sc.summary_model.clone();
|
||
|
||
// 调关联函数(不持有 &self)
|
||
match Self::generate_summary(&provider, &messages, &prompt, model.as_deref(), sc.max_tool_result_chars).await {
|
||
Ok(text) => {
|
||
// 更新 FocusedConfig + 持久化
|
||
if let Some(slot) = self.slots.get_mut(&self.current_slot_id) {
|
||
if let SlotMode::Focused(ref mut cfg) = slot.config.mode {
|
||
cfg.summary_override = Some(text.clone());
|
||
}
|
||
let _ = slot.save(&*self.resolve_store()).await;
|
||
}
|
||
// 全局快照
|
||
let _ = self.session_memory.set("conversation_summary", &text).await;
|
||
self.last_summary_turn = self.turn_index;
|
||
}
|
||
Err(e) => tracing::error!("摘要自动生成失败 (turn={}): {}", self.turn_index, e),
|
||
}
|
||
}
|
||
```
|
||
|
||
**优点**:
|
||
- 代码路径最短最清晰(~50 行核心逻辑)
|
||
- 直接访问所有需要的数据(`cost_so_far`、`slots`、`provider`、`session_memory`)
|
||
- 流式和同步版本统一处理
|
||
- `Option<SummaryConfig>` 本身已提供 opt-in/opt-out
|
||
- 不改变 Hook 系统签名
|
||
|
||
**缺点**:
|
||
- 摘要 LLM 调用延长了 `submit_turn` 的延迟(约 1-3s)
|
||
- 违反"Hook 哲学"(但 `Option` 配置已足够提供可插拔性)
|
||
|
||
### 方案 B(否决):扩展 HookContext
|
||
|
||
**做法**:在 `HookContext` 中增加 `messages: &[Message]`、`usage: &Usage`、`provider: Arc<dyn LlmProvider>` 字段,让 OnTurnEnd Hook 实现者自行做摘要。
|
||
|
||
**否决原因**:
|
||
1. **生命周期冲突**:`&[Message]` 要求 Hook 调用点消息已就绪但未被 `&mut self` 借用——在 `submit_turn` 第 7 步(slot.save)后消息已就绪,但 to pass `&[Message]` 到 HookContext 需要与 `slot.messages` 的不可变引用共存,而 `submit_turn` 流程中后续步骤需要 `&mut self`
|
||
2. **流式路径不可行**:`finalize_turn` 触发 OnTurnEnd 时 cycle 已销毁,消息只能从 slot 获取,但 slot 在 `append_messages` 后已被 `&mut` 借用
|
||
3. **`Arc<dyn LlmProvider>` 的 `'static` 需求**与 `HookContext<'a>` 的设计冲突
|
||
|
||
### 方案 C(否决):后台 spawn 异步摘要
|
||
|
||
**做法**:token 检测通过后,`tokio::spawn` 后台任务做摘要生成和写入。
|
||
|
||
**否决原因**:
|
||
1. **写入冲突**:后台任务无法获取 `&mut AgentSession` 来更新 slot config
|
||
2. **绕过方式增加复杂度**:后台任务需要直接操作 `Arc<dyn MemoryStore>` 的原始 key(`slot_config:{session_id}:{slot_id}`),绕过了 `ContextSlot::save()` 的封装
|
||
3. **并发风险**:如果前一轮摘要尚未完成而下一轮 `finalize_turn` 又触发,可能导致覆盖写
|
||
|
||
---
|
||
|
||
## 推荐方案(内联检查点)
|
||
|
||
### 架构图
|
||
|
||
```
|
||
submit_turn(user_input)
|
||
│
|
||
├─ 1. Readonly 检查
|
||
├─ 2. OnTurnStart hook
|
||
├─ 3. slot.load_messages() ← 历史摘要已注入(如有)
|
||
├─ 4. LlmCycle.submit_with_tools
|
||
├─ 5. cost_so_far.add(usage)
|
||
├─ 6. slot.append_messages + save
|
||
├─ 7. OnTurnEnd hook ← 纯通知,不做摘要
|
||
│
|
||
├─ [8.5] 摘要检查点 ──────────────────────────────┐
|
||
│ ├─ should_summarize(cfg) │
|
||
│ │ ├─ cost_so_far >= max * ratio? │
|
||
│ │ └─ turn - last_summary >= debounce? │
|
||
│ │ │
|
||
│ ├─ generate_summary() ← 新 LlmCycle │
|
||
│ │ ├─ format_messages_as_text() │
|
||
│ │ ├─ replace {messages} │
|
||
│ │ └─ submit_messages(无 tools) │
|
||
│ │ │
|
||
│ └─ 成功 → 更新 summary_override + save │
|
||
│ → SessionMemory.set() │
|
||
│ → last_summary_turn = turn_index │
|
||
│ (流式路径用 saturating_sub(1) 修正) │
|
||
│ 失败 → tracing::error! 静默 │
|
||
│ │
|
||
├─ 9. turn_index++
|
||
└─ 10. return Ok(response)
|
||
```
|
||
|
||
### 模块划分
|
||
|
||
**新增文件**:`src/agent/summary.rs`
|
||
|
||
```
|
||
src/agent/summary.rs
|
||
├── SummaryConfig // 摘要自动生成配置
|
||
├── format_messages_as_text() // 消息 → 纯文本(简洁版)
|
||
└── DEFAULT_SUMMARY_PROMPT // 默认 prompt 模板
|
||
```
|
||
|
||
**修改文件**:
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `agent/runtime.rs` | `AgentConfig` 新增 `summary_config: Option<SummaryConfig>` |
|
||
| `agent/builder.rs` | 新增 `summary_config(cfg)` 方法 |
|
||
| `agent/session.rs` | 新增 `last_summary_turn` 字段;`submit_turn` / `finalize_turn` 插入检查点;关联函数 `generate_summary`;`get_conversation_summary()` |
|
||
| `agent.rs` | `pub mod summary` + re-export |
|
||
|
||
**不变的文件**(无需改动):
|
||
|
||
| 文件 | 原因 |
|
||
|------|------|
|
||
| `llm/hooks.rs` | 内联方案不扩展 HookContext |
|
||
| `llm/cycle.rs` | 摘要调用通过 `submit_messages` 独立使用 |
|
||
| `agent/context.rs` | `FocusedConfig` 消费端已在 Phase 10 就绪 |
|
||
| `Cargo.toml` | 零新外部依赖 |
|
||
|
||
### 核心接口定义
|
||
|
||
**`SummaryConfig`**(`agent/summary.rs`):
|
||
|
||
```rust
|
||
#[derive(Debug, Clone)]
|
||
pub struct SummaryConfig {
|
||
/// Token 水位触发比例(0.0 ~ 1.0)。默认 0.75。
|
||
pub trigger_token_ratio: f64,
|
||
/// 模型上下文窗口大小(token)。默认 32_000,覆盖大部分开源模型。
|
||
/// 修改为匹配实际使用模型的上下文窗口。
|
||
/// ⚠️ 设置为超过模型窗口的值会导致摘要永远不触发。
|
||
pub max_context_tokens: u32,
|
||
/// 摘要 prompt 模板。`{messages}` 将被替换为对话历史文本。
|
||
pub summary_prompt: String,
|
||
/// 防抖轮次。默认 3。
|
||
pub debounce_turns: u32,
|
||
/// 摘要生成模型(None = 沿用主 provider 默认模型)。
|
||
/// 默认 None。推荐设为便宜模型(如 "gpt-4o-mini")以节省成本。
|
||
pub summary_model: Option<String>,
|
||
/// 单个 ToolResult 在格式化时保留的最大字符数。默认 500。
|
||
/// 超过此值从尾部截断。字符级安全(`chars().take()`)。
|
||
pub max_tool_result_chars: usize,
|
||
}
|
||
```
|
||
|
||
**`generate_summary`**(`AgentSession` 关联函数):
|
||
|
||
```rust
|
||
impl AgentSession {
|
||
async fn generate_summary(
|
||
provider: &Arc<dyn LlmProvider>,
|
||
messages: &[Message],
|
||
prompt_template: &str,
|
||
summary_model: Option<&str>,
|
||
max_tool_result_chars: usize,
|
||
) -> Result<String, LlmError> { ... }
|
||
}
|
||
```
|
||
|
||
**`should_summarize`**(`AgentSession` 方法):
|
||
|
||
```rust
|
||
fn should_summarize(&self, cfg: &SummaryConfig) -> bool {
|
||
self.turn_index - self.last_summary_turn >= cfg.debounce_turns
|
||
&& self.cost_so_far.total().total_tokens as f64
|
||
>= cfg.max_context_tokens as f64 * cfg.trigger_token_ratio
|
||
}
|
||
```
|
||
|
||
### 消息格式化(简洁版)
|
||
|
||
`format_messages_as_text` 输出格式:
|
||
|
||
```
|
||
System: 你是一个翻译助手
|
||
User: 把这段英文翻译成中文
|
||
Assistant: 请提供英文文本 [Tool: translate]
|
||
Tool Result: 这是中文翻译
|
||
User: 谢谢
|
||
Assistant: 不客气
|
||
```
|
||
|
||
处理规则:
|
||
- `ContentBlock::Text { text }` → 直接拼接
|
||
- `ContentBlock::ToolUse { name, .. }` → `[Tool: {name}]`(不显示参数 JSON)
|
||
- `Message::ToolResult { content, is_error, tool_call_id }` → `Tool Result [{tool_call_id}]:` / `Tool Error [{tool_call_id}]:`,便于多工具场景下关联调用的返回
|
||
- ToolResult 文本截断到前 `max_tool_result_chars` 个 Unicode 字符(`chars().take(n)`,字符级安全,避免多字节截断)
|
||
- 整段对话若超过 30K 字符,从前面截断(优先保留最新消息)
|
||
- `Message::UserImage { .. }` → `User: [image]`
|
||
- 非 Text block(Image / Audio / File 等)统一标记为 `[{kind}]`
|
||
- 每条消息一行,空行分隔
|
||
|
||
---
|
||
|
||
## 实现计划
|
||
|
||
### Step 16.1 — `SummaryConfig` 结构体
|
||
|
||
**文件**:新增 `src/agent/summary.rs`
|
||
|
||
**内容**:
|
||
- `SummaryConfig` 结构体定义(6 个字段 + doc comments)
|
||
- `DEFAULT_SUMMARY_PROMPT` 常量(约 100 字中文 prompt,含 `{messages}` 占位符)
|
||
- `impl Default for SummaryConfig`
|
||
- `format_messages_as_text(messages: &[Message]) -> String` 辅助函数
|
||
|
||
**验证**:`cargo build`
|
||
|
||
### Step 16.2 — `AgentConfig` 扩展 + `AgentBuilder` 方法
|
||
|
||
**文件**:`src/agent/runtime.rs` + `src/agent/builder.rs`
|
||
|
||
**改动**:
|
||
- `AgentConfig` 新增字段:`pub summary_config: Option<SummaryConfig>`
|
||
- `AgentBuilder` 新增方法:
|
||
```rust
|
||
pub fn summary_config(mut self, cfg: SummaryConfig) -> Self {
|
||
let mut config = self.config.take().unwrap_or_default();
|
||
config.summary_config = Some(cfg);
|
||
self.config = Some(config);
|
||
self
|
||
}
|
||
```
|
||
|
||
**验证**:`AgentBuilder` 单元测试 + `cargo test`
|
||
|
||
### Step 16.3 — `AgentSession` 新字段 + 检查点
|
||
|
||
**文件**:`src/agent/session.rs`
|
||
|
||
**改动**:
|
||
|
||
1. `AgentSession` 新增字段:`last_summary_turn: u32`(初始化 0)
|
||
2. `submit_turn` 中 OnTurnEnd 之后、turn_index 之前插入检查点
|
||
3. `finalize_turn` 中 OnTurnEnd 之后插入对称检查点。注意:流式路径中 `turn_index` 已在 `submit_turn_stream` 中递增,检查点赋值使用 `self.turn_index.saturating_sub(1)`(与 `OnTurnEnd` hook 保持一致)。
|
||
4. 关联函数 `generate_summary`:
|
||
- 接收 `provider`、`messages`、`prompt_template`、`summary_model`、`max_tool_result_chars`
|
||
- 入口守卫:`messages.is_empty()` 时直接返回 `Ok(String::new())`
|
||
- 构造 `LlmCycle`(`max_tokens = Some(1024)`)
|
||
- 调 `cycle.submit_messages(vec![Message::user_text(prompt)], vec![])`
|
||
- 提取 text 返回
|
||
5. 公开 API:`get_conversation_summary()` → `self.session_memory.get("conversation_summary")`
|
||
|
||
**验证**:`cargo build --all-targets`
|
||
|
||
### Step 16.4 — re-export
|
||
|
||
**文件**:`src/agent.rs`
|
||
|
||
**改动**:
|
||
```rust
|
||
pub mod summary;
|
||
pub use summary::SummaryConfig;
|
||
```
|
||
|
||
**验证**:`cargo test --all-targets`
|
||
|
||
### Step 16.5 — 测试
|
||
|
||
| 测试 | 验证点 | 方式 |
|
||
|------|--------|------|
|
||
| `summary_config_defaults` | 默认值正确 | 单元测试 |
|
||
| `summary_not_generated_below_threshold` | token < 阈值时不触发 | `MockProvider` + `Usage::from_input_output(10, 5)` |
|
||
| `summary_generated_above_threshold` | token ≥ 阈值时触发 | 设置 `max_context_tokens=20` + `trigger_token_ratio=0.5` |
|
||
| `summary_debounce_works` | debounce 内不重复 | 强行触发摘要后验证 3 轮内不触发 |
|
||
| `summary_injected_into_focused` | Focused 模式 `load_messages()` 含 `[上下文摘要]` | 检查 Message 内容 |
|
||
| `summary_written_to_session_memory` | `get_session_data("conversation_summary")` 有值 | 集成测试 |
|
||
| `summary_not_injected_in_full_mode` | Full 模式不改 slot config | 验证 `summary_override` 为 None |
|
||
| `summary_failure_does_not_block` | LLM error 不阻断 `submit_turn` | MockProvider 返回错误 |
|
||
| `summary_stream_path` | 流式路径 `finalize_turn` 正确触发 | `submit_turn_stream` 端到端 |
|
||
| `summary_format_messages` | 格式化输出结构正确 | 单元测试验证格式 |
|
||
| `summary_skipped_for_empty_messages` | 空消息不调用 LLM | `generate_summary` 直接返回 `""` |
|
||
| `summary_not_generated_if_max_context_unreachable` | `max_context_tokens` 过大时不触发 | 验证条件不满足 |
|
||
|
||
**验证**:`cargo test --all-targets` 全绿
|
||
|
||
---
|
||
|
||
## 规模估算
|
||
|
||
| 组件 | 纯实现 | 测试 | 合计 |
|
||
|------|--------|------|------|
|
||
| `agent/summary.rs`(SummaryConfig + format_messages + 默认 prompt + 截断守卫) | 60 | 10 | 70 |
|
||
| `agent/runtime.rs`(1 个字段) | 3 | — | 3 |
|
||
| `agent/builder.rs`(1 个方法) | 8 | 3 | 11 |
|
||
| `agent/session.rs`(检查点 + generate_summary + get_conversation_summary) | 40 | 100 | 140 |
|
||
| `agent.rs`(module 声明 + re-export) | 3 | — | 3 |
|
||
| **合计** | **109** | **113** | **~222** |
|
||
|
||
---
|
||
|
||
## 风险评估
|
||
|
||
### 已知风险
|
||
|
||
| 风险 | 概率 | 影响 | 缓解措施 |
|
||
|------|------|------|---------|
|
||
| **同步阻塞**:摘要 LLM 调用延长 submit_turn 延迟 | 高 | 长对话用户多等 1-3s | 对于已达 75% 水位的长对话,用户感知可接受;所有错误静默处理 |
|
||
| **默认模型不兼容**:非 OpenAI 用户未设置 `summary_model` 但默认 `None` 沿用主模型 | 低 | 无影响 | `summary_model` 默认 `None`,沿用主 provider 默认模型,零兼容问题 |
|
||
| **无限循环**:摘要不减少 cost_so_far,每轮都超阈值 | 中 | 频繁 LLM 调用浪费 token | `debounce_turns=3` 强制隔断;`last_summary_turn` 记录确保了间隔。注意:摘要 token 不计入 `cost_so_far`(独立 LlmCycle),阈值不会因摘要本身加速膨胀 |
|
||
| **Focusd 模式摘要位置**:注入为 `system` 消息排在列表末尾 | 低 | LLM 近因效应,摘要可能过度受关注 | 这是 v0.2 消费端的设计选择,Phase 16 不改变 |
|
||
| **SessionMemory key 冲突**:用户手动写入 `"conversation_summary"` 会被覆盖 | 低 | 数据被摘要覆盖 | 文档建议用户自定义 key;或未来使用 namespaced key |
|
||
| **可观测性盲区**:`tracing::warn!` 依赖用户配置了 tracing subscriber | 中 | 失败静默不可见 | 提升到 `tracing::error!` 级别,或加 `eprintln!` fallback |
|
||
|
||
### 不做的事
|
||
|
||
- ❌ 不扩展 `HookContext`
|
||
- ❌ 不引入 `tokio::spawn` 后台摘要
|
||
- ❌ 不做增量摘要(`SummaryStrategy::Incremental` 留待 v0.4)
|
||
- ❌ 不改 `filter_focused()` 的摘要注入位置
|
||
- ❌ 不追踪摘要 token 消耗(`summary_cost_so_far`)
|
||
- ❌ 不添加运行时 prompt 校验(不检查 `{messages}` 是否存在)
|
||
- ❌ 不添加 `MergeStrategy::Summarize` 变体(`context.rs:108` 预占注释将在实施时同步移除或更新)
|
||
|
||
---
|
||
|
||
## 验收标准
|
||
|
||
### 编译与测试
|
||
|
||
| 检查项 | 指标 |
|
||
|--------|------|
|
||
| `cargo build --all-targets` | ✅ 通过 |
|
||
| `cargo test --all-targets` | ✅ 全量通过(预计 335 → ~345,新增 ~10 测试) |
|
||
| `cargo clippy --all-targets -- -D warnings` | ✅ 0 警告 |
|
||
| 测试覆盖范围 | F1-F8、N1-N4 |
|
||
|
||
### 功能验收场景
|
||
|
||
**场景 1:启用摘要后的长对话**
|
||
|
||
```rust
|
||
let session = AgentSession::new(agent, "session-1", Arc::new(
|
||
AgentBuilder::new()
|
||
.provider(provider)
|
||
.tool_registry(registry)
|
||
.hook_executor(executor)
|
||
.summary_config(SummaryConfig {
|
||
max_context_tokens: 100,
|
||
trigger_token_ratio: 0.5,
|
||
debounce_turns: 2,
|
||
..Default::default()
|
||
})
|
||
.build()?
|
||
));
|
||
session.submit_turn("msg 1").await?;
|
||
// ... submit_turn 多次直到 token 超 50 ...
|
||
// 第 N 轮:摘要自动生成
|
||
let summary = session.get_session_data("conversation_summary").await?;
|
||
assert!(summary.is_some());
|
||
// Focused 模式下 load_messages 包含摘要
|
||
```
|
||
|
||
**场景 2:不启用时零影响**
|
||
|
||
```rust
|
||
let session = AgentSession::new(agent, "session-2", bundle); // 无 summary_config
|
||
for i in 0..50 {
|
||
session.submit_turn(&format!("msg {}", i)).await?;
|
||
}
|
||
// 没有摘要产生,没有额外的 LLM 调用
|
||
```
|
||
|
||
---
|
||
|
||
## 参考来源
|
||
|
||
- Phase 10 方案文档:`docs/17-phase10-contextslot.md`(§5 FocusedConfig 消费端设计)
|
||
- Phase 14 方案文档:`docs/20-phase14-document-and-embedding.md`(Provider 复用模式)
|
||
- 当前代码:`src/agent/session.rs`(submit_turn 流程,OnTurnEnd 位置)
|
||
- 当前代码:`src/llm/cycle.rs`(submit_messages 签名)
|
||
- 当前代码:`src/agent/context.rs`(FocusedConfig.summary_override + filter_focused 消费逻辑)
|
||
- 当前代码:`src/agent/runtime.rs`(AgentConfig 结构)
|
||
- 当前代码:`src/agent/builder.rs`(Builder 链式模式)
|
||
- 当前代码:`src/agent/session_memory.rs`(set/get API)
|