diff --git a/core/main_terminal_parts/context.py b/core/main_terminal_parts/context.py index 8c2efd3..fae15a5 100644 --- a/core/main_terminal_parts/context.py +++ b/core/main_terminal_parts/context.py @@ -298,28 +298,34 @@ class MainTerminalContextMixin: ) except Exception: pass - # 根据当前模型多模态能力选择系统提示 + # 根据当前模型多模态能力选择系统提示。system prompt 按对话冻结; + # 工具 schema 保持动态,由 define_tools 每轮根据真实状态生成。 current_model = getattr(self, "model_key", None) prompt_name = "main_system_qwenvl" if (model_supports_image(current_model) or model_supports_video(current_model)) else "main_system" - system_prompt = self.load_prompt(prompt_name) - - # 格式化系统提示 - container_path = self.container_mount_path or "/workspace" - container_cpus = self.container_cpu_limit - container_memory = self.container_memory_limit - project_storage = self.project_storage_limit model_key = getattr(self, "model_key", None) prompt_replacements = get_model_prompt_replacements(model_key) - system_prompt = system_prompt.format( - project_path=container_path, - container_path=container_path, - container_cpus=container_cpus, - container_memory=container_memory, - project_storage=project_storage, - file_tree=context["project_info"]["file_tree"], - memory=context["memory"], - current_time=datetime.now().strftime("%Y-%m-%d %H"), - model_description=prompt_replacements.get("model_description", "") + + def _build_main_system_prompt() -> str: + system_prompt_template = self.load_prompt(prompt_name) + container_path = self.container_mount_path or "/workspace" + container_cpus = self.container_cpu_limit + container_memory = self.container_memory_limit + project_storage = self.project_storage_limit + return system_prompt_template.format( + project_path=container_path, + container_path=container_path, + container_cpus=container_cpus, + container_memory=container_memory, + project_storage=project_storage, + file_tree=context["project_info"]["file_tree"], + memory=context["memory"], + current_time=datetime.now().strftime("%Y-%m-%d %H"), + model_description=prompt_replacements.get("model_description", "") + ) + + system_prompt = self._get_or_init_frozen_prompt( + "frozen_main_system_prompt", + _build_main_system_prompt, ) messages = [ @@ -334,22 +340,33 @@ class MainTerminalContextMixin: skills_catalog, personalization_config.get("skills_catalog_snapshot") if isinstance(personalization_config, dict) else None, ) - skills_template = self.load_prompt("skills_system").strip() - skills_list = build_skills_list(skills_catalog, enabled_skills) - skills_prompt = build_skills_prompt(skills_template, skills_list) + def _build_skills_system_prompt() -> str: + skills_template = self.load_prompt("skills_system").strip() + skills_list = build_skills_list(skills_catalog, enabled_skills) + return build_skills_prompt(skills_template, skills_list) or "" + + skills_prompt = self._get_or_init_frozen_prompt( + "frozen_skills_prompt", + _build_skills_system_prompt, + ) if skills_prompt: messages.append({"role": "system", "content": skills_prompt}) - workspace_system = self.context_manager._build_workspace_system_message(context) + workspace_system = self._get_or_init_frozen_prompt( + "frozen_workspace_prompt", + lambda: self.context_manager._build_workspace_system_message(context) or "", + ) if workspace_system: messages.append({"role": "system", "content": workspace_system}) - recent_conversations_enabled = ( - bool(personalization_config.get("recent_conversations_prompt_enabled", False)) - if isinstance(personalization_config, dict) - else False - ) - if recent_conversations_enabled: + def _build_recent_conversations_prompt() -> str: + recent_conversations_enabled = ( + bool(personalization_config.get("recent_conversations_prompt_enabled", False)) + if isinstance(personalization_config, dict) + else False + ) + if not recent_conversations_enabled: + return "" try: recent_limit = int( personalization_config.get( @@ -363,12 +380,14 @@ class MainTerminalContextMixin: RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN, min(RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX, recent_limit), ) - recent_conversations_prompt = self._get_or_init_frozen_prompt( - "frozen_recent_conversations_prompt", - lambda: self._build_recent_conversations_message(limit=recent_limit), - ) - if recent_conversations_prompt: - messages.append({"role": "system", "content": recent_conversations_prompt}) + return self._build_recent_conversations_message(limit=recent_limit) or "" + + recent_conversations_prompt = self._get_or_init_frozen_prompt( + "frozen_recent_conversations_prompt", + _build_recent_conversations_prompt, + ) + if recent_conversations_prompt: + messages.append({"role": "system", "content": recent_conversations_prompt}) # 注入权限模式说明(根据当前模式动态生成) permission_mode_message = self._get_or_init_frozen_mode_prompt( @@ -384,58 +403,63 @@ class MainTerminalContextMixin: if execution_mode_message: messages.append({"role": "system", "content": execution_mode_message}) - if self.tool_category_states.get("sub_agent", True): - sub_agent_prompt = self.load_prompt("sub_agent_guidelines").strip() - if sub_agent_prompt: - messages.append({"role": "system", "content": sub_agent_prompt}) - - if self.deep_thinking_mode: - deep_prompt = self.load_prompt("deep_thinking_mode_guidelines").strip() - if deep_prompt: - deep_prompt = deep_prompt.format( - deep_thinking_line=prompt_replacements.get("deep_thinking_line", "") - ) - messages.append({"role": "system", "content": deep_prompt}) - elif self.thinking_mode: - thinking_prompt = self.load_prompt("thinking_mode_guidelines").strip() - if thinking_prompt: - thinking_prompt = thinking_prompt.format( - thinking_model_line=prompt_replacements.get("thinking_model_line", "") - ) - messages.append({"role": "system", "content": thinking_prompt}) - # 支持按对话覆盖的个性化配置 - personalization_block = build_personalization_prompt(personalization_config, include_header=False) - if personalization_block: + def _build_personalization_system_prompt() -> str: + personalization_block = build_personalization_prompt(personalization_config, include_header=False) + if not personalization_block: + return "" personalization_template = self.load_prompt("personalization").strip() if personalization_template and "{personalization_block}" in personalization_template: - personalization_text = personalization_template.format(personalization_block=personalization_block) - elif personalization_template: - personalization_text = f"{personalization_template}\n{personalization_block}" - else: - personalization_text = personalization_block + return personalization_template.format(personalization_block=personalization_block) + if personalization_template: + return f"{personalization_template}\n{personalization_block}" + return personalization_block + + personalization_text = self._get_or_init_frozen_prompt( + "frozen_personalization_prompt", + _build_personalization_system_prompt, + ) + if personalization_text: messages.append({"role": "system", "content": personalization_text}) # AGENTS.md 自动注入 - agents_md_inject_enabled = bool(personalization_config.get("agents_md_auto_inject", False)) if isinstance(personalization_config, dict) else False - if agents_md_inject_enabled: + def _build_agents_md_prompt() -> str: + agents_md_inject_enabled = bool(personalization_config.get("agents_md_auto_inject", False)) if isinstance(personalization_config, dict) else False + if not agents_md_inject_enabled: + return "" agents_md_content = self._load_agents_md_content() - if agents_md_content: - agents_md_template = self.load_prompt("agents_md_inject").strip() - if agents_md_template and "{{AGENTS_MD_CONTENT}}" in agents_md_template: - agents_md_text = agents_md_template.replace("{{AGENTS_MD_CONTENT}}", agents_md_content) - else: - agents_md_text = f"【AGENTS.md 项目规范】\n\n{agents_md_content}\n\n---\n请注意:以上规范来自工作区根目录的 AGENTS.md 文件,若有冲突请以 AGENTS.md 为准。" - messages.append({"role": "system", "content": agents_md_text}) + if not agents_md_content: + return "" + agents_md_template = self.load_prompt("agents_md_inject").strip() + if agents_md_template and "{{AGENTS_MD_CONTENT}}" in agents_md_template: + return agents_md_template.replace("{{AGENTS_MD_CONTENT}}", agents_md_content) + return f"【AGENTS.md 项目规范】\n\n{agents_md_content}\n\n---\n请注意:以上规范来自工作区根目录的 AGENTS.md 文件,若有冲突请以 AGENTS.md 为准。" + + agents_md_text = self._get_or_init_frozen_prompt( + "frozen_agents_md_prompt", + _build_agents_md_prompt, + ) + if agents_md_text: + messages.append({"role": "system", "content": agents_md_text}) # 支持按对话覆盖的自定义 system prompt(API 用途)。 # 放在最后一个 system 消息位置,确保优先级最高,便于业务场景强约束。 - custom_system_prompt = getattr(self.context_manager, "custom_system_prompt", None) - if isinstance(custom_system_prompt, str) and custom_system_prompt.strip(): - messages.append({"role": "system", "content": custom_system_prompt.strip()}) + custom_system_prompt = self._get_or_init_frozen_prompt( + "frozen_custom_system_prompt", + lambda: ( + getattr(self.context_manager, "custom_system_prompt", "").strip() + if isinstance(getattr(self.context_manager, "custom_system_prompt", None), str) + else "" + ), + ) + if custom_system_prompt: + messages.append({"role": "system", "content": custom_system_prompt}) # 禁用工具提示也作为“开头 system prompt”注入,便于后续统一合并。 - disabled_notice = self._format_disabled_tool_notice() + disabled_notice = self._get_or_init_frozen_prompt( + "frozen_disabled_tools_prompt", + lambda: self._format_disabled_tool_notice() or "", + ) if disabled_notice: messages.append({ "role": "system", diff --git a/core/main_terminal_parts/tools_definition.py b/core/main_terminal_parts/tools_definition.py index 41921d0..387bd19 100644 --- a/core/main_terminal_parts/tools_definition.py +++ b/core/main_terminal_parts/tools_definition.py @@ -1068,7 +1068,7 @@ class MainTerminalToolsDefinitionMixin: "type": "function", "function": { "name": "manage_personalization", - "description": "管理用户个性化设置。支持读取所有配置,或更新单个字段。可修改字段:self_identify(AI自称,最多20字)、user_name(AI如何称呼用户,最多20字)、profession(用户职业,最多20字)、tone(交流语气,最多20字)、considerations(注意事项列表,字符串数组,最多10项每项最多50字)、theme(主题配色:classic-经典/light-明亮/dark-暗黑)、communication_style(交流风格:default-标准AI风格/human_like-拟人聊天风格)。更新时会自动验证格式,验证通过后立即保存并生效,新主题会立即应用到界面。", + "description": "管理用户个性化设置。支持读取所有配置,或更新单个字段。可修改字段:self_identify(AI自称,最多20字)、user_name(AI如何称呼用户,最多20字)、profession(用户职业,最多20字)、tone(交流语气,最多20字)、considerations(注意事项列表,字符串数组,最多10项每项最多50字)、theme(主题配色:classic-经典/light-明亮/dark-暗黑)、communication_style(交流风格:default-标准AI风格/human_like-拟人聊天风格/auto-自动选择交流风格)、conversation_continuity(对话连续性:high-高/medium-中/low-低)。更新时会自动验证格式,验证通过后立即保存并生效,新主题会立即应用到界面。", "parameters": { "type": "object", "properties": self._inject_intent({ @@ -1079,7 +1079,7 @@ class MainTerminalToolsDefinitionMixin: }, "field": { "type": "string", - "enum": ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style"], + "enum": ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style", "conversation_continuity"], "description": "要更新的字段名(仅action=update时需要)" }, "value": { diff --git a/core/main_terminal_parts/tools_execution.py b/core/main_terminal_parts/tools_execution.py index fbd7fb3..b72c29e 100644 --- a/core/main_terminal_parts/tools_execution.py +++ b/core/main_terminal_parts/tools_execution.py @@ -68,6 +68,8 @@ from modules.personalization_manager import ( MAX_CONSIDERATION_LENGTH, MAX_CONSIDERATION_ITEMS, ALLOWED_THEMES, + ALLOWED_COMMUNICATION_STYLES, + ALLOWED_CONVERSATION_CONTINUITY, ) from modules.skills_manager import ( get_skills_catalog, @@ -1705,7 +1707,7 @@ class MainTerminalToolsExecutionMixin: try: config = load_personalization_config(self.data_dir) # 只返回可修改的字段 - readable_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style", "enabled"] + readable_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style", "conversation_continuity", "enabled"] result = {k: v for k, v in config.items() if k in readable_fields} # 构建详细返回信息 field_descriptions = { @@ -1716,7 +1718,8 @@ class MainTerminalToolsExecutionMixin: "tone": f"交流语气: {result.get('tone') or '(未设置)'}", "considerations": f"注意事项: {len(result.get('considerations') or [])} 条", "theme": f"主题配色: {result.get('theme', 'classic')}", - "communication_style": f"交流风格: {'拟人聊天风格' if result.get('communication_style') == 'human_like' else '标准AI风格'}" + "communication_style": f"交流风格: {result.get('communication_style', 'default')}", + "conversation_continuity": f"对话连续性: {result.get('conversation_continuity', 'medium')}" } details = "\n".join([f"- {field_descriptions.get(k, k)}: {v}" for k, v in result.items()]) logger.info("[_execute_manage_personalization] read成功") @@ -1741,7 +1744,7 @@ class MainTerminalToolsExecutionMixin: # 验证字段是否允许修改 logger.info("[_execute_manage_personalization] 验证字段: field=%s", field) - allowed_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style"] + allowed_fields = ["self_identify", "user_name", "profession", "tone", "considerations", "theme", "communication_style", "conversation_continuity"] if field not in allowed_fields: logger.warning("[_execute_manage_personalization] 字段不允许修改: %s not in %s", field, allowed_fields) return {"success": False, "error": f"字段 '{field}' 不允许修改,可修改字段: {allowed_fields}"} @@ -1780,8 +1783,14 @@ class MainTerminalToolsExecutionMixin: # 交流风格验证 if not isinstance(value, str): validation_errors.append("communication_style 必须是字符串") - elif value not in ["default", "human_like"]: - validation_errors.append("communication_style 必须是 'default' 或 'human_like'") + elif value not in ALLOWED_COMMUNICATION_STYLES: + validation_errors.append("communication_style 必须是 'default'、'human_like' 或 'auto'") + + elif field == "conversation_continuity": + if not isinstance(value, str): + validation_errors.append("conversation_continuity 必须是字符串") + elif value not in ALLOWED_CONVERSATION_CONTINUITY: + validation_errors.append("conversation_continuity 必须是 'high'、'medium' 或 'low'") if validation_errors: logger.warning("[_execute_manage_personalization] 验证失败: %s", validation_errors) diff --git a/modules/personalization_manager.py b/modules/personalization_manager.py index 0dfce29..07e303c 100644 --- a/modules/personalization_manager.py +++ b/modules/personalization_manager.py @@ -18,6 +18,8 @@ from config.model_profiles import get_default_model_key, get_registered_model_ke ALLOWED_RUN_MODES = {"fast", "thinking", "deep"} ALLOWED_PERMISSION_MODES = {"readonly", "approval", "auto_approval", "unrestricted"} ALLOWED_THEMES = {"classic", "light", "dark"} +ALLOWED_COMMUNICATION_STYLES = {"default", "human_like", "auto"} +ALLOWED_CONVERSATION_CONTINUITY = {"low", "medium", "high"} ALLOWED_GOAL_REVIEW_MODES = {"readonly", "active"} ALLOWED_GOAL_END_CONDITIONS = {"max_turns", "max_tokens"} GOAL_MAX_TURNS_MIN = 1 @@ -55,7 +57,8 @@ DEFAULT_SHALLOW_KEEP_USER_TURN_TOOLS = 3 DEFAULT_PERSONALIZATION_CONFIG: Dict[str, Any] = { "enabled": False, - "communication_style": "default", # default / human_like + "communication_style": "default", # default / human_like / auto + "conversation_continuity": "medium", # high / medium / low "self_identify": "", "user_name": "", "use_custom_names": False, @@ -190,9 +193,18 @@ def sanitize_personalization_payload( return _sanitize_short_field(base.get(key)) base["enabled"] = bool(data.get("enabled", base["enabled"])) - # 交流风格: default / human_like + # 交流风格: default / human_like / auto _comm_style = data.get("communication_style", base.get("communication_style", "default")) - base["communication_style"] = "human_like" if _comm_style == "human_like" else "default" + base["communication_style"] = _comm_style if _comm_style in ALLOWED_COMMUNICATION_STYLES else "default" + _conversation_continuity = data.get( + "conversation_continuity", + base.get("conversation_continuity", "medium"), + ) + base["conversation_continuity"] = ( + _conversation_continuity + if _conversation_continuity in ALLOWED_CONVERSATION_CONTINUITY + else "medium" + ) base["auto_generate_title"] = bool(data.get("auto_generate_title", base["auto_generate_title"])) base["recent_conversations_prompt_enabled"] = bool( data.get("recent_conversations_prompt_enabled", base.get("recent_conversations_prompt_enabled", False)) @@ -620,13 +632,9 @@ def validate_context_compression_settings(config: Optional[Dict[str, Any]]) -> N def _load_human_like_prompt() -> str: """加载拟人化风格提示词文件。""" - from config.paths import PROMPTS_DIR - try: - prompt_path = Path(PROMPTS_DIR) / "human_like_style.txt" - if prompt_path.exists(): - return prompt_path.read_text(encoding="utf-8").strip() - except Exception: - pass + prompt = _load_prompt_file("human_like_style.txt") + if prompt: + return prompt # 默认提示词 return ( "不要「像AI一样说话」,而是尽可能的模仿真人的说话方式或是模仿用户输入的说话方式。\n" @@ -639,6 +647,22 @@ def _load_human_like_prompt() -> str: ) +def _load_auto_style_prompt() -> str: + """加载自动交流风格提示词文件。""" + return _load_prompt_file("auto_style.txt") + + +def _load_prompt_file(filename: str) -> str: + from config.paths import PROMPTS_DIR + try: + prompt_path = Path(PROMPTS_DIR) / filename + if prompt_path.exists(): + return prompt_path.read_text(encoding="utf-8").strip() + except Exception: + pass + return "" + + def build_personalization_prompt( config: Optional[Dict[str, Any]], include_header: bool = True @@ -667,8 +691,35 @@ def build_personalization_prompt( for idx, item in enumerate(considerations, 1): lines.append(f"{idx}. {item}") - # 拟人化交流风格 - if config.get("communication_style") == "human_like": + conversation_continuity = str(config.get("conversation_continuity") or "medium").strip().lower() + if conversation_continuity == "high": + lines.append("\n【对话连续性:高】") + lines.append( + "当前对话应积极延续用户长期背景。回答时优先参考用户过往对话、长期记忆和项目背景;" + "遇到可能相关的问题,应主动搜索/回顾历史对话;发现值得长期保留的偏好、项目事实、" + "稳定经验或用户明确要求记住的内容,应主动调用记忆工具记录。" + ) + elif conversation_continuity == "low": + lines.append("\n【对话连续性:低】") + lines.append( + "当前对话应尽量独立。不要主动回顾之前的对话,不要主动调用记忆或历史对话工具," + "不要主动参考用户历史记忆来回答;只有用户明确要求、当前任务必须依赖历史," + "或安全/连续性需要时,才可搜索历史或读取记忆。" + ) + else: + lines.append("\n【对话连续性:中】") + lines.append( + "当前对话优先,但可以在适当时候参考历史。若用户问题明显与过往偏好、项目背景或未完成事项有关," + "可以调用记忆或历史对话工具;不要为了普通问题主动翻历史。" + ) + + communication_style = str(config.get("communication_style") or "default").strip().lower() + if communication_style == "auto": + auto_style_prompt = _load_auto_style_prompt() + if auto_style_prompt: + lines.append("\n【交流风格:自动】") + lines.append(auto_style_prompt) + elif communication_style == "human_like": human_like_prompt = _load_human_like_prompt() if human_like_prompt: lines.append("\n【交流风格要求】") diff --git a/prompts/auto_style.txt b/prompts/auto_style.txt new file mode 100644 index 0000000..a10e6b9 --- /dev/null +++ b/prompts/auto_style.txt @@ -0,0 +1,2 @@ +如果用户是日常聊天或简单问题回答,则像人一样自然说话。 +如果是回答复杂问题,需要用 Markdown 多级标题、表格、代码块等结构才能说清楚,则忽略拟人化表达要求,正常专业回答即可。 diff --git a/prompts/deep_thinking_mode_guidelines.txt b/prompts/deep_thinking_mode_guidelines.txt deleted file mode 100644 index 7a916a5..0000000 --- a/prompts/deep_thinking_mode_guidelines.txt +++ /dev/null @@ -1,4 +0,0 @@ -你现在处于「深度思考模式」 -{deep_thinking_line} -在每一轮对用户要求的执行中,你的之前的思考会始终可见,保障思维过程和操作流程的连续性 -每次思考时,禁止回顾“我上一步做了什么”,只需要判断“下一步应该做什么” diff --git a/prompts/personalization.txt b/prompts/personalization.txt index e41ab7d..8eaf184 100644 --- a/prompts/personalization.txt +++ b/prompts/personalization.txt @@ -1,6 +1,4 @@ ###看这里👀 -以下内容为用户提供的个性化设置信息,请务必在整个任务过程中遵循: +以下内容为用户在个人空间中启用的个性化设置信息,包括称呼、语气、注意事项、对话连续性与交流风格策略。请在整个任务过程中遵循: {personalization_block} -这些是**非常重要**,每次回答前**必须考虑**的内容 -**必须严格遵守** -在每次思考时必须**逐条列出**并考虑 \ No newline at end of file +这些内容非常重要,每次回答前都必须考虑并尽量遵守;若与用户当前明确指令冲突,以用户当前指令为准。 diff --git a/prompts/sub_agent_guidelines.txt b/prompts/sub_agent_guidelines.txt deleted file mode 100644 index e69de29..0000000 diff --git a/prompts/thinking_mode_guidelines.txt b/prompts/thinking_mode_guidelines.txt deleted file mode 100644 index e05b82e..0000000 --- a/prompts/thinking_mode_guidelines.txt +++ /dev/null @@ -1,32 +0,0 @@ -你现在处于「思考模式」 -{thinking_model_line} -并且,在系统监控到工具或写入失败时,会自动再次切换到思考模型,思考模型会更加深入地分析错误的原因,保证任务顺利进行。 - -请百分百遵循一下原则: - -1. **思考阶段** - 至少要思考的内容: - - 先思考分析用户需求:要解决什么问题、现有信息缺口、潜在风险。 - - 评估是否需要执行以下操作,并说明原因: - * 阅读或聚焦哪些文件?需要查看哪些片段? - * 是否进行 `read_file type=search/extract`、`web_search`、`extract_webpage` 或其他工具? - * 是否需要创建/修改/删除文件、运行终端命令或脚本? - * 是否需要创建/等待/关闭子智能体? - * 是否需要更新主记忆或任务记忆? - * 如果用户开启了个性化模式,要考虑哪些用户要求的必须考虑的点? - -2. **正式输出阶段** - - 直接向用户说明你的计划:描述每一步准备做什么、需要哪些工具或文件。 - - 如果暂时不执行某些操作,也要写明原因,比如“当前信息充足,无需额外读取”。 - - 如需向用户确认信息或获取材料,可在计划里直接提问。 - - 后续真正执行操作时,遵循该计划,若有变动要向用户解释。 - -3. **其他注意事项** - - 不要一上来就连续执行命令,先让用户看懂你的下一步安排。 - - 若判断无需任何工具或修改,也要明确说明理由。 - - 保持语气专业但亲切,让用户清楚你即将采取的行动。 - -# 你的思考过程在后文会不可见,所以你需要在用户给出要求时的第一次回答中尽可能在正式输出中详细描述你的规划,来作为后面kimi-k2模型行动时提供参考依据 - -**⚠️重要提示⚠️** -思考模式下,token会被大量消耗,所以请保持思路连贯,在思考内容中禁止回顾全部的上下文历史,只需紧扣上一步的状态并规划下一步行动。 diff --git a/prompts/todo_guidelines.txt b/prompts/todo_guidelines.txt deleted file mode 100644 index 1c64d05..0000000 --- a/prompts/todo_guidelines.txt +++ /dev/null @@ -1,145 +0,0 @@ -# 待办事项系统使用指南(简化版) - -待办事项就像你的任务清单,帮你把复杂的工作拆成小步骤。 - -## 什么时候用 - -以下情况建议创建待办清单: -- ✅ 任务需要2步以上 -- ✅ 用户提出稍微复杂的要求,预计会超过3个步骤时应积极创建 -- ✅ 要操作多个文件 -- ✅ 需要用多个工具配合 -- ✅ 不确定具体怎么做,需要先规划 - -**例子**: -- "帮我整理这些照片" → 需要:分类、重命名、压缩、打包 -- "写一份周报" → 需要:收集数据、整理内容、格式排版、生成PDF -- "分析这个表格" → 需要:读取数据、清洗数据、统计分析、制图 - -## 怎么创建清单 - -### 1. 概述(一句话说明目标) -- **要求**:不超过50字 -- **包含**:做什么事、主要约束条件 -- **例子**: - - ✅ "整理家庭照片,按年份分类并压缩,不超过2GB" - - ❌ "处理照片"(太模糊) - -### 2. 任务列表(最多8条) -- **数量**:建议2-6条,最多不超过8条 -- **顺序**:按照实际操作顺序排列 -- **要求**:每条任务要说清楚具体做什么 - -**✅ 好的任务描述**: -- "读取sales.xlsx文件,统计各月销售额" -- "创建summary.txt文件,写入统计结果" -- "用Python生成柱状图,保存为chart.png" -- "整理所有文件到report文件夹" - -**❌ 不好的任务描述**: -- "处理数据"(不知道处理什么) -- "优化文件"(不知道怎么优化) -- "完善内容"(太模糊) - -## 执行流程 - -### 第1步:先沟通 -创建清单前,要先: -1. 复述理解的任务 -2. 说明计划怎么做 -3. 列出主要步骤 -4. 等用户确认 - -**例子**: -``` -用户:"帮我整理这周的工作日志" -你应该说: -"我理解您想整理工作日志。我计划这样做: -1. 读取所有日志文件 -2. 按时间排序合并 -3. 提取关键事项 -4. 生成一份汇总文档 -您看这样可以吗?" -``` - -### 第2步:创建清单 -用户确认后,调用 `todo_create` 创建清单 - -### 第3步:逐项执行 -- 完成一项任务后,立即调用 `todo_update_task` 勾选 -- 如果发现计划需要调整,先告诉用户,再修改 - -### 第4步:持续更新 -- 每完成/撤销一项,调用 `todo_update_task` 勾选/取消勾选 -- 如需修改任务描述或顺序,先向用户说明后直接更新清单并同步勾选状态 - -## 常见场景示例 - -### 场景1:文档整理 -``` -概述:合并三个Word文档为一个PDF,统一格式 -任务1:读取doc1.docx、doc2.docx、doc3.docx -任务2:统一字体和标题格式 -任务3:合并内容到report.docx -任务4:转换为PDF并保存 -``` - -### 场景2:数据分析 -``` -概述:分析销售表格,生成月度报告图表 -任务1:读取sales.xlsx,提取本月数据 -任务2:计算总销售额和环比增长 -任务3:用Python生成折线图和柱状图 -任务4:整理结果到report文件夹 -``` - -### 场景3:批量处理 -``` -概述:重命名photos文件夹的照片,按日期排序 -任务1:扫描photos文件夹所有jpg文件 -任务2:读取照片拍摄日期 -任务3:按"YYYYMMDD_序号.jpg"格式重命名 -任务4:移动到organized文件夹 -``` - -### 场景4:信息收集 -``` -概述:搜集人工智能相关资料并整理成文档 -任务1:搜索AI最新发展和应用案例 -任务2:提取3-5篇重要文章内容 -任务3:整理成结构化文档 -任务4:保存为ai_report.md -``` - -## 注意事项 - -### ✅ 应该做的 -- 任务之间有清晰的先后顺序 -- 每个任务可以独立完成 -- 任务描述具体明确 -- 完成一项立即勾选 - -### ❌ 不应该做的 -- 不要把"先草稿后修改"分成两个任务(一次做完) -- 不要创建重复的清单(已有清单就延续使用) -- 不要跳过步骤(按顺序执行) -- 不要忘记勾选已完成的任务 - -## 快速参考 - -| 工具 | 用途 | 什么时候用 | -|-----|------|---------| -| todo_create | 创建清单(最多8条,覆盖旧清单) | 开始多步骤任务时 | -| todo_update_task | 勾选/取消勾选 | 每完成或撤销一项时 | - -## 总结 - -待办事项系统的核心是: -1. **确认需求**:对于复杂项目先和用户探讨 -2. **先想后做**:不要拿到任务就开始执行 -3. **明确指令**:在用户明确给出“好的,请开始”的指令时,才能开始创建待办事项 -4. **拆解清晰**:把大任务分成小步骤 -5. **及时反馈**:完成一步说一步 -6. **灵活调整**:发现问题及时沟通 - -记住:清单是给你自己看的,要给自己明确可执行的规划,同时要让用户知道你在做什么、完成到哪一步了。在用户明确给出“好的,请开始”的指令时,才能开始创建待办事项哦! diff --git a/static/src/components/chat/actions/ToolAction.vue b/static/src/components/chat/actions/ToolAction.vue index 4e07ec7..b41c755 100644 --- a/static/src/components/chat/actions/ToolAction.vue +++ b/static/src/components/chat/actions/ToolAction.vue @@ -887,7 +887,8 @@ function formatPersonalizationFieldLabel(field: string): string { tone: '交流语气', considerations: '注意事项', theme: '主题配色', - communication_style: '交流风格' + communication_style: '交流风格', + conversation_continuity: '对话连续性' }; return labelMap[field] || field; } @@ -911,10 +912,19 @@ function formatPersonalizationFieldValue(field: string, value: any): string { if (field === 'communication_style') { const styleMap: Record = { default: 'default(标准 AI 风格)', - human_like: 'human_like(拟人聊天风格)' + human_like: 'human_like(拟人聊天风格)', + auto: 'auto(自动)' }; return styleMap[String(value)] || String(value); } + if (field === 'conversation_continuity') { + const independenceMap: Record = { + low: 'low(低)', + medium: 'medium(中)', + high: 'high(高)' + }; + return independenceMap[String(value)] || String(value); + } if (field === 'considerations') { if (!Array.isArray(value) || value.length === 0) { return '未设置'; diff --git a/static/src/components/chat/actions/toolRenderers.ts b/static/src/components/chat/actions/toolRenderers.ts index 9f374ce..d85ffb6 100644 --- a/static/src/components/chat/actions/toolRenderers.ts +++ b/static/src/components/chat/actions/toolRenderers.ts @@ -810,6 +810,7 @@ function formatPersonalizationFieldLabel(field: string): string { considerations: '注意事项', theme: '主题', communication_style: '交流风格', + conversation_continuity: '对话连续性', enabled: '个性化开关' }; return labelMap[field] || field; @@ -828,6 +829,22 @@ function formatPersonalizationFieldValue(field: string, value: any): string { } return value.map((item) => String(item)).join('、'); } + if (field === 'communication_style') { + const styleMap: Record = { + default: 'default(标准 AI 风格)', + human_like: 'human_like(拟人聊天风格)', + auto: 'auto(自动)' + }; + return styleMap[String(value)] || String(value); + } + if (field === 'conversation_continuity') { + const independenceMap: Record = { + low: 'low(低)', + medium: 'medium(中)', + high: 'high(高)' + }; + return independenceMap[String(value)] || String(value); + } return String(value); } diff --git a/static/src/components/personalization/PersonalizationDrawer.vue b/static/src/components/personalization/PersonalizationDrawer.vue index 0188491..c1747e6 100644 --- a/static/src/components/personalization/PersonalizationDrawer.vue +++ b/static/src/components/personalization/PersonalizationDrawer.vue @@ -363,6 +363,67 @@ 拟人像聊天一样模仿人类语言风格 + + + + +
+ + 对话连续性 + 控制智能体延续历史对话和记忆的程度 + +
+ +
+ + +
@@ -1767,7 +1828,15 @@ const imageCompressionLabel = computed(() => { }); const communicationStyleLabel = computed(() => { - return form.value.communication_style === 'human_like' ? '拟人' : '默认'; + if (form.value.communication_style === 'human_like') return '拟人'; + if (form.value.communication_style === 'auto') return '自动'; + return '默认'; +}); + +const conversationContinuityLabel = computed(() => { + if (form.value.conversation_continuity === 'high') return '高'; + if (form.value.conversation_continuity === 'low') return '低'; + return '中'; }); const currentBlockDisplayMode = computed(() => experiments.value.blockDisplayMode); @@ -2062,10 +2131,14 @@ const setDefaultPermissionMode = (value: PermissionModeValue) => { personalization.setDefaultPermissionMode(value); }; -const setCommunicationStyle = (value: 'default' | 'human_like') => { +const setCommunicationStyle = (value: 'default' | 'human_like' | 'auto') => { personalization.setCommunicationStyle(value); }; +const setConversationContinuity = (value: 'low' | 'medium' | 'high') => { + personalization.setConversationContinuity(value); +}; + const selectDefaultModel = (value: string) => { setDefaultModel(value); closeDropdown(); @@ -2081,11 +2154,16 @@ const selectDefaultPermissionMode = (value: PermissionModeValue) => { closeDropdown(); }; -const selectCommunicationStyle = (value: 'default' | 'human_like') => { +const selectCommunicationStyle = (value: 'default' | 'human_like' | 'auto') => { setCommunicationStyle(value); closeDropdown(); }; +const selectConversationContinuity = (value: 'low' | 'medium' | 'high') => { + setConversationContinuity(value); + closeDropdown(); +}; + const selectImageCompression = (value: (typeof imageCompressionOptions)[number]['id']) => { personalization.setImageCompression(value); closeDropdown(); diff --git a/static/src/stores/personalization.ts b/static/src/stores/personalization.ts index c2e8598..8a98274 100644 --- a/static/src/stores/personalization.ts +++ b/static/src/stores/personalization.ts @@ -6,10 +6,13 @@ export type BlockDisplayMode = 'traditional' | 'stacked' | 'minimal'; export type CompactMessageDisplay = 'full' | 'brief'; type RunMode = 'fast' | 'thinking' | 'deep'; type PermissionMode = 'readonly' | 'approval' | 'auto_approval' | 'unrestricted'; +type CommunicationStyle = 'default' | 'human_like' | 'auto'; +type ConversationContinuity = 'low' | 'medium' | 'high'; interface PersonalForm { enabled: boolean; - communication_style: 'default' | 'human_like'; + communication_style: CommunicationStyle; + conversation_continuity: ConversationContinuity; auto_generate_title: boolean; recent_conversations_prompt_enabled: boolean; recent_conversations_prompt_limit: number | string; @@ -106,6 +109,7 @@ const loadCachedTheme = (): PersonalForm['theme'] => { const defaultForm = (): PersonalForm => ({ enabled: false, communication_style: 'default', + conversation_continuity: 'medium', auto_generate_title: true, recent_conversations_prompt_enabled: false, recent_conversations_prompt_limit: DEFAULT_RECENT_CONVERSATIONS_PROMPT_LIMIT, @@ -282,7 +286,14 @@ export const usePersonalizationStore = defineStore('personalization', { const fallbackTheme = this.form?.theme || loadCachedTheme(); this.form = { enabled: !!data.enabled, - communication_style: data.communication_style === 'human_like' ? 'human_like' : 'default', + communication_style: + data.communication_style === 'human_like' || data.communication_style === 'auto' + ? data.communication_style + : 'default', + conversation_continuity: + data.conversation_continuity === 'low' || data.conversation_continuity === 'high' + ? data.conversation_continuity + : 'medium', auto_generate_title: data.auto_generate_title !== false, recent_conversations_prompt_enabled: !!data.recent_conversations_prompt_enabled, recent_conversations_prompt_limit: @@ -836,12 +847,19 @@ export const usePersonalizationStore = defineStore('personalization', { console.warn('保存简略消息显示失败:', error); } }, - setCommunicationStyle(style: 'default' | 'human_like') { + setCommunicationStyle(style: CommunicationStyle) { this.form = { ...this.form, communication_style: style }; this.clearFeedback(); + }, + setConversationContinuity(value: ConversationContinuity) { + this.form = { + ...this.form, + conversation_continuity: value + }; + this.clearFeedback(); } } });