mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 02:14:49 +00:00
New feature: allow users to override prompts. Extended CLI for that
This commit is contained in:
1 parent
d624c1c0b9
commit
caa2c545e4
4 files changed
+99
-6
No files matched your search
@@ -7,14 +7,14 @@ from pathlib import Path
|
||||
from sensai.util import logging
|
||||
|
||||
from interprompt import autogenerate_prompt_factory_module
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR, REPO_ROOT
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR_INTERNAL, REPO_ROOT
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
autogenerate_prompt_factory_module(
|
||||
prompts_dir=PROMPT_TEMPLATES_DIR,
|
||||
prompts_dir=PROMPT_TEMPLATES_DIR_INTERNAL,
|
||||
target_module_path=str(Path(REPO_ROOT) / "src" / "serena" / "generated" / "generated_prompt_factory.py"),
|
||||
)
|
||||
|
||||
|
||||
+90
-1
@@ -1,3 +1,4 @@
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -16,6 +17,8 @@ from serena.config.serena_config import ProjectConfig, SerenaConfig, SerenaPaths
|
||||
from serena.constants import (
|
||||
DEFAULT_CONTEXT,
|
||||
DEFAULT_MODES,
|
||||
PROMPT_TEMPLATES_DIR_IN_USER_HOME,
|
||||
PROMPT_TEMPLATES_DIR_INTERNAL,
|
||||
SERENA_LOG_FORMAT,
|
||||
SERENA_MANAGED_DIR_IN_HOME,
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR,
|
||||
@@ -476,12 +479,98 @@ class ToolCommands(AutoRegisteringGroup):
|
||||
ToolRegistry().print_tool_overview()
|
||||
|
||||
|
||||
class PromptCommands(AutoRegisteringGroup):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(name="prompts", help="Commands related to Serena's prompts that are outside of contexts and modes.")
|
||||
|
||||
@staticmethod
|
||||
def _get_user_prompt_yaml_path(prompt_yaml_name: str) -> str:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
return os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list", help="Lists yamls that are used for defining prompts.")
|
||||
def list() -> None:
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
for prompt_yaml_name in serena_prompt_yaml_names:
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if os.path.exists(user_prompt_yaml_path):
|
||||
click.echo(f"{user_prompt_yaml_path} merged with default prompts in {prompt_yaml_name}")
|
||||
else:
|
||||
click.echo(prompt_yaml_name)
|
||||
|
||||
@staticmethod
|
||||
@click.command("create-override", help="Create an override of an internal prompts yaml for customizing Serena's prompts")
|
||||
@click.argument("prompt_yaml_name")
|
||||
def create_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
:param prompt_yaml_name: The yaml name of the prompt you want to override. Call the `list` command for discovering valid prompt yaml names.
|
||||
:return:
|
||||
"""
|
||||
# for convenience, we can pass names without .yml
|
||||
if not prompt_yaml_name.endswith(".yml"):
|
||||
prompt_yaml_name = prompt_yaml_name + ".yml"
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if os.path.exists(user_prompt_yaml_path):
|
||||
raise FileExistsError(f"{user_prompt_yaml_path} already exists.")
|
||||
serena_prompt_yaml_path = os.path.join(PROMPT_TEMPLATES_DIR_INTERNAL, prompt_yaml_name)
|
||||
shutil.copyfile(serena_prompt_yaml_path, user_prompt_yaml_path)
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("edit-override", help="Edit an existing prompt override file")
|
||||
@click.argument("prompt_yaml_name")
|
||||
def edit_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
:param prompt_yaml_name: The yaml name of the prompt override to edit.
|
||||
:return:
|
||||
"""
|
||||
# for convenience, we can pass names without .yml
|
||||
if not prompt_yaml_name.endswith(".yml"):
|
||||
prompt_yaml_name = prompt_yaml_name + ".yml"
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if not os.path.exists(user_prompt_yaml_path):
|
||||
click.echo(f"Override file '{prompt_yaml_name}' not found. Create it with: prompts create-override {prompt_yaml_name}")
|
||||
return
|
||||
_open_in_editor(user_prompt_yaml_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("list-overrides", help="List existing prompt override files")
|
||||
def list_overrides() -> None:
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
serena_prompt_yaml_names = [os.path.basename(f) for f in glob.glob(PROMPT_TEMPLATES_DIR_INTERNAL + "/*.yml")]
|
||||
override_files = glob.glob(os.path.join(PROMPT_TEMPLATES_DIR_IN_USER_HOME, "*.yml"))
|
||||
for file_path in override_files:
|
||||
if os.path.basename(file_path) in serena_prompt_yaml_names:
|
||||
click.echo(file_path)
|
||||
|
||||
@staticmethod
|
||||
@click.command("delete-override", help="Delete a prompt override file")
|
||||
@click.argument("prompt_yaml_name")
|
||||
def delete_override(prompt_yaml_name: str) -> None:
|
||||
"""
|
||||
|
||||
:param prompt_yaml_name: The yaml name of the prompt override to delete."
|
||||
:return:
|
||||
"""
|
||||
# for convenience, we can pass names without .yml
|
||||
if not prompt_yaml_name.endswith(".yml"):
|
||||
prompt_yaml_name = prompt_yaml_name + ".yml"
|
||||
user_prompt_yaml_path = PromptCommands._get_user_prompt_yaml_path(prompt_yaml_name)
|
||||
if not os.path.exists(user_prompt_yaml_path):
|
||||
click.echo(f"Override file '{prompt_yaml_name}' not found.")
|
||||
return
|
||||
os.remove(user_prompt_yaml_path)
|
||||
click.echo(f"Deleted override file '{prompt_yaml_name}'.")
|
||||
|
||||
|
||||
# Expose groups so we can reference them in pyproject.toml
|
||||
mode = ModeCommands()
|
||||
context = ContextCommands()
|
||||
project = ProjectCommands()
|
||||
config = SerenaConfigCommands()
|
||||
tools = ToolCommands()
|
||||
prompts = PromptCommands()
|
||||
|
||||
# Expose toplevel commands for the same reason
|
||||
top_level = TopLevelCommands()
|
||||
@@ -489,7 +578,7 @@ start_mcp_server = top_level.start_mcp_server
|
||||
index_project = project.index_deprecated
|
||||
|
||||
# needed for the help script to work - register all subcommands to the top-level group
|
||||
for subgroup in (mode, context, project, config, tools):
|
||||
for subgroup in (mode, context, project, config, tools, prompts):
|
||||
top_level.add_command(subgroup)
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ SERENA_MANAGED_DIR_IN_HOME = str(_serena_in_home_managed_dir)
|
||||
|
||||
# TODO: Path-related constants should be moved to SerenaPaths; don't add further constants here.
|
||||
REPO_ROOT = str(_repo_root_path)
|
||||
PROMPT_TEMPLATES_DIR = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
|
||||
PROMPT_TEMPLATES_DIR_INTERNAL = str(_serena_pkg_path / "resources" / "config" / "prompt_templates")
|
||||
PROMPT_TEMPLATES_DIR_IN_USER_HOME = str(_serena_in_home_managed_dir / "prompt_templates")
|
||||
SERENAS_OWN_CONTEXT_YAMLS_DIR = str(_serena_pkg_path / "resources" / "config" / "contexts")
|
||||
"""The contexts that are shipped with the Serena package, i.e. the default contexts."""
|
||||
USER_CONTEXT_YAMLS_DIR = str(_serena_in_home_managed_dir / "contexts")
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR
|
||||
import os
|
||||
|
||||
from serena.constants import PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL
|
||||
from serena.generated.generated_prompt_factory import PromptFactory
|
||||
|
||||
|
||||
@@ -8,4 +10,5 @@ class SerenaPromptFactory(PromptFactory):
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__(PROMPT_TEMPLATES_DIR)
|
||||
os.makedirs(PROMPT_TEMPLATES_DIR_IN_USER_HOME, exist_ok=True)
|
||||
super().__init__(prompts_dir=[PROMPT_TEMPLATES_DIR_IN_USER_HOME, PROMPT_TEMPLATES_DIR_INTERNAL])
|
||||
Reference in new issue
Block a user