mirror of
https://github.com/tiennm99/serena.git
synced 2026-08-01 20:21:35 +00:00
Made tool selection configurable
More things: - list tools using .subclasses instead of inspect - entrypoint for listing tools - util for listing selected tools - documentation
This commit is contained in:
@@ -74,7 +74,7 @@ We thus built Serena with the prospect of being able to cancel most other subscr
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "/abs/path/to/uv",
|
||||
"args": ["run", "--directory", "/abs/path/to/serena", "serena", "/abs/path/to/myproject.yml"]
|
||||
"args": ["run", "--directory", "/abs/path/to/serena", "serena-mcp-server", "/abs/path/to/myproject.yml"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+44
-1
@@ -4,7 +4,50 @@ project_root: /path/to/project
|
||||
language: python
|
||||
# list of directories to ignore: either names (e.g. "temp") or relative paths (e.g. "build/foo")
|
||||
ignored_dirs: [".git", "temp"]
|
||||
# whether to open a graphical window with Serena's logs (not supported on MacOS)
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run serena-list-tools`.
|
||||
#
|
||||
# check_onboarding_performed
|
||||
# create_text_file
|
||||
# delete_lines
|
||||
# delete_memory
|
||||
# execute_shell_command
|
||||
# find_referencing_symbols
|
||||
# find_symbol
|
||||
# get_dir_overview
|
||||
# get_document_overview
|
||||
# insert_after_symbol
|
||||
# insert_at_line
|
||||
# insert_before_symbol
|
||||
# list_dir
|
||||
# list_memories
|
||||
# onboarding
|
||||
# prepare_for_new_conversation
|
||||
# read_file
|
||||
# read_memory
|
||||
# replace_symbol_body
|
||||
# search_in_all_code
|
||||
# summarize_changes
|
||||
# think_about_collected_information
|
||||
# think_about_task_adherence
|
||||
# think_about_whether_you_are_done
|
||||
# write_memory
|
||||
excluded_tools: []
|
||||
|
||||
|
||||
|
||||
# Whether to open a graphical window with Serena's logs (not supported on MacOS).
|
||||
# This is useful both for troubleshooting and for monitoring the tool calls,
|
||||
# especially when using the agno playground, since the tool calls are not always shown,
|
||||
# and the input params are never shown in the agno UI.
|
||||
# When used as MCP server for Claude Desktop, the logs are primarily for troubleshooting.
|
||||
# Note: unfortunately, the various entities starting the Serena server or agent do so in
|
||||
# mysterious ways, often starting multiple instances of the process without shutting down
|
||||
# previous instances. This leads to multiple log windows being opened, and only the last
|
||||
# window being updated. Since we can't control how agno or Claude Desktop starts Serena, we have to live with this limitation for now.
|
||||
gui_log_window: True
|
||||
# minimum log level for the GUI log window (10 = debug, 20 = info, 30 = warning, 40 = error)
|
||||
gui_log_level: 20
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
serena = "serena.mcp:start_mcp_server"
|
||||
serena-mcp-server = "serena.mcp:start_mcp_server"
|
||||
serena-list-tools = "serena.agent:print_tool_overview"
|
||||
|
||||
[project.license]
|
||||
text = "MIT"
|
||||
|
||||
+24
-27
@@ -1,19 +1,13 @@
|
||||
import os
|
||||
from logging import Logger
|
||||
|
||||
from agno.agent.agent import Agent
|
||||
from agno.memory.agent import AgentMemory
|
||||
from agno.models.anthropic.claude import Claude
|
||||
from agno.models.google.gemini import Gemini
|
||||
from agno.playground.playground import Playground
|
||||
from agno.playground.serve import serve_playground_app
|
||||
from agno.storage.agent.sqlite import SqliteAgentStorage
|
||||
from dotenv import load_dotenv
|
||||
from sensai.util import logging
|
||||
from sensai.util.helper import mark_used
|
||||
|
||||
from serena.agent import SerenaAgent
|
||||
from serena.agno import SerenaAgnoToolkit
|
||||
|
||||
mark_used(Gemini, Claude)
|
||||
|
||||
@@ -36,27 +30,30 @@ os.makedirs(os.path.dirname(sql_db_path), exist_ok=True)
|
||||
if os.path.exists(sql_db_path):
|
||||
os.remove(sql_db_path)
|
||||
|
||||
model = Claude(id="claude-3-7-sonnet-20250219")
|
||||
# model = Gemini(id="gemini-2.5-pro-exp-03-25")
|
||||
for tool_name in serena_agent.list_tool_names():
|
||||
print(tool_name)
|
||||
|
||||
agno_agent = Agent(
|
||||
name="Serena",
|
||||
model=model,
|
||||
storage=SqliteAgentStorage(table_name="serena_agent_sessions", db_file=sql_db_path),
|
||||
description="A fully-featured coding assistant",
|
||||
tools=[SerenaAgnoToolkit(serena_agent)], # type: ignore
|
||||
show_tool_calls=False,
|
||||
markdown=True,
|
||||
system_message=serena_agent.prompt_factory.create_system_prompt(),
|
||||
read_tool_call_history=False,
|
||||
telemetry=False,
|
||||
memory=AgentMemory(),
|
||||
add_history_to_messages=True,
|
||||
num_history_responses=100, # you might want to adjust this (expense vs. history awareness)
|
||||
)
|
||||
# model = Claude(id="claude-3-7-sonnet-20250219")
|
||||
# # model = Gemini(id="gemini-2.5-pro-exp-03-25")
|
||||
|
||||
# The app object must be in the module scope so that the server can access it for hot reloading
|
||||
app = Playground(agents=[agno_agent]).get_app()
|
||||
# agno_agent = Agent(
|
||||
# name="Serena",
|
||||
# model=model,
|
||||
# storage=SqliteAgentStorage(table_name="serena_agent_sessions", db_file=sql_db_path),
|
||||
# description="A fully-featured coding assistant",
|
||||
# tools=[SerenaAgnoToolkit(serena_agent)], # type: ignore
|
||||
# show_tool_calls=False,
|
||||
# markdown=True,
|
||||
# system_message=serena_agent.prompt_factory.create_system_prompt(),
|
||||
# read_tool_call_history=False,
|
||||
# telemetry=False,
|
||||
# memory=AgentMemory(),
|
||||
# add_history_to_messages=True,
|
||||
# num_history_responses=100, # you might want to adjust this (expense vs. history awareness)
|
||||
# )
|
||||
|
||||
if __name__ == "__main__":
|
||||
serve_playground_app("agno_agent:app", reload=False)
|
||||
# # The app object must be in the module scope so that the server can access it for hot reloading
|
||||
# app = Playground(agents=[agno_agent]).get_app()
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# serve_playground_app("agno_agent:app", reload=False)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
from serena.agent import SerenaAgent
|
||||
|
||||
if __name__ == "__main__":
|
||||
agent = SerenaAgent("myproject.yml")
|
||||
tools = {}
|
||||
for tool in agent.tools.values():
|
||||
tools[tool.get_name()] = tool
|
||||
for tool_name in sorted(tools.keys()):
|
||||
tool = tools[tool_name]
|
||||
print(f" * `{tool_name}`: {tool.get_tool_description().strip()}")
|
||||
+30
-9
@@ -11,7 +11,7 @@ import traceback
|
||||
import warnings
|
||||
from abc import ABC
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Iterator
|
||||
from collections.abc import Callable, Generator, Iterable, Iterator
|
||||
from contextlib import contextmanager
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
@@ -29,6 +29,7 @@ from serena.gui_log_viewer import GuiLogViewer, GuiLogViewerHandler
|
||||
from serena.llm.prompt_factory import PromptFactory
|
||||
from serena.symbol import SymbolLocation, SymbolManager
|
||||
from serena.util.file_system import scan_directory
|
||||
from serena.util.inspection import iter_subclasses
|
||||
from serena.util.shell import execute_shell_command
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -87,11 +88,12 @@ class SerenaAgent:
|
||||
self.memories_manager = MemoriesManager(memories_dir)
|
||||
|
||||
# find all tool classes and instantiate them
|
||||
excluded_tools = project_config.get("excluded_tools", [])
|
||||
self.tools: dict[type[Tool], Tool] = {}
|
||||
tool_classes = [
|
||||
cls for name, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass) if issubclass(cls, Tool) and cls is not Tool
|
||||
]
|
||||
for tool_class in tool_classes:
|
||||
for tool_class in iter_tool_classes():
|
||||
if (tool_name := tool_class.get_name()) in excluded_tools:
|
||||
log.info(f"Skipping tool {tool_name} because it is in the exclude list")
|
||||
continue
|
||||
tool_instance = tool_class(self)
|
||||
self.tools[tool_class] = tool_instance
|
||||
log.info(f"Loaded tools: {', '.join([tool.get_name() for tool in self.tools.values()])}")
|
||||
@@ -101,11 +103,12 @@ class SerenaAgent:
|
||||
log.info("Starting the language server ...")
|
||||
self.language_server.start()
|
||||
|
||||
self._is_initialized = True
|
||||
|
||||
def get_tool(self, tool_class: type[TTool]) -> TTool:
|
||||
return self.tools[tool_class] # type: ignore
|
||||
|
||||
def print_tool_overview(self) -> None:
|
||||
_print_tool_overview(self.tools.values())
|
||||
|
||||
def get_serena_managed_dir(self) -> str:
|
||||
return os.path.join(self.project_root, ".serena")
|
||||
|
||||
@@ -195,8 +198,9 @@ class Tool(Component):
|
||||
raise Exception(f"{self} does not define method apply")
|
||||
return apply_fn
|
||||
|
||||
def get_tool_description(self) -> str:
|
||||
docstring = self.__class__.__doc__
|
||||
@classmethod
|
||||
def get_tool_description(cls) -> str:
|
||||
docstring = cls.__doc__
|
||||
if docstring is None:
|
||||
return ""
|
||||
return docstring.strip()
|
||||
@@ -878,3 +882,20 @@ class ExecuteShellCommandTool(Tool):
|
||||
result = execute_shell_command(command, cwd=_cwd, capture_stderr=capture_stderr)
|
||||
result = result.json()
|
||||
return self._limit_length(result, max_answer_chars)
|
||||
|
||||
|
||||
def iter_tool_classes() -> Generator[type[Tool], None, None]:
|
||||
return iter_subclasses(Tool)
|
||||
|
||||
|
||||
def print_tool_overview() -> None:
|
||||
_print_tool_overview(iter_tool_classes())
|
||||
|
||||
|
||||
def _print_tool_overview(tools: Iterable[type[Tool] | Tool]) -> None:
|
||||
tool_dict: dict[str, type[Tool] | Tool] = {}
|
||||
for tool in tools:
|
||||
tool_dict[tool.get_name()] = tool
|
||||
for tool_name in sorted(tool_dict.keys()):
|
||||
tool = tool_dict[tool_name]
|
||||
print(f" * `{tool_name}`: {tool.get_tool_description().strip()}")
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
from collections.abc import Generator
|
||||
from typing import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def iter_subclasses(cls: type[T], recursive: bool = True) -> Generator[type[T], None, None]:
|
||||
"""Iterate over all subclasses of a class. If recursive is True, also iterate over all subclasses of all subclasses."""
|
||||
for subclass in cls.__subclasses__():
|
||||
yield subclass
|
||||
if recursive:
|
||||
yield from iter_subclasses(subclass, recursive)
|
||||
Reference in New Issue
Block a user