Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: populate default settings, warn user if key missing #19

Merged
merged 7 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 26 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,17 @@ 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 .
- 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
19 changes: 16 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,20 @@ def runtime_requirements(self):
)

def initialize(self):
self.settings.merge(DEFAULT_SETTINGS, new_only=True)
self.unconfigured_message = f"ChatGPT not configured yet, please set your API key in {self.settings_path}"
mikejgray marked this conversation as resolved.
Show resolved Hide resolved
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 +86,15 @@ 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)
self.speak_dialog("gpt_error")
mikejgray marked this conversation as resolved.
Show resolved Hide resolved
if not answered:
self.speak_dialog("gpt_error")

def ask_chatgpt(self, message):
if "key" not in self.settings:
self.log.error(self.unconfigured_message)
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.
58 changes: 58 additions & 0 deletions test/unittests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from time import sleep
from unittest.mock import Mock, MagicMock
import pytest
from ovos_utils.fakebus import FakeBus

from skill_ovos_fallback_chatgpt import ChatGPTSkill, DEFAULT_SETTINGS


class TestChatGPTSkill:
bus = FakeBus()
bus.emitter = bus.ee
bus.run_forever()

def test_default_no_key(self):
skill = ChatGPTSkill(bus=self.bus, skill_id="test")
skill.speak = Mock()
skill.speak_dialog = Mock()
skill.play_audio = Mock()
skill.log = MagicMock()
while not skill.is_fully_initialized:
sleep(0.5)
assert not skill.settings.get("key")
skill.ask_chatgpt("Will my test pass?")
skill.log.error.assert_called()
skill.speak_dialog.assert_not_called() # no key, we log an error before speaking ever happens
assert skill.settings.get("persona") == DEFAULT_SETTINGS["persona"]
assert skill.settings.get("model") == DEFAULT_SETTINGS["model"]

def test_default_with_key(self):
skill = ChatGPTSkill(bus=self.bus, skill_id="test", settings={"key": "test"})
skill.speak = Mock()
skill.speak_dialog = Mock()
skill.play_audio = Mock()
while not skill.is_fully_initialized:
sleep(0.5)
assert skill.settings.get("key") == "test"
assert skill.settings.get("persona") == DEFAULT_SETTINGS["persona"]
assert skill.settings.get("model") == DEFAULT_SETTINGS["model"]

def test_overriding_all_settings(self):
skill = ChatGPTSkill(bus=self.bus, skill_id="test", settings={
"key": "test",
"persona": "I am a test persona",
"model": "gpt-4-nitro"
})
skill.speak = Mock()
skill.speak_dialog = Mock()
skill.play_audio = Mock()
while not skill.is_fully_initialized:
sleep(0.5)
assert skill.settings.get("key") == "test"
assert skill.settings.get("persona") == "I am a test persona"
assert skill.settings.get("model") == "gpt-4-nitro"
assert skill.settings.get("persona") != DEFAULT_SETTINGS["persona"]
assert skill.settings.get("model") != DEFAULT_SETTINGS["model"]

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