From f0f3d8004e92e653c484c18ac5efe05eb34d280e Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Sun, 21 Dec 2025 10:17:50 +0700 Subject: [PATCH] Improved output structure in GetSymbolsOverviewTool, major token reduction! As before for jetbrains, grouped and without name_path. Using recursive structure for depth>0 --- src/serena/tools/jetbrains_tools.py | 2 ++ src/serena/tools/symbol_tools.py | 46 +++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/serena/tools/jetbrains_tools.py b/src/serena/tools/jetbrains_tools.py index 0c9f644e..846ccb6f 100644 --- a/src/serena/tools/jetbrains_tools.py +++ b/src/serena/tools/jetbrains_tools.py @@ -98,6 +98,8 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp Retrieves an overview of the top-level symbols within a specified file using the JetBrains backend """ + # TODO: support depth param, structure output in the same way as in GetSymbolsOverviewTool (by extracting + # the post-processing logic used there into a shared utility function) def apply( self, relative_path: str, diff --git a/src/serena/tools/symbol_tools.py b/src/serena/tools/symbol_tools.py index 75d0e5c9..64261874 100644 --- a/src/serena/tools/symbol_tools.py +++ b/src/serena/tools/symbol_tools.py @@ -7,6 +7,8 @@ from collections.abc import Sequence from copy import copy from typing import Any +from black.trans import defaultdict + from serena.tools import ( SUCCESS_RESULT, Tool, @@ -61,7 +63,15 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead): :param max_answer_chars: if the overview is longer than this number of characters, no content will be returned. -1 means the default value from the config will be used. Don't adjust unless there is really no other way to get the content required for the task. - :return: a JSON object containing info about top-level symbols in the file + :return: a JSON object containing symbols grouped by kind in a compact format. + + For depth=0, the structure is: {"Kind1": ["name1", "name2"], "Kind2": ["name3"]} + For depth>0, symbols with children use nested dicts: {"Class": [{"ClassName": {"Method": ["method1", "method2"]}}]} + + The name_path for any symbol can be inferred from the structure: + - Top-level symbols: name_path = name + - Nested symbols: name_path = parent_name + "/" + name + Example: "method1" under class "ClassName" has name_path "ClassName/method1" """ symbol_retriever = self.create_language_server_symbol_retriever() file_path = os.path.join(self.project.project_root, relative_path) @@ -73,9 +83,41 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead): if os.path.isdir(file_path): raise ValueError(f"Expected a file path, but got a directory path: {relative_path}. ") result = symbol_retriever.get_symbol_overview(relative_path, depth=depth)[relative_path] - result_json_str = self._to_json(result) + # Transform to compact format + compact_result = self._transform_symbols_to_compact_format(result) + result_json_str = self._to_json(compact_result) return self._limit_length(result_json_str, max_answer_chars) + @staticmethod + def _transform_symbols_to_compact_format(symbols: list[dict[str, Any]]) -> dict[str, list]: + """ + Transform symbol overview from verbose format to compact grouped format. + + Groups symbols by kind and uses names instead of full symbol objects. + For symbols with children, creates nested dictionaries. + + The name_path can be inferred from the hierarchical structure: + - Top-level symbols: name_path = name + - Nested symbols: name_path = parent_name + "/" + name + For example, "convert" under class "ProjectType" has name_path "ProjectType/convert". + """ + result = defaultdict(list) + + for symbol in symbols: + kind = symbol.get("kind", "Unknown") + name = symbol.get("name", "unknown") + children = symbol.get("children", []) + + if children: + # Symbol has children: create nested dict {name: children_dict} + children_dict = GetSymbolsOverviewTool._transform_symbols_to_compact_format(children) + result[kind].append({name: children_dict}) + else: + # Symbol has no children: just add the name + result[kind].append(name) + + return result + class FindSymbolTool(Tool, ToolMarkerSymbolicRead): """