From 322f3532a838511efc25b2721d971dbdc1063b86 Mon Sep 17 00:00:00 2001 From: "David L. Qiu" Date: Tue, 25 Jun 2024 15:54:16 -0700 Subject: [PATCH] add jupyter_ai_test package for developer testing --- .gitignore | 2 - packages/jupyter-ai-test/LICENSE | 29 +++++++ packages/jupyter-ai-test/README.md | 58 +++++++++++++ .../jupyter_ai_test/__init__.py | 1 + .../jupyter_ai_test/_version.py | 4 + .../jupyter_ai_test/test_llms.py | 55 +++++++++++++ .../jupyter_ai_test/test_providers.py | 81 +++++++++++++++++++ .../jupyter_ai_test/test_slash_commands.py | 27 +++++++ .../jupyter_ai_test/tests/__init__.py | 1 + packages/jupyter-ai-test/package.json | 25 ++++++ packages/jupyter-ai-test/project.json | 4 + packages/jupyter-ai-test/pyproject.toml | 41 ++++++++++ packages/jupyter-ai-test/setup.py | 1 + packages/jupyter-ai/pyproject.toml | 2 +- yarn.lock | 6 ++ 15 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 packages/jupyter-ai-test/LICENSE create mode 100644 packages/jupyter-ai-test/README.md create mode 100644 packages/jupyter-ai-test/jupyter_ai_test/__init__.py create mode 100644 packages/jupyter-ai-test/jupyter_ai_test/_version.py create mode 100644 packages/jupyter-ai-test/jupyter_ai_test/test_llms.py create mode 100644 packages/jupyter-ai-test/jupyter_ai_test/test_providers.py create mode 100644 packages/jupyter-ai-test/jupyter_ai_test/test_slash_commands.py create mode 100644 packages/jupyter-ai-test/jupyter_ai_test/tests/__init__.py create mode 100644 packages/jupyter-ai-test/package.json create mode 100644 packages/jupyter-ai-test/project.json create mode 100644 packages/jupyter-ai-test/pyproject.toml create mode 100644 packages/jupyter-ai-test/setup.py diff --git a/.gitignore b/.gitignore index 7bcadb5c8..8ea61ccdc 100644 --- a/.gitignore +++ b/.gitignore @@ -135,5 +135,3 @@ dev.sh .conda/ -# reserved for testing cookiecutter -packages/jupyter-ai-test diff --git a/packages/jupyter-ai-test/LICENSE b/packages/jupyter-ai-test/LICENSE new file mode 100644 index 000000000..eb0d24e83 --- /dev/null +++ b/packages/jupyter-ai-test/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024, Project Jupyter +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/jupyter-ai-test/README.md b/packages/jupyter-ai-test/README.md new file mode 100644 index 000000000..93e74f132 --- /dev/null +++ b/packages/jupyter-ai-test/README.md @@ -0,0 +1,58 @@ +# jupyter_ai_test + +`jupyter_ai_test` is a Jupyter AI module, a package +that registers additional model providers and slash commands for the Jupyter AI +extension. + +## Requirements + +- Python 3.8 - 3.11 +- JupyterLab 4 + +## Install + +To install the extension, execute: + +```bash +pip install jupyter_ai_test +``` + +## Uninstall + +To remove the extension, execute: + +```bash +pip uninstall jupyter_ai_test +``` + +## Contributing + +### Development install + +```bash +cd jupyter-ai-test +pip install -e "." +``` + +### Development uninstall + +```bash +pip uninstall jupyter_ai_test +``` + +#### Backend tests + +This package uses [Pytest](https://docs.pytest.org/) for Python testing. + +Install test dependencies (needed only once): + +```sh +cd jupyter-ai-test +pip install -e ".[test]" +``` + +To execute them, run: + +```sh +pytest -vv -r ap --cov jupyter_ai_test +``` diff --git a/packages/jupyter-ai-test/jupyter_ai_test/__init__.py b/packages/jupyter-ai-test/jupyter_ai_test/__init__.py new file mode 100644 index 000000000..8dee4bf82 --- /dev/null +++ b/packages/jupyter-ai-test/jupyter_ai_test/__init__.py @@ -0,0 +1 @@ +from ._version import __version__ diff --git a/packages/jupyter-ai-test/jupyter_ai_test/_version.py b/packages/jupyter-ai-test/jupyter_ai_test/_version.py new file mode 100644 index 000000000..133868ac5 --- /dev/null +++ b/packages/jupyter-ai-test/jupyter_ai_test/_version.py @@ -0,0 +1,4 @@ +# This file is auto-generated by Hatchling. As such, do not: +# - modify +# - track in version control e.g. be sure to add to .gitignore +__version__ = VERSION = '0.1.0' diff --git a/packages/jupyter-ai-test/jupyter_ai_test/test_llms.py b/packages/jupyter-ai-test/jupyter_ai_test/test_llms.py new file mode 100644 index 000000000..2e328eb2c --- /dev/null +++ b/packages/jupyter-ai-test/jupyter_ai_test/test_llms.py @@ -0,0 +1,55 @@ +import time +from typing import Any, List, Optional, Iterator + +from langchain_core.callbacks.manager import CallbackManagerForLLMRun +from langchain_core.language_models.llms import LLM +from langchain_core.outputs.generation import GenerationChunk + +class TestLLM(LLM): + model_id: str = "test" + + @property + def _llm_type(self) -> str: + return "custom" + + def _call( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + time.sleep(3) + return f"Hello! This is a dummy response from a test LLM." + +class TestLLMWithStreaming(LLM): + model_id: str = "test" + + @property + def _llm_type(self) -> str: + return "custom" + + def _call( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> str: + time.sleep(3) + return f"Hello! This is a dummy response from a test LLM." + + def _stream( + self, + prompt: str, + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> Iterator[GenerationChunk]: + time.sleep(5) + yield GenerationChunk(text="Hello! This is a dummy response from a test LLM. I will now count from 1 to 100.\n\n") + for i in range(1, 101): + time.sleep(0.5) + yield GenerationChunk(text=f"{i}, ") + + \ No newline at end of file diff --git a/packages/jupyter-ai-test/jupyter_ai_test/test_providers.py b/packages/jupyter-ai-test/jupyter_ai_test/test_providers.py new file mode 100644 index 000000000..edbdff2ad --- /dev/null +++ b/packages/jupyter-ai-test/jupyter_ai_test/test_providers.py @@ -0,0 +1,81 @@ +from typing import ClassVar, List +from jupyter_ai import AuthStrategy, BaseProvider, Field + +from .test_llms import TestLLM, TestLLMWithStreaming + + +class TestProvider(BaseProvider, TestLLM): + id: ClassVar[str] = "test-provider" + """ID for this provider class.""" + + name: ClassVar[str] = "Test Provider" + """User-facing name of this provider.""" + + models: ClassVar[List[str]] = [ + "test" + ] + """List of supported models by their IDs. For registry providers, this will + be just ["*"].""" + + help: ClassVar[str] = None + """Text to display in lieu of a model list for a registry provider that does + not provide a list of models.""" + + model_id_key: ClassVar[str] = "model_id" + """Kwarg expected by the upstream LangChain provider.""" + + model_id_label: ClassVar[str] = "Model ID" + """Human-readable label of the model ID.""" + + pypi_package_deps: ClassVar[List[str]] = [] + """List of PyPi package dependencies.""" + + auth_strategy: ClassVar[AuthStrategy] = None + """Authentication/authorization strategy. Declares what credentials are + required to use this model provider. Generally should not be `None`.""" + + registry: ClassVar[bool] = False + """Whether this provider is a registry provider.""" + + fields: ClassVar[List[Field]] = [] + """User inputs expected by this provider when initializing it. Each `Field` `f` + should be passed in the constructor as a keyword argument, keyed by `f.key`.""" + + +class TestProviderWithStreaming(BaseProvider, TestLLMWithStreaming): + id: ClassVar[str] = "test-provider-with-streaming" + """ID for this provider class.""" + + name: ClassVar[str] = "Test Provider (streaming)" + """User-facing name of this provider.""" + + models: ClassVar[List[str]] = [ + "test" + ] + """List of supported models by their IDs. For registry providers, this will + be just ["*"].""" + + help: ClassVar[str] = None + """Text to display in lieu of a model list for a registry provider that does + not provide a list of models.""" + + model_id_key: ClassVar[str] = "model_id" + """Kwarg expected by the upstream LangChain provider.""" + + model_id_label: ClassVar[str] = "Model ID" + """Human-readable label of the model ID.""" + + pypi_package_deps: ClassVar[List[str]] = [] + """List of PyPi package dependencies.""" + + auth_strategy: ClassVar[AuthStrategy] = None + """Authentication/authorization strategy. Declares what credentials are + required to use this model provider. Generally should not be `None`.""" + + registry: ClassVar[bool] = False + """Whether this provider is a registry provider.""" + + fields: ClassVar[List[Field]] = [] + """User inputs expected by this provider when initializing it. Each `Field` `f` + should be passed in the constructor as a keyword argument, keyed by `f.key`.""" + diff --git a/packages/jupyter-ai-test/jupyter_ai_test/test_slash_commands.py b/packages/jupyter-ai-test/jupyter_ai_test/test_slash_commands.py new file mode 100644 index 000000000..876d21372 --- /dev/null +++ b/packages/jupyter-ai-test/jupyter_ai_test/test_slash_commands.py @@ -0,0 +1,27 @@ +from jupyter_ai.chat_handlers.base import BaseChatHandler, SlashCommandRoutingType +from jupyter_ai.models import HumanChatMessage + +class TestSlashCommand(BaseChatHandler): + """ + A test slash command implementation that developers should build from. The + string used to invoke this command is set by the `slash_id` keyword argument + in the `routing_type` attribute. The command is mainly implemented in the + `process_message()` method. See built-in implementations under + `jupyter_ai/handlers` for further reference. + + The provider is made available to Jupyter AI by the entry point declared in + `pyproject.toml`. If this class or parent module is renamed, make sure the + update the entry point there as well. + """ + id = "test" + name = "Test" + help = "A test slash command." + routing_type = SlashCommandRoutingType(slash_id="test") + + uses_llm = False + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + async def process_message(self, message: HumanChatMessage): + self.reply("This is the `/test` slash command.") diff --git a/packages/jupyter-ai-test/jupyter_ai_test/tests/__init__.py b/packages/jupyter-ai-test/jupyter_ai_test/tests/__init__.py new file mode 100644 index 000000000..bfecc1ade --- /dev/null +++ b/packages/jupyter-ai-test/jupyter_ai_test/tests/__init__.py @@ -0,0 +1 @@ +"""Python unit tests for jupyter_ai_test.""" diff --git a/packages/jupyter-ai-test/package.json b/packages/jupyter-ai-test/package.json new file mode 100644 index 000000000..92d5d07b7 --- /dev/null +++ b/packages/jupyter-ai-test/package.json @@ -0,0 +1,25 @@ +{ + "name": "@jupyter-ai/test", + "version": "2.18.1", + "description": "Jupyter AI test package. Not published on NPM or PyPI.", + "private": true, + "homepage": "https://github.com/jupyterlab/jupyter-ai", + "bugs": { + "url": "https://github.com/jupyterlab/jupyter-ai/issues", + "email": "jupyter@googlegroups.com" + }, + "license": "BSD-3-Clause", + "author": { + "name": "Project Jupyter", + "email": "jupyter@googlegroups.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/jupyterlab/jupyter-ai.git" + }, + "scripts": { + "dev-install": "pip install -e .", + "dev-uninstall": "pip uninstall jupyter_ai_test -y", + "install-from-src": "pip install ." + } +} diff --git a/packages/jupyter-ai-test/project.json b/packages/jupyter-ai-test/project.json new file mode 100644 index 000000000..9af45c206 --- /dev/null +++ b/packages/jupyter-ai-test/project.json @@ -0,0 +1,4 @@ +{ + "name": "@jupyter-ai/test", + "implicitDependencies": ["@jupyter-ai/core"] +} diff --git a/packages/jupyter-ai-test/pyproject.toml b/packages/jupyter-ai-test/pyproject.toml new file mode 100644 index 000000000..4401ff736 --- /dev/null +++ b/packages/jupyter-ai-test/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["hatchling>=1.4.0", "jupyterlab~=4.0"] +build-backend = "hatchling.build" + +[project] +name = "jupyter_ai_test" +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.8" +classifiers = [ + "Framework :: Jupyter", + "Framework :: Jupyter :: JupyterLab", + "Framework :: Jupyter :: JupyterLab :: 4", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] +version = "0.1.0" +description = "A Jupyter AI extension." +authors = [{ name = "Project Jupyter", email = "jupyter@googlegroups.com" }] +dependencies = ["jupyter_ai"] + +[project.optional-dependencies] +test = ["coverage", "pytest", "pytest-asyncio", "pytest-cov"] + +[project.entry-points."jupyter_ai.model_providers"] +test-provider = "jupyter_ai_test.test_providers:TestProvider" +test-provider-with-streaming = "jupyter_ai_test.test_providers:TestProviderWithStreaming" + +[project.entry-points."jupyter_ai.chat_handlers"] +test-slash-command = "jupyter_ai_test.test_slash_commands:TestSlashCommand" + +[tool.hatch.build.hooks.version] +path = "jupyter_ai_test/_version.py" + +[tool.check-wheel-contents] +ignore = ["W002"] diff --git a/packages/jupyter-ai-test/setup.py b/packages/jupyter-ai-test/setup.py new file mode 100644 index 000000000..aefdf20db --- /dev/null +++ b/packages/jupyter-ai-test/setup.py @@ -0,0 +1 @@ +__import__("setuptools").setup() diff --git a/packages/jupyter-ai/pyproject.toml b/packages/jupyter-ai/pyproject.toml index 4328c9aca..15e660cfc 100644 --- a/packages/jupyter-ai/pyproject.toml +++ b/packages/jupyter-ai/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "importlib_metadata>=5.2.0", "jupyter_ai_magics>=2.13.0", "dask[distributed]", - "faiss-cpu", # Not distributed by official repo + "faiss-cpu<=1.8.0", # Not distributed by official repo "typing_extensions>=4.5.0", "traitlets>=5.0", "deepmerge>=1.0", diff --git a/yarn.lock b/yarn.lock index 710e780ca..76ddf97ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2279,6 +2279,12 @@ __metadata: languageName: unknown linkType: soft +"@jupyter-ai/test@workspace:packages/jupyter-ai-test": + version: 0.0.0-use.local + resolution: "@jupyter-ai/test@workspace:packages/jupyter-ai-test" + languageName: unknown + linkType: soft + "@jupyter/collaboration@npm:^1": version: 1.2.1 resolution: "@jupyter/collaboration@npm:1.2.1"