From 162815f54fbaf130d41d41a7719d31a9d20239a6 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Thu, 21 Sep 2023 13:40:04 -0700 Subject: [PATCH 01/35] Adds attributes, starts adding to subclasses --- .../jupyter-ai/jupyter_ai/chat_handlers/ask.py | 5 +++++ .../jupyter-ai/jupyter_ai/chat_handlers/base.py | 17 ++++++++++++++++- packages/jupyter-ai/jupyter_ai/extension.py | 1 + packages/jupyter-ai/pyproject.toml | 8 ++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py index e5c852051..84d2b0a34 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py @@ -25,6 +25,11 @@ class AskChatHandler(BaseChatHandler): query the documents from the index, and sends this context to the LLM to generate the final reply. """ + id = 'ask-chat-handler' + name = "Ask with Local Data" + description = 'Ask a question augmented with learned data' + help = 'Asks a question with retrieval augmented generation (RAG)' + slash_id = 'ask' def __init__(self, retriever, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index c7ca70f97..821e900f6 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -10,13 +10,28 @@ from jupyter_ai.models import AgentChatMessage, HumanChatMessage from jupyter_ai_magics.providers import BaseProvider +from traitlets.config import Configurable + if TYPE_CHECKING: from jupyter_ai.handlers import RootChatHandler -class BaseChatHandler: +class BaseChatHandler(Configurable): """Base ChatHandler class containing shared methods and attributes used by multiple chat handler classes.""" + id: str = 'base-chat-handler' # TODO: make NotImplemented + name: str = 'Base Chat Handler' # TODO: make NotImplemented + # Description used for routing requests, to be used when dispatching + # messages to model providers. Also shown in the UI. + description: str = "Handler for messages that are not commands" # TODO: make NotImplemented + # What this chat handler does, which third-party models it contacts, + # the format of the data it returns to the user, etc. Used in the UI. + # TODO: make NotImplemented + help: str = "This is used when the message in the chat interface is not a command" + # Slash ID for routing a chat command to this handler. Only one handler + # may declare a particular slash ID. Must contain only alphanumerics and + # underscores. + slash_id: Optional[str] def __init__( self, diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 8ab8c0cc6..d979f5a1c 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -181,6 +181,7 @@ def initialize_settings(self): help_chat_handler = HelpChatHandler(**chat_handler_kwargs) retriever = Retriever(learn_chat_handler=learn_chat_handler) ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) + # TODO: use eps.select("jupyter_ai.chat_handlers") to instantiate chat handlers self.settings["jai_chat_handlers"] = { "default": default_chat_handler, "/ask": ask_chat_handler, diff --git a/packages/jupyter-ai/pyproject.toml b/packages/jupyter-ai/pyproject.toml index 0f8d4a58a..f3d9e5cf2 100644 --- a/packages/jupyter-ai/pyproject.toml +++ b/packages/jupyter-ai/pyproject.toml @@ -39,6 +39,14 @@ dependencies = [ dynamic = ["version", "description", "authors", "urls", "keywords"] +[project.entry-points."jupyter_ai.chat_handlers"] +ask = "jupyter_ai:AskChatHandler" +clear = "jupyter_ai:ClearChatHandler" +default = "jupyter_ai:DefaultChatHandler" +generate = "jupyter_ai:GenerateChatHandler" +help = "jupyter_ai:HelpChatHandler" +learn = "jupyter_ai:LearnChatHandler" + [project.entry-points."jupyter_ai.default_tasks"] core_default_tasks = "jupyter_ai:tasks" From abaf3c6dadc0cd53ded1eb11cfbc5a5b88b123de Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Thu, 21 Sep 2023 15:55:39 -0700 Subject: [PATCH 02/35] Consistent syntax --- .../jupyter_ai/chat_handlers/base.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 821e900f6..c79029148 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -19,19 +19,27 @@ class BaseChatHandler(Configurable): """Base ChatHandler class containing shared methods and attributes used by multiple chat handler classes.""" - id: str = 'base-chat-handler' # TODO: make NotImplemented + + # Class attributes + id: str = 'base-chat-handler' + """ID for this chat handler; should be unique""" + name: str = 'Base Chat Handler' # TODO: make NotImplemented - # Description used for routing requests, to be used when dispatching - # messages to model providers. Also shown in the UI. + """User-facing name of this handler""" + description: str = "Handler for messages that are not commands" # TODO: make NotImplemented - # What this chat handler does, which third-party models it contacts, - # the format of the data it returns to the user, etc. Used in the UI. + """Description used for routing requests, to be used when dispatching + messages to model providers. Also shown in the UI.""" + # TODO: make NotImplemented help: str = "This is used when the message in the chat interface is not a command" - # Slash ID for routing a chat command to this handler. Only one handler - # may declare a particular slash ID. Must contain only alphanumerics and - # underscores. + """What this chat handler does, which third-party models it contacts, + the format of the data it returns to the user, etc. Used in the UI.""" + slash_id: Optional[str] + """Slash ID for routing a chat command to this handler. Only one handler + may declare a particular slash ID. Must contain only alphanumerics and + underscores.""" def __init__( self, From 9aa348f55d04a1b4362d2774547a59510f1743bb Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Thu, 21 Sep 2023 16:12:11 -0700 Subject: [PATCH 03/35] Help for all handlers --- packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py | 8 ++++---- packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py | 6 ++++++ packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py | 6 +++++- packages/jupyter-ai/jupyter_ai/chat_handlers/help.py | 6 ++++++ packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py | 6 ++++++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py index 84d2b0a34..3b34f9513 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py @@ -25,11 +25,11 @@ class AskChatHandler(BaseChatHandler): query the documents from the index, and sends this context to the LLM to generate the final reply. """ - id = 'ask-chat-handler' + id = "ask-chat-handler" name = "Ask with Local Data" - description = 'Ask a question augmented with learned data' - help = 'Asks a question with retrieval augmented generation (RAG)' - slash_id = 'ask' + description = "Ask a question augmented with learned data" + help = "Asks a question with retrieval augmented generation (RAG)" + slash_id = "ask" def __init__(self, retriever, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py index a2a39bb00..e2b95f76c 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py @@ -6,6 +6,12 @@ class ClearChatHandler(BaseChatHandler): + id = "clear-chat-handler" + name = "Clear chat messages" + description = "Clear the chat message history display" + help = "Clears the displayed chat message history only; does not clear the context sent to chat providers" + slash_id = "clear" + def __init__(self, chat_history: List[ChatMessage], *args, **kwargs): super().__init__(*args, **kwargs) self._chat_history = chat_history diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py index 42cb0e497..24b31c3b2 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py @@ -216,7 +216,11 @@ def create_notebook(outline): class GenerateChatHandler(BaseChatHandler): - """Generates a Jupyter notebook given a description.""" + id = "generate-chat-handler" + name = "Generate Notebook" + description = "Generates a Jupyter notebook given a description" + help = "Generates a Jupyter notebook, including name, outline, and section contents" + slash_id = "generate" def __init__(self, root_dir: str, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py index be89d1165..d8433a981 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py @@ -29,6 +29,12 @@ def HelpMessage(): class HelpChatHandler(BaseChatHandler): + id = "help-chat-handler" + name = "Help" + description = "Displays information about available commands" + help = "Displays a help message in the chat message area" + slash_id = "help" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py index d07e76b35..43adc9349 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py @@ -31,6 +31,12 @@ class LearnChatHandler(BaseChatHandler): + id = "learn-chat-handler" + name = "Learn Local Data" + description = "Embed a list of files and directories for use with /ask" + help = "Pass a list of files and directories. Once converted to vector format, you can ask about them with /ask." + slash_id = "ask" + def __init__( self, root_dir: str, dask_client_future: Awaitable[DaskClient], *args, **kwargs ): From 5086b3c787a1e4118e7170c8cb6c9e89981c7873 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Thu, 21 Sep 2023 16:13:31 -0700 Subject: [PATCH 04/35] Fix slash ID error --- packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py index 43adc9349..34724e0ef 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py @@ -35,7 +35,7 @@ class LearnChatHandler(BaseChatHandler): name = "Learn Local Data" description = "Embed a list of files and directories for use with /ask" help = "Pass a list of files and directories. Once converted to vector format, you can ask about them with /ask." - slash_id = "ask" + slash_id = "learn" def __init__( self, root_dir: str, dask_client_future: Awaitable[DaskClient], *args, **kwargs From fd8529febbc36b881ab13df9c17cdab843dbed50 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Thu, 21 Sep 2023 16:48:04 -0700 Subject: [PATCH 05/35] Iterate through entry points --- packages/jupyter-ai/jupyter_ai/extension.py | 30 ++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index d979f5a1c..b72c1dee2 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -1,3 +1,5 @@ +from importlib_metadata import entry_points +import logging import time from dask.distributed import Client as DaskClient @@ -93,6 +95,9 @@ class AiExtension(ExtensionApp): ) def initialize_settings(self): + log = logging.getLogger() + log.addHandler(logging.NullHandler()) + start = time.time() # Read from allowlist and blocklist @@ -156,7 +161,31 @@ def initialize_settings(self): # consumers a Future that resolves to the Dask client when awaited. dask_client_future = loop.create_task(self._get_dask_client()) + eps = entry_points() # initialize chat handlers + chat_handler_eps = eps.select("jupyter_ai.chat_handlers") + jai_chat_handlers = {} + for chat_handler_ep in chat_handler_eps: + # Each slash ID, including None (default), must be used only once. + # Slash IDs may contain only alphanumerics and underscores. + slash_id = chat_handler_ep.slash_id + + if slash_id: + # TODO: Validate slash ID (/^[A-Za-z0-9_]+$/) + command_name = f"/{slash_id}" + else: + command_name = "default" + + if command_name in jai_chat_handlers: + log.error( + f"Unable to register chat handler `{chat_handler.id}` because command `{command_name}` already has a handler" + ) + continue + jai_chat_handlers[command_name] = chat_handler_ep # Instantiate? + log.info(f"Registered chat handler `{chat_handler.id}` with command `{command_name}`.") + + self.settings["jai_chat_handlers"] = jai_chat_handlers + chat_handler_kwargs = { "log": self.log, "config_manager": self.settings["jai_config_manager"], @@ -181,7 +210,6 @@ def initialize_settings(self): help_chat_handler = HelpChatHandler(**chat_handler_kwargs) retriever = Retriever(learn_chat_handler=learn_chat_handler) ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) - # TODO: use eps.select("jupyter_ai.chat_handlers") to instantiate chat handlers self.settings["jai_chat_handlers"] = { "default": default_chat_handler, "/ask": ask_chat_handler, From 908455486f3c617efb419feee2a83220690a0142 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Mon, 25 Sep 2023 13:29:13 -0700 Subject: [PATCH 06/35] Fix typo in call to select() --- packages/jupyter-ai/jupyter_ai/extension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index b72c1dee2..3d24a3081 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -163,7 +163,7 @@ def initialize_settings(self): eps = entry_points() # initialize chat handlers - chat_handler_eps = eps.select("jupyter_ai.chat_handlers") + chat_handler_eps = eps.select(group="jupyter_ai.chat_handlers") jai_chat_handlers = {} for chat_handler_ep in chat_handler_eps: # Each slash ID, including None (default), must be used only once. From ffbc583967bdb2d53a19a0be20c4d6b021efd592 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 26 Sep 2023 17:03:21 -0700 Subject: [PATCH 07/35] Moves config to magics, modifies extensions to attempt to load classes --- packages/jupyter-ai-magics/pyproject.toml | 8 ++++ packages/jupyter-ai/jupyter_ai/extension.py | 48 ++++++++++++++------- packages/jupyter-ai/pyproject.toml | 8 ---- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/packages/jupyter-ai-magics/pyproject.toml b/packages/jupyter-ai-magics/pyproject.toml index bb7a6dc57..6dffe6e05 100644 --- a/packages/jupyter-ai-magics/pyproject.toml +++ b/packages/jupyter-ai-magics/pyproject.toml @@ -53,6 +53,14 @@ all = [ "boto3" ] +[project.entry-points."jupyter_ai.chat_handlers"] +ask = "jupyter_ai:AskChatHandler" +clear = "jupyter_ai:ClearChatHandler" +default = "jupyter_ai:DefaultChatHandler" +generate = "jupyter_ai:GenerateChatHandler" +help = "jupyter_ai:HelpChatHandler" +learn = "jupyter_ai:LearnChatHandler" + [project.entry-points."jupyter_ai.model_providers"] ai21 = "jupyter_ai_magics:AI21Provider" anthropic = "jupyter_ai_magics:AnthropicProvider" diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 3d24a3081..eec95761a 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -95,9 +95,6 @@ class AiExtension(ExtensionApp): ) def initialize_settings(self): - log = logging.getLogger() - log.addHandler(logging.NullHandler()) - start = time.time() # Read from allowlist and blocklist @@ -163,26 +160,52 @@ def initialize_settings(self): eps = entry_points() # initialize chat handlers + self.log.info("Found entry point groups " + ", ".join(sorted(eps.groups))) chat_handler_eps = eps.select(group="jupyter_ai.chat_handlers") + jai_chat_handlers = {} + + chat_handler_kwargs = { + "log": self.log, + "config_manager": self.settings["jai_config_manager"], + "root_chat_handlers": self.settings["jai_root_chat_handlers"], + # Everything below this line is the union of all arguments used to + # instantiate chat handlers. + "chat_history": self.settings["chat_history"], + "root_dir": self.serverapp.root_dir, + "dask_client_future": dask_client_future, + # TODO: Set ask_chat_handler based on a retriever related to the learn_chat_handler + "retriever": None + } + for chat_handler_ep in chat_handler_eps: + try: + chat_handler = chat_handler_ep.load() + except: + self.log.error( + f"Unable to load chat handler class from entry point `{chat_handler_ep.name}`." + ) + continue + # Each slash ID, including None (default), must be used only once. # Slash IDs may contain only alphanumerics and underscores. - slash_id = chat_handler_ep.slash_id + slash_id = chat_handler.slash_id if slash_id: # TODO: Validate slash ID (/^[A-Za-z0-9_]+$/) command_name = f"/{slash_id}" else: command_name = "default" + self.log.info(f"Trying to register chat handler `{chat_handler.id}` with command `{command_name}`") if command_name in jai_chat_handlers: - log.error( + self.log.error( f"Unable to register chat handler `{chat_handler.id}` because command `{command_name}` already has a handler" ) continue - jai_chat_handlers[command_name] = chat_handler_ep # Instantiate? - log.info(f"Registered chat handler `{chat_handler.id}` with command `{command_name}`.") + + jai_chat_handlers[command_name] = chat_handler + self.log.info(f"Registered chat handler `{chat_handler.id}` with command `{command_name}`.") self.settings["jai_chat_handlers"] = jai_chat_handlers @@ -208,17 +231,10 @@ def initialize_settings(self): dask_client_future=dask_client_future, ) help_chat_handler = HelpChatHandler(**chat_handler_kwargs) + """ retriever = Retriever(learn_chat_handler=learn_chat_handler) ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) - self.settings["jai_chat_handlers"] = { - "default": default_chat_handler, - "/ask": ask_chat_handler, - "/clear": clear_chat_handler, - "/generate": generate_chat_handler, - "/learn": learn_chat_handler, - "/help": help_chat_handler, - } - + """ latency_ms = round((time.time() - start) * 1000) self.log.info(f"Initialized Jupyter AI server extension in {latency_ms} ms.") diff --git a/packages/jupyter-ai/pyproject.toml b/packages/jupyter-ai/pyproject.toml index f3d9e5cf2..0f8d4a58a 100644 --- a/packages/jupyter-ai/pyproject.toml +++ b/packages/jupyter-ai/pyproject.toml @@ -39,14 +39,6 @@ dependencies = [ dynamic = ["version", "description", "authors", "urls", "keywords"] -[project.entry-points."jupyter_ai.chat_handlers"] -ask = "jupyter_ai:AskChatHandler" -clear = "jupyter_ai:ClearChatHandler" -default = "jupyter_ai:DefaultChatHandler" -generate = "jupyter_ai:GenerateChatHandler" -help = "jupyter_ai:HelpChatHandler" -learn = "jupyter_ai:LearnChatHandler" - [project.entry-points."jupyter_ai.default_tasks"] core_default_tasks = "jupyter_ai:tasks" From 87db726a6f37051eb4e739b9ea180579ef3046a1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 27 Sep 2023 00:05:35 +0000 Subject: [PATCH 08/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../jupyter-ai/jupyter_ai/chat_handlers/ask.py | 1 + .../jupyter-ai/jupyter_ai/chat_handlers/base.py | 10 ++++++---- packages/jupyter-ai/jupyter_ai/extension.py | 16 ++++++++++------ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py index 3b34f9513..0a7df6f49 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py @@ -25,6 +25,7 @@ class AskChatHandler(BaseChatHandler): query the documents from the index, and sends this context to the LLM to generate the final reply. """ + id = "ask-chat-handler" name = "Ask with Local Data" description = "Ask a question augmented with learned data" diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index c79029148..a9d9b1bd1 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -19,15 +19,17 @@ class BaseChatHandler(Configurable): """Base ChatHandler class containing shared methods and attributes used by multiple chat handler classes.""" - + # Class attributes - id: str = 'base-chat-handler' + id: str = "base-chat-handler" """ID for this chat handler; should be unique""" - name: str = 'Base Chat Handler' # TODO: make NotImplemented + name: str = "Base Chat Handler" # TODO: make NotImplemented """User-facing name of this handler""" - description: str = "Handler for messages that are not commands" # TODO: make NotImplemented + description: str = ( + "Handler for messages that are not commands" # TODO: make NotImplemented + ) """Description used for routing requests, to be used when dispatching messages to model providers. Also shown in the UI.""" diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index eec95761a..56382e174 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -1,8 +1,8 @@ -from importlib_metadata import entry_points import logging import time from dask.distributed import Client as DaskClient +from importlib_metadata import entry_points from jupyter_ai.chat_handlers.learn import Retriever from jupyter_ai_magics.utils import get_em_providers, get_lm_providers from jupyter_server.extension.application import ExtensionApp @@ -175,7 +175,7 @@ def initialize_settings(self): "root_dir": self.serverapp.root_dir, "dask_client_future": dask_client_future, # TODO: Set ask_chat_handler based on a retriever related to the learn_chat_handler - "retriever": None + "retriever": None, } for chat_handler_ep in chat_handler_eps: @@ -196,8 +196,10 @@ def initialize_settings(self): command_name = f"/{slash_id}" else: command_name = "default" - self.log.info(f"Trying to register chat handler `{chat_handler.id}` with command `{command_name}`") - + self.log.info( + f"Trying to register chat handler `{chat_handler.id}` with command `{command_name}`" + ) + if command_name in jai_chat_handlers: self.log.error( f"Unable to register chat handler `{chat_handler.id}` because command `{command_name}` already has a handler" @@ -205,8 +207,10 @@ def initialize_settings(self): continue jai_chat_handlers[command_name] = chat_handler - self.log.info(f"Registered chat handler `{chat_handler.id}` with command `{command_name}`.") - + self.log.info( + f"Registered chat handler `{chat_handler.id}` with command `{command_name}`." + ) + self.settings["jai_chat_handlers"] = jai_chat_handlers chat_handler_kwargs = { From bfde43fc2d0e5a6f313e71b72f69f920b41b2ee6 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Wed, 27 Sep 2023 17:04:36 -0700 Subject: [PATCH 09/35] Moves config to proper location, improves error logging --- packages/jupyter-ai-magics/pyproject.toml | 8 -------- packages/jupyter-ai/jupyter_ai/extension.py | 5 +++-- packages/jupyter-ai/pyproject.toml | 8 ++++++++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/jupyter-ai-magics/pyproject.toml b/packages/jupyter-ai-magics/pyproject.toml index 6dffe6e05..bb7a6dc57 100644 --- a/packages/jupyter-ai-magics/pyproject.toml +++ b/packages/jupyter-ai-magics/pyproject.toml @@ -53,14 +53,6 @@ all = [ "boto3" ] -[project.entry-points."jupyter_ai.chat_handlers"] -ask = "jupyter_ai:AskChatHandler" -clear = "jupyter_ai:ClearChatHandler" -default = "jupyter_ai:DefaultChatHandler" -generate = "jupyter_ai:GenerateChatHandler" -help = "jupyter_ai:HelpChatHandler" -learn = "jupyter_ai:LearnChatHandler" - [project.entry-points."jupyter_ai.model_providers"] ai21 = "jupyter_ai_magics:AI21Provider" anthropic = "jupyter_ai_magics:AnthropicProvider" diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 56382e174..79cecec1b 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -181,9 +181,10 @@ def initialize_settings(self): for chat_handler_ep in chat_handler_eps: try: chat_handler = chat_handler_ep.load() - except: + except Exception as err: self.log.error( - f"Unable to load chat handler class from entry point `{chat_handler_ep.name}`." + f"Unable to load chat handler class from entry point `{chat_handler_ep.name}`: " + + f"Unexpected {err=}, {type(err)=}" ) continue diff --git a/packages/jupyter-ai/pyproject.toml b/packages/jupyter-ai/pyproject.toml index 0f8d4a58a..4ca9f86ea 100644 --- a/packages/jupyter-ai/pyproject.toml +++ b/packages/jupyter-ai/pyproject.toml @@ -39,6 +39,14 @@ dependencies = [ dynamic = ["version", "description", "authors", "urls", "keywords"] +[project.entry-points."jupyter_ai.chat_handlers"] +ask = "jupyter_ai:chat_handlers:AskChatHandler" +clear = "jupyter_ai:chat_handlers:ClearChatHandler" +default = "jupyter_ai:chat_handlers:DefaultChatHandler" +generate = "jupyter_ai:chat_handlers:GenerateChatHandler" +help = "jupyter_ai:chat_handlers:HelpChatHandler" +learn = "jupyter_ai:chat_handlers:LearnChatHandler" + [project.entry-points."jupyter_ai.default_tasks"] core_default_tasks = "jupyter_ai:tasks" From e5abafe290b0f62a8e598f115cce22569ab51580 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Oct 2023 23:45:19 +0000 Subject: [PATCH 10/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index a9d9b1bd1..c42d48636 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -9,7 +9,6 @@ from jupyter_ai.config_manager import ConfigManager, Logger from jupyter_ai.models import AgentChatMessage, HumanChatMessage from jupyter_ai_magics.providers import BaseProvider - from traitlets.config import Configurable if TYPE_CHECKING: From f053e08b411154fc7c7ce40ac2ee996d50386320 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 24 Oct 2023 15:49:08 -0700 Subject: [PATCH 11/35] WIP: Updates per feedback, adds custom handler --- .../jupyter_ai/chat_handlers/__init__.py | 1 + .../jupyter_ai/chat_handlers/ask.py | 4 +- .../jupyter_ai/chat_handlers/base.py | 23 ++++--- .../jupyter_ai/chat_handlers/clear.py | 4 +- .../jupyter_ai/chat_handlers/custom.py | 33 ++++++++++ .../jupyter_ai/chat_handlers/generate.py | 4 +- .../jupyter_ai/chat_handlers/help.py | 4 +- .../jupyter_ai/chat_handlers/learn.py | 4 +- packages/jupyter-ai/jupyter_ai/extension.py | 65 +++++++++---------- packages/jupyter-ai/pyproject.toml | 7 +- 10 files changed, 88 insertions(+), 61 deletions(-) create mode 100644 packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py index c3c64b789..5da53d8ff 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py @@ -1,6 +1,7 @@ from .ask import AskChatHandler from .base import BaseChatHandler from .clear import ClearChatHandler +from .custom import CustomChatHandler from .default import DefaultChatHandler from .generate import GenerateChatHandler from .help import HelpChatHandler diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py index 0a7df6f49..284bfdcad 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py @@ -26,10 +26,10 @@ class AskChatHandler(BaseChatHandler): to the LLM to generate the final reply. """ - id = "ask-chat-handler" + id = "ask" name = "Ask with Local Data" - description = "Ask a question augmented with learned data" help = "Asks a question with retrieval augmented generation (RAG)" + routing_method = "slash_command" slash_id = "ask" def __init__(self, retriever, *args, **kwargs): diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index c42d48636..e6987b88b 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -3,7 +3,7 @@ import traceback # necessary to prevent circular import -from typing import TYPE_CHECKING, Any, Dict, Optional, Type +from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type from uuid import uuid4 from jupyter_ai.config_manager import ConfigManager, Logger @@ -20,27 +20,30 @@ class BaseChatHandler(Configurable): multiple chat handler classes.""" # Class attributes - id: str = "base-chat-handler" + id: ClassVar[str] = "base" """ID for this chat handler; should be unique""" - name: str = "Base Chat Handler" # TODO: make NotImplemented + name: ClassVar[str] = "Base Chat Handler" # TODO: make NotImplemented """User-facing name of this handler""" - description: str = ( - "Handler for messages that are not commands" # TODO: make NotImplemented - ) + description: ClassVar[Optional[str]] """Description used for routing requests, to be used when dispatching - messages to model providers. Also shown in the UI.""" + messages to model providers. Also shown in the UI for transparency; + optimized for model interpretation, not human-facing help. + Not necessary when the routing method is "slash_command".""" # TODO: make NotImplemented - help: str = "This is used when the message in the chat interface is not a command" + help: ClassVar[str] = "This is used when the message in the chat interface is not a command" """What this chat handler does, which third-party models it contacts, the format of the data it returns to the user, etc. Used in the UI.""" - slash_id: Optional[str] + # TODO: make NotImplemented + routing_method: ClassVar[str] = "slash_command" + + slash_id: ClassVar[Optional[str]] """Slash ID for routing a chat command to this handler. Only one handler may declare a particular slash ID. Must contain only alphanumerics and - underscores.""" + underscores. Must not be specified if routing method is "natural_language".""" def __init__( self, diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py index e2b95f76c..c3db4e969 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py @@ -6,10 +6,10 @@ class ClearChatHandler(BaseChatHandler): - id = "clear-chat-handler" + id = "clear" name = "Clear chat messages" - description = "Clear the chat message history display" help = "Clears the displayed chat message history only; does not clear the context sent to chat providers" + routing_method = "slash_command" slash_id = "clear" def __init__(self, chat_history: List[ChatMessage], *args, **kwargs): diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py new file mode 100644 index 000000000..2f419721a --- /dev/null +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py @@ -0,0 +1,33 @@ +import time +from typing import List +from uuid import uuid4 + +from jupyter_ai.models import AgentChatMessage, HumanChatMessage + +from .base import BaseChatHandler + +CUSTOM_MESSAGE = "This handler displays a custom message in response to any prompt." + +def CustomMessage(): + return AgentChatMessage( + id=uuid4().hex, + time=time.time(), + body=CUSTOM_MESSAGE, + reply_to="", + ) + +""" +This is a sample custom chat handler class to demonstrate entry points. +""" +class CustomChatHandler(BaseChatHandler): + id = "custom" + name = "Custom" + help = "Displays a custom message in the chat message area" + routing_method = "slash_command" + slash_id = "custom" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def _process_message(self, message: HumanChatMessage): + self.reply(CUSTOM_MESSAGE, message) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py index 24b31c3b2..9e4b508cc 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py @@ -216,10 +216,10 @@ def create_notebook(outline): class GenerateChatHandler(BaseChatHandler): - id = "generate-chat-handler" + id = "generate" name = "Generate Notebook" - description = "Generates a Jupyter notebook given a description" help = "Generates a Jupyter notebook, including name, outline, and section contents" + routing_method = "slash_command" slash_id = "generate" def __init__(self, root_dir: str, *args, **kwargs): diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py index d8433a981..a380d99c7 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py @@ -29,10 +29,10 @@ def HelpMessage(): class HelpChatHandler(BaseChatHandler): - id = "help-chat-handler" + id = "help" name = "Help" - description = "Displays information about available commands" help = "Displays a help message in the chat message area" + routing_method = "slash_command" slash_id = "help" def __init__(self, *args, **kwargs): diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py index 34724e0ef..c1583ff03 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py @@ -31,10 +31,10 @@ class LearnChatHandler(BaseChatHandler): - id = "learn-chat-handler" + id = "learn" name = "Learn Local Data" - description = "Embed a list of files and directories for use with /ask" help = "Pass a list of files and directories. Once converted to vector format, you can ask about them with /ask." + routing_method = "slash_command" slash_id = "learn" def __init__( diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 79cecec1b..ae70a73f2 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -42,7 +42,7 @@ class AiExtension(ExtensionApp): allowed_providers = List( Unicode(), default_value=None, - help="Identifiers of allow-listed providers. If `None`, all are allowed.", + help="Identifiers of allowlisted providers. If `None`, all are allowed.", allow_none=True, config=True, ) @@ -50,7 +50,7 @@ class AiExtension(ExtensionApp): blocked_providers = List( Unicode(), default_value=None, - help="Identifiers of block-listed providers. If `None`, none are blocked.", + help="Identifiers of blocklisted providers. If `None`, none are blocked.", allow_none=True, config=True, ) @@ -163,19 +163,38 @@ def initialize_settings(self): self.log.info("Found entry point groups " + ", ".join(sorted(eps.groups))) chat_handler_eps = eps.select(group="jupyter_ai.chat_handlers") - jai_chat_handlers = {} - chat_handler_kwargs = { "log": self.log, "config_manager": self.settings["jai_config_manager"], "root_chat_handlers": self.settings["jai_root_chat_handlers"], - # Everything below this line is the union of all arguments used to - # instantiate chat handlers. - "chat_history": self.settings["chat_history"], - "root_dir": self.serverapp.root_dir, - "dask_client_future": dask_client_future, - # TODO: Set ask_chat_handler based on a retriever related to the learn_chat_handler - "retriever": None, + } + + default_chat_handler = DefaultChatHandler( + **chat_handler_kwargs, chat_history=self.settings["chat_history"] + ) + clear_chat_handler = ClearChatHandler( + **chat_handler_kwargs, chat_history=self.settings["chat_history"] + ) + generate_chat_handler = GenerateChatHandler( + **chat_handler_kwargs, + root_dir=self.serverapp.root_dir, + ) + learn_chat_handler = LearnChatHandler( + **chat_handler_kwargs, + root_dir=self.serverapp.root_dir, + dask_client_future=dask_client_future, + ) + help_chat_handler = HelpChatHandler(**chat_handler_kwargs) + retriever = Retriever(learn_chat_handler=learn_chat_handler) + ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) + + jai_chat_handlers = { + "default": default_chat_handler, + "/ask": ask_chat_handler, + "/clear": clear_chat_handler, + "/generate": generate_chat_handler, + "/learn": learn_chat_handler, + "/help": help_chat_handler, } for chat_handler_ep in chat_handler_eps: @@ -214,32 +233,8 @@ def initialize_settings(self): self.settings["jai_chat_handlers"] = jai_chat_handlers - chat_handler_kwargs = { - "log": self.log, - "config_manager": self.settings["jai_config_manager"], - "root_chat_handlers": self.settings["jai_root_chat_handlers"], - "model_parameters": self.settings["model_parameters"], - } - default_chat_handler = DefaultChatHandler( - **chat_handler_kwargs, chat_history=self.settings["chat_history"] - ) - clear_chat_handler = ClearChatHandler( - **chat_handler_kwargs, chat_history=self.settings["chat_history"] - ) - generate_chat_handler = GenerateChatHandler( - **chat_handler_kwargs, - root_dir=self.serverapp.root_dir, - ) - learn_chat_handler = LearnChatHandler( - **chat_handler_kwargs, - root_dir=self.serverapp.root_dir, - dask_client_future=dask_client_future, - ) - help_chat_handler = HelpChatHandler(**chat_handler_kwargs) - """ retriever = Retriever(learn_chat_handler=learn_chat_handler) ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) - """ latency_ms = round((time.time() - start) * 1000) self.log.info(f"Initialized Jupyter AI server extension in {latency_ms} ms.") diff --git a/packages/jupyter-ai/pyproject.toml b/packages/jupyter-ai/pyproject.toml index 4ca9f86ea..eb8fa4283 100644 --- a/packages/jupyter-ai/pyproject.toml +++ b/packages/jupyter-ai/pyproject.toml @@ -40,12 +40,7 @@ dependencies = [ dynamic = ["version", "description", "authors", "urls", "keywords"] [project.entry-points."jupyter_ai.chat_handlers"] -ask = "jupyter_ai:chat_handlers:AskChatHandler" -clear = "jupyter_ai:chat_handlers:ClearChatHandler" -default = "jupyter_ai:chat_handlers:DefaultChatHandler" -generate = "jupyter_ai:chat_handlers:GenerateChatHandler" -help = "jupyter_ai:chat_handlers:HelpChatHandler" -learn = "jupyter_ai:chat_handlers:LearnChatHandler" +custom = "jupyter_ai:chat_handlers.CustomChatHandler" [project.entry-points."jupyter_ai.default_tasks"] core_default_tasks = "jupyter_ai:tasks" From 526366cf880fb453e52590f2ba6a0b90cb4b5fa0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 22:49:45 +0000 Subject: [PATCH 12/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 4 +++- packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index e6987b88b..74c3e5f2d 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -33,7 +33,9 @@ class BaseChatHandler(Configurable): Not necessary when the routing method is "slash_command".""" # TODO: make NotImplemented - help: ClassVar[str] = "This is used when the message in the chat interface is not a command" + help: ClassVar[ + str + ] = "This is used when the message in the chat interface is not a command" """What this chat handler does, which third-party models it contacts, the format of the data it returns to the user, etc. Used in the UI.""" diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py index 2f419721a..d69b29413 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py @@ -8,6 +8,7 @@ CUSTOM_MESSAGE = "This handler displays a custom message in response to any prompt." + def CustomMessage(): return AgentChatMessage( id=uuid4().hex, @@ -16,9 +17,12 @@ def CustomMessage(): reply_to="", ) + """ This is a sample custom chat handler class to demonstrate entry points. """ + + class CustomChatHandler(BaseChatHandler): id = "custom" name = "Custom" From 963495b951d5e3af573a2f4df67a4155f360577a Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 24 Oct 2023 16:50:32 -0700 Subject: [PATCH 13/35] Removes redundant code, style fixes --- .../jupyter-ai/jupyter_ai/chat_handlers/base.py | 14 ++++++-------- packages/jupyter-ai/jupyter_ai/extension.py | 14 +++----------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 74c3e5f2d..be667a741 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -20,10 +20,10 @@ class BaseChatHandler(Configurable): multiple chat handler classes.""" # Class attributes - id: ClassVar[str] = "base" + id: ClassVar[str] = ... """ID for this chat handler; should be unique""" - name: ClassVar[str] = "Base Chat Handler" # TODO: make NotImplemented + name: ClassVar[str] = ... """User-facing name of this handler""" description: ClassVar[Optional[str]] @@ -32,15 +32,13 @@ class BaseChatHandler(Configurable): optimized for model interpretation, not human-facing help. Not necessary when the routing method is "slash_command".""" - # TODO: make NotImplemented - help: ClassVar[ - str - ] = "This is used when the message in the chat interface is not a command" + help: ClassVar[str] = ... """What this chat handler does, which third-party models it contacts, the format of the data it returns to the user, etc. Used in the UI.""" - # TODO: make NotImplemented - routing_method: ClassVar[str] = "slash_command" + routing_method: ClassVar[str] = ... + """The routing method that sends commands to this handler. + Either "natural_language" or "slash_command".""" slash_id: ClassVar[Optional[str]] """Slash ID for routing a chat command to this handler. Only one handler diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index ae70a73f2..b2eefcfce 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -160,7 +160,6 @@ def initialize_settings(self): eps = entry_points() # initialize chat handlers - self.log.info("Found entry point groups " + ", ".join(sorted(eps.groups))) chat_handler_eps = eps.select(group="jupyter_ai.chat_handlers") chat_handler_kwargs = { @@ -211,14 +210,9 @@ def initialize_settings(self): # Slash IDs may contain only alphanumerics and underscores. slash_id = chat_handler.slash_id - if slash_id: - # TODO: Validate slash ID (/^[A-Za-z0-9_]+$/) - command_name = f"/{slash_id}" - else: - command_name = "default" - self.log.info( - f"Trying to register chat handler `{chat_handler.id}` with command `{command_name}`" - ) + # TODO: Validate slash ID (/^[A-Za-z0-9_]+$/) + # The "default" handler above takes precedence over any "default" command here + command_name = f"/{chat_handler.slash_id}" if chat_handler.slash_id else "default" if command_name in jai_chat_handlers: self.log.error( @@ -233,8 +227,6 @@ def initialize_settings(self): self.settings["jai_chat_handlers"] = jai_chat_handlers - retriever = Retriever(learn_chat_handler=learn_chat_handler) - ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) latency_ms = round((time.time() - start) * 1000) self.log.info(f"Initialized Jupyter AI server extension in {latency_ms} ms.") From 7caf1ef02c5e297ad7c428f86b7c59f2a028aca2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 24 Oct 2023 23:52:57 +0000 Subject: [PATCH 14/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/extension.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index b2eefcfce..f38c6d4f9 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -212,7 +212,9 @@ def initialize_settings(self): # TODO: Validate slash ID (/^[A-Za-z0-9_]+$/) # The "default" handler above takes precedence over any "default" command here - command_name = f"/{chat_handler.slash_id}" if chat_handler.slash_id else "default" + command_name = ( + f"/{chat_handler.slash_id}" if chat_handler.slash_id else "default" + ) if command_name in jai_chat_handlers: self.log.error( From 1709d2147bcf94cb84921c48a87776755c44be42 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 24 Oct 2023 17:02:49 -0700 Subject: [PATCH 15/35] Removes unnecessary custom message --- .../jupyter-ai/jupyter_ai/chat_handlers/custom.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py index d69b29413..88ddec42b 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py @@ -1,23 +1,10 @@ -import time -from typing import List -from uuid import uuid4 - -from jupyter_ai.models import AgentChatMessage, HumanChatMessage +from jupyter_ai.models import HumanChatMessage from .base import BaseChatHandler CUSTOM_MESSAGE = "This handler displays a custom message in response to any prompt." -def CustomMessage(): - return AgentChatMessage( - id=uuid4().hex, - time=time.time(), - body=CUSTOM_MESSAGE, - reply_to="", - ) - - """ This is a sample custom chat handler class to demonstrate entry points. """ From 0a6ab9d494a09852740e42320d0a0bec49937bdf Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Wed, 25 Oct 2023 14:05:06 -0700 Subject: [PATCH 16/35] Instantiates class --- packages/jupyter-ai/jupyter_ai/extension.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index f38c6d4f9..93dc67ee8 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -222,7 +222,8 @@ def initialize_settings(self): ) continue - jai_chat_handlers[command_name] = chat_handler + # The entry point is a class; we need to instantiate the class to send messages to it + jai_chat_handlers[command_name] = chat_handler(**chat_handler_kwargs) self.log.info( f"Registered chat handler `{chat_handler.id}` with command `{command_name}`." ) From daecd575f6561c95c3a2046a042d64bfb6ea7bde Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Wed, 25 Oct 2023 14:25:58 -0700 Subject: [PATCH 17/35] Validates slash ID --- packages/jupyter-ai/jupyter_ai/extension.py | 22 ++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 93dc67ee8..31178ac2d 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -1,4 +1,5 @@ import logging +import re import time from dask.distributed import Client as DaskClient @@ -196,6 +197,7 @@ def initialize_settings(self): "/help": help_chat_handler, } + slash_command_pattern = r'^[a-zA-Z0-9_]+$' for chat_handler_ep in chat_handler_eps: try: chat_handler = chat_handler_ep.load() @@ -210,11 +212,21 @@ def initialize_settings(self): # Slash IDs may contain only alphanumerics and underscores. slash_id = chat_handler.slash_id - # TODO: Validate slash ID (/^[A-Za-z0-9_]+$/) - # The "default" handler above takes precedence over any "default" command here - command_name = ( - f"/{chat_handler.slash_id}" if chat_handler.slash_id else "default" - ) + if chat_handler.routing_method == 'slash_command': + if chat_handler.slash_id: + # Validate slash ID (/^[A-Za-z0-9_]+$/) + if re.match(slash_command_pattern, chat_handler.slash_id): + command_name = f"/{chat_handler.slash_id}" + else: + self.log.error( + f"Handler `{chat_handler_ep.name}` has an invalid slash command " + + f"`{chat_handler.slash_id}`; must contain only letters, numbers, " + + "and underscores" + ) + continue + else: # No slash ID provided; assume default + # The "default" handler above takes precedence over any "default" command here + command_name = "default" if command_name in jai_chat_handlers: self.log.error( From b7b550175fd53ac0d663ceba702ff1b3cc292426 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 21:28:50 +0000 Subject: [PATCH 18/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/extension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 31178ac2d..6b0b6b271 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -197,7 +197,7 @@ def initialize_settings(self): "/help": help_chat_handler, } - slash_command_pattern = r'^[a-zA-Z0-9_]+$' + slash_command_pattern = r"^[a-zA-Z0-9_]+$" for chat_handler_ep in chat_handler_eps: try: chat_handler = chat_handler_ep.load() @@ -212,7 +212,7 @@ def initialize_settings(self): # Slash IDs may contain only alphanumerics and underscores. slash_id = chat_handler.slash_id - if chat_handler.routing_method == 'slash_command': + if chat_handler.routing_method == "slash_command": if chat_handler.slash_id: # Validate slash ID (/^[A-Za-z0-9_]+$/) if re.match(slash_command_pattern, chat_handler.slash_id): From a4f33b69c698bf5cada729eee3a5d8742afab95e Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Wed, 25 Oct 2023 15:48:08 -0700 Subject: [PATCH 19/35] Consistent arguments to chat handlers --- .../jupyter_ai/chat_handlers/base.py | 12 ++++++++-- .../jupyter_ai/chat_handlers/clear.py | 3 +-- .../jupyter_ai/chat_handlers/default.py | 7 +++--- .../jupyter_ai/chat_handlers/generate.py | 3 +-- .../jupyter_ai/chat_handlers/learn.py | 6 +---- packages/jupyter-ai/jupyter_ai/extension.py | 22 ++++++------------- packages/jupyter-ai/jupyter_ai/handlers.py | 2 +- 7 files changed, 24 insertions(+), 31 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index be667a741..34d7e0c81 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -1,13 +1,15 @@ import argparse +import os import time import traceback # necessary to prevent circular import -from typing import TYPE_CHECKING, ClassVar, Dict, Optional, Type +from typing import TYPE_CHECKING, Awaitable, ClassVar, Dict, List, Optional, Type from uuid import uuid4 +from dask.distributed import Client as DaskClient from jupyter_ai.config_manager import ConfigManager, Logger -from jupyter_ai.models import AgentChatMessage, HumanChatMessage +from jupyter_ai.models import AgentChatMessage, ChatMessage, HumanChatMessage from jupyter_ai_magics.providers import BaseProvider from traitlets.config import Configurable @@ -51,12 +53,18 @@ def __init__( config_manager: ConfigManager, root_chat_handlers: Dict[str, "RootChatHandler"], model_parameters: Dict[str, Dict], + chat_history: List[ChatMessage], + root_dir: str, + dask_client_future: Awaitable[DaskClient], ): self.log = log self.config_manager = config_manager self._root_chat_handlers = root_chat_handlers self.model_parameters = model_parameters + self._chat_history = chat_history self.parser = argparse.ArgumentParser() + self.root_dir = os.path.abspath(os.path.expanduser(root_dir)) + self.dask_client_future = dask_client_future self.llm = None self.llm_params = None self.llm_chain = None diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py index c3db4e969..bff11c235 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py @@ -12,9 +12,8 @@ class ClearChatHandler(BaseChatHandler): routing_method = "slash_command" slash_id = "clear" - def __init__(self, chat_history: List[ChatMessage], *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self._chat_history = chat_history async def process_message(self, _): self._chat_history.clear() diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py index d329e05e2..24c5bf3c5 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py @@ -32,10 +32,9 @@ class DefaultChatHandler(BaseChatHandler): - def __init__(self, chat_history: List[ChatMessage], *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.memory = ConversationBufferWindowMemory(return_messages=True, k=2) - self.chat_history = chat_history def create_llm_chain( self, provider: Type[BaseProvider], provider_params: Dict[str, str] @@ -80,8 +79,8 @@ def clear_memory(self): self.reply(reply_message) # clear transcript for new chat clients - if self.chat_history: - self.chat_history.clear() + if self._chat_history: + self._chat_history.clear() async def process_message(self, message: HumanChatMessage): self.get_llm_chain() diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py index 9e4b508cc..1bfea49b2 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py @@ -222,9 +222,8 @@ class GenerateChatHandler(BaseChatHandler): routing_method = "slash_command" slash_id = "generate" - def __init__(self, root_dir: str, *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.root_dir = os.path.abspath(os.path.expanduser(root_dir)) self.llm = None def create_llm_chain( diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py index c1583ff03..8feabdefd 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py @@ -37,12 +37,8 @@ class LearnChatHandler(BaseChatHandler): routing_method = "slash_command" slash_id = "learn" - def __init__( - self, root_dir: str, dask_client_future: Awaitable[DaskClient], *args, **kwargs - ): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.root_dir = root_dir - self.dask_client_future = dask_client_future self.parser.prog = "/learn" self.parser.add_argument("-a", "--all-files", action="store_true") self.parser.add_argument("-v", "--verbose", action="store_true") diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 6b0b6b271..ac16b9515 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -167,23 +167,15 @@ def initialize_settings(self): "log": self.log, "config_manager": self.settings["jai_config_manager"], "root_chat_handlers": self.settings["jai_root_chat_handlers"], + "chat_history": self.settings["chat_history"], + "root_dir": self.serverapp.root_dir, + "dask_client_future": dask_client_future, } - default_chat_handler = DefaultChatHandler( - **chat_handler_kwargs, chat_history=self.settings["chat_history"] - ) - clear_chat_handler = ClearChatHandler( - **chat_handler_kwargs, chat_history=self.settings["chat_history"] - ) - generate_chat_handler = GenerateChatHandler( - **chat_handler_kwargs, - root_dir=self.serverapp.root_dir, - ) - learn_chat_handler = LearnChatHandler( - **chat_handler_kwargs, - root_dir=self.serverapp.root_dir, - dask_client_future=dask_client_future, - ) + default_chat_handler = DefaultChatHandler(**chat_handler_kwargs) + clear_chat_handler = ClearChatHandler(**chat_handler_kwargs) + generate_chat_handler = GenerateChatHandler(**chat_handler_kwargs) + learn_chat_handler = LearnChatHandler(**chat_handler_kwargs) help_chat_handler = HelpChatHandler(**chat_handler_kwargs) retriever = Retriever(learn_chat_handler=learn_chat_handler) ask_chat_handler = AskChatHandler(**chat_handler_kwargs, retriever=retriever) diff --git a/packages/jupyter-ai/jupyter_ai/handlers.py b/packages/jupyter-ai/jupyter_ai/handlers.py index 126a6c94c..230b308d5 100644 --- a/packages/jupyter-ai/jupyter_ai/handlers.py +++ b/packages/jupyter-ai/jupyter_ai/handlers.py @@ -168,7 +168,7 @@ def open(self): def broadcast_message(self, message: Message): """Broadcasts message to all connected clients. - Appends message to `self.chat_history`. + Appends message to chat history. """ self.log.debug("Broadcasting message: %s to all clients...", message) From 6fcf87eed09236dd550116d0cee17203ea078b77 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 25 Oct 2023 22:48:21 +0000 Subject: [PATCH 20/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py index 1bfea49b2..85fd8c665 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py @@ -222,7 +222,7 @@ class GenerateChatHandler(BaseChatHandler): routing_method = "slash_command" slash_id = "generate" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.llm = None From 178b0bee543c3693125e995ef1d196870f90c6fc Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Thu, 2 Nov 2023 16:01:08 -0700 Subject: [PATCH 21/35] Refactors to avoid intentionally unused params --- .../jupyter_ai/chat_handlers/__init__.py | 2 +- .../jupyter_ai/chat_handlers/ask.py | 5 +-- .../jupyter_ai/chat_handlers/base.py | 37 ++++++++++------- .../jupyter_ai/chat_handlers/clear.py | 5 +-- .../jupyter_ai/chat_handlers/custom.py | 5 +-- .../jupyter_ai/chat_handlers/default.py | 7 +++- .../jupyter_ai/chat_handlers/generate.py | 5 +-- .../jupyter_ai/chat_handlers/help.py | 5 +-- .../jupyter_ai/chat_handlers/learn.py | 5 +-- packages/jupyter-ai/jupyter_ai/extension.py | 41 ++++++++++--------- 10 files changed, 64 insertions(+), 53 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py index 5da53d8ff..bfe4cac12 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py @@ -1,5 +1,5 @@ from .ask import AskChatHandler -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType from .clear import ClearChatHandler from .custom import CustomChatHandler from .default import DefaultChatHandler diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py index 284bfdcad..bfb55ce21 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/ask.py @@ -7,7 +7,7 @@ from langchain.memory import ConversationBufferWindowMemory from langchain.prompts import PromptTemplate -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType PROMPT_TEMPLATE = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question. @@ -29,8 +29,7 @@ class AskChatHandler(BaseChatHandler): id = "ask" name = "Ask with Local Data" help = "Asks a question with retrieval augmented generation (RAG)" - routing_method = "slash_command" - slash_id = "ask" + routing_type = SlashCommandRoutingType(slash_id="ask") def __init__(self, retriever, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 34d7e0c81..faf58d065 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -4,6 +4,7 @@ import traceback # necessary to prevent circular import +from pydantic import BaseModel from typing import TYPE_CHECKING, Awaitable, ClassVar, Dict, List, Optional, Type from uuid import uuid4 @@ -16,6 +17,27 @@ if TYPE_CHECKING: from jupyter_ai.handlers import RootChatHandler +# Chat handler type, with specific attributes for each +class HandlerRoutingType(BaseModel): + routing_method: ClassVar[str] = ... + """The routing method that sends commands to this handler. + Either "natural_language" or "slash_command".""" + +class SlashCommandRoutingType(HandlerRoutingType): + routing_method = "slash_command" + + slash_id: Optional[str] + """Slash ID for routing a chat command to this handler. Only one handler + may declare a particular slash ID. Must contain only alphanumerics and + underscores.""" + +class NaturalLanguageRoutingType(HandlerRoutingType): + routing_method = "natural_language" + + description: str + """Description used for routing requests, to be used when dispatching + messages to model providers. Also shown in the UI for transparency; + optimized for model interpretation, not human-facing help.""" class BaseChatHandler(Configurable): """Base ChatHandler class containing shared methods and attributes used by @@ -28,24 +50,11 @@ class BaseChatHandler(Configurable): name: ClassVar[str] = ... """User-facing name of this handler""" - description: ClassVar[Optional[str]] - """Description used for routing requests, to be used when dispatching - messages to model providers. Also shown in the UI for transparency; - optimized for model interpretation, not human-facing help. - Not necessary when the routing method is "slash_command".""" - help: ClassVar[str] = ... """What this chat handler does, which third-party models it contacts, the format of the data it returns to the user, etc. Used in the UI.""" - routing_method: ClassVar[str] = ... - """The routing method that sends commands to this handler. - Either "natural_language" or "slash_command".""" - - slash_id: ClassVar[Optional[str]] - """Slash ID for routing a chat command to this handler. Only one handler - may declare a particular slash ID. Must contain only alphanumerics and - underscores. Must not be specified if routing method is "natural_language".""" + routing_type: HandlerRoutingType = ... def __init__( self, diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py index bff11c235..7042c4632 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/clear.py @@ -2,15 +2,14 @@ from jupyter_ai.models import ChatMessage, ClearMessage -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType class ClearChatHandler(BaseChatHandler): id = "clear" name = "Clear chat messages" help = "Clears the displayed chat message history only; does not clear the context sent to chat providers" - routing_method = "slash_command" - slash_id = "clear" + routing_type = SlashCommandRoutingType(slash_id="clear") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py index 88ddec42b..eb69552b2 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py @@ -1,6 +1,6 @@ from jupyter_ai.models import HumanChatMessage -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType CUSTOM_MESSAGE = "This handler displays a custom message in response to any prompt." @@ -14,8 +14,7 @@ class CustomChatHandler(BaseChatHandler): id = "custom" name = "Custom" help = "Displays a custom message in the chat message area" - routing_method = "slash_command" - slash_id = "custom" + routing_type = SlashCommandRoutingType(slash_id="custom") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py index 24c5bf3c5..5bd839ca5 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/default.py @@ -12,7 +12,7 @@ SystemMessagePromptTemplate, ) -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType SYSTEM_PROMPT = """ You are Jupyternaut, a conversational assistant living in JupyterLab to help users. @@ -32,6 +32,11 @@ class DefaultChatHandler(BaseChatHandler): + id = "default" + name = "Default" + help = "Responds to prompts that are not otherwise handled by a chat handler" + routing_type = SlashCommandRoutingType(slash_id=None) + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.memory = ConversationBufferWindowMemory(return_messages=True, k=2) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py index 85fd8c665..b1ccb3b6e 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/generate.py @@ -6,7 +6,7 @@ from typing import Dict, List, Optional, Type import nbformat -from jupyter_ai.chat_handlers import BaseChatHandler +from jupyter_ai.chat_handlers import BaseChatHandler, SlashCommandRoutingType from jupyter_ai.models import HumanChatMessage from jupyter_ai_magics.providers import BaseProvider from langchain.chains import LLMChain @@ -219,8 +219,7 @@ class GenerateChatHandler(BaseChatHandler): id = "generate" name = "Generate Notebook" help = "Generates a Jupyter notebook, including name, outline, and section contents" - routing_method = "slash_command" - slash_id = "generate" + routing_type = SlashCommandRoutingType(slash_id="generate") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py index a380d99c7..cbf4c19c9 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/help.py @@ -4,7 +4,7 @@ from jupyter_ai.models import AgentChatMessage, HumanChatMessage -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType HELP_MESSAGE = """Hi there! I'm Jupyternaut, your programming assistant. You can ask me a question using the text box below. You can also use these commands: @@ -32,8 +32,7 @@ class HelpChatHandler(BaseChatHandler): id = "help" name = "Help" help = "Displays a help message in the chat message area" - routing_method = "slash_command" - slash_id = "help" + routing_type = SlashCommandRoutingType(slash_id="help") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py index 8feabdefd..68a59bef1 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/learn.py @@ -24,7 +24,7 @@ ) from langchain.vectorstores import FAISS -from .base import BaseChatHandler +from .base import BaseChatHandler, SlashCommandRoutingType INDEX_SAVE_DIR = os.path.join(jupyter_data_dir(), "jupyter_ai", "indices") METADATA_SAVE_PATH = os.path.join(INDEX_SAVE_DIR, "metadata.json") @@ -34,8 +34,7 @@ class LearnChatHandler(BaseChatHandler): id = "learn" name = "Learn Local Data" help = "Pass a list of files and directories. Once converted to vector format, you can ask about them with /ask." - routing_method = "slash_command" - slash_id = "learn" + routing_type = SlashCommandRoutingType(slash_id="learn") def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index ac16b9515..947ddc426 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -200,25 +200,28 @@ def initialize_settings(self): ) continue - # Each slash ID, including None (default), must be used only once. - # Slash IDs may contain only alphanumerics and underscores. - slash_id = chat_handler.slash_id - - if chat_handler.routing_method == "slash_command": - if chat_handler.slash_id: - # Validate slash ID (/^[A-Za-z0-9_]+$/) - if re.match(slash_command_pattern, chat_handler.slash_id): - command_name = f"/{chat_handler.slash_id}" - else: - self.log.error( - f"Handler `{chat_handler_ep.name}` has an invalid slash command " - + f"`{chat_handler.slash_id}`; must contain only letters, numbers, " - + "and underscores" - ) - continue - else: # No slash ID provided; assume default - # The "default" handler above takes precedence over any "default" command here - command_name = "default" + if chat_handler.routing_type.routing_method == "slash_command": + # Each slash ID must be used only once. + # Slash IDs may contain only alphanumerics and underscores. + slash_id = chat_handler.routing_type.slash_id + + if slash_id is None: + self.log.error( + f"Handler `{chat_handler_ep.name}` has an invalid slash command " + + f"`None`; only the default chat handler may use this" + ) + continue + + # Validate slash ID (/^[A-Za-z0-9_]+$/) + if re.match(slash_command_pattern, slash_id): + command_name = f"/{slash_id}" + else: + self.log.error( + f"Handler `{chat_handler_ep.name}` has an invalid slash command " + + f"`{slash_id}`; must contain only letters, numbers, " + + "and underscores" + ) + continue if command_name in jai_chat_handlers: self.log.error( From 29d4964ebd1ea306d044ac8e00bbf2b53d9bdf1d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 23:01:43 +0000 Subject: [PATCH 22/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index faf58d065..0c33f270e 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -2,9 +2,6 @@ import os import time import traceback - -# necessary to prevent circular import -from pydantic import BaseModel from typing import TYPE_CHECKING, Awaitable, ClassVar, Dict, List, Optional, Type from uuid import uuid4 @@ -12,17 +9,22 @@ from jupyter_ai.config_manager import ConfigManager, Logger from jupyter_ai.models import AgentChatMessage, ChatMessage, HumanChatMessage from jupyter_ai_magics.providers import BaseProvider + +# necessary to prevent circular import +from pydantic import BaseModel from traitlets.config import Configurable if TYPE_CHECKING: from jupyter_ai.handlers import RootChatHandler + # Chat handler type, with specific attributes for each class HandlerRoutingType(BaseModel): routing_method: ClassVar[str] = ... """The routing method that sends commands to this handler. Either "natural_language" or "slash_command".""" + class SlashCommandRoutingType(HandlerRoutingType): routing_method = "slash_command" @@ -31,6 +33,7 @@ class SlashCommandRoutingType(HandlerRoutingType): may declare a particular slash ID. Must contain only alphanumerics and underscores.""" + class NaturalLanguageRoutingType(HandlerRoutingType): routing_method = "natural_language" @@ -39,6 +42,7 @@ class NaturalLanguageRoutingType(HandlerRoutingType): messages to model providers. Also shown in the UI for transparency; optimized for model interpretation, not human-facing help.""" + class BaseChatHandler(Configurable): """Base ChatHandler class containing shared methods and attributes used by multiple chat handler classes.""" From 91a50cbc5f53ef0bba409c3af31b880b230fa6fe Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Mon, 6 Nov 2023 14:57:16 -0800 Subject: [PATCH 23/35] Updates docs, removes custom handler from source and config --- docs/source/contributors/index.md | 3 +- docs/source/developers/index.md | 40 +++++++++++++++++++ .../jupyter_ai/chat_handlers/__init__.py | 1 - .../jupyter_ai/chat_handlers/custom.py | 23 ----------- packages/jupyter-ai/pyproject.toml | 3 -- 5 files changed, 42 insertions(+), 28 deletions(-) delete mode 100644 packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py diff --git a/docs/source/contributors/index.md b/docs/source/contributors/index.md index 0cc174098..3e0658704 100644 --- a/docs/source/contributors/index.md +++ b/docs/source/contributors/index.md @@ -2,7 +2,8 @@ This page is intended for people interested in building new or modified functionality for Jupyter AI. -If you would like to build applications that enhance Jupyter AI, please see the {doc}`developer's guide `. +If you would like to build applications that enhance Jupyter AI, +please see the {doc}`developer's guide `. ## Design principles diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index 12e714048..3370311ff 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -120,3 +120,43 @@ class MyProvider(BaseProvider, FakeListLLM): ``` Please note that this will only work with Jupyter AI magics (the `%ai` and `%%ai` magic commands). Custom prompt templates are not used in the chat interface yet. + +## Custom slash commands in the chat UI + +You can add a custom slash command to the chat interface by +creating a new class that inherits from `BaseChatHandler`. Set +its `id`, `name`, `help` message for display in the user interface, +and `routing_type`. Each custom slash command must have a unique +slash command. Slash commands can only contain ASCII letters, numerals, +and underscores. Each slash command must be unique; custom slash +commands cannot replace built-in slash commands. + +Add your custom handler in Python code: + +```python +from jupyter_ai.base import BaseChatHandler, SlashCommandRoutingType + +class CustomChatHandler(BaseChatHandler): + id = "custom" + name = "Custom" + help = "A chat handler that does something custom" + routing_type = SlashCommandRoutingType(slash_id="custom") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def _process_message(self, message: HumanChatMessage): + # Put your custom logic here +``` + +Jupyter AI uses entry points to support custom slash commands. +In the `pyproject.toml` file, add your custom handler to the +`[project.entry-points."jupyter_ai.chat_handlers"]` section: + +``` +[project.entry-points."jupyter_ai.chat_handlers"] +custom = "custom_package:chat_handlers.CustomChatHandler" +``` + +Then, install your package so that Jupyter AI adds custom chat handlers +to the existing chat handlers. diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py index bfe4cac12..e4c69f012 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/__init__.py @@ -1,7 +1,6 @@ from .ask import AskChatHandler from .base import BaseChatHandler, SlashCommandRoutingType from .clear import ClearChatHandler -from .custom import CustomChatHandler from .default import DefaultChatHandler from .generate import GenerateChatHandler from .help import HelpChatHandler diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py deleted file mode 100644 index eb69552b2..000000000 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/custom.py +++ /dev/null @@ -1,23 +0,0 @@ -from jupyter_ai.models import HumanChatMessage - -from .base import BaseChatHandler, SlashCommandRoutingType - -CUSTOM_MESSAGE = "This handler displays a custom message in response to any prompt." - - -""" -This is a sample custom chat handler class to demonstrate entry points. -""" - - -class CustomChatHandler(BaseChatHandler): - id = "custom" - name = "Custom" - help = "Displays a custom message in the chat message area" - routing_type = SlashCommandRoutingType(slash_id="custom") - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - async def _process_message(self, message: HumanChatMessage): - self.reply(CUSTOM_MESSAGE, message) diff --git a/packages/jupyter-ai/pyproject.toml b/packages/jupyter-ai/pyproject.toml index eb8fa4283..0f8d4a58a 100644 --- a/packages/jupyter-ai/pyproject.toml +++ b/packages/jupyter-ai/pyproject.toml @@ -39,9 +39,6 @@ dependencies = [ dynamic = ["version", "description", "authors", "urls", "keywords"] -[project.entry-points."jupyter_ai.chat_handlers"] -custom = "jupyter_ai:chat_handlers.CustomChatHandler" - [project.entry-points."jupyter_ai.default_tasks"] core_default_tasks = "jupyter_ai:tasks" From e04e09bda518f464518bd03ab0527555d42f0413 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 22:57:40 +0000 Subject: [PATCH 24/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/developers/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index 3370311ff..d3ac9ab32 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -149,7 +149,7 @@ class CustomChatHandler(BaseChatHandler): # Put your custom logic here ``` -Jupyter AI uses entry points to support custom slash commands. +Jupyter AI uses entry points to support custom slash commands. In the `pyproject.toml` file, add your custom handler to the `[project.entry-points."jupyter_ai.chat_handlers"]` section: From 7e4d597f68fe813951954cf478368158217bda5e Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Mon, 6 Nov 2023 15:14:29 -0800 Subject: [PATCH 25/35] Renames process_message to match base class --- docs/source/developers/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index d3ac9ab32..eee528bb3 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -145,7 +145,7 @@ class CustomChatHandler(BaseChatHandler): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - async def _process_message(self, message: HumanChatMessage): + async def process_message(self, message: HumanChatMessage): # Put your custom logic here ``` From 3a031e4f761cb5350d2b430174bf28b1ded8f58e Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Mon, 4 Dec 2023 14:42:45 -0800 Subject: [PATCH 26/35] Adds needed parameter that had been deleted --- packages/jupyter-ai/jupyter_ai/extension.py | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/jupyter-ai/jupyter_ai/extension.py b/packages/jupyter-ai/jupyter_ai/extension.py index 947ddc426..ec08d9962 100644 --- a/packages/jupyter-ai/jupyter_ai/extension.py +++ b/packages/jupyter-ai/jupyter_ai/extension.py @@ -170,6 +170,7 @@ def initialize_settings(self): "chat_history": self.settings["chat_history"], "root_dir": self.serverapp.root_dir, "dask_client_future": dask_client_future, + "model_parameters": self.settings["model_parameters"], } default_chat_handler = DefaultChatHandler(**chat_handler_kwargs) From ffb77a1f544518e5d414695d332dfe9ad1a9c36d Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 5 Dec 2023 12:23:26 -0800 Subject: [PATCH 27/35] Joins lines in contributor doc --- docs/source/contributors/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/source/contributors/index.md b/docs/source/contributors/index.md index 3e0658704..0cc174098 100644 --- a/docs/source/contributors/index.md +++ b/docs/source/contributors/index.md @@ -2,8 +2,7 @@ This page is intended for people interested in building new or modified functionality for Jupyter AI. -If you would like to build applications that enhance Jupyter AI, -please see the {doc}`developer's guide `. +If you would like to build applications that enhance Jupyter AI, please see the {doc}`developer's guide `. ## Design principles From 74c6d21091034220c844ff3d3393203287f9f1a9 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 5 Dec 2023 14:51:10 -0800 Subject: [PATCH 28/35] Removes natural language routing type, which is not yet used --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 0c33f270e..8dd4736c3 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -21,8 +21,7 @@ # Chat handler type, with specific attributes for each class HandlerRoutingType(BaseModel): routing_method: ClassVar[str] = ... - """The routing method that sends commands to this handler. - Either "natural_language" or "slash_command".""" + """The routing method that sends commands to this handler.""" class SlashCommandRoutingType(HandlerRoutingType): @@ -34,15 +33,6 @@ class SlashCommandRoutingType(HandlerRoutingType): underscores.""" -class NaturalLanguageRoutingType(HandlerRoutingType): - routing_method = "natural_language" - - description: str - """Description used for routing requests, to be used when dispatching - messages to model providers. Also shown in the UI for transparency; - optimized for model interpretation, not human-facing help.""" - - class BaseChatHandler(Configurable): """Base ChatHandler class containing shared methods and attributes used by multiple chat handler classes.""" From bea1f9948e998835e134098cb634c738340af75f Mon Sep 17 00:00:00 2001 From: Jason Weill <93281816+JasonWeill@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:56:06 -0800 Subject: [PATCH 29/35] Update docs/source/developers/index.md Co-authored-by: Piyush Jain --- docs/source/developers/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index eee528bb3..35607525d 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -134,7 +134,8 @@ commands cannot replace built-in slash commands. Add your custom handler in Python code: ```python -from jupyter_ai.base import BaseChatHandler, SlashCommandRoutingType +from jupyter_ai.chat_handlers.base import BaseChatHandler, SlashCommandRoutingType +from jupyter_ai.models import HumanChatMessage class CustomChatHandler(BaseChatHandler): id = "custom" From 1589146c352ca7d6f8137ac0c39762d7e5e2cd37 Mon Sep 17 00:00:00 2001 From: Jason Weill <93281816+JasonWeill@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:56:18 -0800 Subject: [PATCH 30/35] Update docs/source/developers/index.md Co-authored-by: Piyush Jain --- docs/source/developers/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index 35607525d..8fd98b424 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -148,6 +148,7 @@ class CustomChatHandler(BaseChatHandler): async def process_message(self, message: HumanChatMessage): # Put your custom logic here + self.reply("", message) ``` Jupyter AI uses entry points to support custom slash commands. From dedeb6f9669862d276fd63d9405291b78f806da2 Mon Sep 17 00:00:00 2001 From: Jason Weill <93281816+JasonWeill@users.noreply.github.com> Date: Tue, 5 Dec 2023 17:56:28 -0800 Subject: [PATCH 31/35] Update docs/source/developers/index.md Co-authored-by: Piyush Jain --- docs/source/developers/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/developers/index.md b/docs/source/developers/index.md index 8fd98b424..ba3c969d7 100644 --- a/docs/source/developers/index.md +++ b/docs/source/developers/index.md @@ -157,7 +157,7 @@ In the `pyproject.toml` file, add your custom handler to the ``` [project.entry-points."jupyter_ai.chat_handlers"] -custom = "custom_package:chat_handlers.CustomChatHandler" +custom = "custom_package:CustomChatHandler" ``` Then, install your package so that Jupyter AI adds custom chat handlers From 7fcaa7bd9c929d2e98c29afd81c3a6f3c7f40e73 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 5 Dec 2023 17:57:25 -0800 Subject: [PATCH 32/35] Revises per @3coins, avoids Latinism --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 8dd4736c3..9479746a4 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -46,7 +46,7 @@ class BaseChatHandler(Configurable): help: ClassVar[str] = ... """What this chat handler does, which third-party models it contacts, - the format of the data it returns to the user, etc. Used in the UI.""" + the data it returns to the user, and so on, for display in the UI.""" routing_type: HandlerRoutingType = ... From 7c98f4e9ebabeab8273c8e0289f63c2781b69e59 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 5 Dec 2023 18:03:58 -0800 Subject: [PATCH 33/35] Removes Configurable, since we do not yet have configurable traits --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 9479746a4..2644707a0 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -12,7 +12,6 @@ # necessary to prevent circular import from pydantic import BaseModel -from traitlets.config import Configurable if TYPE_CHECKING: from jupyter_ai.handlers import RootChatHandler @@ -33,7 +32,7 @@ class SlashCommandRoutingType(HandlerRoutingType): underscores.""" -class BaseChatHandler(Configurable): +class BaseChatHandler: """Base ChatHandler class containing shared methods and attributes used by multiple chat handler classes.""" From 55b3f086d5ff53f26a506fc5559a6925beab2fe7 Mon Sep 17 00:00:00 2001 From: Jason Weill Date: Tue, 5 Dec 2023 18:08:24 -0800 Subject: [PATCH 34/35] Uses Literal for validation --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 2644707a0..4f5e6ecea 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -2,7 +2,7 @@ import os import time import traceback -from typing import TYPE_CHECKING, Awaitable, ClassVar, Dict, List, Optional, Type +from typing import TYPE_CHECKING, Awaitable, ClassVar, Dict, List, Literal, Optional, Type from uuid import uuid4 from dask.distributed import Client as DaskClient @@ -19,7 +19,7 @@ # Chat handler type, with specific attributes for each class HandlerRoutingType(BaseModel): - routing_method: ClassVar[str] = ... + routing_method: ClassVar[str] = Literal["slash_command"] """The routing method that sends commands to this handler.""" From ef9704553ddfdc69c5152d53e324edd893589222 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 02:09:18 +0000 Subject: [PATCH 35/35] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- packages/jupyter-ai/jupyter_ai/chat_handlers/base.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py index 4f5e6ecea..15aef6788 100644 --- a/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py +++ b/packages/jupyter-ai/jupyter_ai/chat_handlers/base.py @@ -2,7 +2,16 @@ import os import time import traceback -from typing import TYPE_CHECKING, Awaitable, ClassVar, Dict, List, Literal, Optional, Type +from typing import ( + TYPE_CHECKING, + Awaitable, + ClassVar, + Dict, + List, + Literal, + Optional, + Type, +) from uuid import uuid4 from dask.distributed import Client as DaskClient