diff --git a/scripts/demo_run_tools.py b/scripts/demo_run_tools.py index ebdafef8..23726f12 100644 --- a/scripts/demo_run_tools.py +++ b/scripts/demo_run_tools.py @@ -26,4 +26,4 @@ if __name__ == "__main__": # apply a tool find_refs_tool = agent.get_tool(FindReferencingSymbolsTool) print("Finding the symbol 'SyncLanguageServer'\n") - pprint(json.loads(find_refs_tool.apply(name_path="SyncLanguageServer", relative_file_path="src/multilspy/language_server.py"))) + pprint(json.loads(find_refs_tool.apply(name_path="SyncLanguageServer", relative_path="src/multilspy/language_server.py"))) diff --git a/src/serena/agent.py b/src/serena/agent.py index 039174c7..c919ada5 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -445,6 +445,8 @@ class MemoriesManagerMDFilesInProject(MemoriesManager): self._memory_dir.mkdir(parents=True, exist_ok=True) def _get_memory_file_path(self, name: str) -> Path: + # strip all .md from the name. Models tend to get confused, sometimes passing the .md extension and sometimes not. + name = name.replace(".md", "") filename = f"{name}.md" return self._memory_dir / filename @@ -462,7 +464,7 @@ class MemoriesManagerMDFilesInProject(MemoriesManager): return f"Memory {name} written." def list_memories(self) -> list[str]: - return [f.name for f in self._memory_dir.iterdir() if f.is_file()] + return [f.name.replace(".md", "") for f in self._memory_dir.iterdir() if f.is_file()] def delete_memory(self, name: str) -> str: memory_file_path = self._get_memory_file_path(name) @@ -1167,7 +1169,7 @@ class FindSymbolTool(Tool): self, name_path: str, depth: int = 0, - within_relative_path: str | None = None, + relative_path: str | None = None, include_body: bool = False, include_kinds: list[int] | None = None, exclude_kinds: list[int] | None = None, @@ -1207,7 +1209,11 @@ class FindSymbolTool(Tool): :param name_path: The name path pattern to search for, see above for details. :param depth: Depth to retrieve descendants (e.g., 1 for class methods/attributes). - :param within_relative_path: Optional. Restrict search to this file or directory. If None, searches entire codebase. + :param relative_path: Optional. Restrict search to this file or directory. If None, searches entire codebase. + If a directory is passed, the search will be restricted to the files in that directory. + If a file is passed, the search will be restricted to that file. + If you have some knowledge about the codebase, you should use this parameter, as it will significantly + speed up the search as well as reduce the number of results. :param include_body: If True, include the symbol's source code. Use judiciously. :param include_kinds: Optional. List of LSP symbol kind integers to include. (e.g., 5 for Class, 12 for Function). Valid kinds: 1=file, 2=module, 3=namespace, 4=package, 5=class, 6=method, 7=property, 8=field, 9=constructor, 10=enum, @@ -1226,7 +1232,7 @@ class FindSymbolTool(Tool): include_kinds=parsed_include_kinds, exclude_kinds=parsed_exclude_kinds, substring_matching=substring_matching, - within_relative_path=within_relative_path, + within_relative_path=relative_path, ) symbol_dicts = [s.to_dict(kind=True, location=True, depth=depth, include_body=include_body) for s in symbols] result = json.dumps(symbol_dicts) @@ -1241,7 +1247,7 @@ class FindReferencingSymbolsTool(Tool): def apply( self, name_path: str, - relative_file_path: str, + relative_path: str, include_kinds: list[int] | None = None, exclude_kinds: list[int] | None = None, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH, @@ -1253,7 +1259,8 @@ class FindReferencingSymbolsTool(Tool): as subclasses are referencing symbols that have the kind class. :param name_path: for finding the symbol to find references for, same logic as in the `find_symbol` tool. - :param relative_file_path: the relative path to the file containing the symbol for which to find references. + :param relative_path: the relative path to the file containing the symbol for which to find references. + Note that here you can't pass a directory but must pass a file. :param include_kinds: same as in the `find_symbol` tool. :param exclude_kinds: same as in the `find_symbol` tool. :param max_answer_chars: same as in the `find_symbol` tool. @@ -1264,7 +1271,7 @@ class FindReferencingSymbolsTool(Tool): parsed_exclude_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in exclude_kinds] if exclude_kinds else None references_in_symbols = self.symbol_manager.find_referencing_symbols( name_path, - relative_file_path=relative_file_path, + relative_file_path=relative_path, include_body=include_body, include_kinds=parsed_include_kinds, exclude_kinds=parsed_exclude_kinds, @@ -1610,12 +1617,13 @@ class WriteMemoryTool(Tool): Writes a named memory (for future reference) to Serena's project-specific memory store. """ - def apply(self, memory_file_name: str, content: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: + def apply(self, memory_name: str, content: str, max_answer_chars: int = _DEFAULT_MAX_ANSWER_LENGTH) -> str: """ - Write some information about this project that can be useful for future tasks to a memory file. + Write some information about this project that can be useful for future tasks to a memory. + Use markdown formatting for the content. The information should be short and to the point. - The memory file name should be meaningful, such that from the name you can infer what the information is about. - It is better to have multiple small memory files than to have a single large one because + The memory name should be meaningful, such that from the name you can infer what the information is about. + It is better to have multiple small memories than to have a single large one because memories will be read one by one and we only ever want to read relevant memories. This tool is either called during the onboarding process or when you have identified @@ -1623,11 +1631,10 @@ class WriteMemoryTool(Tool): """ if len(content) > max_answer_chars: raise ValueError( - f"Content for {memory_file_name} is too long. Max length is {max_answer_chars} characters. " - + "Please make the content shorter." + f"Content for {memory_name} is too long. Max length is {max_answer_chars} characters. " + "Please make the content shorter." ) - return self.memories_manager.save_memory(memory_file_name, content) + return self.memories_manager.save_memory(memory_name, content) class ReadMemoryTool(Tool): diff --git a/src/serena/resources/config/modes/planning.yml b/src/serena/resources/config/modes/planning.yml index c76501e8..a3f76d79 100644 --- a/src/serena/resources/config/modes/planning.yml +++ b/src/serena/resources/config/modes/planning.yml @@ -3,27 +3,6 @@ prompt: | You are operating in planning mode. Your task is to analyze code but not write any code. The user may ask you to assist in creating a comprehensive plan, or to learn something about the codebase - either a small aspect of it or about the whole project. - When reading code in order to answer a user question or task, you should try reading only the necessary code. - Some tasks may require you to understand the architecture of large parts of the codebase, while for others, - it may be enough to read a single file or a small set of symbols. - You can achieve the intelligent reading of code by using the symbolic tools for getting an overview of symbols and - the relations between them, and then only reading the bodies of symbols that are necessary to answer the question - or complete the task. You can also use the standard tools like list_dir, find_file and search_for_pattern if you need to. - - Symbols are identified by their name path and relative file path, see the description of the `find_symbols` tool for more details - on how the `name_path` matches symbols. - You can get information about available symbols by using the `get_symbols_overview` tool for finding top-level symbols in a file - or directory, or by using `find_symbol` if you already know the symbol's name path. You generally try to read as little code as possible - while still solving your task, meaning you only read the bodies when you need to, and after you have found the symbol you want to edit. - For example, if you are working with python code and already know that you need to read the body of the constructor of the class Foo, you can directly - use `find_symbol` with the name path `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit, - you can use `find_symbol` with the name path `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding - to read the desired methods with `include_body=True` - You can unterstand relationships between symbols by using the `find_referencing_symbols` tool. - - You generally have access to memories and it may be useful for you to read them, but also only if they help you - to answer the question or complete the task. You can infer which memories are relevant to the current task by reading - the memory names and descriptions. excluded_tools: - create_text_file - replace_symbol_body diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml index 344fc11e..fae3b25a 100644 --- a/src/serena/resources/config/prompt_templates/system_prompt.yml +++ b/src/serena/resources/config/prompt_templates/system_prompt.yml @@ -8,6 +8,33 @@ prompts: files containing general information about the codebase. You operate in a frugal and intelligent manner, always keeping in mind to not read or generate content that is not needed for the task at hand. + When reading code in order to answer a user question or task, you should try reading only the necessary code. + Some tasks may require you to understand the architecture of large parts of the codebase, while for others, + it may be enough to read a single file or a small set of symbols. + You can achieve the intelligent reading of code by using the symbolic tools for getting an overview of symbols and + the relations between them, and then only reading the bodies of symbols that are necessary to answer the question + or complete the task. You can also use the standard tools like list_dir, find_file and search_for_pattern if you need to. + When tools allow it, you pass the `relative_path` parameter to restrict the search to a specific file or directory. + For some tools, `relative_path` can only be a file path, so make sure to properly read the tool descriptions. + If you are unsure about a symbol's name or location (to the extent that substring_matching for the symbol name is not enough), you can use the `search_for_pattern` tool, which allows fast + and flexible search for patterns in the codebase. This way you can first find candidates for symbols or files, + and then proceed with the symbolic tools. + + Symbols are identified by their `name_path and `relative_path`, see the description of the `find_symbols` tool for more details + on how the `name_path` matches symbols. + You can get information about available symbols by using the `get_symbols_overview` tool for finding top-level symbols in a file + or directory, or by using `find_symbol` if you already know the symbol's name path. You generally try to read as little code as possible + while still solving your task, meaning you only read the bodies when you need to, and after you have found the symbol you want to edit. + For example, if you are working with python code and already know that you need to read the body of the constructor of the class Foo, you can directly + use `find_symbol` with the name path `Foo/__init__` and `include_body=True`. If you don't know yet which methods in `Foo` you need to read or edit, + you can use `find_symbol` with the name path `Foo`, `include_body=False` and `depth=1` to get all (top-level) methods of `Foo` before proceeding + to read the desired methods with `include_body=True` + You can unterstand relationships between symbols by using the `find_referencing_symbols` tool. + + You generally have access to memories and it may be useful for you to read them, but also only if they help you + to answer the question or complete the task. You can infer which memories are relevant to the current task by reading + the memory names and descriptions. + The context and modes of operation are described below. From them you can infer how to interact with your user and which tasks and kinds of interactions are expected of you. diff --git a/test/serena/test_serena_agent.py b/test/serena/test_serena_agent.py index a005a5df..3ec99838 100644 --- a/test/serena/test_serena_agent.py +++ b/test/serena/test_serena_agent.py @@ -83,7 +83,7 @@ class TestSerenaAgent: agent = serena_agent # Find the symbol location first find_symbol_tool = agent.get_tool(FindSymbolTool) - result = find_symbol_tool.apply(symbol_name, within_relative_path=def_file) + result = find_symbol_tool.apply(symbol_name, relative_path=def_file) time.sleep(1) symbols = json.loads(result) # Find the definition location @@ -92,7 +92,7 @@ class TestSerenaAgent: # sel_start = def_symbol["location"]["selectionRange"]["start"] # Now find references find_refs_tool = agent.get_tool(FindReferencingSymbolsTool) - result = find_refs_tool.apply(name_path=def_symbol["name_path"], relative_file_path=loc["relative_path"]) + result = find_refs_tool.apply(name_path=def_symbol["name_path"], relative_path=loc["relative_path"]) refs = json.loads(result) assert any( ref["location"]["relative_path"] == ref_file for ref in refs @@ -178,7 +178,7 @@ class TestSerenaAgent: result = find_symbol_tool.apply( name_path=name_path, depth=0, - within_relative_path=None, + relative_path=None, include_body=False, include_kinds=None, exclude_kinds=None,