新增: 上下文管理演进 Wave1(契约定义 + T1动态阈值 + T2命名空间)
Agent 3: 共享契约类型定义 - CompressedSummary(T3输出格式) - WorkflowContextBlock(T4工作流DAG上下文) - WorkingContext + GoalItem/DecisionItem/StepInfo(T5常驻上下文) - 脏标记版本追踪 + reset_all 压缩联动 T1: 动态压缩阈值 - maybe_auto_compress 加 sys_tokens 参数,触发条件从 budget*0.6 改为 available*0.6 - 调用处传 sys_tokens(loop顶部已缓存) - 3 行核心改动 T2: 工具命名空间存储 - crates/df-ai/src/namespace_store.rs: 完整实现 + 单元测试 - process_tool_calls: Low/Med/High 路径 namespace 路由 - save_conversation: namespace 引用展开为原文再落 DB - AiSession 新增 namespace_store 字段
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
//! 上下文管理纯函数与数据类型 — 从 context.rs 抽离的无 `self` 依赖部分
|
||||
//! 上下文管理纯函数与数据类型 — 从 context.rs 抽离的无 `self` 依赖部分
|
||||
//!
|
||||
//! 职责:
|
||||
//! - Token 粗估器(字符级近似,无 tokenizer 依赖)
|
||||
@@ -553,6 +553,9 @@ pub const TOOL_MISSING_PREFIX: &str = "tool_missing_";
|
||||
/// 兜底:sanitize view-only 不改持久化,失败只影响单请求,flag 关→行为完全等价改动前。
|
||||
pub const PLACEHOLDER_INTEGRITY_ENABLED: bool = true;
|
||||
|
||||
/// namespace 引用路径前缀(转发至 namespace_store,供 context.rs 统一路径引用)
|
||||
pub const NAMESPACE_REF_PREFIX: &str = crate::namespace_store::NAMESPACE_REF_PREFIX;
|
||||
|
||||
/// 占位 tool_result 内容中嵌入的唯一标记前缀(供 sanitize 识别"审批挂起占位,不可裁")。
|
||||
///
|
||||
/// 写入处(audit/cache.rs:PENDING_APPROVAL_PLACEHOLDER)格式:`{占位文本}{__PENDING__}{tc_id}`,
|
||||
@@ -604,6 +607,169 @@ pub fn extract_pending_tc_id(content: &str) -> Option<&str> {
|
||||
.filter(|id| !id.is_empty())
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Wave 共享契约类型(由 上下文管理演进-执行计划 Agent 3 定义)
|
||||
// ============================================================
|
||||
|
||||
/// 压缩摘要双格式(T3 输出, T5 消费)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CompressedSummary {
|
||||
/// JSON 卡片序列化(主题/决策/待办/token节省)
|
||||
pub json_card: String,
|
||||
/// 自然语言摘要(向前兼容关键词兜底降级)
|
||||
pub nl_summary: String,
|
||||
}
|
||||
|
||||
/// 工作流 DAG 上下文块(T4 输出 → 注入 system prompt)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WorkflowContextBlock {
|
||||
pub workflow_id: String,
|
||||
pub workflow_name: String,
|
||||
pub total_nodes: usize,
|
||||
pub completed_nodes: usize,
|
||||
pub current_node: Option<String>,
|
||||
pub next_nodes: Vec<String>,
|
||||
}
|
||||
|
||||
impl WorkflowContextBlock {
|
||||
/// 格式化为 system prompt 可读文本
|
||||
pub fn to_system_text(&self) -> String {
|
||||
let mut s = format!("工作流: {} (ID: {})", self.workflow_name, self.workflow_id);
|
||||
s.push_str(&format!("\n进度: {}/{}", self.completed_nodes, self.total_nodes));
|
||||
if let Some(ref cur) = self.current_node {
|
||||
s.push_str(&format!("\n当前步骤: {}", cur));
|
||||
}
|
||||
if !self.next_nodes.is_empty() {
|
||||
s.push_str(&format!("\n后续: {}", self.next_nodes.join(" → ")));
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
/// 目标状态(T5 WorkingContext)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum GoalStatus {
|
||||
Active,
|
||||
Completed,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// 步骤状态
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum StepStatus {
|
||||
Pending,
|
||||
InProgress,
|
||||
Done,
|
||||
Blocked,
|
||||
}
|
||||
|
||||
/// 目标条目
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GoalItem {
|
||||
pub id: String,
|
||||
pub text: String,
|
||||
pub status: GoalStatus,
|
||||
}
|
||||
|
||||
/// 决策条目
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DecisionItem {
|
||||
pub question: String,
|
||||
pub decision: String,
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
/// 步骤信息
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StepInfo {
|
||||
pub step_name: String,
|
||||
pub status: StepStatus,
|
||||
}
|
||||
|
||||
/// WorkingContext 版本号(脏标记追踪)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WorkingContextVersion {
|
||||
pub goals_changed_at: u32,
|
||||
pub decisions_changed_at: u32,
|
||||
pub unresolved_changed_at: u32,
|
||||
pub step_changed_at: u32,
|
||||
pub current_turn: u32,
|
||||
}
|
||||
|
||||
impl WorkingContextVersion {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
goals_changed_at: 0,
|
||||
decisions_changed_at: 0,
|
||||
unresolved_changed_at: 0,
|
||||
step_changed_at: 0,
|
||||
current_turn: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// 压缩后重置:将所有字段标记为本轮变更
|
||||
pub fn reset_all(&mut self, _summary: &str, current_turn: u32) {
|
||||
self.goals_changed_at = current_turn;
|
||||
self.decisions_changed_at = current_turn;
|
||||
self.unresolved_changed_at = current_turn;
|
||||
self.current_turn = current_turn;
|
||||
}
|
||||
}
|
||||
|
||||
/// WorkingContext 常驻上下文(T5 实现)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct WorkingContext {
|
||||
pub goals: Vec<GoalItem>,
|
||||
pub decisions: Vec<DecisionItem>,
|
||||
pub unresolved: Vec<String>,
|
||||
pub current_step: Option<StepInfo>,
|
||||
pub version: WorkingContextVersion,
|
||||
}
|
||||
|
||||
impl WorkingContext {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
goals: Vec::new(),
|
||||
decisions: Vec::new(),
|
||||
unresolved: Vec::new(),
|
||||
current_step: None,
|
||||
version: WorkingContextVersion::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 构建本轮需要注入的 L1 内容(L1a 必带 + L1b 条件注入)
|
||||
pub fn build_injection(&self, recent_turns: u32) -> String {
|
||||
let mut parts: Vec<String> = Vec::new();
|
||||
|
||||
// L1a: 当前步骤(每轮必带)
|
||||
if let Some(ref step) = self.current_step {
|
||||
parts.push(format!("当前步骤: {} [{:?}]", step.step_name, step.status));
|
||||
}
|
||||
|
||||
// L1b: 过去 N 轮内有变更才注入
|
||||
let threshold = self.version.current_turn.saturating_sub(recent_turns);
|
||||
let has_active = self.goals.iter().any(|g| g.status == GoalStatus::Active);
|
||||
if has_active && self.version.goals_changed_at >= threshold {
|
||||
let active: Vec<&str> = self.goals.iter()
|
||||
.filter(|g| g.status == GoalStatus::Active)
|
||||
.map(|g| g.text.as_str())
|
||||
.collect();
|
||||
parts.push(format!("目标({}): {}", active.len(), active.join("; ")));
|
||||
}
|
||||
if self.version.decisions_changed_at >= threshold && !self.decisions.is_empty() {
|
||||
let recent: Vec<&str> = self.decisions.iter().map(|d| d.decision.as_str()).collect();
|
||||
parts.push(format!("近期决策: {}", recent.join("; ")));
|
||||
}
|
||||
if self.version.unresolved_changed_at >= threshold && !self.unresolved.is_empty() {
|
||||
parts.push(format!("待解: {}", self.unresolved.join("; ")));
|
||||
}
|
||||
|
||||
parts.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Reference in New Issue
Block a user