feat: init memory into ai chat messaging

This commit is contained in:
swve 2023-12-31 16:31:43 +00:00
parent f7d76eea1e
commit cf681b2260
9 changed files with 163 additions and 22 deletions

View file

@ -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",

View file

@ -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

View file

@ -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

View file

@ -24,3 +24,4 @@ openai
chromadb chromadb
sentence-transformers sentence-transformers
python-dotenv python-dotenv
redis

View file

@ -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
)

View file

@ -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"],
)

View file

@ -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}

View file

@ -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

View file

@ -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