Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
AI Tutor
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Salma Mohammed Hamed
AI Tutor
Commits
edfaebe2
Commit
edfaebe2
authored
Sep 13, 2025
by
SalmaMohammedHamedMustafa
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
use posrgress for chat history
parent
425d7b68
Changes
5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
178 additions
and
85 deletions
+178
-85
Dockerfile
self_hosted_env/voice_agent/Dockerfile
+5
-2
main.py
self_hosted_env/voice_agent/main.py
+2
-2
agent_service.py
self_hosted_env/voice_agent/services/agent_service.py
+66
-42
chat_database_service.py
..._hosted_env/voice_agent/services/chat_database_service.py
+80
-0
chat_service.py
self_hosted_env/voice_agent/services/chat_service.py
+25
-39
No files found.
self_hosted_env/voice_agent/Dockerfile
View file @
edfaebe2
...
@@ -7,5 +7,8 @@ COPY requirements.txt .
...
@@ -7,5 +7,8 @@ COPY requirements.txt .
RUN
pip
install
--no-cache-dir
-r
requirements.txt
RUN
pip
install
--no-cache-dir
-r
requirements.txt
COPY
. .
COPY
. .
#keep the container running always
#just keep the container running without doing anything
CMD
["python", "main.py"]
CMD
["sh", "-c", "while :; do sleep 10; done"]
#run the app automatically when the container starts
#CMD ["python", "main.py"]
self_hosted_env/voice_agent/main.py
View file @
edfaebe2
...
@@ -3,7 +3,7 @@ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
...
@@ -3,7 +3,7 @@ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from
fastapi.middleware.cors
import
CORSMiddleware
from
fastapi.middleware.cors
import
CORSMiddleware
from
typing
import
Optional
from
typing
import
Optional
import
uvicorn
import
uvicorn
from
core
import
AppConfig
from
core
import
AppConfig
,
StudentNationality
from
repositories
import
StorageRepository
,
MinIOStorageRepository
from
repositories
import
StorageRepository
,
MinIOStorageRepository
from
handlers
import
AudioMessageHandler
,
TextMessageHandler
from
handlers
import
AudioMessageHandler
,
TextMessageHandler
from
services
import
(
from
services
import
(
...
@@ -64,7 +64,7 @@ def create_app() -> FastAPI:
...
@@ -64,7 +64,7 @@ def create_app() -> FastAPI:
Handles incoming chat messages (either text or audio).
Handles incoming chat messages (either text or audio).
Generates responses locally using the agent service.
Generates responses locally using the agent service.
"""
"""
return
container
.
chat_service
.
process_message
(
file
,
text
)
return
container
.
chat_service
.
process_message
(
student_id
=
"student_001"
,
file
=
file
,
text
=
text
,
nationality
=
StudentNationality
.
EGYPTIAN
)
@
app
.
get
(
"/get-audio-response"
)
@
app
.
get
(
"/get-audio-response"
)
async
def
get_audio_response
():
async
def
get_audio_response
():
...
...
self_hosted_env/voice_agent/services/agent_service.py
View file @
edfaebe2
This diff is collapsed.
Click to expand it.
self_hosted_env/voice_agent/services/chat_database_service.py
0 → 100644
View file @
edfaebe2
import
os
import
psycopg2
from
psycopg2.extras
import
RealDictCursor
from
typing
import
List
,
Dict
import
logging
logger
=
logging
.
getLogger
(
__name__
)
class
ChatDatabaseService
:
"""Simple service for managing chat history in PostgreSQL"""
def
__init__
(
self
):
self
.
conn
=
psycopg2
.
connect
(
host
=
os
.
getenv
(
"POSTGRES_HOST"
,
"postgres"
),
user
=
os
.
getenv
(
"POSTGRES_USER"
),
password
=
os
.
getenv
(
"POSTGRES_PASSWORD"
),
dbname
=
os
.
getenv
(
"POSTGRES_DB"
),
)
def
get_chat_history
(
self
,
student_id
:
str
,
limit
:
int
=
20
)
->
List
[
Dict
[
str
,
str
]]:
"""Get chat history for a student, returns in chronological order"""
with
self
.
conn
.
cursor
(
cursor_factory
=
RealDictCursor
)
as
cur
:
cur
.
execute
(
"""
SELECT role, content
FROM chat_history
WHERE student_id =
%
s
ORDER BY created_at DESC
LIMIT
%
s;
"""
,
(
student_id
,
limit
)
)
results
=
cur
.
fetchall
()
# Return in chronological order (oldest first)
return
[{
"role"
:
row
[
"role"
],
"content"
:
row
[
"content"
]}
for
row
in
reversed
(
results
)]
def
add_message
(
self
,
student_id
:
str
,
role
:
str
,
content
:
str
):
"""Add a message to chat history"""
with
self
.
conn
.
cursor
()
as
cur
:
cur
.
execute
(
"""
INSERT INTO chat_history (student_id, role, content)
VALUES (
%
s,
%
s,
%
s);
"""
,
(
student_id
,
role
,
content
)
)
self
.
conn
.
commit
()
def
clear_history
(
self
,
student_id
:
str
):
"""Clear chat history for a student"""
with
self
.
conn
.
cursor
()
as
cur
:
cur
.
execute
(
"DELETE FROM chat_history WHERE student_id =
%
s"
,
(
student_id
,)
)
self
.
conn
.
commit
()
def
limit_history
(
self
,
student_id
:
str
,
max_messages
:
int
=
40
):
"""Keep only recent messages for a student"""
with
self
.
conn
.
cursor
()
as
cur
:
cur
.
execute
(
"""
DELETE FROM chat_history
WHERE student_id =
%
s
AND role != 'system'
AND id NOT IN (
SELECT id FROM chat_history
WHERE student_id =
%
s AND role != 'system'
ORDER BY created_at DESC
LIMIT
%
s
);
"""
,
(
student_id
,
student_id
,
max_messages
)
)
self
.
conn
.
commit
()
def
close
(
self
):
if
self
.
conn
:
self
.
conn
.
close
()
\ No newline at end of file
self_hosted_env/voice_agent/services/chat_service.py
View file @
edfaebe2
...
@@ -21,7 +21,7 @@ class ChatService:
...
@@ -21,7 +21,7 @@ class ChatService:
self
.
openai_service
=
openai_service
self
.
openai_service
=
openai_service
self
.
agent_service
=
agent_service
self
.
agent_service
=
agent_service
# Message handlers
(no webhook dependencies)
# Message handlers
self
.
handlers
=
{
self
.
handlers
=
{
MessageType
.
AUDIO
:
AudioMessageHandler
(
MessageType
.
AUDIO
:
AudioMessageHandler
(
storage_repo
,
storage_repo
,
...
@@ -31,99 +31,85 @@ class ChatService:
...
@@ -31,99 +31,85 @@ class ChatService:
MessageType
.
TEXT
:
TextMessageHandler
()
MessageType
.
TEXT
:
TextMessageHandler
()
}
}
def
process_message
(
self
,
file
:
Optional
[
UploadFile
]
=
None
,
text
:
Optional
[
str
]
=
None
)
->
dict
:
def
process_message
(
self
,
"""Process incoming message and generate agent response directly"""
student_id
:
str
,
file
:
Optional
[
UploadFile
]
=
None
,
text
:
Optional
[
str
]
=
None
,
nationality
:
StudentNationality
=
StudentNationality
.
EGYPTIAN
)
->
dict
:
"""Process message for student using database memory"""
self
.
response_manager
.
clear_response
()
self
.
response_manager
.
clear_response
()
try
:
try
:
# Process the input message
first
# Process the input message
if
file
and
file
.
filename
:
if
file
and
file
.
filename
:
# Handle audio message - transcribe first
result
=
self
.
handlers
[
MessageType
.
AUDIO
]
.
handle
(
file
=
file
)
result
=
self
.
handlers
[
MessageType
.
AUDIO
]
.
handle
(
file
=
file
)
if
result
.
get
(
"status"
)
==
"success"
:
if
result
.
get
(
"status"
)
==
"success"
:
# Get transcribed text from the result
user_message
=
result
.
get
(
"transcription"
,
""
)
user_message
=
result
.
get
(
"transcription"
,
""
)
if
not
user_message
:
if
not
user_message
:
# Fallback message if transcription failed
user_message
=
"تم إرسال رسالة صوتية - فشل في التفريغ المحلي"
user_message
=
"تم إرسال رسالة صوتية - فشل في التفريغ المحلي"
else
:
else
:
raise
HTTPException
(
status_code
=
400
,
detail
=
"Failed to process audio message"
)
raise
HTTPException
(
status_code
=
400
,
detail
=
"Failed to process audio message"
)
elif
text
:
elif
text
:
# Handle text message
result
=
self
.
handlers
[
MessageType
.
TEXT
]
.
handle
(
text
=
text
)
result
=
self
.
handlers
[
MessageType
.
TEXT
]
.
handle
(
text
=
text
)
user_message
=
text
user_message
=
text
else
:
else
:
raise
HTTPException
(
status_code
=
400
,
detail
=
"No text or audio file provided."
)
raise
HTTPException
(
status_code
=
400
,
detail
=
"No text or audio file provided."
)
# Generate agent response using
the local agent servic
e
# Generate agent response using
databas
e
try
:
try
:
agent_response
=
self
.
agent_service
.
generate_response
(
user_message
,
nationality
=
StudentNationality
.
EGYPTIAN
)
agent_response
=
self
.
agent_service
.
generate_response
(
user_message
=
user_message
,
student_id
=
student_id
,
nationality
=
nationality
)
# Generate TTS audio
from the response
# Generate TTS audio
audio_filename
=
self
.
_generate_and_upload_audio
(
agent_response
)
audio_filename
=
self
.
_generate_and_upload_audio
(
agent_response
)
# Store response for retrieval
# Store response for retrieval
self
.
response_manager
.
store_response
(
agent_response
,
audio_filename
)
self
.
response_manager
.
store_response
(
agent_response
,
audio_filename
)
print
(
f
"Generated
agent response
: {agent_response[:100]}..."
)
print
(
f
"Generated
response for student {student_id}
: {agent_response[:100]}..."
)
return
{
return
{
"status"
:
"success"
,
"status"
:
"success"
,
"message"
:
"Message processed and agent response ready"
,
"message"
:
"Message processed and agent response ready"
,
"student_id"
:
student_id
,
"agent_response"
:
agent_response
,
"agent_response"
:
agent_response
,
"audio_filename"
:
audio_filename
"audio_filename"
:
audio_filename
}
}
except
Exception
as
agent_error
:
except
Exception
as
agent_error
:
print
(
f
"Agent
service error
: {agent_error}"
)
print
(
f
"Agent
error for student {student_id}
: {agent_error}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"Agent response
generation
failed: {str(agent_error)}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"Agent response failed: {str(agent_error)}"
)
except
Exception
as
e
:
except
Exception
as
e
:
print
(
f
"Error processing message: {e}"
)
print
(
f
"Error processing message
for student {student_id}
: {e}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"Failed to process message: {str(e)}"
)
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"Failed to process message: {str(e)}"
)
def
_generate_and_upload_audio
(
self
,
text
:
str
)
->
str
:
def
_generate_and_upload_audio
(
self
,
text
:
str
)
->
str
:
"""Generate a
udio from text and upload to MinIO, return filename
"""
"""Generate a
nd upload TTS audio
"""
try
:
try
:
import
time
import
time
# Generate audio using OpenAI service
temp_file_path
=
self
.
openai_service
.
generate_speech
(
text
)
temp_file_path
=
self
.
openai_service
.
generate_speech
(
text
)
# Generate unique filename for MinIO
timestamp
=
int
(
time
.
time
())
timestamp
=
int
(
time
.
time
())
filename
=
f
"agent_response_{timestamp}.mp3"
filename
=
f
"agent_response_{timestamp}.mp3"
minio_file_path
=
f
"audio/{filename}"
minio_file_path
=
f
"audio/{filename}"
print
(
f
"Uploading
generated
audio to MinIO: {minio_file_path}"
)
print
(
f
"Uploading audio to MinIO: {minio_file_path}"
)
# Upload to MinIO
with
open
(
temp_file_path
,
'rb'
)
as
audio_file
:
with
open
(
temp_file_path
,
'rb'
)
as
audio_file
:
self
.
storage_repo
.
upload_file
(
audio_file
,
self
.
config
.
minio_bucket
,
minio_file_path
)
self
.
storage_repo
.
upload_file
(
audio_file
,
self
.
config
.
minio_bucket
,
minio_file_path
)
# Clean up temporary file
self
.
openai_service
.
cleanup_temp_file
(
temp_file_path
)
self
.
openai_service
.
cleanup_temp_file
(
temp_file_path
)
print
(
f
"Successfully generated
and uploaded
TTS audio: {filename}"
)
print
(
f
"Successfully generated TTS audio: {filename}"
)
return
filename
return
filename
except
Exception
as
e
:
except
Exception
as
e
:
print
(
f
"Error generating and uploading audio: {e}"
)
print
(
f
"Error generating audio: {e}"
)
# Don't fail the entire request if TTS fails
return
None
return
None
\ No newline at end of file
def
get_agent_stats
(
self
,
conversation_id
:
str
=
"default"
)
->
dict
:
"""Get conversation statistics from agent service"""
return
self
.
agent_service
.
get_conversation_stats
(
conversation_id
)
def
clear_conversation
(
self
,
conversation_id
:
str
=
"default"
):
"""Clear conversation history"""
self
.
agent_service
.
clear_conversation
(
conversation_id
)
return
{
"status"
:
"success"
,
"message"
:
f
"Conversation {conversation_id} cleared"
}
def
set_system_prompt
(
self
,
prompt
:
str
):
"""Update the agent's system prompt"""
self
.
agent_service
.
set_system_prompt
(
prompt
)
return
{
"status"
:
"success"
,
"message"
:
"System prompt updated"
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment