diff --git a/src/memos/mem_reader/read_skill_memory/process_skill_memory.py b/src/memos/mem_reader/read_skill_memory/process_skill_memory.py index 7bd3f3ebb..3ac45c99a 100644 --- a/src/memos/mem_reader/read_skill_memory/process_skill_memory.py +++ b/src/memos/mem_reader/read_skill_memory/process_skill_memory.py @@ -1,3 +1,4 @@ +import copy import json import os import shutil @@ -19,12 +20,18 @@ from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata from memos.memories.textual.tree_text_memory.retrieve.searcher import Searcher from memos.templates.skill_mem_prompt import ( + OTHERS_GENERATION_PROMPT, + OTHERS_GENERATION_PROMPT_ZH, + SCRIPT_GENERATION_PROMPT, SKILL_MEMORY_EXTRACTION_PROMPT, + SKILL_MEMORY_EXTRACTION_PROMPT_MD, + SKILL_MEMORY_EXTRACTION_PROMPT_MD_ZH, SKILL_MEMORY_EXTRACTION_PROMPT_ZH, TASK_CHUNKING_PROMPT, TASK_CHUNKING_PROMPT_ZH, TASK_QUERY_REWRITE_PROMPT, TASK_QUERY_REWRITE_PROMPT_ZH, + TOOL_GENERATION_PROMPT, ) from memos.types import MessageList @@ -36,6 +43,200 @@ logger = get_logger(__name__) +def _generate_content_by_llm(llm: BaseLLM, prompt_template: str, **kwargs) -> Any: + """Generate content using LLM.""" + try: + prompt = prompt_template.format(**kwargs) + response = llm.generate([{"role": "user", "content": prompt}]) + if "json" in prompt_template.lower(): + response = response.replace("```json", "").replace("```", "").strip() + return json.loads(response) + return response.strip() + except Exception as e: + logger.warning(f"[PROCESS_SKILLS] LLM generation failed: {e}") + return {} if "json" in prompt_template.lower() else "" + + +def _batch_extract_skills( + task_chunks: dict[str, MessageList], + related_memories_map: dict[str, list[TextualMemoryItem]], + llm: BaseLLM, + chat_history: MessageList, +) -> list[tuple[dict[str, Any], str, MessageList]]: + """Phase 1: Batch extract base skill structures from all task chunks in parallel.""" + results = [] + with ContextThreadPoolExecutor(max_workers=min(5, len(task_chunks))) as executor: + futures = { + executor.submit( + _extract_skill_memory_by_llm_md, + messages=messages, + old_memories=related_memories_map.get(task_type, []), + llm=llm, + chat_history=chat_history, + ): task_type + for task_type, messages in task_chunks.items() + } + + for future in as_completed(futures): + task_type = futures[future] + try: + skill_memory = future.result() + if skill_memory: + results.append((skill_memory, task_type, task_chunks.get(task_type, []))) + except Exception as e: + logger.warning( + f"[PROCESS_SKILLS] Error extracting skill memory for task '{task_type}': {e}" + ) + return results + + +def _batch_generate_skill_details( + raw_skills_data: list[tuple[dict[str, Any], str, MessageList]], + related_skill_memories_map: dict[str, list[TextualMemoryItem]], + llm: BaseLLM, +) -> list[dict[str, Any]]: + """Phase 2: Batch generate details (scripts, tools, others, examples) for all skills in parallel.""" + generation_tasks = [] + + # Helper to create task objects + def create_task(skill_mem, gen_type, prompt, requirements, context, **kwargs): + return { + "type": gen_type, + "skill_memory": skill_mem, + "func": _generate_content_by_llm, + "args": (llm, prompt), + "kwargs": {"requirements": requirements, "context": context, **kwargs}, + } + + # 1. Collect all generation tasks from all skills + for skill_memory, task_type, messages in raw_skills_data: + messages_context = "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages]) + + # Script + script_req = copy.deepcopy(skill_memory.get("scripts")) + if script_req: + generation_tasks.append( + create_task( + skill_memory, "scripts", SCRIPT_GENERATION_PROMPT, script_req, messages_context + ) + ) + # TODO Add loop verification after code completion to ensure the generated script meets requirements + else: + skill_memory["scripts"] = {} + + # Tool + tool_req = skill_memory.get("tool") + if tool_req: + # Extract available tool schemas from related memories + tool_memories = [ + memory + for memory in related_skill_memories_map.get(task_type, []) + if memory.metadata.memory_type == "ToolSchemaMemory" + ] + tool_schemas_list = [memory.memory for memory in tool_memories] + + tool_schemas_str = ( + "\n\n".join( + [ + f"Tool Schema {i + 1}:\n{schema}" + for i, schema in enumerate(tool_schemas_list) + ] + ) + if tool_schemas_list + else "No specific tool schemas available." + ) + + generation_tasks.append( + create_task( + skill_memory, + "tool", + TOOL_GENERATION_PROMPT, + tool_req, + messages_context, + tool_schemas=tool_schemas_str, + ) + ) + else: + skill_memory["tool"] = {} + + lang = detect_lang(messages_context) + others_req = skill_memory.get("others") + if others_req and isinstance(others_req, dict): + for filename, summary in others_req.items(): + generation_tasks.append( + { + "type": "others", + "skill_memory": skill_memory, + "key": filename, + "func": _generate_content_by_llm, + "args": ( + llm, + OTHERS_GENERATION_PROMPT_ZH + if lang == "zh" + else OTHERS_GENERATION_PROMPT, + ), + "kwargs": { + "filename": filename, + "summary": summary, + "context": messages_context, + }, + } + ) + else: + skill_memory["others"] = {} + + if not generation_tasks: + return [item[0] for item in raw_skills_data] + + # 2. Execute all tasks in parallel + with ContextThreadPoolExecutor(max_workers=min(len(generation_tasks), 5)) as executor: + futures = { + executor.submit(t["func"], *t["args"], **t["kwargs"]): t for t in generation_tasks + } + + for future in as_completed(futures): + task_info = futures[future] + try: + result = future.result() + if not result: + continue + + skill_mem = task_info["skill_memory"] + + if task_info["type"] == "scripts": + if isinstance(result, dict): + # Combine code with script_req + try: + skill_mem["scripts"] = { + filename: f"# {abstract}:\n{code}" + for abstract, (filename, code) in zip( + script_req, result.items(), strict=False + ) + } + except ValueError: + logger.warning( + f"[PROCESS_SKILLS] Invalid script generation result: {result}" + ) + skill_mem["scripts"] = {} + + elif task_info["type"] == "tool": + skill_mem["tool"] = result + + elif task_info["type"] == "others": + if "others" not in skill_mem or not isinstance(skill_mem["others"], dict): + skill_mem["others"] = {} + skill_mem["others"][task_info["key"]] = ( + f"# {task_info['kwargs']['summary']}\n{result}" + ) + + except Exception as e: + logger.warning( + f"[PROCESS_SKILLS] Error in generation task {task_info['type']}: {e}" + ) + + return [item[0] for item in raw_skills_data] + + def add_id_to_mysql(memory_id: str, mem_cube_id: str): """Add id to mysql, will deprecate this function in the future""" # TODO: tmp function, deprecate soon @@ -267,6 +468,125 @@ def _extract_skill_memory_by_llm( return None +def _extract_skill_memory_by_llm_md( + messages: MessageList, + old_memories: list[TextualMemoryItem], + llm: BaseLLM, + chat_history: MessageList, + chat_history_max_length: int = 5000, +) -> dict[str, Any]: + old_memories_dict = [memory.model_dump() for memory in old_memories] + old_memories_context = {} + old_skill_content = [] + seen_messages = set() + + for mem in old_memories_dict: + if mem["metadata"]["memory_type"] == "SkillMemory" and mem["metadata"]["relativity"] > 0.02: + old_skill_content.append( + { + "id": mem["id"], + "name": mem["metadata"]["name"], + "description": mem["metadata"]["description"], + "procedure": mem["metadata"]["procedure"], + "experience": mem["metadata"]["experience"], + "preference": mem["metadata"]["preference"], + "examples": mem["metadata"]["examples"], + "others": mem["metadata"].get("others"), # TODO: maybe remove, too long + } + ) + else: + # Filter and deduplicate messages + unique_messages = [] + for item in mem["metadata"]["sources"]: + message_content = f"{item['role']}: {item['content']}" + if message_content not in seen_messages: + seen_messages.add(message_content) + unique_messages.append(message_content) + + if unique_messages: + old_memories_context.setdefault(mem["metadata"]["memory_type"], []).extend( + unique_messages + ) + + # Prepare current conversation context + messages_context = "\n".join( + [f"{message['role']}: {message['content']}" for message in messages] + ) + + # Prepare history context + chat_history_context = "\n".join( + [f"{history['role']}: {history['content']}" for history in chat_history] + ) + chat_history_context = chat_history_context[-chat_history_max_length:] + + # Prepare old memories context + old_skill_content = ( + ("Exsit Skill Schemas: \n" + json.dumps(old_skill_content, ensure_ascii=False, indent=2)) + if old_skill_content + else "" + ) + + old_memories_context = "Relavant Context:\n" + "\n".join( + [f"{k}:\n{v}" for k, v in old_memories_context.items()] + ) + + # Prepare prompt + lang = detect_lang(messages_context) + template = ( + SKILL_MEMORY_EXTRACTION_PROMPT_MD_ZH if lang == "zh" else SKILL_MEMORY_EXTRACTION_PROMPT_MD + ) + prompt_content = ( + template.replace("{old_memories}", old_memories_context + old_skill_content) + .replace("{messages}", messages_context) + .replace("{chat_history}", chat_history_context) + ) + + prompt = [{"role": "user", "content": prompt_content}] + logger.info(f"[Skill Memory]: _extract_skill_memory_by_llm_md: Prompt {prompt_content}") + + # Call LLM to extract skill memory with retry logic + for attempt in range(3): + try: + # Only pass model_name_or_path if SKILLS_LLM is set + skills_llm = os.getenv("SKILLS_LLM", None) + llm_kwargs = {"model_name_or_path": skills_llm} if skills_llm else {} + response_text = llm.generate(prompt, **llm_kwargs) + # Clean up response (remove Markdown code blocks if present) + logger.info(f"[Skill Memory]: response_text {response_text}") + response_text = response_text.strip() + response_text = response_text.replace("```json", "").replace("```", "").strip() + + # Parse JSON response + skill_memory = json.loads(response_text) + + # If LLM returns null (parsed as None), log and return None + if skill_memory is None: + logger.info( + "[PROCESS_SKILLS] No skill memory extracted from conversation (LLM returned null)" + ) + return None + + return skill_memory + + except json.JSONDecodeError as e: + logger.warning(f"[PROCESS_SKILLS] JSON decode failed (attempt {attempt + 1}): {e}") + logger.debug(f"[PROCESS_SKILLS] Response text: {response_text}") + if attempt == 2: + logger.warning("[PROCESS_SKILLS] Failed to parse skill memory after 3 retries") + return None + except Exception as e: + logger.warning( + f"[PROCESS_SKILLS] LLM skill memory extraction failed (attempt {attempt + 1}): {e}" + ) + if attempt == 2: + logger.warning( + "[PROCESS_SKILLS] LLM skill memory extraction failed after 3 retries" + ) + return None + + return None + + def _recall_related_skill_memories( task_type: str, messages: MessageList, @@ -280,7 +600,7 @@ def _recall_related_skill_memories( related_skill_memories = searcher.search( query, top_k=5, - memory_type="SkillMemory", + memory_type="All", info=info, include_skill_memory=True, user_name=mem_cube_id, @@ -392,6 +712,11 @@ def _write_skills_to_file( --- """ + # 加入trigger + trigger = skill_memory.get("trigger", "") + if trigger: + skill_md_content += f"\n## Trigger\n{trigger}\n" + # Add Procedure section only if present procedure = skill_memory.get("procedure", "") if procedure and procedure.strip(): @@ -426,6 +751,10 @@ def _write_skills_to_file( for script_name in scripts: skill_md_content += f"- `./scripts/{script_name}`\n" + tool_usage = skill_memory.get("tool", "") + if tool_usage: + skill_md_content += f"\n## Tool Usage\n{tool_usage}\n" + # Add others - handle both inline content and separate markdown files others = skill_memory.get("others") if others and isinstance(others, dict): @@ -451,7 +780,7 @@ def _write_skills_to_file( skill_md_content += "\n## Additional Information\n" skill_md_content += "\nSee also:\n" for md_filename in md_files: - skill_md_content += f"- [{md_filename}](./{md_filename})\n" + skill_md_content += f"- [{md_filename}](./reference/{md_filename})\n" # Write SKILL.md file skill_md_path = skill_dir / "SKILL.md" @@ -462,7 +791,9 @@ def _write_skills_to_file( if others and isinstance(others, dict): for key, value in others.items(): if key.endswith(".md"): - md_file_path = skill_dir / key + md_file_dir = skill_dir / "reference" + md_file_dir.mkdir(parents=True, exist_ok=True) + md_file_path = md_file_dir / key with open(md_file_path, "w", encoding="utf-8") as f: f.write(value) @@ -521,7 +852,7 @@ def create_skill_memory_item( session_id=session_id, memory_type="SkillMemory", status="activated", - tags=skill_memory.get("tags", []), + tags=skill_memory.get("tags") or skill_memory.get("trigger", []), key=skill_memory.get("name", ""), sources=[], usage=[], @@ -568,6 +899,7 @@ def process_skill_memory_fine( rewrite_query: bool = True, oss_config: dict[str, Any] | None = None, skills_dir_config: dict[str, Any] | None = None, + complete_skill_memory: bool = True, **kwargs, ) -> list[TextualMemoryItem]: # Validate required configurations @@ -641,26 +973,56 @@ def process_skill_memory_fine( ) related_skill_memories_by_task[task_name] = [] - skill_memories = [] - with ContextThreadPoolExecutor(max_workers=5) as executor: - futures = { - executor.submit( - _extract_skill_memory_by_llm, - messages, - related_skill_memories_by_task.get(task_type, []), - llm, - chat_history, - ): task_type - for task_type, messages in task_chunks.items() - } - for future in as_completed(futures): - try: - skill_memory = future.result() - if skill_memory: # Only add non-None results - skill_memories.append(skill_memory) - except Exception as e: - logger.warning(f"[PROCESS_SKILLS] Error extracting skill memory: {e}") - continue + def _simple_extract(): + # simple extract skill memory, only one stage + memories = [] + with ContextThreadPoolExecutor(max_workers=min(5, len(task_chunks))) as executor: + futures = { + executor.submit( + _extract_skill_memory_by_llm, + messages=chunk_messages, + # Filter only SkillMemory types + old_memories=[ + item + for item in related_skill_memories_by_task.get(task_type, []) + if item and getattr(item.metadata, "memory_type", "") == "SkillMemory" + ], + llm=llm, + chat_history=chat_history, + ): task_type + for task_type, chunk_messages in task_chunks.items() + } + + for future in as_completed(futures): + task_type = futures[future] + try: + skill_memory = future.result() + if skill_memory: + memories.append(skill_memory) + except Exception as e: + logger.warning( + f"[PROCESS_SKILLS] _simple_extract: Error processing task '{task_type}': {e}" + ) + return memories + + def _full_extract(): + # full extract skill memory, include two stage + raw_extraction_results = _batch_extract_skills( + task_chunks=task_chunks, + related_memories_map=related_skill_memories_by_task, + llm=llm, + chat_history=chat_history, + ) + if not raw_extraction_results: + return [] + return _batch_generate_skill_details( + raw_skills_data=raw_extraction_results, + related_skill_memories_map=related_skill_memories_by_task, + llm=llm, + ) + + # Execute both parts in parallel + skill_memories = _simple_extract() if not complete_skill_memory else _full_extract() # write skills to file and get zip paths skill_memory_with_paths = [] diff --git a/src/memos/templates/skill_mem_prompt.py b/src/memos/templates/skill_mem_prompt.py index df64d736d..7c60e2147 100644 --- a/src/memos/templates/skill_mem_prompt.py +++ b/src/memos/templates/skill_mem_prompt.py @@ -3,29 +3,29 @@ {{messages}} # Role -You are an expert in natural language processing (NLP) and dialogue logic analysis. You excel at organizing logical threads from complex long conversations and accurately extracting users' core intentions. +You are an expert in natural language processing (NLP) and dialogue logic analysis. You excel at organizing logical threads from complex long conversations and accurately extracting users' core intentions to segment the dialogue into distinct tasks. # Task -Please analyze the provided conversation records, identify all independent "tasks" that the user has asked the AI to perform, and assign the corresponding dialogue message numbers to each task. +Please analyze the provided conversation records, identify all independent "tasks" that the user has asked the AI to perform, and assign the corresponding dialogue message indices to each task. -**Note**: Tasks should be high-level and general, typically divided by theme or topic. For example: "Travel Planning", "PDF Operations", "Code Review", "Data Analysis", etc. Avoid being too specific or granular. +**Note**: Tasks should be high-level and general. Group similar activities under broad themes such as "Travel Planning", "Project Engineering & Implementation", "Code Review", "Data Analysis", etc. Avoid being overly specific or granular. # Rules & Constraints -1. **Task Independence**: If multiple unrelated topics are discussed in the conversation, identify them as different tasks. -2. **Non-continuous Processing**: Pay attention to identifying "jumping" conversations. For example, if the user made travel plans in messages 8-11, switched to consulting about weather in messages 12-22, and then returned to making travel plans in messages 23-24, be sure to assign both 8-11 and 23-24 to the task "Making travel plans". However, if messages are continuous and belong to the same task, do not split them apart. -3. **Filter Chit-chat**: Only extract tasks with clear goals, instructions, or knowledge-based discussions. Ignore meaningless greetings (such as "Hello", "Are you there?") or closing remarks unless they are part of the task context. -4. **Main Task and Subtasks**: Carefully identify whether subtasks serve a main task. If a subtask supports the main task (e.g., "checking weather" serves "travel planning"), do NOT separate it as an independent task. Instead, include all related conversations in the main task. Only split tasks when they are truly independent and unrelated. -5. **Output Format**: Please strictly follow the JSON format for output to facilitate my subsequent processing. -6. **Language Consistency**: The language used in the task_name field must match the language used in the conversation records. -7. **Generic Task Names**: Use generic, reusable task names, not specific descriptions. For example, use "Travel Planning" instead of "Planning a 5-day trip to Chengdu". +1. **Task Independence**: If multiple completely unrelated topics are discussed, identify them as different tasks. +2. **Main Task and Subtasks**: Carefully identify whether a subtask serves a primary objective. If a specific request supports a larger goal (e.g., "checking weather" within a "Travel Planning" thread), do NOT separate it. Include all supporting conversations within the main task. **Only split tasks when they are truly independent and unrelated.** +3. **Non-continuous Processing**: Identify "jumping" or "interleaved" conversations. For example, if the user works on Travel Planning in messages 8-11, switches topics in 12-22, and returns to Travel Planning in 23-24, assign both [8, 11] and [23, 24] to the same "Travel Planning" task. Conversely, if messages are continuous and belong to the same task, keep them as a single range. +4. **Filter Chit-chat**: Only extract tasks with clear goals, instructions, or knowledge-based discussions. Ignore meaningless greetings (e.g., "Hello", "Are you there?") or polite closings unless they contain necessary context for the task. +5. **Output Format**: Strictly follow the JSON format below for automated processing. +6. **Language Consistency**: The language used in the `task_name` field must match the primary language used in the conversation records. +7. **Generic Task Names**: Use broad, reusable task categories. For example, use "Travel Planning" instead of "Planning a 5-day trip to Chengdu". ```json [ { "task_id": 1, - "task_name": "Generic task name (e.g., Travel Planning, Code Review, Data Analysis)", - "message_indices": [[0, 5],[16, 17]], # 0-5 and 16-17 are the message indices for this task - "reasoning": "Briefly explain why these messages are grouped together" + "task_name": "Generic task name (e.g., Travel Planning, Code Review)", + "message_indices": [[0, 5], [16, 17]], + "reasoning": "Briefly explain the logic behind grouping these indices and how they relate to the core intent." }, ... ] @@ -34,31 +34,30 @@ TASK_CHUNKING_PROMPT_ZH = """ -# 上下文(对话记录) +# 上下文(历史对话记录) {{messages}} # 角色 -你是自然语言处理(NLP)和对话逻辑分析的专家。你擅长从复杂的长对话中整理逻辑线索,准确提取用户的核心意图。 +你是自然语言处理(NLP)和对话逻辑分析的专家。你擅长从复杂的长对话中整理逻辑线索,准确提取用户的不同意图,从而按照不同的意图对上述对话进行任务划分。 -# 任务 +# 目标 请分析提供的对话记录,识别所有用户要求 AI 执行的独立"任务",并为每个任务分配相应的对话消息编号。 -**注意**:任务应该是高层次和通用的,通常按主题或话题划分。例如:"旅行计划"、"PDF操作"、"代码审查"、"数据分析"等。避免过于具体或细化。 +**注意**:上述划分"任务"应该是高层次且通用的,通常按主题或任务类型划分,对同目标或相似的任务进行合并,例如:"旅行计划"、"项目工程设计与实现"、"代码审查" 等,避免过于具体或细化。 # 规则与约束 -1. **任务独立性**:如果对话中讨论了多个不相关的话题,请将它们识别为不同的任务。 -2. **非连续处理**:注意识别"跳跃式"对话。例如,如果用户在消息 8-11 中制定旅行计划,在消息 12-22 中切换到咨询天气,然后在消息 23-24 中返回到制定旅行计划,请务必将 8-11 和 23-24 都分配给"制定旅行计划"任务。但是,如果消息是连续的且属于同一任务,不能将其分开。 -3. **过滤闲聊**:仅提取具有明确目标、指令或基于知识的讨论的任务。忽略无意义的问候(例如"你好"、"在吗?")或结束语,除非它们是任务上下文的一部分。 -4. **主任务与子任务识别**:仔细识别子任务是否服务于主任务。如果子任务是为主任务服务的(例如"查天气"服务于"旅行规划"),不要将其作为独立任务分离出来,而是将所有相关对话都划分到主任务中。只有真正独立且无关联的任务才需要分开。 +1. **任务独立性**:如果对话中讨论了多个完全不相关的话题,请将它们识别为不同的任务。 +2. **主任务与子任务识别**:仔细识别划分的任务是否服务于主任务。如果某一个任务是为了完成主任务而服务的(例如"旅行规划"的对话中出现了"查天气"),不要将其作为独立任务分离出来,而是将所有相关对话都划分到主任务中。**只有真正独立且无关联的任务才需要分开。** +3. **非连续处理**:注意识别"跳跃式"对话。例如,如果用户在消息 8-11 中制定旅行计划,在消息 12-22 中切换到其他任务,然后在消息 23-24 中返回到制定旅行计划,请务必将 8-11 和 23-24 都分配给"制定旅行计划"任务。按照规则2的描述,如果消息是连续的且属于同一任务,不能将其分开。 +4. **过滤闲聊**:仅提取具有明确目标、指令或基于知识的讨论的任务。忽略无意义的问候(例如"你好"、"在吗?")或结束语,除非它们是任务上下文的一部分。 5. **输出格式**:请严格遵循 JSON 格式输出,以便我后续处理。 -6. **语言一致性**:task_name 字段使用的语言必须与对话记录中使用的语言相匹配。 -7. **通用任务名称**:使用通用的、可复用的任务名称,而不是具体的描述。例如,使用"旅行规划"而不是"规划成都5日游"。 +6. **通用任务名称**:使用通用的、可复用的任务名称,而不是具体的描述。例如,使用"旅行规划"而不是"规划成都5日游"。 ```json [ { "task_id": 1, - "task_name": "通用任务名称(例如:旅行规划、代码审查、数据分析)", + "task_name": "通用任务名称", "message_indices": [[0, 5],[16, 17]], # 0-5 和 16-17 是此任务的消息索引 "reasoning": "简要解释为什么这些消息被分组在一起" }, @@ -67,7 +66,6 @@ ``` """ - SKILL_MEMORY_EXTRACTION_PROMPT = """ # Role You are an expert in skill abstraction and knowledge extraction. You excel at distilling general, reusable methodologies from specific conversations. @@ -229,6 +227,152 @@ """ +SKILL_MEMORY_EXTRACTION_PROMPT_MD = """ +# Role +You are an expert in skill abstraction and knowledge extraction. You excel at distilling general, reusable methodologies and executable workflows from specific conversations to enable direct application in future similar scenarios. + +# Task +Analyze the current messages and chat history to extract a universal, effective skill template. Compare the extracted methodology with existing skill memories (checking descriptions and triggers) to determine if this should be a new entry or an update to an existing one. + +# Prerequisites +## Long Term Relevant Memories +{old_memories} + +## Short Term Conversation +{chat_history} + +## Conversation Messages +{messages} + +# Skill Extraction Principles +To define the content of a skill, comprehensively analyze the dialogue content to create a list of reusable resources, including scripts, reference materials, and resources. Please generate the skill according to the following principles: +1. **Generalization**: Extract abstract methodologies that can be applied across scenarios. Avoid specific details (e.g., 'travel planning' rather than 'Beijing travel planning'). Moreover, the skills acquired should be durable and effective, rather than tied to a specific time. +2. **Similarity Check**: If a similar skill exists, set "update": true and provide the "old_memory_id". Otherwise, set "update": false and leave "old_memory_id" blank. +3. **Language Consistency**: Keep consistent with the language of the dialogue. +4. **Historical Usage Constraint**: Use 'historically related dialogues' as auxiliary context. If the current historical messages are insufficient to form a complete skill, and the historically related dialogue can provide missing information in the messages that is related to the current task objectives, execution methods, or constraints, it may be considered. +5. If the abstract methodology you extract and an existing skill memory describe the same topic (such as the same life scenario), be sure to use the update operation rather than creating a new methodology. Properly append it to the existing skill memory to ensure fluency and retain the information of the existing methodology. + +# Output Format and Field Specifications +## Output Format +```json +{ + "name": "General skill name (e.g., 'Travel Itinerary Planning', 'Code Review Workflow')", + "description": "Universal description of what this skill accomplishes and its scope", + "trigger": ["keyword1", "keyword2"], + "procedure": "Generic step-by-step process: 1. Step one 2. Step two...", + "experience": ["General principles or lessons learned", "Error handling strategies", "Best practices..."], + "preference": ["User's general preference patterns", "Preferred approaches or constraints..."], + "update": false, + "old_memory_id": "", + "content_of_current_message": "Summary of core content from current messages", + "whether_use_chat_history": false, + "content_of_related_chat_history": "", + "examples": ["Complete formatted output example in markdown format showing the final deliverable structure, content can be abbreviated with '...' but should demonstrate the format and structure"], + "scripts": a TODO list of code and requirements. Use null if no specific code are required. + "tool": List of specific external tools required (for example, if links or API information appear in the context, a websearch or external API may be needed), not product names or system tools (e.g., Python, Redis, or MySQL). If no specific tools are needed, please use null. + "others": {"reference.md": "A concise summary of other reference need to be provided (e.g., examples, tutorials, or best practices) "}. Only need to give the writing requirements, no need to provide the full documentation content. +} +``` + +## Field Specifications +- **name**: Generic skill identifier without specific instances. +- **description**: Universal purpose and applicability. +- **trigger**: List of keywords that should activate this skill. +- **procedure**: Abstract, reusable process steps without specific details. Should be generalizable to similar tasks. +- **experience**: General lessons, principles, or insights. +- **preference**: User's overarching preference patterns. +- **update**: true if updating existing skill, false if new. +- **old_memory_id**: ID of skill being updated, or empty string if new. +- **whether_use_chat_history**: Indicates whether information from chat_history that does not appear in messages was incorporated into the skill. +- **content_of_related_chat_history**: If whether_use_chat_history is true, provide a high-level summary of the type of historical information used (e.g., “long-term preference: prioritizes cultural attractions”); do not quote the original dialogue verbatim. If not used, leave this field as an empty string. +- **examples**: Complete output templates showing the final deliverable format and structure. Should demonstrate how the task result looks when this skill is applied, including format, sections, and content organization. Content can be abbreviated but must show the complete structure. Use markdown format for better readability +- **scripts**: If the skill examples requires an implementation involving code, you must provide a TODO list that clearly enumerates: (1) The components or steps that need to be implemented, (2) The expected inputs, (3)The expected outputs. Detailed code or full implementations are not required. Use null if no specific code is required. +- **tool**: If links or interface information appear in the context, it indicates that the skill needs to rely on specific tools (such as websearch, external APIs, or system tools) during the answering process. Please list the tool names. If no specific tools are detected, please use null. +- **others**: If must have additional supporting sections for the skill or other dependencies, structured as key–value pairs. For example: {"reference.md": "A concise summary of the reference content"}. Only need to give the writing requirements, no need to provide the full documentation content. + +# Key Guidelines +- Return null if a skill cannot be extracted. +- Only create a new methodology when necessary. In the same scenario, try to merge them ("update": true). +For example, merge dietary planning into one entry. Do not add a new "Keto Diet Planning" if "Dietary Planning" already exists, because skills are a universal template. You can choose to add preferences and triggers to update "Dietary Planning". + +# Output Format +Output the JSON object only. +""" + + +SKILL_MEMORY_EXTRACTION_PROMPT_MD_ZH = """ +# 角色 +你是技能抽象和知识提取的专家。你擅长从上下文的具体对话中提炼通用的、可复用的方法流程,从而可以在后续遇到相似任务中允许直接执行该工作流程及脚本。 + +# 任务 +通过分析历史相关对话和**给定当前对话消息**中提取可应用于类似场景的**有效且通用**的技能模板,同时还需要分析现有的技能的描述和触发关键字(trigger),判断与当前对话是否相关,从而决定技能是需要新建还是更新。 + +# 先决条件 +## 长期相关记忆 +{old_memories} + +## 短期对话 +{chat_history} + +## 当前对话消息 +{messages} + +# 技能提取原则 +为了确定技能的内容,综合分析对话内容以创建可重复使用资源的清单,包括脚本、参考资料和资源,请你按照下面的原则来生成技能: +1. **通用化**:提取可跨场景应用的抽象方法论。避免具体细节(如"旅行规划"而非"北京旅行规划")。 而且提取的技能应该是持久有效的,而非与特定时间绑定。 +2. **相似性检查**:如存在相似技能,设置"update": true 及"old_memory_id"。否则设置"update": false 并将"old_memory_id"留空。 +3. **语言一致性**:与对话语言保持一致。 +4. **历史使用约束**:“历史相关对话”作为辅助上下文,若当前历史消息不足以形成完整的技能,且历史相关对话能提供 messages 中缺失、且与当前任务目标、执行方式或约束相关的信息增量时,可以纳入考虑。 +5. 如果你提取的抽象方法论和已有的技能记忆描述的是同一个主题(比如同一个生活场景),请务必使用更新操作,不要新建一个方法论,注意合理的追加到已有的技能记忆上,保证通顺且不丢失已有方法论的信息。 + +# 输出格式的模版和字段规范描述 +## 输出格式 +```json +{ + "name": "通用技能名称(如:'旅行行程规划'、'代码审查流程')", + "description": "技能作用的通用描述", + "trigger": ["关键词1", "关键词2"], + "procedure": "通用的分步流程:1. 步骤一 2. 步骤二...", + "experience": ["通用原则或经验教训", "对于可能出现错误的处理情况", "可应用于类似场景的最佳实践..."], + "preference": ["用户的通用偏好模式", "偏好的方法或约束..."], + "update": false, + "old_memory_id": "", + "content_of_current_message": "", + "whether_use_chat_history": false, + "content_of_related_chat_history": "", + "examples": ["展示最终交付成果的完整格式范本(使用 markdown 格式), 内容可用'...'省略,但需展示完整格式和结构"], + "scripts": "一个代码待办列表和需求说明。如果不需要特定代码,请使用 null.", + "tool": "所需特定外部工具列表(例如,如果上下文中出现了链接或接口信息,则需要使用websearch或外部 API)。", + "others": {"reference.md": "其他对于执行技能必须的参考内容(例如,示例、教程或最佳实践)"}。只需要给出撰写要求,无需完整的文档内容。 +} +``` + +## 字段规范 +- **name**:通用技能标识符,不含具体实例 +- **description**:通用用途和适用范围 +- **trigger**:触发技能执行的关键字列表,用于自动识别任务场景 +- **procedure**:抽象的、可复用的流程步骤,不含具体细节。应当能够推广到类似任务 +- **experience**:通用经验、原则或见解 +- **preference**:用户的整体偏好模式 +- **update**:更新现有技能为true,新建为false +- **old_memory_id**:被更新技能的ID,新建则为空字符串 +- **content_of_current_message**: 从当前对话消息中提取的核心内容(简写但必填), +- **whether_use_chat_history**:是否从 chat_history 中引用了 messages 中没有的内容并提取到skill中 +- **content_of_related_chat_history**:若 whether_use_chat_history 为 true,仅需概括性说明所使用的历史信息类型(如“长期偏好:文化类景点优先”),不要求逐字引用原始对话内容;若未使用,则置为空字符串。 +- **examples**:展示最终任务成果的输出模板,包括格式、章节和内容组织结构。应展示应用此技能后任务结果的样子,包含所有必要的部分。内容可以省略但必须展示完整结构。使用 markdown 格式以提高可读性 +- **scripts**:如果技能examples需要实现代码,必须提供一个待办列表,清晰枚举:(1) 需实现的组件或步骤,(2) 预期输入,(3) 预期输出。详细代码或完整实现不是必须的。如果不需要特定代码,请使用 null. +- **tool**:如果上下文中出现了链接或接口信息,则表明在回答过程中技能需要依赖特定工具(如websearch或外部 API),请列出工具名称。 +- **others**:如果必须要其他支持性章节或其他依赖项,格式为键值对,例如:{"reference.md": "参考内容的简要总结"}。只需要给出撰写要求,无需完整的文档内容。 + +# 关键指导 +- 无法提取技能时返回null +- 一定仅在必要时才新建方法论,同样的场景尽量合并("update": true), +如饮食规划合并为一条,不要已有“饮食规划”的情况下,再新增一个“生酮饮食规划”,因为技能是一个通用的模版,可以选择添加preference和trigger来更新“饮食规划”。 + +请生成技能模版,返回上述JSON对象 +""" + + TASK_QUERY_REWRITE_PROMPT = """ # Role You are an expert in understanding user intentions and task requirements. You excel at analyzing conversations and extracting the core task description. @@ -284,3 +428,107 @@ SKILLS_AUTHORING_PROMPT = """ """ + + +SCRIPT_GENERATION_PROMPT = """ +# Role +You are a Senior Python Developer and Architect. + +# Task +Generate production-ready, executable Python scripts based on the provided requirements and context. +The scripts will be part of a skill package used by an AI agent or a developer. + +# Requirements +{requirements} + +# Context +{context} + +# Instructions +1. **Completeness**: The code must be fully functional and self-contained. DO NOT use placeholders like `# ...`, `pass` (unless necessary), or `TODO`. +2. **Robustness**: Include comprehensive error handling (try-except blocks) and input validation. +3. **Style**: Follow PEP 8 guidelines. Use type hints for all function signatures. +4. **Dependencies**: Use standard libraries whenever possible. If external libraries are needed, list them in a comment at the top. +5. **Main Guard**: Include `if __name__ == "__main__":` blocks with example usage or test cases. + +# Output Format +Return ONLY a valid JSON object where keys are filenames (e.g., "utils.py", "main_task.py") and values are the raw code strings. +```json +{{ + "filename.py": "import os\\n\\ndef func():\\n ..." +}} +``` +""" + +TOOL_GENERATION_PROMPT = """ +# Task +Analyze the `Requirements` and `Context` to identify the relevant tools from the provided `Available Tools`. Return a list of the **names** of the matching tools. + +# Constraints +1. **Selection Criteria**: Include a tool name only if the tool's schema directly addresses the user's requirements. +2. **Empty Set Logic**: If `Available Tools` is empty or no relevant tools are found, you **must** return an empty JSON array: `[]`. +3. **Format Purity**: Return ONLY the JSON array of strings. Do not provide commentary, justifications, or any text outside the JSON block. + +# Available Tools +{tool_schemas} + +# Requirements +{requirements} + +# Context +{context} + +# Output +```json +[ + "tool_name_1", + "tool_name_2" +] +``` +""" + +OTHERS_GENERATION_PROMPT = """ +# Task +Create detailed, well-structured documentation for the file '{filename}' based on the provided summary and context. + +# Summary +{summary} + +# Context +{context} + +# Instructions +1. **Structure**: + - **Introduction**: Brief overview of the topic. + - **Detailed Content**: The main body of the documentation, organized with headers (##, ###). + - **Key Concepts/Reference**: Definitions or reference tables if applicable. + - **Conclusion/Next Steps**: Wrap up or point to related resources. +2. **Formatting**: Use Markdown effectively (lists, tables, code blocks, bold text) to enhance readability. +3. **Language Consistency**: Keep consistent with **the language of the context**. + +# Output Format +Return the content directly in Markdown format. +""" + +OTHERS_GENERATION_PROMPT_ZH = """ +# 任务 +根据提供的摘要和上下文,为文件 '{filename}' 创建详细且结构良好的文档。 + +# 摘要 +{summary} + +# 上下文 +{context} + +# 指南 +1. **结构**: +- **简介**:对主题进行简要概述。 +- **详细内容**:文档的主体内容,使用标题(##, ###)进行组织。 +- **关键概念/参考**:如果适用,提供定义或参考表格。 +- **结论/下一步**:总结或指向相关资源。 +2. **格式**:有效使用 Markdown(列表、表格、代码块、加粗文本)以增强可读性。 +3. **语言一致性**:保持与**上下文语言**一致。 + +# 输出格式 +以 Markdown 格式直接返回内容。 +"""