From 1b99a5a9caa57e18ccebe5b8342a11691ce23101 Mon Sep 17 00:00:00 2001 From: miararoy Date: Thu, 7 Dec 2023 20:20:42 +0200 Subject: [PATCH 1/8] Update README.md and change href (#202) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c6e45c49..99f13364 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Canopy

- + Supported Python versions - + Package version

From f372033bb63d8171ed9395895af8a022126a7d80 Mon Sep 17 00:00:00 2001 From: Ed Burnette Date: Thu, 7 Dec 2023 13:25:05 -0500 Subject: [PATCH 2/8] Fix some typos, add dev container, faux streaming (#200) * Fix some typos, add dev container, faux streaming * Go back to Python 3.9, oldest supported version * Missing whitespace after colon --------- Co-authored-by: Ed Burnette --- .devcontainer/devcontainer.json | 22 ++++++++++++++++++++ .env.example | 5 +++++ .gitignore | 1 + src/canopy_cli/cli.py | 25 ++++++++++++++--------- src/canopy_server/models/v1/api_models.py | 2 +- 5 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .env.example diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..eeb3e61a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,22 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python 3", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/python:1-3.9-bullseye" + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "pip3 install --user -r requirements.txt", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..9019eddc --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +PINECONE_API_KEY="" +PINECONE_ENVIRONMENT="" +OPENAI_API_KEY="" +INDEX_NAME="" +CANOPY_CONFIG_FILE="config/config.yaml" diff --git a/.gitignore b/.gitignore index 4469e9f0..37595cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,4 @@ cython_debug/ datafiles/* canopy-api-docs.html .vscode/ +*.jsonl diff --git a/src/canopy_cli/cli.py b/src/canopy_cli/cli.py index f7f4f595..3104a08a 100644 --- a/src/canopy_cli/cli.py +++ b/src/canopy_cli/cli.py @@ -405,24 +405,24 @@ def _chat( if stream: for chunk in openai_response: openai_response_id = chunk.id - intenal_model = chunk.model + internal_model = chunk.model text = chunk.choices[0].delta.content or "" output += text click.echo(text, nl=False) click.echo() debug_info = ChatDebugInfo( id=openai_response_id, - intenal_model=intenal_model, + internal_model=internal_model, duration_in_sec=round(duration_in_sec, 2), ) else: - intenal_model = openai_response.model + internal_model = openai_response.model text = openai_response.choices[0].message.content or "" output = text click.echo(text, nl=False) debug_info = ChatDebugInfo( id=openai_response.id, - intenal_model=intenal_model, + internal_model=internal_model, duration_in_sec=duration_in_sec, prompt_tokens=openai_response.usage.prompt_tokens, generated_tokens=openai_response.usage.completion_tokens, @@ -469,10 +469,11 @@ def chat(chat_server_url, rag, debug, stream): ) for c in note_msg: click.echo(click.style(c, fg="red"), nl=False) - time.sleep(0.01) + if (stream): + time.sleep(0.01) click.echo() note_white_message = ( - "This method should be used by developers to test the RAG data and model" + "This method should be used by developers to test the RAG data and model " "during development. " "When you are ready to deploy, run the Canopy server as a REST API " "backend for your chatbot UI. \n\n" @@ -480,7 +481,8 @@ def chat(chat_server_url, rag, debug, stream): ) for c in note_white_message: click.echo(click.style(c, fg="white"), nl=False) - time.sleep(0.01) + if (stream): + time.sleep(0.01) click.echo() history_with_pinecone = [] @@ -523,7 +525,7 @@ def chat(chat_server_url, rag, debug, stream): _ = _chat( speaker="Without Context (No RAG)", speaker_color="yellow", - model=dubug_info.intenal_model, + model=dubug_info.internal_model, history=history_without_pinecone, message=message, stream=stream, @@ -554,6 +556,8 @@ def chat(chat_server_url, rag, debug, stream): """ ) ) +@click.option("--stream/--no-stream", default=True, + help="Stream the response from the RAG chatbot word by word.") @click.option("--host", default="0.0.0.0", help="Hostname or address to bind the server to. Defaults to 0.0.0.0") @click.option("--port", default=8000, @@ -565,7 +569,7 @@ def chat(chat_server_url, rag, debug, stream): "defaults will be used.") @click.option("--index-name", default=None, help="Index name, if not provided already as an environment variable.") -def start(host: str, port: str, reload: bool, +def start(host: str, port: str, reload: bool, stream: bool, config: Optional[str], index_name: Optional[str]): validate_pinecone_connection() _validate_chat_engine(config) @@ -585,7 +589,8 @@ def start(host: str, port: str, reload: bool, ) for c in note_msg + msg_suffix: click.echo(click.style(c, fg="red"), nl=False) - time.sleep(0.01) + if (stream): + time.sleep(0.01) click.echo() if index_name: diff --git a/src/canopy_server/models/v1/api_models.py b/src/canopy_server/models/v1/api_models.py index 1ceb34d2..224fbf8c 100644 --- a/src/canopy_server/models/v1/api_models.py +++ b/src/canopy_server/models/v1/api_models.py @@ -57,7 +57,7 @@ class HealthStatus(BaseModel): class ChatDebugInfo(BaseModel): id: str duration_in_sec: float - intenal_model: str + internal_model: str prompt_tokens: Optional[int] = None generated_tokens: Optional[int] = None From e0a5a56db92156421cf0ee806af5bad925e65231 Mon Sep 17 00:00:00 2001 From: miararoy Date: Fri, 8 Dec 2023 12:42:38 +0200 Subject: [PATCH 3/8] Fix issue 205 (#208) * fix issue, make sure CLI error appear when no rag is used, note that --no-rag will need to change dramatically * Add readme note --- README.md | 2 ++ src/canopy_cli/cli.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 99f13364..1efe6363 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,8 @@ This will open a chat interface in your terminal. You can ask questions and the To compare the chat response with and without RAG use the `--no-rag` flag +> **Note**: This method is only supported with OpenAI at the moment. + ```bash canopy chat --no-rag ``` diff --git a/src/canopy_cli/cli.py b/src/canopy_cli/cli.py index 3104a08a..b7021f69 100644 --- a/src/canopy_cli/cli.py +++ b/src/canopy_cli/cli.py @@ -382,13 +382,23 @@ def _chat( model, history, message, + openai_api_key=None, api_base=None, stream=True, print_debug_info=False, ): + if openai_api_key is None: + openai_api_key = os.environ.get("OPENAI_API_KEY") + if openai_api_key is None and api_base is None: + raise CLIError( + "No OpenAI API key provided. When using the `--no-rag` flag " + "You will need to have a valid OpenAI API key. " + "Please set the OPENAI_API_KEY environment " + "variable." + ) output = "" history += [{"role": "user", "content": message}] - client = openai.OpenAI(base_url=api_base) + client = openai.OpenAI(base_url=api_base, api_key=openai_api_key) start = time.time() try: @@ -517,6 +527,7 @@ def chat(chat_server_url, rag, debug, stream): history=history_with_pinecone, message=message, stream=stream, + openai_api_key="canopy", api_base=chat_server_url, print_debug_info=debug, ) From 52289b7a68665d64f034bda4a0b41358007a7d21 Mon Sep 17 00:00:00 2001 From: igiloh-pinecone <118673156+igiloh-pinecone@users.noreply.github.com> Date: Sun, 10 Dec 2023 12:23:01 +0200 Subject: [PATCH 4/8] Add feature-request.yml (#209) We are missing an issue template for feature requests and suggestions --- .github/ISSUE_TEMPLATE/feature-request.yml | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..d0b36d9b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,54 @@ +name: ✨ Feature +description: Propose a straightforward extension +title: "[Feature] " +labels: ["enhancement", "triage"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this feature request! + - type: checkboxes + attributes: + label: Is this your first time submitting a feature request? + description: > + We want to make sure that features are distinct and discoverable, + so that other members of the community can find them and offer their thoughts. + + Issues are the right place to request straightforward extensions of existing functionality. + options: + - label: I have searched the existing issues, and I could not find an existing issue for this feature + required: true + - label: I am requesting a straightforward extension of existing functionality + - type: textarea + attributes: + label: Describe the feature + description: A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + attributes: + label: Describe alternatives you've considered + description: | + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + - type: textarea + attributes: + label: Who will this benefit? + description: | + What kind of use case will this feature be useful for? Please be specific and provide examples, this will help us prioritize properly. + validations: + required: false + - type: input + attributes: + label: Are you interested in contributing this feature? + description: Let us know if you want to write some code, and how we can help. + validations: + required: false + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the feature you are suggesting! + validations: + required: false From b129a051719240a79c20755c9c3fa4418042f0ee Mon Sep 17 00:00:00 2001 From: Michael Anckaert <michael.anckaert@sinax.be> Date: Sun, 10 Dec 2023 18:31:17 +0100 Subject: [PATCH 5/8] CLI: read config file from env location (Issue #189) (#190) * CLI: read config file from env location This commit updates the way the config file is loaded by the Canopy CLI. When the config file location is not specified through the `-c` parameter, we use the file location specified in the environment variable CANOPY_COPY_FILE. * [CLI] Use click's envvar setting to set config file * [cli] remove redundant print --------- Co-authored-by: miararoy <miararoy@gmail.com> Co-authored-by: ilai <ilai@pinecone.io> --- src/canopy_cli/cli.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/canopy_cli/cli.py b/src/canopy_cli/cli.py index b7021f69..c8f12226 100644 --- a/src/canopy_cli/cli.py +++ b/src/canopy_cli/cli.py @@ -215,9 +215,10 @@ def health(url): ) ) @click.argument("index-name", nargs=1, envvar="INDEX_NAME", type=str, required=True) -@click.option("--config", "-c", default=None, - help="Path to a Canopy config file. Optional, otherwise configuration " - "defaults will be used.") +@click.option("--config", "-c", default=None, envvar="CANOPY_CONFIG_FILE", + help="Path to a canopy config file. Can also be set by the " + "`CANOPY_CONFIG_FILE` envrionment variable. Otherwise, the built-in" + "defualt configuration will be used.") def new(index_name: str, config: Optional[str]): _initialize_tokenizer() kb_config = _load_kb_config(config) @@ -280,9 +281,10 @@ def _batch_documents_by_chunks(chunker: Chunker, "be uploaded. " "When set to True, the upsert process will continue on failure, as " "long as less than 10% of the documents have failed to be uploaded.") -@click.option("--config", "-c", default=None, - help="Path to a Canopy config file. Optional, otherwise configuration " - "defaults will be used.") +@click.option("--config", "-c", default=None, envvar="CANOPY_CONFIG_FILE", + help="Path to a canopy config file. Can also be set by the " + "`CANOPY_CONFIG_FILE` envrionment variable. Otherwise, the built-in" + "defualt configuration will be used.") def upsert(index_name: str, data_path: str, allow_failures: bool, @@ -575,9 +577,10 @@ def chat(chat_server_url, rag, debug, stream): help="TCP port to bind the server to. Defaults to 8000") @click.option("--reload/--no-reload", default=False, help="Set the server to reload on code changes. Defaults to False") -@click.option("--config", "-c", default=None, - help="Path to a canopy config file. Optional, otherwise configuration " - "defaults will be used.") +@click.option("--config", "-c", default=None, envvar="CANOPY_CONFIG_FILE", + help="Path to a canopy config file. Can also be set by the " + "`CANOPY_CONFIG_FILE` envrionment variable. Otherwise, the built-in" + "defualt configuration will be used.") @click.option("--index-name", default=None, help="Index name, if not provided already as an environment variable.") def start(host: str, port: str, reload: bool, stream: bool, From 405d73df837469286c386dea21d3205f8fbe1bb4 Mon Sep 17 00:00:00 2001 From: izellevy <izel@pinecone.io> Date: Sun, 10 Dec 2023 21:12:46 +0200 Subject: [PATCH 6/8] Add last message query generator (#210) * Add last message query generator * Fix lint * Fix lint * Improve docs * Fix docstring * Take only user messages * Fix lint * Fix behavior * Fix sample messages * [config] Changed Anyscale config to use LastMessageQueryGenerator Since Anyscale don't support function calling for now * [config] Update anyscale.yaml - The new QueryGenerator doesn't need an LLM Otherwise it will error out --------- Co-authored-by: ilai <ilai@pinecone.io> --- config/anyscale.yaml | 12 +----- config/config.yaml | 2 +- .../chat_engine/query_generator/__init__.py | 1 + .../query_generator/last_message.py | 36 ++++++++++++++++ .../test_last_message_query_generator.py | 41 +++++++++++++++++++ 5 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 src/canopy/chat_engine/query_generator/last_message.py create mode 100644 tests/unit/query_generators/test_last_message_query_generator.py diff --git a/config/anyscale.yaml b/config/anyscale.yaml index 816a0ffc..7f5f28ef 100644 --- a/config/anyscale.yaml +++ b/config/anyscale.yaml @@ -24,14 +24,4 @@ chat_engine: # The query builder is responsible for generating textual queries given user message history. # -------------------------------------------------------------------- query_builder: - type: FunctionCallingQueryGenerator # Options: [FunctionCallingQueryGenerator] - params: - prompt: *query_builder_prompt # The query builder's system prompt for calling the LLM - function_description: # A function description passed to the LLM's `function_calling` API - Query search engine for relevant information - - llm: # The LLM that the query builder will use to generate queries. - #Use OpenAI for function call for now - type: OpenAILLM - params: - model_name: gpt-3.5-turbo + type: LastMessageQueryGenerator # Options: [FunctionCallingQueryGenerator, LastMessageQueryGenerator] \ No newline at end of file diff --git a/config/config.yaml b/config/config.yaml index 0d3576cd..8fc78572 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -59,7 +59,7 @@ chat_engine: # The query builder is responsible for generating textual queries given user message history. # -------------------------------------------------------------------- query_builder: - type: FunctionCallingQueryGenerator # Options: [FunctionCallingQueryGenerator] + type: FunctionCallingQueryGenerator # Options: [FunctionCallingQueryGenerator, LastMessageQueryGenerator] params: prompt: *query_builder_prompt # The query builder's system prompt for calling the LLM function_description: # A function description passed to the LLM's `function_calling` API diff --git a/src/canopy/chat_engine/query_generator/__init__.py b/src/canopy/chat_engine/query_generator/__init__.py index 13ffd0d0..9005d02b 100644 --- a/src/canopy/chat_engine/query_generator/__init__.py +++ b/src/canopy/chat_engine/query_generator/__init__.py @@ -1,2 +1,3 @@ from .base import QueryGenerator from .function_calling import FunctionCallingQueryGenerator +from .last_message import LastMessageQueryGenerator diff --git a/src/canopy/chat_engine/query_generator/last_message.py b/src/canopy/chat_engine/query_generator/last_message.py new file mode 100644 index 00000000..74e15661 --- /dev/null +++ b/src/canopy/chat_engine/query_generator/last_message.py @@ -0,0 +1,36 @@ +from typing import List + +from canopy.chat_engine.query_generator import QueryGenerator +from canopy.models.data_models import Messages, Query, Role + + +class LastMessageQueryGenerator(QueryGenerator): + """ + Returns the last message as a query without running any LLMs. This can be + considered as the most basic query generation. Please use other query generators + for more accurate results. + """ + + def generate(self, + messages: Messages, + max_prompt_tokens: int) -> List[Query]: + """ + max_prompt_token is dismissed since we do not consume any token for + generating the queries. + """ + + if len(messages) == 0: + raise ValueError("Passed chat history does not contain any messages. " + "Please include at least one message in the history.") + + last_message = messages[-1] + + if last_message.role != Role.USER: + raise ValueError(f"Expected a UserMessage, got {type(last_message)}.") + + return [Query(text=last_message.content)] + + async def agenerate(self, + messages: Messages, + max_prompt_tokens: int) -> List[Query]: + return self.generate(messages, max_prompt_tokens) diff --git a/tests/unit/query_generators/test_last_message_query_generator.py b/tests/unit/query_generators/test_last_message_query_generator.py new file mode 100644 index 00000000..308c1b4d --- /dev/null +++ b/tests/unit/query_generators/test_last_message_query_generator.py @@ -0,0 +1,41 @@ +import pytest + +from canopy.chat_engine.query_generator import LastMessageQueryGenerator +from canopy.models.data_models import UserMessage, Query, AssistantMessage + + +@pytest.fixture +def sample_messages(): + return [ + UserMessage(content="What is photosynthesis?") + ] + + +@pytest.fixture +def query_generator(): + return LastMessageQueryGenerator() + + +def test_generate(query_generator, sample_messages): + expected = [Query(text=sample_messages[-1].content)] + actual = query_generator.generate(sample_messages, 0) + assert actual == expected + + +@pytest.mark.asyncio +async def test_agenerate(query_generator, sample_messages): + expected = [Query(text=sample_messages[-1].content)] + actual = await query_generator.agenerate(sample_messages, 0) + assert actual == expected + + +def test_generate_fails_with_empty_history(query_generator): + with pytest.raises(ValueError): + query_generator.generate([], 0) + + +def test_generate_fails_with_no_user_message(query_generator): + with pytest.raises(ValueError): + query_generator.generate([ + AssistantMessage(content="Hi! How can I help you?") + ], 0) From 3b546730b724d6f59ab1b4c5609923e679ee0063 Mon Sep 17 00:00:00 2001 From: igiloh-pinecone <118673156+igiloh-pinecone@users.noreply.github.com> Date: Sun, 10 Dec 2023 21:57:15 +0200 Subject: [PATCH 7/8] Update CHANGELOG for release 0.3.0 (#212) * [Changelog] Updated for v2.0.1 * [Changelog] Changed version to 0.3.0 Added LastMessageQueryGenerator, so now we have a full Anyscale offerring * [changelog] fix version --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72050fa1..767fb93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## [0.3.0] - 2023-12-10 + +### Bug fixes +* Fix some typos, add dev container, faux streaming [#200](https://github.com/pinecone-io/canopy/pull/200) (Thanks @eburnette!) +* CLI requires OpenAI API key, even if OpenAI is not being used by[#208](https://github.com/pinecone-io/canopy/pull/208) +* CLI: read config file from env location[#190](https://github.com/pinecone-io/canopy/pull/190) (Thanks @MichaelAnckaert!) + + +### Documentation +* Add document field explanations and python version badges [#187](https://github.com/pinecone-io/canopy/pull/187) +* Update README.md [#192](https://github.com/pinecone-io/canopy/pull/192) (Thanks @tomer-w!) +* Tweaks to CLI help texts [#193](https://github.com/pinecone-io/canopy/pull/193) (Thanks @jseldess!) +* Update README.md and change href [#202](https://github.com/pinecone-io/canopy/pull/202) + +### CI Improvements +* Added bug-report template [#184](https://github.com/pinecone-io/canopy/pull/184) +* Add feature-request.yml [#209](https://github.com/pinecone-io/canopy/pull/209) + +### Added +* Add Anyscale Endpoint support and Llama Tokenizer [#173](https://github.com/pinecone-io/canopy/pull/173) (Thanks @kylehh!) +* Add last message query generator [#210](https://github.com/pinecone-io/canopy/pull/210) + + +**Full Changelog**: https://github.com/pinecone-io/canopy/compare/V0.2.0...V0.3.0 + ## [0.2.0] - 2023-11-15 ### Bug fixes From 89e870fe8cbab1805827dfff187f75f9b8b4523c Mon Sep 17 00:00:00 2001 From: ilai <ilai@pinecone.io> Date: Sun, 10 Dec 2023 23:33:15 +0200 Subject: [PATCH 8/8] [pyproject] Update pytest-html version There seem to be an error on Windows --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fbad958d..3cd7f87d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ jupyter = "^1.0.0" pytest = "^7.3.2" mypy = "^1.4.1" flake8 = "^6.1.0" -pytest-html = "^3.2.0" +pytest-html = "^4.1.0" flake8-pyproject = "^1.2.3" asyncio = "^3.4.3" pytest-asyncio = "^0.14.0"