Skip to content
This repository has been archived by the owner on Jan 31, 2025. It is now read-only.

Commit

Permalink
feat: populate default settings, warn user if key missing (#19)
Browse files Browse the repository at this point in the history
* feat: populate default settings, warn user if key missing

* satisfy our pipeline overlords

* bump unit tests

* another bump

* address feedback

* pull in version of workshop with fix for unittests

* revamp tests
  • Loading branch information
mikejgray authored Feb 17, 2024
1 parent b106d55 commit b98849f
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 29 deletions.
54 changes: 28 additions & 26 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,43 @@ on:
branches:
- dev
paths-ignore:
- 'version.py'
- 'requirements.txt'
- 'examples/**'
- '.github/**'
- '.gitignore'
- 'LICENSE'
- 'CHANGELOG.md'
- 'MANIFEST.in'
- 'readme.md'
- 'scripts/**'
- "version.py"
- "requirements.txt"
- "examples/**"
- ".github/**"
- ".gitignore"
- "LICENSE"
- "CHANGELOG.md"
- "MANIFEST.in"
- "readme.md"
- "scripts/**"
push:
branches:
- master
paths-ignore:
- 'version.py'
- 'requirements.txt'
- 'examples/**'
- '.github/**'
- '.gitignore'
- 'LICENSE'
- 'CHANGELOG.md'
- 'MANIFEST.in'
- 'readme.md'
- 'scripts/**'
- "version.py"
- "requirements.txt"
- "examples/**"
- ".github/**"
- ".gitignore"
- "LICENSE"
- "CHANGELOG.md"
- "MANIFEST.in"
- "readme.md"
- "scripts/**"
workflow_dispatch:

jobs:
unit_tests:
strategy:
max-parallel: 2
matrix:
python-version: [ 3.7, 3.8, 3.9, "3.10" ]
python-version: [3.8, 3.9, "3.10", "3.11"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install System Dependencies
Expand All @@ -57,17 +57,19 @@ jobs:
sudo apt install libfann-dev
- name: Install ovos dependencies
run: |
pip install ovos-plugin-manager ovos-core[skills_lgpl]>=0.0.6a21
pip install --pre ovos-plugin-manager ovos-core[skills_lgpl]>=0.0.8
- name: Install core repo
run: |
pip install .
pip install --pre -U ovos-workshop # 0.0.16a5 required for the class to load properly in unit tests
pip list
- name: Run unittests
run: |
pytest --cov=ovos-skill-template-repo --cov-report xml test/unittests
pytest --cov=. --cov-report xml test/unittests
# NOTE: additional pytest invocations should also add the --cov-append flag
# or they will overwrite previous invocations' coverage reports
# (for an example, see OVOS Skill Manager's workflow)
- name: Upload coverage
env:
CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}}
uses: codecov/codecov-action@v2
uses: codecov/codecov-action@v4
17 changes: 14 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from ovos_workshop.skills.fallback import FallbackSkill


DEFAULT_SETTINGS = {
"persona": "You are a helpful voice assistant with a friendly tone and fun sense of humor. You respond in 40 words or fewer.",
"model": "gpt-3.5-turbo",
}

class ChatGPTSkill(FallbackSkill):
sessions = {}

Expand All @@ -18,14 +23,19 @@ def runtime_requirements(self):
)

def initialize(self):
self.settings.merge(DEFAULT_SETTINGS, new_only=True)
self.add_event("speak", self.handle_speak)
self.add_event("recognizer_loop:utterance", self.handle_utterance)
self.register_fallback(self.ask_chatgpt, 85)

@property
def chat(self):
"""created fresh to allow key/url rotation when settings.json is edited"""
return OpenAIPersonaSolver(config=self.settings)
try:
return OpenAIPersonaSolver(config=self.settings)
except Exception as err:
self.log.error(err)
return None

def handle_utterance(self, message):
utt = message.data.get("utterances")[0]
Expand Down Expand Up @@ -75,13 +85,14 @@ def _async_ask(self, message):
for utt in self.chat.stream_utterances(utterance):
answered = True
self.speak(utt)
except: # speak error on any network issue / no credits etc
pass
except Exception as err: # speak error on any network issue / no credits etc
self.log.error(err)
if not answered:
self.speak_dialog("gpt_error")

def ask_chatgpt(self, message):
if "key" not in self.settings:
self.log.error("ChatGPT not configured yet, please set your API key in %s", self.settings.path)
return False # ChatGPT not configured yet
utterance = message.data["utterance"]
self.speak_dialog("asking")
Expand Down
Empty file added test/unittests/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions test/unittests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import shutil
from os import environ, getenv
from os.path import dirname, join
from threading import Event
from time import sleep
from unittest import TestCase
from unittest.mock import MagicMock, Mock

import pytest
from ovos_utils.fakebus import FakeBus
from skill_ovos_fallback_chatgpt import DEFAULT_SETTINGS, ChatGPTSkill


class TestChatGPTSkill(TestCase):
# Define test directories
test_fs = join(dirname(__file__), "skill_fs")
data_dir = join(test_fs, "data")
conf_dir = join(test_fs, "config")
environ["XDG_DATA_HOME"] = data_dir
environ["XDG_CONFIG_HOME"] = conf_dir

bus = FakeBus()
bus.emitter = bus.ee
bus.connected_event = Event()
bus.connected_event.set()
bus.run_forever()
test_skill_id = 'test_skill.test'

skill = None

@classmethod
def setUpClass(cls) -> None:
# Get test skill
cls.skill = ChatGPTSkill(skill_id=cls.test_skill_id, bus=cls.bus)
# Override speak and speak_dialog to test passed arguments
cls.skill.speak = Mock()
cls.skill.speak_dialog = Mock()

def setUp(self):
self.skill.speak.reset_mock()
self.skill.speak_dialog.reset_mock()
self.skill.play_audio = Mock()
self.skill.log = MagicMock()

@classmethod
def tearDownClass(cls) -> None:
shutil.rmtree(cls.test_fs)

def test_default_no_key(self):
assert not self.skill.settings.get("key")
self.skill.ask_chatgpt("Will my test pass?")
self.skill.log.error.assert_called()
self.skill.speak_dialog.assert_not_called() # no key, we log an error before speaking ever happens
assert self.skill.settings.get("persona") == DEFAULT_SETTINGS["persona"]
assert self.skill.settings.get("model") == DEFAULT_SETTINGS["model"]

def test_default_with_key(self):
self.skill.settings["key"] = "test"
self.skill.settings.store()
assert self.skill.settings.get("key") == "test"
assert self.skill.settings.get("persona") == DEFAULT_SETTINGS["persona"]
assert self.skill.settings.get("model") == DEFAULT_SETTINGS["model"]

def test_overriding_all_settings(self):
self.skill.settings["key"] = "test"
self.skill.settings["persona"] = "I am a test persona"
self.skill.settings["model"] = "gpt-4-nitro"
self.skill.settings.store()
assert self.skill.settings.get("key") == "test"
assert self.skill.settings.get("persona") == "I am a test persona"
assert self.skill.settings.get("model") == "gpt-4-nitro"
assert self.skill.settings.get("persona") != DEFAULT_SETTINGS["persona"]
assert self.skill.settings.get("model") != DEFAULT_SETTINGS["model"]

if __name__ == "__main__":
pytest.main()

0 comments on commit b98849f

Please sign in to comment.