Skip to content

Commit

Permalink
fixup! ✨(backend) create ai endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoLC committed Sep 30, 2024
1 parent 09f6230 commit bab5602
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 242 deletions.
15 changes: 15 additions & 0 deletions src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from rest_framework import exceptions, serializers

from core import models
from core.services.ai_services import ACTION_CONFIGS


class UserSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -350,3 +351,17 @@ class VersionFilterSerializer(serializers.Serializer):
page_size = serializers.IntegerField(
required=False, min_value=1, max_value=50, default=20
)


class AIRequestSerializer(serializers.Serializer):
"""Serializer for AI task requests."""

action = serializers.ChoiceField(choices=ACTION_CONFIGS, required=True)
text = serializers.CharField(required=True)

def validate_text(self, value):
"""Ensure the text field is not empty."""

if len(value.strip()) == 0:
raise serializers.ValidationError("Text field cannot be empty.")
return value
112 changes: 7 additions & 105 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""API endpoints"""

import json
import os
import re
import uuid
Expand All @@ -19,7 +18,6 @@
from django.http import Http404

from botocore.exceptions import ClientError
from openai import OpenAI
from rest_framework import (
decorators,
exceptions,
Expand All @@ -34,6 +32,7 @@
)

from core import models
from core.services.ai_services import AIService

from . import permissions, serializers, utils

Expand Down Expand Up @@ -798,110 +797,13 @@ def create(self, request):
translate_en, translate_de, translate_fr]
Return JSON response with the processed text.
"""
if not request.user.is_authenticated:
raise exceptions.NotAuthenticated()

if (
settings.AI_BASE_URL is None
or settings.AI_API_KEY is None
or settings.AI_MODEL is None
):
raise exceptions.ValidationError({"error": "AI configuration not set"})

action = request.data.get("action")
text = request.data.get("text")

action_configs = {
"prompt": {
"system_content": (
"Answer the prompt in markdown format. Return JSON: "
'{"answer": "Your markdown answer"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"correct": {
"system_content": (
"Correct grammar and spelling of the markdown text, "
"preserving language and markdown formatting. "
'Return JSON: {"answer": "your corrected markdown text"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"rephrase": {
"system_content": (
"Rephrase the given markdown text, "
"preserving language and markdown formatting. "
'Return JSON: {"answer": "your rephrased markdown text"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"summarize": {
"system_content": (
"Summarize the markdown text, preserving language and markdown formatting. "
'Return JSON: {"answer": "your markdown summary"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"translate_en": {
"system_content": (
"Translate the markdown text to English, preserving markdown formatting. "
'Return JSON: {"answer": "Your translated markdown text in English"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"translate_de": {
"system_content": (
"Translate the markdown text to German, preserving markdown formatting. "
'Return JSON: {"answer": "Your translated markdown text in German"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"translate_fr": {
"system_content": (
"Translate the markdown text to French, preserving markdown formatting. "
'Return JSON: {"answer": "Your translated markdown text in French"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
}

if action not in action_configs:
raise exceptions.ValidationError({"error": "Invalid action"})

config = action_configs[action]

try:
client = OpenAI(base_url=settings.AI_BASE_URL, api_key=settings.AI_API_KEY)
response = client.chat.completions.create(
model=settings.AI_MODEL,
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": config["system_content"]},
{"role": "user", "content": json.dumps({"mardown_input": text})},
],
)

corrected_response = json.loads(response.choices[0].message.content)

if "answer" not in corrected_response:
raise exceptions.ValidationError("Invalid response format")
serializer = serializers.AIRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)

return drf_response.Response(corrected_response, status=status.HTTP_200_OK)
text = serializer.validated_data["text"]
action = serializer.validated_data["action"]

except exceptions.ValidationError as e:
return drf_response.Response(
{"error": e.detail}, status=status.HTTP_400_BAD_REQUEST
)
response = AIService.call_openai_api(action, text)

except exceptions.APIException as e:
return drf_response.Response(
{"error": f"Error processing AI response: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
return drf_response.Response(response, status=status.HTTP_200_OK)
Empty file.
124 changes: 124 additions & 0 deletions src/backend/core/services/ai_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
"""AI services."""

import json
import re

from django.conf import settings

import requests
from openai import OpenAI, OpenAIError
from rest_framework import serializers

ACTION_CONFIGS = {
"prompt": {
"system_content": (
"Answer the prompt in markdown format. Return JSON: "
'{"answer": "Your markdown answer"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"correct": {
"system_content": (
"Correct grammar and spelling of the markdown text, "
"preserving language and markdown formatting. "
'Return JSON: {"answer": "your corrected markdown text"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"rephrase": {
"system_content": (
"Rephrase the given markdown text, "
"preserving language and markdown formatting. "
'Return JSON: {"answer": "your rephrased markdown text"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"summarize": {
"system_content": (
"Summarize the markdown text, preserving language and markdown formatting. "
'Return JSON: {"answer": "your markdown summary"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"translate_en": {
"system_content": (
"Translate the markdown text to English, preserving markdown formatting. "
'Return JSON: {"answer": "Your translated markdown text in English"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"translate_de": {
"system_content": (
"Translate the markdown text to German, preserving markdown formatting. "
'Return JSON: {"answer": "Your translated markdown text in German"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
"translate_fr": {
"system_content": (
"Translate the markdown text to French, preserving markdown formatting. "
'Return JSON: {"answer": "Your translated markdown text in French"}.'
"Do not provide any other information."
),
"response_key": "answer",
},
}


class AIService:
"""Service class for AI-related operations."""

@staticmethod
def check_configuration():
"""Ensure that the AI configuration is set properly."""
if (
settings.AI_BASE_URL is None
or settings.AI_API_KEY is None
or settings.AI_MODEL is None
):
raise serializers.ValidationError("AI configuration not set")

@staticmethod
def call_openai_api(action, text):
"""Call the OpenAI API and return the response."""
AIService.check_configuration()

try:
system_content = ACTION_CONFIGS[action]["system_content"]
client = OpenAI(base_url=settings.AI_BASE_URL, api_key=settings.AI_API_KEY)
response_client = client.chat.completions.create(
model=settings.AI_MODEL,
response_format={"type": "json_object"},
messages=[
{"role": "system", "content": system_content},
{"role": "user", "content": json.dumps({"markdown_input": text})},
],
)
except OpenAIError as e:
raise serializers.ValidationError(f"OpenAI API Error: {str(e)}") from e
except requests.exceptions.RequestException as e:
raise serializers.ValidationError(f"Network Error: {str(e)}") from e
except Exception as e:
raise serializers.ValidationError(f"Unexpected Error: {str(e)}") from e

content = response_client.choices[0].message.content
sanitized_content = re.sub(r"(?<!\\)\n", "\\\\n", content)
sanitized_content = re.sub(r"(?<!\\)\t", "\\\\t", sanitized_content)

try:
json_response = json.loads(sanitized_content)
except json.JSONDecodeError as e:
raise serializers.ValidationError(
f"AI response could not be parsed as JSON: {str(e)}"
) from e

if "answer" not in json_response:
raise serializers.ValidationError("AI response does not contain an answer")

return json_response
Loading

0 comments on commit bab5602

Please sign in to comment.