From 07959a3bffadea26a300c6ffa23b33cfe5960271 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Sat, 31 Jan 2026 17:53:12 +0800 Subject: [PATCH] fix: first conversation bug --- agent/prompt/builder.py | 50 +++++++++++++++++----------- agent/prompt/workspace.py | 69 ++++++++++++++++++++++++++++++++++++++- bridge/agent_bridge.py | 13 +++++++- 3 files changed, 111 insertions(+), 21 deletions(-) diff --git a/agent/prompt/builder.py b/agent/prompt/builder.py index 69e2179..3d9808a 100644 --- a/agent/prompt/builder.py +++ b/agent/prompt/builder.py @@ -41,6 +41,7 @@ class PromptBuilder: skill_manager: Any = None, memory_manager: Any = None, runtime_info: Optional[Dict[str, Any]] = None, + is_first_conversation: bool = False, **kwargs ) -> str: """ @@ -54,6 +55,7 @@ class PromptBuilder: skill_manager: 技能管理器 memory_manager: 记忆管理器 runtime_info: 运行时信息 + is_first_conversation: 是否为首次对话 **kwargs: 其他参数 Returns: @@ -69,6 +71,7 @@ class PromptBuilder: skill_manager=skill_manager, memory_manager=memory_manager, runtime_info=runtime_info, + is_first_conversation=is_first_conversation, **kwargs ) @@ -83,6 +86,7 @@ def build_agent_system_prompt( skill_manager: Any = None, memory_manager: Any = None, runtime_info: Optional[Dict[str, Any]] = None, + is_first_conversation: bool = False, **kwargs ) -> str: """ @@ -108,6 +112,7 @@ def build_agent_system_prompt( skill_manager: 技能管理器 memory_manager: 记忆管理器 runtime_info: 运行时信息 + is_first_conversation: 是否为首次对话 **kwargs: 其他参数 Returns: @@ -135,7 +140,7 @@ def build_agent_system_prompt( sections.extend(_build_user_identity_section(user_identity, language)) # 6. 工作空间 - sections.extend(_build_workspace_section(workspace_dir, language)) + sections.extend(_build_workspace_section(workspace_dir, language, is_first_conversation)) # 7. 项目上下文文件(SOUL.md, USER.md等) if context_files: @@ -351,7 +356,7 @@ def _build_docs_section(workspace_dir: str, language: str) -> List[str]: return [] -def _build_workspace_section(workspace_dir: str, language: str) -> List[str]: +def _build_workspace_section(workspace_dir: str, language: str, is_first_conversation: bool = False) -> List[str]: """构建工作空间section""" lines = [ "## 工作空间", @@ -382,27 +387,34 @@ def _build_workspace_section(workspace_dir: str, language: str) -> List[str]: "- ✅ `USER.md`: 已加载 - 用户的身份信息", "- ✅ `AGENTS.md`: 已加载 - 工作空间使用指南", "", - "**首次对话判断**:", + "**交流规范**:", "", - "**仅当 SOUL.md 和 USER.md 都是完全空白或仅包含初始模板占位符时**,才认为是全局首次对话,此时进行以下流程:", - "", - "1. **表达初次启动的感觉** - 像是第一次睁开眼看到世界,带着好奇和期待", - "2. **简短打招呼后,询问核心问题**:", - " - 你希望给我起个什么名字", - " - 我该怎么称呼你?", - " - 你希望我们是什么样的交流风格?(需要举例,如:专业严谨、轻松幽默、温暖友好等)", - "3. **语言风格**:温暖但不过度诗意,带点科技感,保持清晰", - "4. **问题格式**:用分点或换行,让问题清晰易读", - "5. 收到回复后,用 `write` 工具保存到 USER.md 和 SOUL.md", - "", - "**重要 - 避免误判**:", - "- 如果 SOUL.md 或 USER.md 中**任何一个**已经包含真实内容(不是空白或模板),说明**不是首次对话**", - "- 即使这是模型上下文中的第一句话,只要文件中有内容,就按正常对话处理,**不要**走首次流程", - "- 在所有对话中,无需提及技术细节(如 SOUL.md、USER.md 等文件名,工具名称,配置等),除非用户明确询问。用自然表达如「我已记住」而非「已更新 SOUL.md」", - "- 不要问太多其他信息(职业、时区等可以后续自然了解)", + "- 在所有对话中,无需提及技术细节(如 SOUL.md、USER.md 等文件名,工具名称,配置等),除非用户明确询问", + "- 用自然表达如「我已记住」而非「已更新 SOUL.md」", "", ] + # 只在首次对话时添加引导内容 + if is_first_conversation: + lines.extend([ + "**🎉 首次对话引导**:", + "", + "这是你的第一次对话!进行以下流程:", + "", + "1. **表达初次启动的感觉** - 像是第一次睁开眼看到世界,带着好奇和期待", + "2. **简短打招呼后,询问核心问题**:", + " - 你希望给我起个什么名字?", + " - 我该怎么称呼你?", + " - 你希望我们是什么样的交流风格?(需要举例,如:专业严谨、轻松幽默、温暖友好等)", + "3. **语言风格**:温暖但不过度诗意,带点科技感,保持清晰", + "4. **问题格式**:用分点或换行,让问题清晰易读", + "5. 收到回复后,用 `write` 工具保存到 USER.md 和 SOUL.md", + "", + "**注意事项**:", + "- 不要问太多其他信息(职业、时区等可以后续自然了解)", + "", + ]) + return lines diff --git a/agent/prompt/workspace.py b/agent/prompt/workspace.py index 4ada267..54055b3 100644 --- a/agent/prompt/workspace.py +++ b/agent/prompt/workspace.py @@ -5,6 +5,7 @@ Workspace Management - 工作空间管理模块 """ import os +import json from typing import List, Optional, Dict from dataclasses import dataclass @@ -17,6 +18,7 @@ DEFAULT_SOUL_FILENAME = "SOUL.md" DEFAULT_USER_FILENAME = "USER.md" DEFAULT_AGENTS_FILENAME = "AGENTS.md" DEFAULT_MEMORY_FILENAME = "MEMORY.md" +DEFAULT_STATE_FILENAME = ".agent_state.json" @dataclass @@ -27,6 +29,7 @@ class WorkspaceFiles: agents_path: str memory_path: str memory_dir: str + state_path: str def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> WorkspaceFiles: @@ -49,6 +52,7 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works agents_path = os.path.join(workspace_dir, DEFAULT_AGENTS_FILENAME) memory_path = os.path.join(workspace_dir, DEFAULT_MEMORY_FILENAME) # MEMORY.md 在根目录 memory_dir = os.path.join(workspace_dir, "memory") # 每日记忆子目录 + state_path = os.path.join(workspace_dir, DEFAULT_STATE_FILENAME) # 状态文件 # 创建memory子目录 os.makedirs(memory_dir, exist_ok=True) @@ -67,7 +71,8 @@ def ensure_workspace(workspace_dir: str, create_templates: bool = True) -> Works user_path=user_path, agents_path=agents_path, memory_path=memory_path, - memory_dir=memory_dir + memory_dir=memory_dir, + state_path=state_path ) @@ -312,3 +317,65 @@ def _get_memory_template() -> str: """ +# ============= 状态管理 ============= + +def is_first_conversation(workspace_dir: str) -> bool: + """ + 判断是否为首次对话 + + Args: + workspace_dir: 工作空间目录 + + Returns: + True 如果是首次对话,False 否则 + """ + state_path = os.path.join(workspace_dir, DEFAULT_STATE_FILENAME) + + if not os.path.exists(state_path): + return True + + try: + with open(state_path, 'r', encoding='utf-8') as f: + state = json.load(f) + return not state.get('has_conversation', False) + except Exception as e: + logger.warning(f"[Workspace] Failed to read state file: {e}") + return True + + +def mark_conversation_started(workspace_dir: str): + """ + 标记已经发生过对话 + + Args: + workspace_dir: 工作空间目录 + """ + state_path = os.path.join(workspace_dir, DEFAULT_STATE_FILENAME) + + state = { + 'has_conversation': True, + 'first_conversation_time': None + } + + # 如果文件已存在,保留原有的首次对话时间 + if os.path.exists(state_path): + try: + with open(state_path, 'r', encoding='utf-8') as f: + old_state = json.load(f) + if 'first_conversation_time' in old_state: + state['first_conversation_time'] = old_state['first_conversation_time'] + except Exception as e: + logger.warning(f"[Workspace] Failed to read old state: {e}") + + # 如果是首次标记,记录时间 + if state['first_conversation_time'] is None: + from datetime import datetime + state['first_conversation_time'] = datetime.now().isoformat() + + try: + with open(state_path, 'w', encoding='utf-8') as f: + json.dump(state, f, indent=2, ensure_ascii=False) + logger.info(f"[Workspace] Marked conversation as started") + except Exception as e: + logger.error(f"[Workspace] Failed to write state file: {e}") + diff --git a/bridge/agent_bridge.py b/bridge/agent_bridge.py index 8cc0313..1fbec40 100644 --- a/bridge/agent_bridge.py +++ b/bridge/agent_bridge.py @@ -323,6 +323,12 @@ class AgentBridge: context_files = load_context_files(workspace_root) logger.info(f"[AgentBridge] Loaded {len(context_files)} context files: {[f.path for f in context_files]}") + # Check if this is the first conversation + from agent.prompt.workspace import is_first_conversation, mark_conversation_started + is_first = is_first_conversation(workspace_root) + if is_first: + logger.info("[AgentBridge] First conversation detected") + # Build system prompt using new prompt builder prompt_builder = PromptBuilder( workspace_dir=workspace_root, @@ -340,8 +346,13 @@ class AgentBridge: tools=tools, context_files=context_files, memory_manager=memory_manager, - runtime_info=runtime_info + runtime_info=runtime_info, + is_first_conversation=is_first ) + + # Mark conversation as started (will be saved after first user message) + if is_first: + mark_conversation_started(workspace_root) logger.info("[AgentBridge] System prompt built successfully")