Skip to content

Commit

Permalink
Ensure all slash commands support -h/--help (jupyterlab#878)
Browse files Browse the repository at this point in the history
* Fix help for slash commands

* Do not add `-h` to the help command itself

Co-authored-by: david qiu <david@qiu.dev>

* Fix typo

Co-authored-by: david qiu <david@qiu.dev>

* Centralize handling of `-h`/`--help`, with opt-out via class attribute

by default the `/help` and "default" handlers opt-out from `-h` handling

* Add colon delimiter and wrap the arguments in backticks

* Use Title case for help messages

* Remove final leftover

* Rename `display_arguments_help` to `supports_help`

---------

Co-authored-by: david qiu <david@qiu.dev>
  • Loading branch information
2 people authored and Marchlak committed Oct 28, 2024
1 parent 4107f57 commit 2a4af8e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 11 deletions.
46 changes: 42 additions & 4 deletions packages/jupyter-ai/jupyter_ai/chat_handlers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ class SlashCommandRoutingType(HandlerRoutingType):
underscores."""


class MarkdownHelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
def _get_help_string(self, action):
# do not show "(default: False)" for flags as this is assumed
if action.const is True:
return action.help
return super()._get_help_string(action)

def _format_action_invocation(self, action):
if not action.option_strings:
return super()._format_action_invocation(action)
else:
action_string = super()._format_action_invocation(action)
return f"`{action_string}`:"

def _format_action(self, action):
return "- " + super()._format_action(action)


class BaseChatHandler:
"""Base ChatHandler class containing shared methods and attributes used by
multiple chat handler classes."""
Expand All @@ -79,6 +97,11 @@ class BaseChatHandler:
specified by the config. Subclasses should define this. Should be set to
`False` for handlers like `/help`."""

supports_help: ClassVar[bool] = True
"""Class attribute specifying whether this chat handler should
parse the arguments and display help when user queries with
`-h` or `--help`"""

_requests_count = 0
"""Class attribute set to the number of requests that Jupyternaut is
currently handling."""
Expand All @@ -99,7 +122,14 @@ def __init__(
self._root_chat_handlers = root_chat_handlers
self.model_parameters = model_parameters
self._chat_history = chat_history
self.parser = argparse.ArgumentParser()
self.parser = argparse.ArgumentParser(
add_help=False, description=self.help, formatter_class=MarkdownHelpFormatter
)
# the default help would exit; instead implement a custom help
if self.__class__.supports_help:
self.parser.add_argument(
"-h", "--help", action="store_true", help="Show this help message"
)
self.root_dir = os.path.abspath(os.path.expanduser(root_dir))
self.preferred_dir = get_preferred_dir(self.root_dir, preferred_dir)
self.dask_client_future = dask_client_future
Expand Down Expand Up @@ -140,6 +170,13 @@ async def on_message(self, message: HumanChatMessage):
return

BaseChatHandler._requests_count += 1

if self.__class__.supports_help:
args = self.parse_args(message, silent=True)
if args and args.help:
self.reply(self.parser.format_help(), message)
return

try:
await self.process_message(message)
except Exception as e:
Expand Down Expand Up @@ -304,13 +341,14 @@ def create_llm_chain(
):
raise NotImplementedError("Should be implemented by subclasses")

def parse_args(self, message):
def parse_args(self, message, silent=False):
args = message.body.split(" ")
try:
args = self.parser.parse_args(args[1:])
except (argparse.ArgumentError, SystemExit) as e:
response = f"{self.parser.format_usage()}"
self.reply(response, message)
if not silent:
response = f"{self.parser.format_usage()}"
self.reply(response, message)
return None
return args

Expand Down
1 change: 1 addition & 0 deletions packages/jupyter-ai/jupyter_ai/chat_handlers/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class DefaultChatHandler(BaseChatHandler):
routing_type = SlashCommandRoutingType(slash_id=None)

uses_llm = True
supports_help = False

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down
1 change: 1 addition & 0 deletions packages/jupyter-ai/jupyter_ai/chat_handlers/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class HelpChatHandler(BaseChatHandler):
name = "Help"
help = "Display this help message"
routing_type = SlashCommandRoutingType(slash_id="help")
supports_help = False

uses_llm = False

Expand Down
48 changes: 41 additions & 7 deletions packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
from typing import Any, Coroutine, List, Optional, Tuple

from dask.distributed import Client as DaskClient
from jupyter_ai.document_loaders.directory import arxiv_to_text, get_embeddings, split
from jupyter_ai.document_loaders.directory import (
EXCLUDE_DIRS,
arxiv_to_text,
get_embeddings,
split,
)
from jupyter_ai.document_loaders.splitter import ExtensionSplitter, NotebookSplitter
from jupyter_ai.models import (
DEFAULT_CHUNK_OVERLAP,
Expand Down Expand Up @@ -39,23 +44,52 @@ class LearnChatHandler(BaseChatHandler):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
excluded_dirs = ", ".join(EXCLUDE_DIRS)
self.parser.prog = "/learn"
self.parser.add_argument("-a", "--all-files", action="store_true")
self.parser.add_argument("-v", "--verbose", action="store_true")
self.parser.add_argument("-d", "--delete", action="store_true")
self.parser.add_argument("-l", "--list", action="store_true")
self.parser.add_argument(
"-r", "--remote", action="store", default=None, type=str
"-a",
"--all-files",
action="store_true",
help=f"Include hidden files, hidden directories, and excluded directories ({excluded_dirs})",
)
self.parser.add_argument(
"-v", "--verbose", action="store_true", help="Increase verbosity"
)
self.parser.add_argument(
"-d",
"--delete",
action="store_true",
help="Delete everything previously learned",
)
self.parser.add_argument(
"-l",
"--list",
action="store_true",
help="List directories previously learned",
)
self.parser.add_argument(
"-r",
"--remote",
action="store",
default=None,
type=str,
help="Learn a remote document; currently only *arxiv* is supported",
)
self.parser.add_argument(
"-c", "--chunk-size", action="store", default=DEFAULT_CHUNK_SIZE, type=int
"-c",
"--chunk-size",
action="store",
default=DEFAULT_CHUNK_SIZE,
type=int,
help="Max number of characters in chunk",
)
self.parser.add_argument(
"-o",
"--chunk-overlap",
action="store",
default=DEFAULT_CHUNK_OVERLAP,
type=int,
help="Number of characters overlapping between chunks, helpful to ensure text is not split mid-word or mid-sentence",
)
self.parser.add_argument("path", nargs=argparse.REMAINDER)
self.index_name = "default"
Expand Down

0 comments on commit 2a4af8e

Please sign in to comment.