mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init memory into ai chat messaging
This commit is contained in:
parent
f7d76eea1e
commit
cf681b2260
9 changed files with 163 additions and 22 deletions
|
|
@ -8,6 +8,7 @@
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"eamodio.gitlens",
|
"eamodio.gitlens",
|
||||||
"ms-python.python",
|
"ms-python.python",
|
||||||
|
"ms-python.black-formatter",
|
||||||
"ms-python.vscode-pylance",
|
"ms-python.vscode-pylance",
|
||||||
"styled-components.vscode-styled-components",
|
"styled-components.vscode-styled-components",
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ class DatabaseConfig(BaseModel):
|
||||||
sql_connection_string: Optional[str]
|
sql_connection_string: Optional[str]
|
||||||
mongo_connection_string: Optional[str]
|
mongo_connection_string: Optional[str]
|
||||||
|
|
||||||
|
class RedisConfig(BaseModel):
|
||||||
|
redis_connection_string: Optional[str]
|
||||||
|
|
||||||
class LearnHouseConfig(BaseModel):
|
class LearnHouseConfig(BaseModel):
|
||||||
site_name: str
|
site_name: str
|
||||||
|
|
@ -63,6 +65,7 @@ class LearnHouseConfig(BaseModel):
|
||||||
general_config: GeneralConfig
|
general_config: GeneralConfig
|
||||||
hosting_config: HostingConfig
|
hosting_config: HostingConfig
|
||||||
database_config: DatabaseConfig
|
database_config: DatabaseConfig
|
||||||
|
redis_config: RedisConfig
|
||||||
security_config: SecurityConfig
|
security_config: SecurityConfig
|
||||||
ai_config: AIConfig
|
ai_config: AIConfig
|
||||||
|
|
||||||
|
|
@ -183,6 +186,13 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
"mongo_connection_string"
|
"mongo_connection_string"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Redis config
|
||||||
|
env_redis_connection_string = os.environ.get("LEARNHOUSE_REDIS_CONNECTION_STRING")
|
||||||
|
redis_connection_string = env_redis_connection_string or yaml_config.get(
|
||||||
|
"redis_config", {}
|
||||||
|
).get("redis_connection_string")
|
||||||
|
|
||||||
|
|
||||||
# AI Config
|
# AI Config
|
||||||
env_openai_api_key = os.environ.get("LEARNHOUSE_OPENAI_API_KEY")
|
env_openai_api_key = os.environ.get("LEARNHOUSE_OPENAI_API_KEY")
|
||||||
env_is_ai_enabled = os.environ.get("LEARNHOUSE_IS_AI_ENABLED")
|
env_is_ai_enabled = os.environ.get("LEARNHOUSE_IS_AI_ENABLED")
|
||||||
|
|
@ -255,6 +265,7 @@ def get_learnhouse_config() -> LearnHouseConfig:
|
||||||
database_config=database_config,
|
database_config=database_config,
|
||||||
security_config=SecurityConfig(auth_jwt_secret_key=auth_jwt_secret_key),
|
security_config=SecurityConfig(auth_jwt_secret_key=auth_jwt_secret_key),
|
||||||
ai_config=ai_config,
|
ai_config=ai_config,
|
||||||
|
redis_config=RedisConfig(redis_connection_string=redis_connection_string),
|
||||||
)
|
)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,6 @@ hosting_config:
|
||||||
database_config:
|
database_config:
|
||||||
sql_connection_string: postgresql://learnhouse:learnhouse@db:5432/learnhouse
|
sql_connection_string: postgresql://learnhouse:learnhouse@db:5432/learnhouse
|
||||||
mongo_connection_string: mongodb://learnhouse:learnhouse@mongo:27017/
|
mongo_connection_string: mongodb://learnhouse:learnhouse@mongo:27017/
|
||||||
|
|
||||||
|
redis_config:
|
||||||
|
redis_connection_string: redis://redis:6379/learnhouse
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,4 @@ openai
|
||||||
chromadb
|
chromadb
|
||||||
sentence-transformers
|
sentence-transformers
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
redis
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
from src.services.ai.ai import ai_start_activity_chat_session
|
from src.services.ai.ai import ai_send_activity_chat_message, ai_start_activity_chat_session
|
||||||
from src.services.ai.schemas.ai import StartActivityAIChatSession
|
from src.services.ai.schemas.ai import ActivityAIChatSessionResponse, SendActivityAIChatMessage, StartActivityAIChatSession
|
||||||
from src.core.events.database import get_db_session
|
from src.core.events.database import get_db_session
|
||||||
from src.db.users import PublicUser
|
from src.db.users import PublicUser
|
||||||
from src.security.auth import get_current_user
|
from src.security.auth import get_current_user
|
||||||
|
|
@ -16,10 +16,24 @@ async def api_ai_start_activity_chat_session(
|
||||||
chat_session_object: StartActivityAIChatSession,
|
chat_session_object: StartActivityAIChatSession,
|
||||||
current_user: PublicUser = Depends(get_current_user),
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
db_session: Session = Depends(get_db_session),
|
db_session: Session = Depends(get_db_session),
|
||||||
):
|
)-> ActivityAIChatSessionResponse:
|
||||||
"""
|
"""
|
||||||
Start a new AI Chat session with a Course Activity
|
Start a new AI Chat session with a Course Activity
|
||||||
"""
|
"""
|
||||||
return ai_start_activity_chat_session(
|
return ai_start_activity_chat_session(
|
||||||
request, chat_session_object, current_user, db_session
|
request, chat_session_object, current_user, db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@router.post("/send/activity_chat_message")
|
||||||
|
async def api_ai_send_activity_chat_message(
|
||||||
|
request: Request,
|
||||||
|
chat_session_object: SendActivityAIChatMessage,
|
||||||
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
)-> ActivityAIChatSessionResponse:
|
||||||
|
"""
|
||||||
|
Send a message to an AI Chat session with a Course Activity
|
||||||
|
"""
|
||||||
|
return ai_send_activity_chat_message(
|
||||||
|
request, chat_session_object, current_user, db_session
|
||||||
|
)
|
||||||
|
|
@ -1,13 +1,20 @@
|
||||||
|
from uuid import uuid4
|
||||||
from fastapi import Depends, HTTPException, Request
|
from fastapi import Depends, HTTPException, Request
|
||||||
|
from requests import session
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
from src.db.courses import Course, CourseRead
|
from src.db.courses import Course, CourseRead
|
||||||
from src.core.events.database import get_db_session
|
from src.core.events.database import get_db_session
|
||||||
from src.db.users import PublicUser
|
from src.db.users import PublicUser
|
||||||
from src.db.activities import Activity, ActivityRead
|
from src.db.activities import Activity, ActivityRead
|
||||||
from src.security.auth import get_current_user
|
from src.security.auth import get_current_user
|
||||||
from src.services.ai.base import ask_ai
|
from langchain.memory.chat_message_histories import RedisChatMessageHistory
|
||||||
|
from src.services.ai.base import ask_ai, get_chat_session_history
|
||||||
|
|
||||||
from src.services.ai.schemas.ai import StartActivityAIChatSession
|
from src.services.ai.schemas.ai import (
|
||||||
|
ActivityAIChatSessionResponse,
|
||||||
|
SendActivityAIChatMessage,
|
||||||
|
StartActivityAIChatSession,
|
||||||
|
)
|
||||||
from src.services.courses.activities.utils import (
|
from src.services.courses.activities.utils import (
|
||||||
serialize_activity_text_to_ai_comprehensible_text,
|
serialize_activity_text_to_ai_comprehensible_text,
|
||||||
structure_activity_content_by_type,
|
structure_activity_content_by_type,
|
||||||
|
|
@ -19,7 +26,7 @@ def ai_start_activity_chat_session(
|
||||||
chat_session_object: StartActivityAIChatSession,
|
chat_session_object: StartActivityAIChatSession,
|
||||||
current_user: PublicUser = Depends(get_current_user),
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
db_session: Session = Depends(get_db_session),
|
db_session: Session = Depends(get_db_session),
|
||||||
):
|
) -> ActivityAIChatSessionResponse:
|
||||||
"""
|
"""
|
||||||
Start a new AI Chat session with a Course Activity
|
Start a new AI Chat session with a Course Activity
|
||||||
"""
|
"""
|
||||||
|
|
@ -32,13 +39,14 @@ def ai_start_activity_chat_session(
|
||||||
activity = ActivityRead.from_orm(activity)
|
activity = ActivityRead.from_orm(activity)
|
||||||
|
|
||||||
# Get the Course
|
# Get the Course
|
||||||
statement = select(Course).join(Activity).where(
|
statement = (
|
||||||
Activity.activity_uuid == chat_session_object.activity_uuid
|
select(Course)
|
||||||
|
.join(Activity)
|
||||||
|
.where(Activity.activity_uuid == chat_session_object.activity_uuid)
|
||||||
)
|
)
|
||||||
course = db_session.exec(statement).first()
|
course = db_session.exec(statement).first()
|
||||||
course = CourseRead.from_orm(course)
|
course = CourseRead.from_orm(course)
|
||||||
|
|
||||||
|
|
||||||
if not activity:
|
if not activity:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
|
|
@ -50,16 +58,91 @@ def ai_start_activity_chat_session(
|
||||||
|
|
||||||
# Serialize Activity Content Blocks to a text comprehensible by the AI
|
# Serialize Activity Content Blocks to a text comprehensible by the AI
|
||||||
structured = structure_activity_content_by_type(content)
|
structured = structure_activity_content_by_type(content)
|
||||||
ai_friendly_text = serialize_activity_text_to_ai_comprehensible_text(structured,course,activity)
|
ai_friendly_text = serialize_activity_text_to_ai_comprehensible_text(
|
||||||
|
structured, course, activity
|
||||||
|
)
|
||||||
|
|
||||||
|
chat_session = get_chat_session_history()
|
||||||
|
|
||||||
response = ask_ai(
|
response = ask_ai(
|
||||||
chat_session_object.message,
|
chat_session_object.message,
|
||||||
[],
|
chat_session['message_history'],
|
||||||
ai_friendly_text,
|
ai_friendly_text,
|
||||||
"You are a helpful Education Assistant, and you are helping a student with the associated Course. "
|
"You are a helpful Education Assistant, and you are helping a student with the associated Course. "
|
||||||
"Use the available tools to get context about this question even if the question is not specific enough."
|
"Use the available tools to get context about this question even if the question is not specific enough."
|
||||||
"For context, this is the Course name :" + course.name + " and this is the Lecture name :" + activity.name + "."
|
"For context, this is the Course name :"
|
||||||
"Use your knowledge to help the student."
|
+ course.name
|
||||||
|
+ " and this is the Lecture name :"
|
||||||
|
+ activity.name
|
||||||
|
+ "."
|
||||||
|
"Use your knowledge to help the student.",
|
||||||
)
|
)
|
||||||
|
|
||||||
return response['output']
|
return ActivityAIChatSessionResponse(
|
||||||
|
aichat_uuid=chat_session['aichat_uuid'],
|
||||||
|
activity_uuid=activity.activity_uuid,
|
||||||
|
message=response["output"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def ai_send_activity_chat_message(
|
||||||
|
request: Request,
|
||||||
|
chat_session_object: SendActivityAIChatMessage,
|
||||||
|
current_user: PublicUser = Depends(get_current_user),
|
||||||
|
db_session: Session = Depends(get_db_session),
|
||||||
|
) -> ActivityAIChatSessionResponse:
|
||||||
|
"""
|
||||||
|
Start a new AI Chat session with a Course Activity
|
||||||
|
"""
|
||||||
|
# Get the Activity
|
||||||
|
statement = select(Activity).where(
|
||||||
|
Activity.activity_uuid == chat_session_object.activity_uuid
|
||||||
|
)
|
||||||
|
activity = db_session.exec(statement).first()
|
||||||
|
|
||||||
|
activity = ActivityRead.from_orm(activity)
|
||||||
|
|
||||||
|
# Get the Course
|
||||||
|
statement = (
|
||||||
|
select(Course)
|
||||||
|
.join(Activity)
|
||||||
|
.where(Activity.activity_uuid == chat_session_object.activity_uuid)
|
||||||
|
)
|
||||||
|
course = db_session.exec(statement).first()
|
||||||
|
course = CourseRead.from_orm(course)
|
||||||
|
|
||||||
|
if not activity:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail="Activity not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get Activity Content Blocks
|
||||||
|
content = activity.content
|
||||||
|
|
||||||
|
# Serialize Activity Content Blocks to a text comprehensible by the AI
|
||||||
|
structured = structure_activity_content_by_type(content)
|
||||||
|
ai_friendly_text = serialize_activity_text_to_ai_comprehensible_text(
|
||||||
|
structured, course, activity
|
||||||
|
)
|
||||||
|
|
||||||
|
chat_session = get_chat_session_history(chat_session_object.aichat_uuid)
|
||||||
|
|
||||||
|
response = ask_ai(
|
||||||
|
chat_session_object.message,
|
||||||
|
chat_session['message_history'],
|
||||||
|
ai_friendly_text,
|
||||||
|
"You are a helpful Education Assistant, and you are helping a student with the associated Course. "
|
||||||
|
"Use the available tools to get context about this question even if the question is not specific enough."
|
||||||
|
"For context, this is the Course name :"
|
||||||
|
+ course.name
|
||||||
|
+ " and this is the Lecture name :"
|
||||||
|
+ activity.name
|
||||||
|
+ "."
|
||||||
|
"Use your knowledge to help the student if the context is not enough.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return ActivityAIChatSessionResponse(
|
||||||
|
aichat_uuid=chat_session['aichat_uuid'],
|
||||||
|
activity_uuid=activity.activity_uuid,
|
||||||
|
message=response["output"],
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
from typing import Optional
|
||||||
|
from uuid import uuid4
|
||||||
from langchain.agents import AgentExecutor
|
from langchain.agents import AgentExecutor
|
||||||
from langchain.text_splitter import CharacterTextSplitter
|
from langchain.text_splitter import CharacterTextSplitter
|
||||||
from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings
|
from langchain.embeddings.sentence_transformer import SentenceTransformerEmbeddings
|
||||||
|
|
@ -5,7 +7,7 @@ from langchain.vectorstores import Chroma
|
||||||
from langchain_core.messages import BaseMessage
|
from langchain_core.messages import BaseMessage
|
||||||
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
|
||||||
from langchain.prompts import MessagesPlaceholder
|
from langchain.prompts import MessagesPlaceholder
|
||||||
from langchain.memory import ConversationBufferMemory
|
from langchain.memory.chat_message_histories import RedisChatMessageHistory
|
||||||
from langchain_core.messages import SystemMessage
|
from langchain_core.messages import SystemMessage
|
||||||
from langchain.agents.openai_functions_agent.agent_token_buffer_memory import (
|
from langchain.agents.openai_functions_agent.agent_token_buffer_memory import (
|
||||||
AgentTokenBufferMemory,
|
AgentTokenBufferMemory,
|
||||||
|
|
@ -27,7 +29,7 @@ chat_history = []
|
||||||
|
|
||||||
def ask_ai(
|
def ask_ai(
|
||||||
question: str,
|
question: str,
|
||||||
chat_history: list[BaseMessage],
|
message_history,
|
||||||
text_reference: str,
|
text_reference: str,
|
||||||
message_for_the_prompt: str,
|
message_for_the_prompt: str,
|
||||||
):
|
):
|
||||||
|
|
@ -52,14 +54,13 @@ def ask_ai(
|
||||||
)
|
)
|
||||||
tools = [tool]
|
tools = [tool]
|
||||||
|
|
||||||
llm = ChatOpenAI(
|
llm = ChatOpenAI(temperature=0, api_key=openai_api_key)
|
||||||
temperature=0, api_key=openai_api_key
|
|
||||||
)
|
|
||||||
|
|
||||||
memory_key = "history"
|
memory_key = "history"
|
||||||
|
|
||||||
memory = AgentTokenBufferMemory(memory_key=memory_key, llm=llm)
|
memory = AgentTokenBufferMemory(
|
||||||
|
memory_key=memory_key, llm=llm, chat_memory=message_history
|
||||||
|
)
|
||||||
|
|
||||||
system_message = SystemMessage(content=(message_for_the_prompt))
|
system_message = SystemMessage(content=(message_for_the_prompt))
|
||||||
|
|
||||||
|
|
@ -70,7 +71,6 @@ def ask_ai(
|
||||||
|
|
||||||
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
|
agent = OpenAIFunctionsAgent(llm=llm, tools=tools, prompt=prompt)
|
||||||
|
|
||||||
|
|
||||||
agent_executor = AgentExecutor(
|
agent_executor = AgentExecutor(
|
||||||
agent=agent,
|
agent=agent,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
|
|
@ -80,3 +80,21 @@ def ask_ai(
|
||||||
)
|
)
|
||||||
|
|
||||||
return agent_executor({"input": question})
|
return agent_executor({"input": question})
|
||||||
|
|
||||||
|
|
||||||
|
def get_chat_session_history(aichat_uuid: Optional[str] = None):
|
||||||
|
# Init Message History
|
||||||
|
session_id = aichat_uuid if aichat_uuid else f"aichat_{uuid4()}"
|
||||||
|
|
||||||
|
LH_CONFIG = get_learnhouse_config()
|
||||||
|
redis_conn_string = LH_CONFIG.redis_config.redis_connection_string
|
||||||
|
|
||||||
|
if redis_conn_string:
|
||||||
|
message_history = RedisChatMessageHistory(
|
||||||
|
url=redis_conn_string, ttl=2160000, session_id=session_id
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print("Redis connection string not found, using local memory")
|
||||||
|
message_history = []
|
||||||
|
|
||||||
|
return {"message_history": message_history, "aichat_uuid": session_id}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@ class StartActivityAIChatSession(BaseModel):
|
||||||
activity_uuid: str
|
activity_uuid: str
|
||||||
message: str
|
message: str
|
||||||
|
|
||||||
|
class ActivityAIChatSessionResponse(BaseModel):
|
||||||
|
aichat_uuid: str
|
||||||
|
activity_uuid: str
|
||||||
|
message: str
|
||||||
|
|
||||||
|
|
||||||
class SendActivityAIChatMessage(BaseModel):
|
class SendActivityAIChatMessage(BaseModel):
|
||||||
aichat_uuid: str
|
aichat_uuid: str
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,11 @@ services:
|
||||||
- POSTGRES_USER=learnhouse
|
- POSTGRES_USER=learnhouse
|
||||||
- POSTGRES_PASSWORD=learnhouse
|
- POSTGRES_PASSWORD=learnhouse
|
||||||
- POSTGRES_DB=learnhouse
|
- POSTGRES_DB=learnhouse
|
||||||
|
redis:
|
||||||
|
image: redis:7.2.3
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
mongo:
|
mongo:
|
||||||
image: mongo:5.0
|
image: mongo:5.0
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue