From 33a05e4186cbe008847fedf9ccdf24bef2f7fbcf Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Mon, 9 Oct 2023 10:41:24 -0700 Subject: [PATCH 1/8] Add basic test of Chatbot --- .github/workflows/python-app.yml | 39 ++++++++++++++++++++++++++++++++ .gitignore | 3 ++- Chatbot.py | 6 +++-- app_test.py | 33 +++++++++++++++++++++++++++ requirements-dev.txt | 1 + requirements.txt | 2 +- 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/python-app.yml create mode 100644 app_test.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 000000000..f3d4fca89 --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + pytest diff --git a/.gitignore b/.gitignore index c9863bac6..083db109c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .venv -secrets.toml __pycache__/ +.DS_Store +secrets.toml diff --git a/Chatbot.py b/Chatbot.py index 960f50dce..212be8809 100644 --- a/Chatbot.py +++ b/Chatbot.py @@ -15,7 +15,7 @@ for msg in st.session_state.messages: st.chat_message(msg["role"]).write(msg["content"]) -if prompt := st.chat_input(): +if prompt := st.chat_input(key="chat"): if not openai_api_key: st.info("Please add your OpenAI API key to continue.") st.stop() @@ -23,7 +23,9 @@ openai.api_key = openai_api_key st.session_state.messages.append({"role": "user", "content": prompt}) st.chat_message("user").write(prompt) - response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=st.session_state.messages) + response = openai.ChatCompletion.create( + model="gpt-3.5-turbo", messages=st.session_state.messages + ) msg = response.choices[0].message st.session_state.messages.append(msg) st.chat_message("assistant").write(msg.content) diff --git a/app_test.py b/app_test.py new file mode 100644 index 000000000..ac9dc8ae3 --- /dev/null +++ b/app_test.py @@ -0,0 +1,33 @@ +from unittest.mock import patch +from streamlit.testing.v1 import AppTest +from openai.openai_object import OpenAIObject + + +# See https://github.com/openai/openai-python/issues/398 +def create_openai_object_sync(response: str, role: str = "assistant") -> OpenAIObject: + obj = OpenAIObject() + message = OpenAIObject() + content = OpenAIObject() + content.content = response + content.role = role + message.message = content + obj.choices = [message] + return obj + + +@patch("openai.ChatCompletion.create") +def test_basic_chat(openai_create): + at = AppTest.from_file("Chatbot.py") + # Todo: Remove the key in app and fix this once we have chat_input support + at.session_state["chat"] = "Do you know any jokes?" + at.run() + assert at.get("alert")[0].proto.body == "Please add your OpenAI API key to continue." + assert not at.exception + + JOKE = "Why did the chicken cross the road? To get to the other side." + openai_create.return_value = create_openai_object_sync(JOKE) + at.text_input(key="chatbot_api_key").set_value("sk-...").run() + print(at) + assert at.get("chat_message")[1].markdown[0].value == "Do you know any jokes?" + assert at.get("chat_message")[2].markdown[0].value == JOKE + assert not at.exception diff --git a/requirements-dev.txt b/requirements-dev.txt index 872abf3b2..8635be6db 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,3 +2,4 @@ black==23.3.0 mypy==1.4.1 pre-commit==3.3.3 watchdog +pytest diff --git a/requirements.txt b/requirements.txt index c2737f628..1c9a00b48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -streamlit>=1.26.0 +streamlit-nightly >= 1.26.1.dev20231006 langchain>=0.0.217 openai duckduckgo-search From f1d24690e8357aa6b07e2cc672a1b9e2dd387957 Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Mon, 9 Oct 2023 10:50:22 -0700 Subject: [PATCH 2/8] Add Langchain Quickstart test --- app_test.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app_test.py b/app_test.py index ac9dc8ae3..03924533e 100644 --- a/app_test.py +++ b/app_test.py @@ -16,7 +16,7 @@ def create_openai_object_sync(response: str, role: str = "assistant") -> OpenAIO @patch("openai.ChatCompletion.create") -def test_basic_chat(openai_create): +def test_Chatbot(openai_create): at = AppTest.from_file("Chatbot.py") # Todo: Remove the key in app and fix this once we have chat_input support at.session_state["chat"] = "Do you know any jokes?" @@ -31,3 +31,16 @@ def test_basic_chat(openai_create): assert at.get("chat_message")[1].markdown[0].value == "Do you know any jokes?" assert at.get("chat_message")[2].markdown[0].value == JOKE assert not at.exception + + +@patch("langchain.llms.OpenAI.__call__") +def test_Langchain_Quickstart(langchain_llm): + at = AppTest.from_file("pages/3_Langchain_Quickstart.py").run() + assert at.get("alert")[0].proto.body == "Please add your OpenAI API key to continue." + + RESPONSE = "1. The best way to learn how to code is by practicing..." + langchain_llm.return_value = RESPONSE + at.sidebar.text_input[0].set_value("sk-...") + at.button[0].set_value(True).run() + print(at) + assert at.get("alert")[0].proto.body == RESPONSE From e2809230ab862fad67b2ee6cef1a4314688a56ba Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Mon, 9 Oct 2023 10:58:28 -0700 Subject: [PATCH 3/8] Make CI testing verbose --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index f3d4fca89..a746ede12 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -36,4 +36,4 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest + pytest -vv From 8b847f917cce07470feb36f454ea90ab0d140d80 Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Thu, 12 Oct 2023 10:21:12 -0700 Subject: [PATCH 4/8] Add chat primitives in AppTest --- Chatbot.py | 2 +- app_test.py | 18 +++++++++--------- requirements.txt | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Chatbot.py b/Chatbot.py index 212be8809..e1a2aedc7 100644 --- a/Chatbot.py +++ b/Chatbot.py @@ -15,7 +15,7 @@ for msg in st.session_state.messages: st.chat_message(msg["role"]).write(msg["content"]) -if prompt := st.chat_input(key="chat"): +if prompt := st.chat_input(): if not openai_api_key: st.info("Please add your OpenAI API key to continue.") st.stop() diff --git a/app_test.py b/app_test.py index 03924533e..2f6f1ffe9 100644 --- a/app_test.py +++ b/app_test.py @@ -17,19 +17,19 @@ def create_openai_object_sync(response: str, role: str = "assistant") -> OpenAIO @patch("openai.ChatCompletion.create") def test_Chatbot(openai_create): - at = AppTest.from_file("Chatbot.py") - # Todo: Remove the key in app and fix this once we have chat_input support - at.session_state["chat"] = "Do you know any jokes?" - at.run() - assert at.get("alert")[0].proto.body == "Please add your OpenAI API key to continue." + at = AppTest.from_file("Chatbot.py").run() assert not at.exception + at.chat_input[0].set_value("Do you know any jokes?").run() + assert at.get("alert")[0].proto.body == "Please add your OpenAI API key to continue." JOKE = "Why did the chicken cross the road? To get to the other side." openai_create.return_value = create_openai_object_sync(JOKE) - at.text_input(key="chatbot_api_key").set_value("sk-...").run() - print(at) - assert at.get("chat_message")[1].markdown[0].value == "Do you know any jokes?" - assert at.get("chat_message")[2].markdown[0].value == JOKE + at.text_input(key="chatbot_api_key").set_value("sk-...") + at.chat_input[0].set_value("Do you know any jokes?").run() + # print(at) + assert at.chat_message[1].markdown[0].value == "Do you know any jokes?" + assert at.chat_message[2].markdown[0].value == JOKE + assert at.chat_message[2].avatar == "assistant" assert not at.exception diff --git a/requirements.txt b/requirements.txt index 1c9a00b48..def3e540c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -streamlit-nightly >= 1.26.1.dev20231006 +streamlit-nightly >= 1.26.1.dev20231011 langchain>=0.0.217 openai duckduckgo-search From b659bd7a41ae2646ac6f41f5f4ae27f7106ab679 Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Wed, 18 Oct 2023 13:23:15 -0700 Subject: [PATCH 5/8] Switch to latest and add native .info support --- app_test.py | 6 +++--- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app_test.py b/app_test.py index 2f6f1ffe9..e1565ed5f 100644 --- a/app_test.py +++ b/app_test.py @@ -20,7 +20,7 @@ def test_Chatbot(openai_create): at = AppTest.from_file("Chatbot.py").run() assert not at.exception at.chat_input[0].set_value("Do you know any jokes?").run() - assert at.get("alert")[0].proto.body == "Please add your OpenAI API key to continue." + assert at.info[0].value == "Please add your OpenAI API key to continue." JOKE = "Why did the chicken cross the road? To get to the other side." openai_create.return_value = create_openai_object_sync(JOKE) @@ -36,11 +36,11 @@ def test_Chatbot(openai_create): @patch("langchain.llms.OpenAI.__call__") def test_Langchain_Quickstart(langchain_llm): at = AppTest.from_file("pages/3_Langchain_Quickstart.py").run() - assert at.get("alert")[0].proto.body == "Please add your OpenAI API key to continue." + assert at.info[0].value == "Please add your OpenAI API key to continue." RESPONSE = "1. The best way to learn how to code is by practicing..." langchain_llm.return_value = RESPONSE at.sidebar.text_input[0].set_value("sk-...") at.button[0].set_value(True).run() print(at) - assert at.get("alert")[0].proto.body == RESPONSE + assert at.info[0].value == RESPONSE diff --git a/requirements.txt b/requirements.txt index def3e540c..e8140da3b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -streamlit-nightly >= 1.26.1.dev20231011 +streamlit-nightly >= 1.27.3.dev20231017 langchain>=0.0.217 openai duckduckgo-search From 2f00c4d8afb16f0e4a20937b2d71a5c92e860bec Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Fri, 20 Oct 2023 12:45:00 -0700 Subject: [PATCH 6/8] Update to latest --- app_test.py | 4 ++-- requirements.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app_test.py b/app_test.py index e1565ed5f..0ddb68039 100644 --- a/app_test.py +++ b/app_test.py @@ -26,11 +26,11 @@ def test_Chatbot(openai_create): openai_create.return_value = create_openai_object_sync(JOKE) at.text_input(key="chatbot_api_key").set_value("sk-...") at.chat_input[0].set_value("Do you know any jokes?").run() - # print(at) + print(at) assert at.chat_message[1].markdown[0].value == "Do you know any jokes?" assert at.chat_message[2].markdown[0].value == JOKE assert at.chat_message[2].avatar == "assistant" - assert not at.exception + assert at.exception @patch("langchain.llms.OpenAI.__call__") diff --git a/requirements.txt b/requirements.txt index e8140da3b..8aecc034f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -streamlit-nightly >= 1.27.3.dev20231017 +streamlit-nightly >= 1.27.3.dev20231019 langchain>=0.0.217 openai duckduckgo-search From cf57a936d56b44b32d16d9eb2a7458ecd2a532cd Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Fri, 27 Oct 2023 12:43:45 -0700 Subject: [PATCH 7/8] Finalize with 1.28 release --- app_test.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app_test.py b/app_test.py index 0ddb68039..75cf76570 100644 --- a/app_test.py +++ b/app_test.py @@ -30,7 +30,7 @@ def test_Chatbot(openai_create): assert at.chat_message[1].markdown[0].value == "Do you know any jokes?" assert at.chat_message[2].markdown[0].value == JOKE assert at.chat_message[2].avatar == "assistant" - assert at.exception + assert not at.exception @patch("langchain.llms.OpenAI.__call__") diff --git a/requirements.txt b/requirements.txt index 8aecc034f..9b90305d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -streamlit-nightly >= 1.27.3.dev20231019 +streamlit>=1.28 langchain>=0.0.217 openai duckduckgo-search From 5acfd398a7310e2caf5520462131c79283fd2885 Mon Sep 17 00:00:00 2001 From: Joshua Carroll Date: Fri, 27 Oct 2023 12:50:46 -0700 Subject: [PATCH 8/8] Fix precommit format thing --- .pre-commit-config.yaml | 2 +- Chatbot.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d8afdf641..25c8db679 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,4 +10,4 @@ repos: hooks: - id: black language_version: python3.11 - args: ["--line-length", "100"] + args: ["--line-length", "105"] diff --git a/Chatbot.py b/Chatbot.py index e1a2aedc7..960f50dce 100644 --- a/Chatbot.py +++ b/Chatbot.py @@ -23,9 +23,7 @@ openai.api_key = openai_api_key st.session_state.messages.append({"role": "user", "content": prompt}) st.chat_message("user").write(prompt) - response = openai.ChatCompletion.create( - model="gpt-3.5-turbo", messages=st.session_state.messages - ) + response = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=st.session_state.messages) msg = response.choices[0].message st.session_state.messages.append(msg) st.chat_message("assistant").write(msg.content)