新增: 上下文管理演进 Wave2(T3结构化摘要 + T4工作流DAG占位)
T3: 压缩摘要双格式(向后兼容) - compress_via_llm 返回类型 Result<String> → Result<CompressedSummary> - 新增 parse_sections_to_json: 从四段式 Markdown 摘要解析 JSON 卡片 - context_lifecycle: 插入 NL 摘要(向后兼容),预留 WorkingContext 联动 - manual compress IPC (chat.rs): 适配新返回类型 T4: 工作流 DAG 注入(占位) - workflow_context.rs: 空壳函数,待 PerConvState.workflow_id 落地后接入 - mod.rs: 注入点已就位(日志占位) - 后续需补: DAG 加载 + 活跃路径裁剪 + system_prompt 注入
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
//! 路由(Lite 智力, 无 tool_use, 标准成本上限)兜底 `default_model`。失败返 `Err`,
|
||||
//! 调用方按场景降级(手动压缩报错给用户;自动压缩降级为原裁剪行为)。
|
||||
|
||||
use df_ai::context_helpers::CompressedSummary;
|
||||
use df_ai::provider::{ChatMessage, CompletionRequest, LlmProvider};
|
||||
// F-15: 压缩路由 — TaskRequirements(Standard 智力即可,无需工具)。压缩是结构化
|
||||
// 摘要任务,选 Standard 智力保摘要质量;不传 tools(纯文本出,避免 LLM 调工具跑偏)。
|
||||
@@ -49,7 +50,7 @@ pub(crate) async fn compress_via_llm(
|
||||
lang: &str,
|
||||
conv_id: &str,
|
||||
llm_concurrency: &LlmConcurrency,
|
||||
) -> Result<String, String> {
|
||||
) -> Result<CompressedSummary, String> {
|
||||
if active_msgs.is_empty() {
|
||||
return Err("无可压缩消息(active 消息为空)".to_string());
|
||||
}
|
||||
@@ -115,7 +116,13 @@ pub(crate) async fn compress_via_llm(
|
||||
if summary.is_empty() {
|
||||
return Err("LLM 压缩返回空摘要".to_string());
|
||||
}
|
||||
Ok(summary)
|
||||
// T3: 解析四段式输出为 JSON 卡片 + NL 摘要
|
||||
// 从现有 ## Intent / ## Decisions / ## Files / ## Constraints 格式解析
|
||||
let json_card = parse_sections_to_json(&summary);
|
||||
Ok(CompressedSummary {
|
||||
json_card,
|
||||
nl_summary: summary,
|
||||
})
|
||||
}
|
||||
|
||||
/// 阶段2(占位配对完整性):检测压缩段是否含未闭合占位 tool_result。
|
||||
@@ -151,6 +158,55 @@ fn compress_segment_has_unclosed_placeholder(msgs: &[ChatMessage]) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// 从四段式 Markdown 摘要中解析出 JSON 卡片
|
||||
/// 段标题: ## Intent / ## Decisions / ## Files / ## Constraints
|
||||
fn parse_sections_to_json(summary: &str) -> String {
|
||||
let mut topics = Vec::new();
|
||||
let mut decisions = Vec::new();
|
||||
let mut key_files = Vec::new();
|
||||
|
||||
let mut current_section: Option<&str> = None;
|
||||
for line in summary.lines() {
|
||||
let trimmed = line.trim();
|
||||
if trimmed.starts_with("## ") {
|
||||
current_section = trimmed.strip_prefix("## ");
|
||||
continue;
|
||||
}
|
||||
let text = trimmed.trim_start_matches("- ").trim();
|
||||
if text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
match current_section {
|
||||
Some("Intent") | Some("意图") => {
|
||||
topics.push(text.to_string());
|
||||
}
|
||||
Some("Decisions") | Some("决策") => {
|
||||
decisions.push(serde_json::json!({"summary": text}));
|
||||
}
|
||||
Some("Files") | Some("文件") => {
|
||||
key_files.push(serde_json::json!({"change": text}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::json!({
|
||||
"topics": topics,
|
||||
"decisions": decisions,
|
||||
"key_files": key_files,
|
||||
"parsed_from": "four_section_summary",
|
||||
}).to_string()
|
||||
}
|
||||
|
||||
/// 在文本中提取标记间的内容
|
||||
#[allow(dead_code)]
|
||||
fn extract_between<'a>(text: &'a str, start_marker: &str, end_marker: &str) -> Option<&'a str> {
|
||||
let start = text.find(start_marker)?;
|
||||
let after_start = &text[start + start_marker.len()..];
|
||||
let end = after_start.find(end_marker)?;
|
||||
Some(after_start[..end].trim())
|
||||
}
|
||||
|
||||
/// 清理 LLM 返回的摘要:去首尾空白 / 包裹性 markdown 代码围栏,空则原样返回
|
||||
/// (调用方据空值报错)。保留内部 markdown 结构(## 标题 / 列表),摘要本身就是结构化文本。
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user