Merge pull request #523 from oraios/feature/make_max_tokens_configurable

Config enhancement: allow configuring default_max_tool_answer_chars
This commit is contained in:
Michael Panchenko authored and GitHub committed 2025-08-23 17:17:05 +02:00
commit d6d1e78fee
8 files changed
+45 -31

No files matched your search

+6
View File
@@ -350,6 +350,11 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
on the first run, which can take some time and require internet access. Others, like the Anthropic ones, may require an API key
and rate limits may apply.
"""
default_max_tool_answer_chars: int = 150_000
"""Used as default for tools where the apply method has a default maximal answer length.
Even though the value of the max_answer_chars can be changed when calling the tool, it may make sense to adjust this default
through the global configuration.
"""
CONFIG_FILE = "serena_config.yml"
CONFIG_FILE_DOCKER = "serena_config.docker.yml" # Docker-specific config file; auto-generated if missing, mounted via docker-compose for user customization
@@ -453,6 +458,7 @@ class SerenaConfig(ToolInclusionDefinition, ToStringMixin):
instance.token_count_estimator = loaded_commented_yaml.get(
"token_count_estimator", RegisteredTokenCountEstimator.TIKTOKEN_GPT4O.name
)
instance.default_max_tool_answer_chars = loaded_commented_yaml.get("default_max_tool_answer_chars", 150_000)
# re-save the configuration file if any migrations were performed
if num_project_migrations > 0:
@@ -48,6 +48,11 @@ jetbrains: False
# NOTE: The plugin is yet unreleased. This is for Serena developers only.
default_max_tool_answer_chars: 150000
# Used as default for tools where the apply method has a default maximal answer length.
# Even though the value of the max_answer_chars can be changed when calling the tool, it may make sense to adjust this default
# through the global configuration.
record_tool_usage_stats: False
# whether to record tool usage statistics, they will be shown in the web dashboard if recording is active.
+3 -3
View File
@@ -2,7 +2,7 @@
Tools supporting the execution of (external) commands
"""
from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerCanEdit
from serena.tools import Tool, ToolMarkerCanEdit
from serena.util.shell import execute_shell_command
@@ -16,7 +16,7 @@ class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit):
command: str,
cwd: str | None = None,
capture_stderr: bool = True,
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Execute a shell command and return its output. If there is a memory about suggested commands, read that first.
@@ -26,7 +26,7 @@ class ExecuteShellCommandTool(Tool, ToolMarkerCanEdit):
:param cwd: the working directory to execute the command in. If None, the project root will be used.
:param capture_stderr: whether to capture and return stderr output
:param max_answer_chars: if the output is longer than this number of characters,
no content will be returned. Don't adjust unless there is really no other way to get the content
no content will be returned. -1 means using the default value, don't adjust unless there is no other way to get the content
required for the task.
:return: a JSON object containing the command's stdout and optionally stderr output
"""
+9 -9
View File
@@ -14,7 +14,7 @@ from fnmatch import fnmatch
from pathlib import Path
from serena.text_utils import search_files
from serena.tools import SUCCESS_RESULT, TOOL_DEFAULT_MAX_ANSWER_LENGTH, EditedFileContext, Tool, ToolMarkerCanEdit, ToolMarkerOptional
from serena.tools import SUCCESS_RESULT, EditedFileContext, Tool, ToolMarkerCanEdit, ToolMarkerOptional
from serena.util.file_system import scan_directory
@@ -23,9 +23,7 @@ class ReadFileTool(Tool):
Reads a file within the project directory.
"""
def apply(
self, relative_path: str, start_line: int = 0, end_line: int | None = None, max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH
) -> str:
def apply(self, relative_path: str, start_line: int = 0, end_line: int | None = None, max_answer_chars: int = -1) -> str:
"""
Reads the given file or a chunk of it. Generally, symbolic operations
like find_symbol or find_referencing_symbols should be preferred if you know which symbols you are looking for.
@@ -89,15 +87,15 @@ class ListDirTool(Tool):
Lists files and directories in the given directory (optionally with recursion).
"""
def apply(self, relative_path: str, recursive: bool, max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH) -> str:
def apply(self, relative_path: str, recursive: bool, max_answer_chars: int = -1) -> str:
"""
Lists all non-gitignored files and directories in the given directory (optionally with recursion).
:param relative_path: the relative path to the directory to list; pass "." to scan the project root
:param recursive: whether to scan subdirectories recursively
:param max_answer_chars: if the output is longer than this number of characters,
no content will be returned. Don't adjust unless there is really no other way to get the content
required for the task.
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 with the names of directories and files within the given directory
"""
# Check if the directory exists before validation
@@ -309,7 +307,7 @@ class SearchForPatternTool(Tool):
paths_exclude_glob: str = "",
relative_path: str = "",
restrict_search_to_code_files: bool = False,
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Offers a flexible search for arbitrary patterns in the codebase, including the
@@ -350,7 +348,9 @@ class SearchForPatternTool(Tool):
:param relative_path: only subpaths of this path (relative to the repo root) will be analyzed. If a path to a single
file is passed, only that will be searched. The path must exist, otherwise a `FileNotFoundError` is raised.
:param max_answer_chars: if the output is longer than this number of characters,
no content will be returned. Don't adjust unless there is really no other way to get the content
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. Instead, if the output is too long, you should
make a stricter query.
:param restrict_search_to_code_files: whether to restrict the search to only those files where
+8 -5
View File
@@ -1,6 +1,6 @@
import json
from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool, ToolMarkerOptional, ToolMarkerSymbolicRead
from serena.tools import Tool, ToolMarkerOptional, ToolMarkerSymbolicRead
from serena.tools.jetbrains_plugin_client import JetBrainsPluginClient
@@ -15,7 +15,7 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
depth: int = 0,
relative_path: str | None = None,
include_body: bool = False,
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Retrieves information on all symbols/code entities (classes, methods, etc.) based on the given `name_path`,
@@ -57,6 +57,7 @@ class JetBrainsFindSymbolTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOptional):
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 max_answer_chars: max characters for the JSON result. If exceeded, no content is returned.
-1 means the default value from the config will be used.
:return: JSON string: a list of symbols (with locations) matching the name.
"""
with JetBrainsPluginClient.from_project(self.project) as client:
@@ -79,7 +80,7 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMark
self,
name_path: str,
relative_path: str,
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Finds symbols that reference the symbol at the given `name_path`.
@@ -88,7 +89,8 @@ class JetBrainsFindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead, ToolMark
:param name_path: name path of the symbol for which to find references; matching logic as described in find symbol tool.
: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 max_answer_chars: max characters for the JSON result. If exceeded, no content is returned.
:param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned. -1 means the
default value from the config will be used.
:return: a list of JSON objects with the symbols referencing the requested symbol
"""
with JetBrainsPluginClient.from_project(self.project) as client:
@@ -108,7 +110,7 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp
def apply(
self,
relative_path: str,
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Gets an overview of the top-level symbols in the given file.
@@ -119,6 +121,7 @@ class JetBrainsGetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead, ToolMarkerOp
:param relative_path: the relative path to the file to get the overview of
:param max_answer_chars: max characters for the JSON result. If exceeded, no content is returned.
-1 means the default value from the config will be used.
:return: a JSON object containing the symbols
"""
with JetBrainsPluginClient.from_project(self.project) as client:
+3 -3
View File
@@ -1,6 +1,6 @@
import json
from serena.tools import TOOL_DEFAULT_MAX_ANSWER_LENGTH, Tool
from serena.tools import Tool
class WriteMemoryTool(Tool):
@@ -8,7 +8,7 @@ class WriteMemoryTool(Tool):
Writes a named memory (for future reference) to Serena's project-specific memory store.
"""
def apply(self, memory_name: str, content: str, max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH) -> str:
def apply(self, memory_name: str, content: str, max_answer_chars: int = -1) -> str:
"""
Write some information about this project that can be useful for future tasks to a memory in md format.
The memory name should be meaningful.
@@ -26,7 +26,7 @@ class ReadMemoryTool(Tool):
Reads the memory with the given name from Serena's project-specific memory store.
"""
def apply(self, memory_file_name: str, max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH) -> str:
def apply(self, memory_file_name: str, max_answer_chars: int = -1) -> str:
"""
Read the content of a memory file. This tool should only be used if the information
is relevant to the current task. You can infer whether the information
+6 -6
View File
@@ -11,7 +11,6 @@ from typing import Any
from serena.tools import (
SUCCESS_RESULT,
TOOL_DEFAULT_MAX_ANSWER_LENGTH,
Tool,
ToolMarkerSymbolicEdit,
ToolMarkerSymbolicRead,
@@ -52,7 +51,7 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead):
Gets an overview of the top-level symbols defined in a given file.
"""
def apply(self, relative_path: str, max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH) -> str:
def apply(self, relative_path: str, max_answer_chars: int = -1) -> str:
"""
Use this tool to get a high-level understanding of the code symbols in a file.
This should be the first tool to call when you want to understand a new file, unless you already know
@@ -60,8 +59,8 @@ class GetSymbolsOverviewTool(Tool, ToolMarkerSymbolicRead):
:param relative_path: the relative path to the file to get the overview of
:param max_answer_chars: if the overview is longer than this number of characters,
no content will be returned. Don't adjust unless there is really no other way to get the content
required for the task.
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
"""
symbol_retriever = self.create_language_server_symbol_retriever()
@@ -92,7 +91,7 @@ class FindSymbolTool(Tool, ToolMarkerSymbolicRead):
include_kinds: list[int] = [], # noqa: B006
exclude_kinds: list[int] = [], # noqa: B006
substring_matching: bool = False,
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Retrieves information on all symbols/code entities (classes, methods, etc.) based on the given `name_path`,
@@ -142,6 +141,7 @@ class FindSymbolTool(Tool, ToolMarkerSymbolicRead):
If not provided, no kinds are excluded.
:param substring_matching: If True, use substring matching for the last segment of `name`.
:param max_answer_chars: Max characters for the JSON result. If exceeded, no content is returned.
-1 means the default value from the config will be used.
:return: a list of symbols (with locations) matching the name.
"""
parsed_include_kinds: Sequence[SymbolKind] | None = [SymbolKind(k) for k in include_kinds] if include_kinds else None
@@ -171,7 +171,7 @@ class FindReferencingSymbolsTool(Tool, ToolMarkerSymbolicRead):
relative_path: str,
include_kinds: list[int] = [], # noqa: B006
exclude_kinds: list[int] = [], # noqa: B006
max_answer_chars: int = TOOL_DEFAULT_MAX_ANSWER_LENGTH,
max_answer_chars: int = -1,
) -> str:
"""
Finds references to the symbol at the given `name_path`. The result will contain metadata about the referencing symbols
+5 -5
View File
@@ -70,9 +70,6 @@ class Component(ABC):
return self.agent.lines_read
TOOL_DEFAULT_MAX_ANSWER_LENGTH = int(15e4)
class ToolMarker:
"""
Base class for tool markers.
@@ -214,8 +211,11 @@ class Tool(Component):
params[param] = value
log.info(f"{self.get_name_from_cls()}: {dict_string(params)}")
@staticmethod
def _limit_length(result: str, max_answer_chars: int) -> str:
def _limit_length(self, result: str, max_answer_chars: int) -> str:
if max_answer_chars == -1:
max_answer_chars = self.agent.serena_config.default_max_tool_answer_chars
if max_answer_chars <= 0:
raise ValueError(f"Must be positive or the default (-1), got: {max_answer_chars=}")
if (n_chars := len(result)) > max_answer_chars:
result = (
f"The answer is too long ({n_chars} characters). "