From c4bde293dc9badf9932c4a3f3f1c5700f7cffff7 Mon Sep 17 00:00:00 2001 From: amandasavluchinske Date: Tue, 18 Jun 2024 15:16:48 +0100 Subject: [PATCH 1/6] Starts working on list thread messages tests --- tests/test_views.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/test_views.py b/tests/test_views.py index 7a58763..6dc80f3 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -6,10 +6,10 @@ import pytest from model_bakery import baker -from django_ai_assistant.exceptions import AIAssistantNotDefinedError +from django_ai_assistant.exceptions import AIAssistantNotDefinedError, AIUserNotAllowedError from django_ai_assistant.helpers.assistants import AIAssistant, register_assistant from django_ai_assistant.langchain.tools import BaseModel, Field, method_tool -from django_ai_assistant.models import Thread +from django_ai_assistant.models import Message, Thread # Set up @@ -195,4 +195,22 @@ def test_cannot_delete_thread_if_unauthorized(): # Threads Messages Views (will need VCR) -# TBD +# GET + + +@pytest.mark.django_db(transaction=True) +def test_list_thread_messages(authenticated_client): + thread = baker.make(Thread, created_by=User.objects.first()) + message = baker.make(Message, thread=thread) + response = authenticated_client.get(f"/threads/{thread.id}/messages/") + + assert response.status_code == HTTPStatus.OK + assert response.json()[0].id == message.id + + +@pytest.mark.django_db(transaction=True) +def test_does_not_list_thread_messages_if_not_thread_user(authenticated_client): + with pytest.raises(AIUserNotAllowedError): + thread = baker.make(Thread) + baker.make(Message, thread=thread) + authenticated_client.get(f"/threads/{thread.id}/messages/") From f0ba8bf8bb456fa993662f1af981fd59017fe55e Mon Sep 17 00:00:00 2001 From: amandasavluchinske Date: Wed, 19 Jun 2024 13:03:38 +0100 Subject: [PATCH 2/6] Partial post work --- tests/test_views.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/test_views.py b/tests/test_views.py index d122461..867394f 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -8,8 +8,9 @@ from django_ai_assistant.exceptions import AIAssistantNotDefinedError, AIUserNotAllowedError from django_ai_assistant.helpers.assistants import AIAssistant, register_assistant +from django_ai_assistant.helpers.use_cases import create_thread_message_as_user from django_ai_assistant.langchain.tools import BaseModel, Field, method_tool -from django_ai_assistant.models import Message, Thread +from django_ai_assistant.models import Thread # Set up @@ -223,18 +224,30 @@ def test_cannot_delete_thread_if_unauthorized(): @pytest.mark.django_db(transaction=True) +@pytest.mark.vcr def test_list_thread_messages(authenticated_client): thread = baker.make(Thread, created_by=User.objects.first()) - message = baker.make(Message, thread=thread) - response = authenticated_client.get(f"/threads/{thread.id}/messages/") + create_thread_message_as_user(thread.id, "Hello", User.objects.first()) + response = authenticated_client.get( + reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}) + ) assert response.status_code == HTTPStatus.OK - assert response.json()[0].id == message.id @pytest.mark.django_db(transaction=True) +@pytest.mark.vcr def test_does_not_list_thread_messages_if_not_thread_user(authenticated_client): with pytest.raises(AIUserNotAllowedError): thread = baker.make(Thread) - baker.make(Message, thread=thread) - authenticated_client.get(f"/threads/{thread.id}/messages/") + create_thread_message_as_user(thread.id, "Hello", User.objects.create()) + authenticated_client.get( + reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}) + ) + + +# POST + +# @pytest.mark.django_db(transaction=True) +# def test_create_thread_message(authenticated_client): +# create_thread_message From f4fd99b98dd33ca2d72c1314c20661eebe8c25a2 Mon Sep 17 00:00:00 2001 From: amandasavluchinske Date: Wed, 19 Jun 2024 15:08:25 +0100 Subject: [PATCH 3/6] Adds messages tests and pytest-cov --- poetry.lock | 20 +- pyproject.toml | 1 + .../test_create_thread_message.yaml | 196 ++++++++++++++++++ .../test_delete_thread_message.yaml | 196 ++++++++++++++++++ tests/test_views.py | 70 ++++++- 5 files changed, 473 insertions(+), 10 deletions(-) create mode 100644 tests/cassettes/test_views/test_create_thread_message.yaml create mode 100644 tests/cassettes/test_views/test_delete_thread_message.yaml diff --git a/poetry.lock b/poetry.lock index aef4010..481369f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1811,6 +1811,24 @@ pytest = ">=7.0.0,<9" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "pytest-django" version = "4.8.0" @@ -2724,4 +2742,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.12,<3.13" -content-hash = "2bc5e1b1fe25c2765af0cd3a3b22a560ad9db2f8694771dd10ffab07669673f0" +content-hash = "a691791a02fbae2f75b9854b4e4da4e81a29aba8589be5e66269eac19e339c61" diff --git a/pyproject.toml b/pyproject.toml index 9425735..f0117ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ pytest-asyncio = "^0.23.7" pytest-recording = "^0.13.1" coveralls = "^4.0.1" model-bakery = "^1.18.1" +pytest-cov = "^5.0.0" [tool.poetry.group.example.dependencies] django-webpack-loader = "^3.1.0" diff --git a/tests/cassettes/test_views/test_create_thread_message.yaml b/tests/cassettes/test_views/test_create_thread_message.yaml new file mode 100644 index 0000000..89f3d9f --- /dev/null +++ b/tests/cassettes/test_views/test_create_thread_message.yaml @@ -0,0 +1,196 @@ +interactions: +- request: + body: '{"messages": [{"content": "You are a temperature bot. Today is 2024-06-09.", + "role": "system"}, {"content": "Hello, what is the temperature in SF right now?", + "role": "user"}], "model": "gpt-4o", "n": 1, "stream": true, "temperature": + 1.0, "tools": [{"type": "function", "function": {"name": "fetch_current_temperature", + "description": "Fetch the current temperature data for a location", "parameters": + {"type": "object", "properties": {"location": {"type": "string"}}, "required": + ["location"]}}}, {"type": "function", "function": {"name": "fetch_forecast_temperature", + "description": "Fetch the forecast temperature data for a location", "parameters": + {"type": "object", "properties": {"location": {"type": "string"}, "dt_str": + {"description": "Date in the format ''YYYY-MM-DD''", "type": "string"}}, "required": + ["location", "dt_str"]}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + authorization: + - DUMMY + connection: + - keep-alive + content-length: + - '840' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_DSkKzAmCkD4pfwMPxeDZbXmN","type":"function","function":{"name":"fetch_current_temperature","arguments":""}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"location"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"San"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + Francisco"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":","}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + CA"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0n4JXzttxBAJOECR6Rcmb2ZrFe","object":"chat.completion.chunk","created":1718801253,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + + + data: [DONE] + + + ' + headers: + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: Sun, 09 Jun 2024 23:39:08 GMT + Server: DUMMY + Transfer-Encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "You are a temperature bot. Today is 2024-06-09.", + "role": "system"}, {"content": "Hello, what is the temperature in SF right now?", + "role": "user"}, {"content": null, "role": "assistant", "tool_calls": [{"type": + "function", "id": "call_DSkKzAmCkD4pfwMPxeDZbXmN", "function": {"name": "fetch_current_temperature", + "arguments": "{\"location\": \"San Francisco, CA\"}"}}]}, {"content": "32 degrees + Celsius", "role": "tool", "tool_call_id": "call_DSkKzAmCkD4pfwMPxeDZbXmN"}], + "model": "gpt-4o", "n": 1, "stream": true, "temperature": 1.0, "tools": [{"type": + "function", "function": {"name": "fetch_current_temperature", "description": + "Fetch the current temperature data for a location", "parameters": {"type": + "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}}, + {"type": "function", "function": {"name": "fetch_forecast_temperature", "description": + "Fetch the forecast temperature data for a location", "parameters": {"type": + "object", "properties": {"location": {"type": "string"}, "dt_str": {"description": + "Date in the format ''YYYY-MM-DD''", "type": "string"}}, "required": ["location", + "dt_str"]}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + authorization: + - DUMMY + connection: + - keep-alive + content-length: + - '1162' + content-type: + - application/json + cookie: + - DUMMY + host: + - api.openai.com + user-agent: + - OpenAI/Python + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + current"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + temperature"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + in"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + San"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + Francisco"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + CA"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + is"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + "},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":"32"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + degrees"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":" + Celsius"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bp0oyb19UTjSTZGIZ9S9iH1Tedxm","object":"chat.completion.chunk","created":1718801254,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + + + data: [DONE] + + + ' + headers: + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: Sun, 09 Jun 2024 23:39:08 GMT + Server: DUMMY + Transfer-Encoding: + - chunked + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_views/test_delete_thread_message.yaml b/tests/cassettes/test_views/test_delete_thread_message.yaml new file mode 100644 index 0000000..d5d75d4 --- /dev/null +++ b/tests/cassettes/test_views/test_delete_thread_message.yaml @@ -0,0 +1,196 @@ +interactions: +- request: + body: '{"messages": [{"content": "You are a temperature bot. Today is 2024-06-09.", + "role": "system"}, {"content": "Hello, what is the temperature in SF right now?", + "role": "user"}], "model": "gpt-4o", "n": 1, "stream": true, "temperature": + 1.0, "tools": [{"type": "function", "function": {"name": "fetch_current_temperature", + "description": "Fetch the current temperature data for a location", "parameters": + {"type": "object", "properties": {"location": {"type": "string"}}, "required": + ["location"]}}}, {"type": "function", "function": {"name": "fetch_forecast_temperature", + "description": "Fetch the forecast temperature data for a location", "parameters": + {"type": "object", "properties": {"location": {"type": "string"}, "dt_str": + {"description": "Date in the format ''YYYY-MM-DD''", "type": "string"}}, "required": + ["location", "dt_str"]}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + authorization: + - DUMMY + connection: + - keep-alive + content-length: + - '840' + content-type: + - application/json + host: + - api.openai.com + user-agent: + - OpenAI/Python + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"role":"assistant","content":null,"tool_calls":[{"index":0,"id":"call_jZYnXesjvWYUOtcl9hJtkdGv","type":"function","function":{"name":"fetch_current_temperature","arguments":""}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\""}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"location"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\":\""}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"San"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + Francisco"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":","}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" + CA"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"}"}}]},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFYWsubgNdYM5o0AqjwHHOvD8KL","object":"chat.completion.chunk","created":1718802168,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_9cb5d38cf7","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]} + + + data: [DONE] + + + ' + headers: + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: Sun, 09 Jun 2024 23:39:08 GMT + Server: DUMMY + Transfer-Encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: '{"messages": [{"content": "You are a temperature bot. Today is 2024-06-09.", + "role": "system"}, {"content": "Hello, what is the temperature in SF right now?", + "role": "user"}, {"content": null, "role": "assistant", "tool_calls": [{"type": + "function", "id": "call_jZYnXesjvWYUOtcl9hJtkdGv", "function": {"name": "fetch_current_temperature", + "arguments": "{\"location\": \"San Francisco, CA\"}"}}]}, {"content": "32 degrees + Celsius", "role": "tool", "tool_call_id": "call_jZYnXesjvWYUOtcl9hJtkdGv"}], + "model": "gpt-4o", "n": 1, "stream": true, "temperature": 1.0, "tools": [{"type": + "function", "function": {"name": "fetch_current_temperature", "description": + "Fetch the current temperature data for a location", "parameters": {"type": + "object", "properties": {"location": {"type": "string"}}, "required": ["location"]}}}, + {"type": "function", "function": {"name": "fetch_forecast_temperature", "description": + "Fetch the forecast temperature data for a location", "parameters": {"type": + "object", "properties": {"location": {"type": "string"}, "dt_str": {"description": + "Date in the format ''YYYY-MM-DD''", "type": "string"}}, "required": ["location", + "dt_str"]}}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + authorization: + - DUMMY + connection: + - keep-alive + content-length: + - '1162' + content-type: + - application/json + cookie: + - DUMMY + host: + - api.openai.com + user-agent: + - OpenAI/Python + method: POST + uri: https://api.openai.com/v1/chat/completions + response: + body: + string: 'data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"role":"assistant","content":""},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":"The"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + current"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + temperature"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + in"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + San"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + Francisco"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":","},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + CA"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + is"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + "},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":"32"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + degrees"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":" + Celsius"},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{"content":"."},"logprobs":null,"finish_reason":null}]} + + + data: {"id":"chatcmpl-9bpFZ4XRr8JskwOIpN1uE7TsBwgb0","object":"chat.completion.chunk","created":1718802169,"model":"gpt-4o-2024-05-13","system_fingerprint":"fp_f4e629d0a5","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]} + + + data: [DONE] + + + ' + headers: + Connection: + - keep-alive + Content-Type: + - text/event-stream; charset=utf-8 + Date: Sun, 09 Jun 2024 23:39:08 GMT + Server: DUMMY + Transfer-Encoding: + - chunked + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_views.py b/tests/test_views.py index b13895d..ae04c7d 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -7,10 +7,10 @@ from model_bakery import baker from django_ai_assistant.exceptions import AIAssistantNotDefinedError, AIUserNotAllowedError +from django_ai_assistant.helpers import use_cases from django_ai_assistant.helpers.assistants import AIAssistant, register_assistant -from django_ai_assistant.helpers.use_cases import create_thread_message_as_user from django_ai_assistant.langchain.tools import BaseModel, Field, method_tool -from django_ai_assistant.models import Thread +from django_ai_assistant.models import Message, Thread # Set up @@ -226,23 +226,22 @@ def test_cannot_delete_thread_if_unauthorized(): @pytest.mark.django_db(transaction=True) -@pytest.mark.vcr def test_list_thread_messages(authenticated_client): thread = baker.make(Thread, created_by=User.objects.first()) - create_thread_message_as_user(thread.id, "Hello", User.objects.first()) + use_cases.create_thread_message_as_user(thread.id, "Hello", User.objects.first()) response = authenticated_client.get( reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}) ) assert response.status_code == HTTPStatus.OK + assert len(response.json()) == 1 @pytest.mark.django_db(transaction=True) -@pytest.mark.vcr def test_does_not_list_thread_messages_if_not_thread_user(authenticated_client): with pytest.raises(AIUserNotAllowedError): thread = baker.make(Thread) - create_thread_message_as_user(thread.id, "Hello", User.objects.create()) + use_cases.create_thread_message_as_user(thread.id, "Hello", User.objects.create()) authenticated_client.get( reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}) ) @@ -250,6 +249,59 @@ def test_does_not_list_thread_messages_if_not_thread_user(authenticated_client): # POST -# @pytest.mark.django_db(transaction=True) -# def test_create_thread_message(authenticated_client): -# create_thread_message + +@pytest.mark.django_db(transaction=True) +@pytest.mark.vcr +def test_create_thread_message(authenticated_client): + thread = baker.make(Thread, created_by=User.objects.first()) + response = authenticated_client.post( + reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}), + data={ + "content": "Hello, what is the temperature in SF right now?", + "assistant_id": "temperature_assistant", + }, + content_type="application/json", + ) + + assert response.status_code == HTTPStatus.CREATED + assert Message.objects.count() == 2 + + human_message = Message.objects.filter(message__type="human").first() + ai_message = Message.objects.filter(message__type="ai").first() + + assert ( + human_message.message["data"]["content"] + == "Hello, what is the temperature in SF right now?" + ) + assert ( + ai_message.message["data"]["content"] + == "The current temperature in San Francisco, CA is 32 degrees Celsius." + ) + + +# DELETE + + +@pytest.mark.django_db(transaction=True) +@pytest.mark.vcr +def test_delete_thread_message(authenticated_client): + thread = baker.make(Thread, created_by=User.objects.first()) + authenticated_client.post( + reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}), + data={ + "content": "Hello, what is the temperature in SF right now?", + "assistant_id": "temperature_assistant", + }, + content_type="application/json", + ) + message = Message.objects.first() + + response = authenticated_client.delete( + reverse( + "django_ai_assistant:messages_delete", + kwargs={"thread_id": thread.id, "message_id": message.id}, + ) + ) + + assert response.status_code == HTTPStatus.NO_CONTENT + assert not Message.objects.filter(id=message.id).exists() From e0ce5b11f6fa1810b5a8322a547ae5bf68d0e21a Mon Sep 17 00:00:00 2001 From: amandasavluchinske Date: Wed, 19 Jun 2024 15:56:13 +0100 Subject: [PATCH 4/6] Adds test for openapi json --- manage.py | 2 +- tests/test_views.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/manage.py b/manage.py index effff1a..185ef52 100644 --- a/manage.py +++ b/manage.py @@ -1,6 +1,6 @@ #!/usr/bin/env python """ -Use this manage.py for generate the OpenAPI schema. NOT for examples. +Use this manage.py to generate the OpenAPI schema. NOT for examples. For tests, use pytest directly. """ import os diff --git a/tests/test_views.py b/tests/test_views.py index 58d4b99..69f4055 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -6,6 +6,7 @@ import pytest from model_bakery import baker +from django_ai_assistant import package_name, version from django_ai_assistant.exceptions import AIAssistantNotDefinedError, AIUserNotAllowedError from django_ai_assistant.helpers import use_cases from django_ai_assistant.helpers.assistants import AIAssistant, register_assistant @@ -48,6 +49,18 @@ def authenticated_client(client): return client +# OPENAPI JSON View + + +@pytest.mark.django_db() +def test_generates_json_with_correct_version(authenticated_client): + response = authenticated_client.get("/openapi.json") + + assert response.status_code == HTTPStatus.OK + assert response.json()["info"]["version"] == version + assert response.json()["info"]["title"] == package_name + + # Assistant Views @@ -131,7 +144,7 @@ def test_gets_specific_thread(authenticated_client): ) assert response.status_code == HTTPStatus.OK - assert response.json().get("id") == thread.id + assert response.json()["id"] == thread.id def test_does_not_list_threads_if_unauthorized(): @@ -151,7 +164,7 @@ def test_create_thread(authenticated_client): thread = Thread.objects.first() assert response.status_code == HTTPStatus.OK - assert response.json().get("id") == thread.id + assert response.json()["id"] == thread.id def test_cannot_create_thread_if_unauthorized(): From a8067548a87000e1974d69e3f7d8f4af9766556d Mon Sep 17 00:00:00 2001 From: amandasavluchinske Date: Wed, 19 Jun 2024 15:57:40 +0100 Subject: [PATCH 5/6] Removes will need vcr comment --- tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_views.py b/tests/test_views.py index 69f4055..7145f61 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -236,7 +236,7 @@ def test_cannot_delete_thread_if_unauthorized(): pass -# Threads Messages Views (will need VCR) +# Threads Messages Views # GET From c2c9365052448d121db3a386abde1f7e9d9cf58c Mon Sep 17 00:00:00 2001 From: Amanda Savluchinske Date: Wed, 19 Jun 2024 14:16:02 -0300 Subject: [PATCH 6/6] Update tests/test_views.py Co-authored-by: Pamella Bezerra --- tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_views.py b/tests/test_views.py index 3787ea9..19d64b1 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -253,7 +253,7 @@ def test_cannot_delete_thread_if_unauthorized(): @pytest.mark.django_db(transaction=True) def test_list_thread_messages(authenticated_client): thread = baker.make(Thread, created_by=User.objects.first()) - use_cases.create_thread_message_as_user(thread.id, "Hello", User.objects.first()) + use_cases.create_thread_message_as_user(thread.id, "Hello", thread.created_by) response = authenticated_client.get( reverse("django_ai_assistant:messages_list_create", kwargs={"thread_id": thread.id}) )