Skip to content

Commit

Permalink
feat: replaced openai client with chat completion api
Browse files Browse the repository at this point in the history
  • Loading branch information
irfanuddinahmad committed Jul 3, 2024
1 parent eb5db7c commit 400303d
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/mysql8-migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
pip uninstall -y mysqlclient
pip install --no-binary mysqlclient mysqlclient
pip uninstall -y xmlsec
pip install --no-binary xmlsec xmlsec
pip install --no-binary xmlsec xmlsec==1.3.13
- name: Initiate Services
run: |
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Change Log
Unreleased

[1.51.0] - 2024-07-03
---------------------
* feat: Replaced client for ai chat

[1.50.0] - 2024-03-27
---------------------
* feat: Skill validation can be disbaled for a course or an organization
Expand Down
2 changes: 1 addition & 1 deletion taxonomy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
# 2. MINOR version when you add functionality in a backwards compatible manner, and
# 3. PATCH version when you make backwards compatible bug fixes.
# More details can be found at https://semver.org/
__version__ = '1.50.0'
__version__ = '1.51.0'

default_app_config = 'taxonomy.apps.TaxonomyConfig' # pylint: disable=invalid-name
46 changes: 33 additions & 13 deletions taxonomy/openai/client.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,45 @@
"""openai client"""
"""CHAT_COMPLETION_API client"""
import json
import logging

import openai
import requests
from requests.exceptions import ConnectTimeout

from django.conf import settings

openai.api_key = settings.OPENAI_API_KEY
log = logging.getLogger(__name__)


def chat_completion(prompt):
"""
Use chatGPT https://api.openai.com/v1/chat/completions endpoint to generate a response.
Pass message list to chat endpoint, as defined by the CHAT_COMPLETION_API setting.
Arguments:
prompt (str): chatGPT prompt
"""
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": prompt},
]
)
completion_endpoint = getattr(settings, 'CHAT_COMPLETION_API', None)
completion_endpoint_key = getattr(settings, 'CHAT_COMPLETION_API_KEY', None)
if completion_endpoint and completion_endpoint_key:
headers = {'Content-Type': 'application/json', 'x-api-key': completion_endpoint_key}
connect_timeout = getattr(settings, 'CHAT_COMPLETION_API_CONNECT_TIMEOUT', 1)
read_timeout = getattr(settings, 'CHAT_COMPLETION_API_READ_TIMEOUT', 15)
body = {'message_list': [{'role': 'assistant', 'content': prompt},]}
try:
response = requests.post(
completion_endpoint,
headers=headers,
data=json.dumps(body),
timeout=(connect_timeout, read_timeout)
)
chat = response.json().get('content')
except (ConnectTimeout, ConnectionError) as e:
error_message = str(e)
connection_message = 'Failed to connect to chat completion API.'
log.error(
'%(connection_message)s %(error)s',
{'connection_message': connection_message, 'error': error_message}
)
chat = connection_message
else:
chat = 'Completion endpoint is not defined.'

content = response['choices'][0]['message']['content']
return content
return chat
3 changes: 2 additions & 1 deletion test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ def root(*args):
SKILLS_IGNORED_THRESHOLD = 10
SKILLS_IGNORED_RATIO_THRESHOLD = 0.8

OPENAI_API_KEY = 'I am a key'
CHAT_COMPLETION_API = 'http://test.chat.ai'
CHAT_COMPLETION_API_KEY = 'test chat completion api key'

JOB_DESCRIPTION_PROMPT = 'Generate a description for {job_name} job role.'
JOB_TO_JOB_DESCRIPTION_PROMPT = 'How can a {current_job_name} switch to {future_job_name} job role.'
Empty file added tests/openai/__init__.py
Empty file.
52 changes: 52 additions & 0 deletions tests/openai/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""
Tests for chat completion client.
"""
import responses
from mock import patch

from django.conf import settings

from taxonomy.openai.client import chat_completion
from test_utils.testcase import TaxonomyTestCase


class TestChatCompletionClient(TaxonomyTestCase):
"""
Validate chat_completion client.
"""
@responses.activate
def test_client(self):
"""
Test that the chat completion client works as expected.
"""
chat_prompt = 'how many courses are offered by edx in the data science area'
expected_chat_response = {
"role": "assistant",
"content": "edx offers 500 courses in the data science area"
}
responses.add(
method=responses.POST,
url=settings.CHAT_COMPLETION_API,
json=expected_chat_response,
)
chat_response = chat_completion(chat_prompt)
self.assertEqual(chat_response, expected_chat_response['content'])

@patch('taxonomy.openai.client.requests.post')
def test_client_exceptions(self, post_mock):
"""
Test that the chat completion client handles exceptions as expected.
"""
chat_prompt = 'how many courses are offered by edx in the data science area'
post_mock.side_effect = ConnectionError()
chat_response = chat_completion(chat_prompt)
self.assertEqual(chat_response, 'Failed to connect to chat completion API.')

def test_client_missing_settings(self):
"""
Test that the chat completion client handles missing settings as expected.
"""
chat_prompt = 'how many courses are offered by edx in the data science area'
settings.CHAT_COMPLETION_API_KEY = None
chat_response = chat_completion(chat_prompt)
self.assertEqual(chat_response, 'Completion endpoint is not defined.')
19 changes: 6 additions & 13 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,41 +346,34 @@ def test_string_representation(self):
assert expected_repr == job.__repr__()

@pytest.mark.use_signals
@patch('taxonomy.openai.client.openai.ChatCompletion.create')
@patch('taxonomy.openai.client.requests.post')
@patch('taxonomy.utils.generate_and_store_job_description', wraps=generate_and_store_job_description)
@patch('taxonomy.signals.handlers.generate_job_description.delay', wraps=generate_job_description)
def test_chat_completion_is_called( # pylint: disable=invalid-name
self,
mocked_generate_job_description_task,
mocked_generate_and_store_job_description,
mocked_chat_completion
mock_requests
):
"""
Verify that complete flow works as expected when a Job model object is created.
"""
ai_response = 'One who manages a Computer Network.'
mocked_chat_completion.return_value = {
'choices': [{
'message': {
'content': ai_response
}
}]
mock_requests.return_value.json.return_value = {
"role": "assistant",
"content": ai_response
}

job_external_id = '1111'
job_name = 'Network Admin'
prompt = settings.JOB_DESCRIPTION_PROMPT.format(job_name=job_name)

Job(external_id=job_external_id, name=job_name).save()
job = Job.objects.get(external_id=job_external_id)

assert job.description == ai_response
mocked_generate_job_description_task.assert_called_once_with(job_external_id, job_name)
mocked_generate_and_store_job_description.assert_called_once_with(job_external_id, job_name)
mocked_chat_completion.assert_called_once_with(
model='gpt-3.5-turbo',
messages=[{'role': 'user', 'content': prompt}]
)
mock_requests.assert_called_once()

@pytest.mark.use_signals
@patch('taxonomy.utils.chat_completion')
Expand Down
13 changes: 5 additions & 8 deletions tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,26 +646,23 @@ def setUp(self) -> None:
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.view_url = '/api/v1/job-path/'

@patch('taxonomy.openai.client.openai.ChatCompletion.create')
@patch('taxonomy.openai.client.requests.post')
@patch(
'taxonomy.api.v1.serializers.generate_and_store_job_to_job_description',
wraps=generate_and_store_job_to_job_description
)
def test_job_path_api( # pylint: disable=invalid-name
self,
mocked_generate_and_store_job_to_job_description,
mocked_chat_completion
mock_requests
):
"""
Verify that job path API returns the expected response.
"""
ai_response = 'You can not switch from your current job to future job'
mocked_chat_completion.return_value = {
'choices': [{
'message': {
'content': ai_response
}
}]
mock_requests.return_value.json.return_value = {
"role": "assistant",
"content": ai_response
}

query_params = {
Expand Down

0 comments on commit 400303d

Please sign in to comment.