feat(agent): 实现摘要自动生成(SummaryConfig + 内联检查点)
SummaryConfig 6 字段配置(trigger_token_ratio / max_context_tokens / summary_prompt / debounce_turns / summary_model / max_tool_result_chars), AgentBuilder 链式 summary_config();AgentSession 在 submit_turn / finalize_turn 的 OnTurnEnd 之后内联检查点:水位 + 防抖(首次不受 约束)→ 独立 LlmCycle 调 submit_messages 生成摘要 → 写入 FocusedConfig.summary_override + slot.save() + SessionMemory 全局快照。 should_summarize 接收 current_turn 参数避免流式路径 turn_index 偏差; format_messages_as_text 简洁版格式化含 30K 整体截断保留最新;空消息 守卫直接返回空串。所有错误静默 tracing::error!,成功路径 tracing::info!。 src/agent/summary.rs 新增 ~240 行;agent/session.rs +428 行(双路径 检查点 + 关联函数 + 测试 + 公开 API)。零新外部依赖。全量 335 → 353 测试(+18 新测试),clippy 0 警告,doc 0 warning。两轮审查 PASS—— 第一轮 PM/SA 修复 11 项,第二轮 Code Reviewer 修复 9 项(含 🔴 generate_summary 空消息 bug + 🟡 5 项 + 💭 2 项)。方案文档 docs/ 22-phase16-summary-auto-generation.md(471 行);roadmap.md 标记 Phase 16 完成 + M12 里程碑达成。
This commit is contained in:
@@ -0,0 +1,471 @@
|
||||
# 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)
|
||||
+31
-16
@@ -1,13 +1,13 @@
|
||||
# AG Core Roadmap
|
||||
|
||||
> 定稿日期:2026-05-11
|
||||
> 最后更新:2026-07-09(Phase 15 完成 + M11 里程碑达成 + Phase 16 方案推演)
|
||||
> 最后更新:2026-07-10(Phase 16 第二轮实施审查 PASS + 9 项问题修复)
|
||||
|
||||
## 愿景
|
||||
|
||||
AG Core 定位为构建 AI 智能体的底层工具箱,通过模块化、可插拔的架构,提供大模型调用、提示词工程、工具系统、记忆检索四大核心能力,支持快速组合出符合业务需求的智能体应用。
|
||||
|
||||
**当前状态**:v0.2.0-rc.1 已打标签。Phase 0-15 全部完成。v0.3.0 实施中,Phase 16-19 共 4 个增量 Phase 待交付。目标是从"LLM 调用工具箱"升级为"能构建多 Agent 协作、RAG、长记忆 Agent 产品的基础系统"。
|
||||
**当前状态**:v0.2.0-rc.1 已打标签。Phase 0-16 全部完成。v0.3.0 实施中,Phase 17-19 共 3 个增量 Phase 待交付。目标是从"LLM 调用工具箱"升级为"能构建多 Agent 协作、RAG、长记忆 Agent 产品的基础系统"。
|
||||
|
||||
---
|
||||
|
||||
@@ -738,17 +738,31 @@ graph BT
|
||||
**目标**:闭环长对话能力。v0.2 的 `inject_summary` 消费端(`FocusedConfig.summary_override`)已就绪,缺的是生产端。
|
||||
|
||||
**交付物**:
|
||||
1. `SummaryConfig` 结构体:`trigger_token_ratio`(默认 0.75) / `max_context_tokens`(默认 128_000) / `summary_prompt`(可自定义,含 `{messages}` 占位符) / `debounce_turns`(默认 3) / `summary_model`(默认 `Some("gpt-4o-mini")`)
|
||||
2. 在 `submit_turn` / `finalize_turn` 中 OnTurnEnd 之后插入**内联检查点**(非 Hook 扩展):`should_summarize`(水位检测 + 防抖)→ `generate_summary`(新 `LlmCycle` + `submit_messages` 无工具调用)→ 更新 `FocusedConfig.summary_override` + `slot.save()` 持久化 + `SessionMemory::set("conversation_summary", summary)` 全局快照
|
||||
3. `AgentBuilder` 扩展:`.summary_config(cfg)` 方法
|
||||
1. `SummaryConfig` 结构体:`trigger_token_ratio`(默认 0.75) / `max_context_tokens`(默认 32_000)/ `summary_prompt`(默认中文 `DEFAULT_SUMMARY_PROMPT` 含 `{messages}`) / `debounce_turns`(默认 3) / `summary_model`(默认 `None` 沿用主模型) / `max_tool_result_chars`(默认 500)
|
||||
2. 在 `submit_turn` / `finalize_turn` 中 OnTurnEnd 之后插入**内联检查点**(非 Hook 扩展):`should_summarize`(水位 + 防抖,首次不受防抖约束)→ `generate_summary` 关联函数(新 `LlmCycle` + `submit_messages` 无工具调用)→ 更新 `FocusedConfig.summary_override` + `slot.save()` 持久化 + `SessionMemory::set("conversation_summary", summary)` 全局快照
|
||||
3. `AgentBuilder` 扩展:`.summary_config(cfg)` 方法(不覆盖整个 `AgentConfig`)
|
||||
4. 公开 API:`get_conversation_summary() -> Result<Option<String>, AgentError>`
|
||||
5. `format_messages_as_text()` 简洁版消息格式化(System/User/Assistant + `[Tool: name]` + `Tool Result [id]:` 截断到 `max_tool_result_chars` 字符)
|
||||
|
||||
**设计决策**:内联于 `submit_turn` 流程而非 Hook 扩展(因为 HookContext 无法携带 `&mut self` 引用更新 slot config,且流式路径的 `finalize_turn` 中 `cycle` 已销毁)。`Option<SummaryConfig>` 的 opt-in 机制已足够提供可插拔性,不改变 Hook 系统签名。
|
||||
**设计决策**:内联于 `submit_turn` 流程而非 Hook 扩展(因为 HookContext 无法携带 `&mut self` 引用更新 slot config,且流式路径的 `finalize_turn` 中 `cycle` 已销毁)。`Option<SummaryConfig>` 的 opt-in 机制已足够提供可插拔性,不改变 Hook 系统签名。流式路径中 `submit_turn_stream` 已将 `turn_index` 提前 ++1,检查点使用 `saturating_sub(1)` 修正。
|
||||
|
||||
**依赖**:无(`submit_turn` 流程 + `CostTracker` + `SessionMemory` + `LlmProvider` 均已就绪)
|
||||
**优先级**:P0
|
||||
**预估规模**:约 220 行(含测试约 100 行)
|
||||
**状态**:⏳ 待实施(方案文档已就绪:`docs/22-phase16-summary-auto-generation.md`)
|
||||
**预估规模**:约 220 行(实际约 250 行,含 11 个内联测试)
|
||||
**方案文档**:`docs/22-phase16-summary-auto-generation.md`(471 行,经 PM/SA 审查 11 项修复 + 实施后第二轮审查 9 项修复全部完成)
|
||||
**状态**:✅ Phase 16 全部交付物已完成(含实施后 PM/SA/Code Reviewer 第二轮审查 PASS)
|
||||
|
||||
**实施后审查修复记录**(共 9 项):
|
||||
- 🔴 B1:`generate_summary` 调用 `submit_messages(Vec::new(), vec![])` 发送空消息列表 → 移除 `with_messages()`,直接 `submit_messages(vec![Message::user_text(prompt)], vec![])`
|
||||
- 🟡 W4:`should_summarize` 使用 `self.turn_index` 而非 `current_turn` 参数 → 改签名接收 `current_turn`,流式路径防抖准确
|
||||
- 🟡 W2:`summary_model` 硬编码 `unwrap_or("gpt-4o")` → 改为条件赋值,`None` 时沿用 `CycleConfig::default()`
|
||||
- 🟡 W5:Full 模式 `slot.save()` 无谓调用 → 移入 `SlotMode::Focused` 分支内
|
||||
- 🟡 W3:`format_messages_as_text` 缺 30K 整体截断 → 新增 `MAX_TOTAL_CHARS=30_000` + `truncate_total_chars`,优先保留最新
|
||||
- 🟡 W6:摘要成功无日志 → 添加 `tracing::info!(turn, summary_len, "摘要自动生成成功")`
|
||||
- 🟡 W1/W7:缺 3 个测试 → 新增 `format_total_charset_truncation_keeps_recent` / `summary_written_to_focused_slot_config` / `summary_skipped_for_empty_messages` / `summary_not_generated_if_max_context_unreachable`
|
||||
- 💭 `context.rs:78` 过时注释("v0.3 将支持 Hook 驱动")→ 更新为"v0.3 Phase 16 起 AgentBuilder 内联检查点自动生成摘要"
|
||||
|
||||
**第二轮审查门禁**:PASS(0 🔴 阻塞)。`cargo test --all-targets` **353 passed / 0 failed**,clippy 0 警告,doc 0 warning。
|
||||
|
||||
---
|
||||
|
||||
@@ -839,7 +853,7 @@ graph BT
|
||||
P13["<b>Phase 13: 热身清理</b><br/>旧 types 文件删除<br/>ContextSlot fork/merge"]:::done
|
||||
P14["<b>Phase 14: Document + Embedding</b><br/>Document 类型<br/>RecursiveCharacterSplitter<br/>Embedding trait"]:::done
|
||||
P15["<b>Phase 15: 向量存储持久化</b><br/>VectorStore trait<br/>PersistentVectorStore<br/>RagPipeline<br/>19 新测试"]:::done
|
||||
P16["<b>Phase 16: 摘要自动生成</b><br/>SummaryConfig<br/>内联检查点<br/>FocusedConfig 自更新"]:::pending
|
||||
P16["<b>Phase 16: 摘要自动生成</b><br/>SummaryConfig<br/>内联检查点<br/>首次防抖跳过<br/>18 新测试"]:::done
|
||||
P17["<b>Phase 17: 执行引擎</b><br/>SessionManager<br/>会话树<br/>Time-travel Checkpointer"]:::pending
|
||||
P18["<b>Phase 18: 切换与调度</b><br/>Agent Switch<br/>SubAgent Dispatch<br/>dispatch_all 并发控制"]:::pending
|
||||
P19["<b>Phase 19: 知识图谱</b><br/>KnowledgeGraph trait<br/>InMemoryGraph<br/>双通道检索"]:::pending
|
||||
@@ -858,7 +872,7 @@ graph BT
|
||||
| **M9** | Phase 13 | 旧 types 文件删除、`cargo test --all-targets` 全绿、`fork`/`merge` 测试通过 | ✅ 2026-07-08 |
|
||||
| **M10** | Phase 14 | `Document` + `RecursiveCharacterSplitter` 分割结果验证、`MockEmbedding` 测试通过 | ✅ 2026-07-09 |
|
||||
| **M11** | Phase 15 | `PersistentVectorStore` 持久化 roundtrip、`RagPipeline::ingest → retrieve` 端到端验证 | ✅ 2026-07-09 |
|
||||
| **M12** | Phase 16 | 多轮对话后摘要自动写入 SessionMemory、派生 slot 时摘要正确注入 | ⏳ |
|
||||
| **M12** | Phase 16 | 多轮对话后摘要自动写入 SessionMemory、派生 slot 时摘要正确注入 + 第二轮实施审查 PASS | ✅ 2026-07-10 |
|
||||
| **M13** | **Phase 17 (rc.1)** | `SessionManager` 创建/子树/恢复集成测试通过、`Checkpointer` checkpoint/rollback/fork 验证 | ⏳ |
|
||||
| **M14** | Phase 18 | `switch_agent` 热切换验证、`dispatch`/`dispatch_all` 多轮对话 + 结果回传验证 | ⏳ |
|
||||
| **M15** | Phase 19 | `KnowledgeGraph` 实体-关系 CRUD + `get_related` BFS 验证、双通道检索 Hybrid 策略验证 | ⏳ |
|
||||
@@ -896,7 +910,7 @@ graph BT
|
||||
1. **持久化依赖**:`rusqlite` + `bundled` 零外部依赖编译,但 SQLite 不适配所有场景(分布式/高并发写)。`MemoryStore` trait 的抽象层允许下游自行实现 Redis / PostgreSQL 后端
|
||||
2. **ContextSlot 心智负担**:`ContextSlot` 引入了一等抽象的复杂度。建议通过 `AgentBuilder` 默认创建 `"default"` slot,让简单场景无感使用
|
||||
3. **向量检索规模上限**:v0.3 的 `PersistentVectorStore` 全量加载到内存做余弦搜索,适合 ≤10 万条向量。超出此规模需换用专用向量库。v0.4 可以评估引入
|
||||
4. **Scope 蔓延**:v0.3 新增 `agent/summary` `document/` `engine/` `memory/vector_store` 模块,功能覆盖扩展到多 Agent 基础系统。始终保持 trait + reference impl 的边界,业务循环留给上层
|
||||
4. **Scope 蔓延**:v0.3 新增 `agent/summary` `document/` `engine/` `memory/vector_store` 模块,功能覆盖扩展到多 Agent 基础系统。始终保持 trait + reference impl 的边界,业务循环留给上层(Phase 16 已交付 `agent/summary` 摘要生产端 + `format_messages_as_text` 简洁版格式化 + 30K 字符整体截断保留最新;实施后两轮审查 PASS,0 🔴 阻塞)
|
||||
5. **API 稳定性**:v0.3 引入 `Checkpointer`、`SessionManager`、`VectorStore` 等新公开 API,v0.2 已有的 `#[non_exhaustive]` 和 `#[deprecated]` 机制继续沿用
|
||||
6. **Checkpointer 存储效率**:v0.3 使用全量 JSON 序列化存储 checkpoint,每轮对话约几百 KB。`fork` 从历史 checkpoint 创建新 session 时也会复制全量。等实际使用中发现存储瓶颈时再改为增量模式
|
||||
|
||||
@@ -904,10 +918,10 @@ graph BT
|
||||
|
||||
## 下一步行动
|
||||
|
||||
1. **v0.3.0 Phase 16 启动**:摘要自动生成(`SummaryConfig` 配置 + 内联检查点 + `FocusedConfig.summary_override` 自动更新 + `SessionMemory` 快照),基于已有 `CostTracker` 水位检测
|
||||
2. **Phase 16-19 顺次交付**:按依赖关系推进摘要 → 引擎 → 调度 → 知识图谱
|
||||
1. **v0.3.0 Phase 17 启动**:Agent 执行引擎(`SessionManager` 会话树 + `Checkpointer` time-travel),基于 Phase 10 的 `ContextSlot` 持久化层构建
|
||||
2. **Phase 17-19 顺次交付**:按依赖关系推进引擎 → 调度 → 知识图谱
|
||||
3. **示例先行**:每完成一个 Phase 立即创建/更新对应示例,确保 `cargo run --example` 可验证
|
||||
4. **里程碑追踪**:以 M11(Phase 15)为已达成里程碑,逐 Phase 推进 M12-M15
|
||||
4. **里程碑追踪**:以 M12(Phase 16)为已达成里程碑,逐 Phase 推进 M13-M15
|
||||
|
||||
**已完成 / 进行中阶段**:
|
||||
- ✅ Phase 0 Foundation — 全部交付物已完成
|
||||
@@ -924,14 +938,15 @@ graph BT
|
||||
- ✅ **Phase 9 流式体验增强** — `AgentSession::submit_turn_stream` 流式事件序列 + `LlmCycle::submit_with_tools_stream` spawn + mpsc 状态机 + `StreamEvent::ToolExecutionStarted`/`Completed` 新变体 + 9 单元测试 + 2 集成测试(含 `submit_turn_stream_end_to_end` 端到端 mock 验证 + `submit_turn_stream_triggers_turn_hooks` Hook 触发验证),全量 200 → 211;`CycleConfig` 加 `Clone` derive;方案文档 `docs/16-phase9-streaming-experience.md`(821 行)
|
||||
- ✅ **Phase 10 ContextSlot 上下文管理** — `src/agent/context.rs` 新增 `ContextSlot` 核心类型(Full / Focused / Readonly 三种模式,New / Derived / Static 三种来源)+ JSON blob 批次持久化(每 slot 3-4 条 MemoryItem,`slot_config` key 自恢复支持旧版本兼容);`AgentSession` 扩展 slots 字段 + 5 个管理方法(`create_slot` / `switch_slot` / `list_slots` / `derive_slot` / `delete_slot`,自动创建 `"default"` slot,`delete_slot` 双重保护禁止删 default/最后一个);`submit_turn`/`finalize_turn` 改造为基于当前 slot 的增量追加写回(`cycle.messages()[input_len..]` 提取本轮新增消息,确保 Focused 模式"读时过滤"语义不丢失数据);`finalize_turn` 签名变更(新增 `new_messages_from_cycle: Vec<Message>` 参数,返回 `Result<(), AgentError>`);`agent/error.rs` 新增 3 个 Slot 错误变体(`SlotReadonly` / `SlotNotFound` / `SlotAlreadyExists`);`examples/context_slot_demo.rs` 新增分支对话示例(法律咨询入口 → 两个派生方向 → 切换 → 隔离验证 → 删除保护);方案文档 `docs/17-phase10-contextslot.md`(1227 行,含 §5 推荐方案、§6 实施建议、§9 实施计划,经过 4 轮方案/计划/实施审查 + 1 轮非阻塞建议修复);全量 211 → 254(+43 新测试),clippy 0 警告,doc 0 warning,11 个离线示例全部 exit 0
|
||||
- ✅ **Phase 11 测试与检索补强** — `src/memory/vector.rs` 新增 `VectorRetriever` trait(index + search 抽象)+ `InMemoryVectorRetriever` 引用实现(HashMap + 全量余弦相似度扫描 + 零依赖 `dot()`),6 个内联测试覆盖 basic/empty/zero-vector/k=0/2 个并发;wiremock Provider roundtrip 测试 12 个(OpenAI 8 + Anthropic 4)覆盖请求体/header/401/429/500/529/流式 usage-only/流式错误/ToolUse/结构化错误体;`MemoryStore` 并发测试 5 个(InMemoryStore 3 + SqliteStore 2)覆盖 100 并发写、5 写+5 读混合 2 秒、15 写者容量淘汰;`openai.rs` `handle_error_response` 修复 429 retry-after 解析(5 行,与 anthropic 对齐);方案文档 `docs/18-phase11-testing-and-retrieval.md`(647 行,含 10 项架构决策 + 2 条实施偏差记录 #6 mid-stream mock 模式 + #7 retry-after 修复);全量 254 → 277(+23 新测试),clippy 0 警告,doc 0 warning,并发测试 3 次稳定无 flaky
|
||||
- ✅ **Phase 13 热身清理 + ContextSlot fork/merge** — 3 个旧 types 文件删除(`request.rs` 187 行 + `response.rs` 177 行 + `old_stream.rs` 45 行),所有 OpenAI wire-format 类型迁入 `provider/openai.rs` 可见性 `pub(crate)`(Breaking Change:原 `agcore::llm::types::OpenaiChatRequest/Response/Chunk` 公共 re-export 路径已删除);`ChatResponse` 自 v0.1.0 标记 `#[deprecated]` 后在 Phase 13 整体删除;`ToolChoice` 从 `request.rs` 迁入 `tool.rs`(公共 `agcore::llm::types::ToolChoice` 路径不变);`ContextSlot::fork()` 派生独立子 slot(`SlotSource::Derived { parent_id, strategy }` 血缘可追溯)+ `ContextSlot::merge(child, MergeStrategy)` 合入父 slot(`Append` / `Replace` 两种策略,`#[non_exhaustive]` 为 Phase 16 `Summarize` 预留);`MergeStrategy` 防御性检查(self-merge / 跨 session / Readonly 目标全部阻断);`AgentSession::derive_slot` 重构复用 `fork()` 消除重复;`agent.rs` 追加 `MergeStrategy` re-export;9 个 fork/merge 内联测试覆盖 happy path 与 error path;`stream.rs` 简化为 module doc + `pub use` 重导出(保持 `use crate::llm::stream::StreamEvent` 路径兼容);方案文档 `docs/19-phase13-cleanup-and-fork-merge.md`(640 行);全量 277 → 286(+9 新测试),clippy 0 警告,doc 0 warning
|
||||
- ✅ **Phase 13 热身清理 + ContextSlot fork/merge** — 3 个旧 types 文件删除(`request.rs` 187 行 + `response.rs` 177 行 + `old_stream.rs` 45 行),所有 OpenAI wire-format 类型迁入 `provider/openai.rs` 可见性 `pub(crate)`(Breaking Change:原 `agcore::llm::types::OpenaiChatRequest/Response/Chunk` 公共 re-export 路径已删除);`ChatResponse` 自 v0.1.0 标记 `#[deprecated]` 后在 Phase 13 整体删除;`ToolChoice` 从 `request.rs` 迁入 `tool.rs`(公共 `agcore::llm::types::ToolChoice` 路径不变);`ContextSlot::fork()` 派生独立子 slot(`SlotSource::Derived { parent_id, strategy }` 血缘可追溯)+ `ContextSlot::merge(child, MergeStrategy)` 合入父 slot(`Append` / `Replace` 两种策略,`#[non_exhaustive]` 预留扩展);`MergeStrategy` 防御性检查(self-merge / 跨 session / Readonly 目标全部阻断);`AgentSession::derive_slot` 重构复用 `fork()` 消除重复;`agent.rs` 追加 `MergeStrategy` re-export;9 个 fork/merge 内联测试覆盖 happy path 与 error path;`stream.rs` 简化为 module doc + `pub use` 重导出(保持 `use crate::llm::stream::StreamEvent` 路径兼容);方案文档 `docs/19-phase13-cleanup-and-fork-merge.md`(640 行);全量 277 → 286(+9 新测试),clippy 0 警告,doc 0 warning
|
||||
- ✅ Provider IR 重构 — 统一类型系统 + OpenAI/Anthropic/DeepSeek/Qwen/Ollama 适配
|
||||
- ✅ LlmCycle 简化 — IR 消息类型切换 + Phase 0 桥接层移除
|
||||
- ✅ v0.1 Release — 技术债扫清、MockProvider 公开化、8 个离线示例(含 `simple_visit`)、README + 错误消息友好化、CHANGELOG 初始化
|
||||
- ✅ **v0.2 规划细化完成** — 8 个增量 Phase(Phase 5-12),17 个可验证 Step,覆盖 P0-P2 全部 12 项功能 + ContextSlot
|
||||
- ✅ **v0.3.0 Phase 13 完成** — 技术债清理(3 旧 types 文件 + ChatResponse 删除)+ ContextSlot fork/merge(9 新测试),M9 里程碑达成
|
||||
- ✅ **v0.3.0 Phase 14 完成** — Document 类型(id/content/metadata/mime_type)+ `RecursiveCharacterSplitter` 两阶段算法(按 separator 优先级递归分割 + 贪心合并 overlap,全部 `chars_len()` 字符级比较)+ `Embedding` trait(async + `LlmError` 复用)+ `MockEmbedding`(sin-hash 零依赖伪随机 + L2 归一化)+ 19 Document 测试 + 6 Embedding 测试(含 1 个 split_multibyte_utf8_boundary CJK 边界测试);`src/document.rs`(580 行)+ `src/llm/embedding.rs`(183 行)+ `examples/document_demo.rs`(74 行);`pub use document::Document` 在 lib.rs 重导出;CJK 分隔符(`。`/`?`/`!`)加入 `DEFAULT_SEPARATORS`;方案文档 `docs/20-phase14-document-and-embedding.md`(1417 行);全量 286 → 313(+27 新测试,0 失败),clippy 0 警告,doc 0 warning,零新外部依赖;M10 里程碑达成
|
||||
- ✅ **v0.3.0 Phase 15 完成** — `VectorStore` trait(`add`/`search`/`remove`/`add_one`,返回 `(Document, f32)` 消除调用方 id→Document 维护开销)+ `InMemoryVectorStore`(`Mutex<HashMap>` + 余弦全量扫描 + 预计算 L2 norm 缓存)+ `PersistentVectorStore`(构造时全量加载,先写持久化后写内存,持久化失败时内存不污染重启自动恢复,`remove` 幽灵数据窗口已知)+ `RagPipeline` 组合器(ingest: split→embed→store.add / retrieve: embed→store.search,`splitter: Option<RecursiveCharacterSplitter>` 灵活切换);`src/memory/vector_store.rs`(937 行,19 个内联测试覆盖 14 场景含 2 个性能基准)+ 零新外部依赖(纯 Rust `dot()` 余弦);旧 `VectorRetriever`/`InMemoryVectorRetriever` 标注 `#[deprecated(since = "0.3.0")]` 迁移路径清晰;`search_orthogonal_vectors` 返回 1 条 score≈0(文档已同步修正不过滤低分向量);方案文档 `docs/21-phase15-vector-store-persistence.md`(1570 行,经 3 轮审查 + 文档-代码一致化修复);全量 313 → 335(+22 新测试),clippy 0 警告,doc 0 warning;M11 里程碑达成;Phase 16-19 共 4 个增量 Phase 待实施(摘要 → 引擎 → 调度 → 知识图谱)
|
||||
- ✅ **v0.3.0 Phase 15 完成** — `VectorStore` trait(`add`/`search`/`remove`/`add_one`,返回 `(Document, f32)` 消除调用方 id→Document 维护开销)+ `InMemoryVectorStore`(`Mutex<HashMap>` + 余弦全量扫描 + 预计算 L2 norm 缓存)+ `PersistentVectorStore`(构造时全量加载,先写持久化后写内存,持久化失败时内存不污染重启自动恢复,`remove` 幽灵数据窗口已知)+ `RagPipeline` 组合器(ingest: split→embed→store.add / retrieve: embed→store.search,`splitter: Option<RecursiveCharacterSplitter>` 灵活切换);`src/memory/vector_store.rs`(937 行,19 个内联测试覆盖 14 场景含 2 个性能基准)+ 零新外部依赖(纯 Rust `dot()` 余弦);旧 `VectorRetriever`/`InMemoryVectorRetriever` 标注 `#[deprecated(since = "0.3.0")]` 迁移路径清晰;`search_orthogonal_vectors` 返回 1 条 score≈0(文档已同步修正不过滤低分向量);方案文档 `docs/21-phase15-vector-store-persistence.md`(1570 行,经 3 轮审查 + 文档-代码一致化修复);全量 313 → 335(+22 新测试),clippy 0 警告,doc 0 warning;M11 里程碑达成
|
||||
- ✅ **v0.3.0 Phase 16 完成** — `SummaryConfig` 配置结构体(6 个字段:`trigger_token_ratio=0.75` / `max_context_tokens=32_000` / `summary_prompt` / `debounce_turns=3` / `summary_model=None` / `max_tool_result_chars=500`,默认 `None` 沿用主模型避断裂非 OpenAI 用户)+ `AgentBuilder::summary_config(cfg)` 链式方法 + `AgentConfig.summary_config: Option<SummaryConfig>` 字段;`AgentSession` 新增 `last_summary_turn: Option<u32>` 字段(首次不受防抖约束,`should_summarize` 用 `Option` 哨兵实现)+ `maybe_summarize(current_turn)` 内联检查点(OnTurnEnd 之后 / `turn_index` 之前,对称 `submit_turn` / `finalize_turn` 两个入口,流式路径 `saturating_sub(1)` 修正)+ 关联函数 `generate_summary`(构造独立 `LlmCycle` 调 `submit_messages` 传 `vec![Message::user_text(prompt)]`,`max_tokens=1024`,空消息守卫直接返回空串)+ 公开 API `get_conversation_summary()`;`src/agent/summary.rs`(~240 行,含 8 个 SummaryConfig/`format_messages_as_text` 内联测试——默认值/空输入/系统用户助理/ToolResult(含 `tool_call_id`)/工具调用/Unicode 安全截断/整体 30K 截断保留最新;有效字符数截断多字节安全,droptest 验证保留尾部消息)+ `src/agent/session.rs` 注入 10 个摘要集成测试(默认值不触发 / 超阈值触发 / 防抖阻止重复 / SessionMemory 写入 / Full 模式不注入 / 失败不阻断主流程 / 流式路径触发 / 默认配置零影响 / **Focused `summary_override` 写入正向验证** / **空消息不调用 LLM** / **巨型 `max_context_tokens` 永不触发**);`format_messages_as_text` 简洁版消息格式化(`[Tool: name]` + `Tool Result [id]:` + ToolResult 字符级 `chars().take(max_tool_result_chars)` 截断 + 整段 30K 总长度截断从头部保留最新);所有错误静默(失败用 `tracing::error!`,成功用 `tracing::info!(turn, summary_len)`);`MergeStrategy` 注释中过时 "Summarize 指向"与 `context.rs:78` "v0.3 将支持 Hook 驱动" 过时注释在实施时同步移除/更新;方案文档 `docs/22-phase16-summary-auto-generation.md`(471 行),实施后**两轮审查 PASS**:第一轮 PM/SA 审查 11 项问题修复 + 第二轮实施审查 9 项问题修复(🔴 `generate_summary` 空消息 bug + 🟡 W4 流式路径防抖 + 🟡 W2 模型硬编码 + 🟡 W5 Full 模式无谓 save + 🟡 W3 30K 截断 + 🟡 W6 成功无日志 + 🟡 W1/W7 测试补全 + 💭 注释同步);零新外部依赖;全量 335 → **353**(+18 新测试,含二次审查增补 4 个),clippy 0 警告,doc 0 warning,`quick_start` 示例正常 exit 0;**M12 里程碑达成** + 第二轮审查门禁 PASS;Phase 17-19 共 3 个增量 Phase 待实施(引擎 → 调度 → 知识图谱)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user