From 7b569d9eeda41af5bb352283873bde3a2c8bee9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=B5=20Nh=E1=BA=ADt=20T=C3=A2n?= Date: Wed, 4 Feb 2026 23:55:56 +0700 Subject: [PATCH 01/26] Update: Create AI core for AI service and a basic call API for testing --- ai_core/__init__.py | 0 ai_core/admin.py | 3 +++ ai_core/apps.py | 6 ++++++ ai_core/migrations/__init__.py | 0 ai_core/models.py | 3 +++ ai_core/services/llm_service.py | 34 +++++++++++++++++++++++++++++++++ ai_core/tests.py | 3 +++ ai_core/views.py | 3 +++ requirements/base.txt | 4 ++++ 9 files changed, 56 insertions(+) create mode 100644 ai_core/__init__.py create mode 100644 ai_core/admin.py create mode 100644 ai_core/apps.py create mode 100644 ai_core/migrations/__init__.py create mode 100644 ai_core/models.py create mode 100644 ai_core/services/llm_service.py create mode 100644 ai_core/tests.py create mode 100644 ai_core/views.py diff --git a/ai_core/__init__.py b/ai_core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ai_core/admin.py b/ai_core/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/ai_core/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/ai_core/apps.py b/ai_core/apps.py new file mode 100644 index 00000000..c327b0d7 --- /dev/null +++ b/ai_core/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AiCoreConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'ai_core' diff --git a/ai_core/migrations/__init__.py b/ai_core/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ai_core/models.py b/ai_core/models.py new file mode 100644 index 00000000..71a83623 --- /dev/null +++ b/ai_core/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/ai_core/services/llm_service.py b/ai_core/services/llm_service.py new file mode 100644 index 00000000..ec6886ca --- /dev/null +++ b/ai_core/services/llm_service.py @@ -0,0 +1,34 @@ +"""Simple Gemini AI Service using LangChain""" +import os +import logging +from decouple import config +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain_core.messages import HumanMessage, SystemMessage + +logger = logging.getLogger("ai_core") + + +class GeminiService: + """Simple Gemini AI service.""" + + def __init__(self): + self.llm = ChatGoogleGenerativeAI( + google_api_key=config("GOOGLE_API_KEY"), + model="gemini-2.5-flash", + ) + logger.info("GeminiService initialized") + + def chat(self, message: str, system_prompt: str = None) -> str: + """Send message to Gemini and get response.""" + messages = [] + if system_prompt: + messages.append(SystemMessage(content=system_prompt)) + messages.append(HumanMessage(content=message)) + + logger.info(f"[REQUEST] {message[:100]}...") + + response = self.llm.invoke(messages) + + logger.info(f"[RESPONSE] {response.content[:200]}...") + + return response.content \ No newline at end of file diff --git a/ai_core/tests.py b/ai_core/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/ai_core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/ai_core/views.py b/ai_core/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/ai_core/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/requirements/base.txt b/requirements/base.txt index 7be538b3..0ff367a5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -24,3 +24,7 @@ python-decouple==3.8 # Payments stripe==5.5.0 gopay==2.0.1 + +# AI core +langchain +langchain-google-genai \ No newline at end of file From 369fa41c3f8a5791a429368828fca10b2aeaa0a8 Mon Sep 17 00:00:00 2001 From: Kryslogem <118667583+Kryslogem@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:27:12 +0700 Subject: [PATCH 02/26] Add database configuration for Neon PostgresSQL --- .env.example | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 9cccd1ee..3039e3e9 100644 --- a/.env.example +++ b/.env.example @@ -12,8 +12,17 @@ EMAIL_FROM_ADDRESS="SkyLearn " EMAIL_HOST_USER="" EMAIL_HOST_PASSWORD="" +# ============================= +# Database Neon - PostgresSQL +DB_ENGINE=django.db.backends.postgresql +DB_NAME=neondb +DB_USER=neondb_owner +DB_PASSWORD=npg_B0Hujnsh8zEv +DB_HOST=ep-gentle-shape-a1unb4h2-pooler.ap-southeast-1.aws.neon.tech +DB_PORT=5432 + # ============================= # Other DEBUG=True -SECRET_KEY="" \ No newline at end of file +SECRET_KEY="" From 11a888e4a388b5c5da91e211b82f658908050b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=B5=20Nh=E1=BA=ADt=20T=C3=A2n?= Date: Sat, 7 Feb 2026 22:27:14 +0700 Subject: [PATCH 03/26] Refactor: restructure ai_core with base LLM service and lesson generation API - Add BaseLLMService abstract class for provider-agnostic LLM calls - Move Gemini implementation to dedicated provider module - Add LessonService for HTML lesson generation - Add generate_lesson API endpoint (POST /ai/lesson/generate/) - Register ai_core in INSTALLED_APPS and URL config - Add GOOGLE_API_KEY to .env.example" --- .env.example | 4 +- ai_core/services/__init__.py | 3 ++ ai_core/services/{llm_service.py => base.py} | 34 ++++++++--------- ai_core/services/gemini.py | 14 +++++++ ai_core/services/lesson.py | 39 ++++++++++++++++++++ ai_core/urls.py | 8 ++++ ai_core/views.py | 35 +++++++++++++++++- config/settings.py | 1 + config/urls.py | 1 + 9 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 ai_core/services/__init__.py rename ai_core/services/{llm_service.py => base.py} (53%) create mode 100644 ai_core/services/gemini.py create mode 100644 ai_core/services/lesson.py create mode 100644 ai_core/urls.py diff --git a/.env.example b/.env.example index 9cccd1ee..b87fd085 100644 --- a/.env.example +++ b/.env.example @@ -16,4 +16,6 @@ EMAIL_HOST_PASSWORD="" # Other DEBUG=True -SECRET_KEY="" \ No newline at end of file +SECRET_KEY="" + +GOOGLE_API_KEY=your-api-key-here \ No newline at end of file diff --git a/ai_core/services/__init__.py b/ai_core/services/__init__.py new file mode 100644 index 00000000..165781df --- /dev/null +++ b/ai_core/services/__init__.py @@ -0,0 +1,3 @@ +from .base import BaseLLMService +from .gemini import GeminiService +from .lesson import LessonService \ No newline at end of file diff --git a/ai_core/services/llm_service.py b/ai_core/services/base.py similarity index 53% rename from ai_core/services/llm_service.py rename to ai_core/services/base.py index ec6886ca..89025b7d 100644 --- a/ai_core/services/llm_service.py +++ b/ai_core/services/base.py @@ -1,34 +1,32 @@ -"""Simple Gemini AI Service using LangChain""" -import os +"""Base LLM service interface.""" import logging -from decouple import config -from langchain_google_genai import ChatGoogleGenerativeAI +from abc import ABC, abstractmethod from langchain_core.messages import HumanMessage, SystemMessage logger = logging.getLogger("ai_core") -class GeminiService: - """Simple Gemini AI service.""" - +class BaseLLMService(ABC): + """Abstract base for all LLM providers.""" + + @abstractmethod + def _build_llm(self): + """Return a LangChain chat model instance.""" + pass + def __init__(self): - self.llm = ChatGoogleGenerativeAI( - google_api_key=config("GOOGLE_API_KEY"), - model="gemini-2.5-flash", - ) - logger.info("GeminiService initialized") - + self.llm = self._build_llm() + logger.info(f"{self.__class__.__name__} initialized") + def chat(self, message: str, system_prompt: str = None) -> str: - """Send message to Gemini and get response.""" + """Send a message and return the response text.""" messages = [] if system_prompt: messages.append(SystemMessage(content=system_prompt)) messages.append(HumanMessage(content=message)) - + logger.info(f"[REQUEST] {message[:100]}...") - response = self.llm.invoke(messages) - logger.info(f"[RESPONSE] {response.content[:200]}...") - + return response.content \ No newline at end of file diff --git a/ai_core/services/gemini.py b/ai_core/services/gemini.py new file mode 100644 index 00000000..0db8490f --- /dev/null +++ b/ai_core/services/gemini.py @@ -0,0 +1,14 @@ +"""Gemini LLM provider.""" +from decouple import config +from langchain_google_genai import ChatGoogleGenerativeAI +from .base import BaseLLMService + + +class GeminiService(BaseLLMService): + """Google Gemini provider.""" + + def _build_llm(self): + return ChatGoogleGenerativeAI( + google_api_key=config("GOOGLE_API_KEY"), + model="gemini-2.5-flash", + ) \ No newline at end of file diff --git a/ai_core/services/lesson.py b/ai_core/services/lesson.py new file mode 100644 index 00000000..14314e3b --- /dev/null +++ b/ai_core/services/lesson.py @@ -0,0 +1,39 @@ +"""Lesson generation service.""" +import logging +from .base import BaseLLMService + +logger = logging.getLogger("ai_core") + +LESSON_SYSTEM_PROMPT = ( + "Bạn là một giảng viên đại học chuyên nghiệp. " + "Nhiệm vụ của bạn là tạo bài giảng chi tiết bằng tiếng Việt. " + "Kết quả trả về phải ở dạng HTML thuần (không bao gồm thẻ , , ), " + "sử dụng các thẻ

,

,

,