diff --git a/prompts/deep_compression_summary.txt b/prompts/deep_compression_summary.txt index de7cb56a..a05e4206 100644 --- a/prompts/deep_compression_summary.txt +++ b/prompts/deep_compression_summary.txt @@ -1,4 +1,4 @@ -由于当前对话过长,系统正在自动压缩。请你基于已有上下文输出一份可继续执行的工作总结,要求: +由于当前对话过长,系统正在自动压缩(当前是第{compression_index}次压缩)。请你基于已有上下文输出一份可继续执行的工作总结,要求: 1) 任务目标与用户真实诉求:用户到底想让你完成什么。 2) 已完成工作:按时间顺序列出关键步骤,包含涉及文件和核心结果。 3) 关键决策与原因:为什么这样选,而不是别的方案。 @@ -6,12 +6,12 @@ 5) 工具调用中的重要结果/错误与修复:哪些尝试失败过,最终怎么解决的。 6) 风险与注意事项:继续工作时需要规避的问题。 7) 当前正在执行的任务与进度:正在做什么、做到什么程度、卡在哪里。 -8) 下一步具体行动:必须足够具体,至少包含以下信息: +8) 对用户最新输入的响应进度与下一步行动:用户目前的最新一句输入是「{latest_user_input}」。请明确说明:针对这句输入,已经做到了什么进度,接下来该做什么。下一步行动必须足够具体,至少包含以下信息: - 如果要读取文件,列出具体文件路径。 - 如果要搜索,列出搜索关键词和范围。 - 如果要修改文件,说明文件路径和预期改动。 - 如果要运行命令,列出具体命令。 - 如果要验证,说明验证方式和预期结果。 -请使用中文,结构清晰,尽量具体,不要省略关键上下文。 +{incremental_item}请使用中文,结构清晰,尽量具体,不要省略关键上下文。 不要考虑过往对话,只考虑当前对话任务。 -禁止调用任何工具,必须直接输出总结内容。 \ No newline at end of file +禁止调用任何工具,必须直接输出总结内容。 diff --git a/server/deep_compression.py b/server/deep_compression.py index 08084654..2192bf18 100644 --- a/server/deep_compression.py +++ b/server/deep_compression.py @@ -16,6 +16,28 @@ def _load_summary_prompt(web_terminal) -> str: ) +def _build_summary_prompt(template: str, *, compression_index: int, latest_user_input: str) -> str: + """把压缩轮次、最新用户输入、增量条目占位符替换进总结提示词模板。 + + - {compression_index}: 当前压缩轮次(总是替换) + - {latest_user_input}: 最新一条真实用户输入原文 + - {incremental_item}: 第2次压缩起插入增量总结条目,首次压缩替换为空 + """ + prompt = template or "" + latest = (latest_user_input or "").strip() or "(无)" + if compression_index > 1: + incremental_item = ( + "9) 自上次压缩之后到现在新完成的工作:单独列出这段时间内新完成的工作与关键结果," + "此前已被历次压缩总结覆盖的内容不必重复。\n" + ) + else: + incremental_item = "" + prompt = prompt.replace("{compression_index}", str(compression_index)) + prompt = prompt.replace("{latest_user_input}", latest) + prompt = prompt.replace("{incremental_item}", incremental_item) + return prompt + + def _emit(sender, event_type: str, payload: Dict[str, Any]): if not callable(sender): return @@ -65,6 +87,50 @@ def _normalize_deep_compression_records(metadata: Dict[str, Any]) -> List[Dict[s return deduped +def _collect_runtime_state_lines(web_terminal, conversation_id: str) -> List[str]: + """收集压缩后模型续接所需的运行时状态。 + + - 子智能体编号:仅传统模式列出(该模式下编号由模型指定且重复会创建失败); + 多智能体模式不列编号(模型可通过 list_active_sub_agents 工具查询活跃实例)。 + - 持久终端:只列数量与名称。 + """ + lines: List[str] = [] + is_multi_agent = bool(getattr(web_terminal, "multi_agent_mode", False)) + if not is_multi_agent: + used: List[int] = [] + manager = getattr(web_terminal, "sub_agent_manager", None) + if manager is not None: + try: + agents_map = getattr(manager, "conversation_agents", None) or {} + raw_used = agents_map.get(conversation_id) or [] + used = sorted(int(x) for x in raw_used) + except Exception: + used = [] + if used: + next_id = max(used) + 1 + used_text = "、".join(str(x) for x in used) + lines.append( + f"- 子智能体编号:本对话已使用编号 {used_text},下一个可用编号为 {next_id}。" + "新建子智能体时请使用该编号,重复使用已用编号会创建失败。" + ) + else: + lines.append("- 子智能体编号:本对话尚未创建过子智能体,下一个可用编号为 1。") + terminal_names: List[str] = [] + terminal_manager = getattr(web_terminal, "terminal_manager", None) + if terminal_manager is not None: + try: + terminals = getattr(terminal_manager, "terminals", None) or {} + terminal_names = [str(name) for name in terminals.keys()] + except Exception: + terminal_names = [] + if terminal_names: + names_text = "、".join(terminal_names) + lines.append(f"- 持久终端:当前共有 {len(terminal_names)} 个终端会话:{names_text}。") + else: + lines.append("- 持久终端:当前没有终端会话。") + return lines + + def _build_guide_message(*, compression_index: int, compact_file: str) -> str: """生成文件模式的引导语:仅提示压缩文件位置,由模型自行阅读。""" return f"当前对话已经被第{compression_index}次压缩。请阅读 {compact_file} 并继续工作。" @@ -146,6 +212,7 @@ def _build_inject_guide_message( previous_records: List[Dict[str, Any]], user_inputs: List[str], latest_user_input: str, + runtime_state_lines: Optional[List[str]] = None, ) -> str: """生成直接注入模式的引导语:把历次压缩总结、用户输入按顺序拼入正文。""" lines: List[str] = [ @@ -169,6 +236,10 @@ def _build_inject_guide_message( lines.append(latest_user_input.strip()) else: lines.append("(无)") + if runtime_state_lines: + lines.append("") + lines.append("当前运行时状态") + lines.extend(runtime_state_lines) return "\n".join(lines).strip() @@ -244,6 +315,7 @@ def _write_compact_file( user_inputs: List[str], latest_user_input: str, previous_records: List[Dict[str, Any]], + runtime_state_lines: Optional[List[str]] = None, ) -> str: compact_dir = project_path / ".astrion" / "compact_result" compact_dir.mkdir(parents=True, exist_ok=True) @@ -271,6 +343,10 @@ def _write_compact_file( lines.append(latest_user_input.strip()) else: lines.append("(无)") + if runtime_state_lines: + lines.append("") + lines.append("## 当前运行时状态") + lines.extend(runtime_state_lines) file_path.write_text("\n".join(lines).strip() + "\n", encoding="utf-8") return str(file_path.relative_to(project_path)) @@ -368,7 +444,20 @@ async def run_deep_compression( "job_id": job_id, }) - summary_text, summary_fail_reason = await _generate_summary(web_terminal, _load_summary_prompt(web_terminal), retries=5) + # 提前收集用户输入与运行时状态:总结提示词需要嵌入最新输入与压缩轮次, + # compact 文件 / 注入引导语需要追加运行时状态区块。 + messages = conv_data.get("messages") or [] + user_inputs = _collect_user_texts(messages) + latest_user_input = user_inputs[-1] if user_inputs else "" + user_inputs_before = len(user_inputs) + runtime_state_lines = _collect_runtime_state_lines(web_terminal, conversation_id) + + summary_prompt = _build_summary_prompt( + _load_summary_prompt(web_terminal), + compression_index=target_count, + latest_user_input=latest_user_input, + ) + summary_text, summary_fail_reason = await _generate_summary(web_terminal, summary_prompt, retries=5) if summary_fail_reason: _emit(sender, "system_message", {"content": f"自动压缩总结失败,将使用失败占位文本:{summary_fail_reason}"}) @@ -386,11 +475,6 @@ async def run_deep_compression( "job_id": job_id, }) - messages = conv_data.get("messages") or [] - user_inputs = _collect_user_texts(messages) - latest_user_input = user_inputs[-1] if user_inputs else "" - user_inputs_before = len(user_inputs) - relative_compact_path = _write_compact_file( Path(workspace.project_path), compression_index=target_count, @@ -398,6 +482,7 @@ async def run_deep_compression( user_inputs=user_inputs, latest_user_input=latest_user_input, previous_records=previous_records, + runtime_state_lines=runtime_state_lines, ) cm.set_compression_state( @@ -452,6 +537,7 @@ async def run_deep_compression( previous_records=previous_records, user_inputs=user_inputs, latest_user_input=latest_user_input, + runtime_state_lines=runtime_state_lines, ) else: guide_message = _build_guide_message(