Skip to content

Repo Restructuring #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 94 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,94 @@
# language-bot
# ChatTutor

ChatTutor is Telegram chatbot designed to complement language learning by providing realistic conversation practice. It is designed to supplement traditional language learning by offering users a realistic conversation partner—helping them build practical language skills through natural dialogue.


## Overview

- **Purpose**: ChatTutor offers users the opportunity to practice a language in a conversational setting. The bot simulates real-life interactions, encouraging users to communicate in the language they are learning. It is intended as a supportive tool in the broader language learning process, complementing the role of human tutors rather than replacing them.

- **Current Features**:
- **Text-Only Interaction**: For now, the bot supports unlimited text messaging with one "character" acting as your tutor.
- **Natural Conversations**: The bot responds naturally, keeping the flow of conversation engaging and lifelike. It can explain concepts if asked, but its main role is to act as a conversation partner.


## Planned Enhancements

The project roadmap includes several exciting features aimed at enriching the learning experience and expanding functionality:

- **Multiple Tutor Personalities**: Allowing users to choose from various tutor characters with different personality traits.

- **Media Integration**: Adding support for sticker recognition, voice messages, and potentially video messages.

- **Tutoring Plans**: Future monetization strategies may offer tutoring plans where educators can invest in bulk usage, providing their students with free access to ChatTutor’s features while keeping core interactions accessible to all users.

- **Monetization Model**: Although messaging is currently unlimited, a future monetization strategy will introduce rate limits. Early adopters will retain unlimited text messaging and receive limited free access to premium features (like voice messages) as a token of appreciation.

## Technical Details

- **Language & Frameworks**:
- **Python**: The project is built using Python, leveraging its robust ecosystem for asynchronous programming.
- **Asynchronous Operations**: All interactions and database operations run asynchronously for efficient performance.
- **Database**:
- **PostgreSQL**: Utilized for managing user data, conversation logs, and configuration settings.
- **Localization**:
- **gettext**: Implemented for language localization in the settings menu and onboarding process, ensuring a smooth user experience across multiple languages.

## Installation & Setup

If you’d like to run or contribute to ChatTutor locally, follow these steps:


1. **Clone the Repository**:

```bash
git clone https://github.com/LessVegetables/language-bot
```
2. **Navigate to the Project Directory**:

```bash
cd language-bot
```
3. **Create virtual environment** (optional but recommended):

```bash
python -m venv venv
```
4. **Install Dependencies**:

```bash
pip install -r requirements.txt
```
5. **Configure Environment Variables**:
Create a `.env` file with the following:

```env
BOT_TOKEN=<your-telegram-bot-token>
OPENAI_API_KEY=<your-open-ai-key>

POSTGRES_USER=<your-postgresql-username>
POSTGRES_PASSWORD=<your-postgresql-password>
POSTGRES_DB=<your-postgresql-database-name>
```
6. **Run the Bot**:

```bash
python src/mvp.py
```

## Collaborators

ChatTutor represents a forward-thinking project that leverages natural language processing, asynchronous programming in Python, and robust database management to create a dynamic language learning tool. Key highlights include:

- **Scalable Architecture**: Efficiently handles high concurrency and user growth through asynchronous operations and PostgreSQL.
- **User-Centric Design**: Focused on enhancing the language learning process by providing a realistic, engaging conversation experience—not as a replacement for human tutors, but as a valuable supplement.
- **Internationalization**: Built with localization in mind, ensuring accessibility and usability for a global audience.
- **Future-Proofing**: With a clear roadmap that includes multi-character support, media enhancements, and tutoring plans, ChatTutor is poised to evolve with the needs of modern language learners.

## Contributing

Contributions are welcome! If you'd like to improve ChatTutor, please fork the repository, make your changes, and open a pull request. For major changes, please open an issue first to discuss your ideas.

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
15 changes: 15 additions & 0 deletions app/dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Use an official Python image
FROM python:3.11

# Set the working directory inside the container
WORKDIR /app

# Copy requirements and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy only the src directory
COPY src/ src/

# Run the bot
CMD ["python", "mvp.py"]
File renamed without changes.
169 changes: 169 additions & 0 deletions app/src/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import os
import datetime

from openai import OpenAI
from dotenv import load_dotenv

from database import Database

load_dotenv()

openAI_client = OpenAI(
api_key=os.getenv("OPENAI_KEY")
)
class MyChatGPT:
def __init__(self, database, model="gpt-4o-mini-2024-07-18"):
self.model = model
self.database = database # Store database reference

async def message_chatgpt(self, text: str, user_id: int):

chat_id = await self.database.get_current_chat_id(user_id)
message = await self.generate_prompt(chat_id, text) # get past memory from DB with userID
assistant_response = await self.get_response(message) # get chatgpt response
await self.database.store_conversation(chat_id, text, assistant_response) # store user_message and chatgpt response

return assistant_response


async def get_response(self, message):
completion = openAI_client.chat.completions.create(
model=self.model,
# store=True,
messages=message
)
return completion.choices[0].message.content


# async def generate_prompt(self, chat_id: str, user_message: str) -> list:

# past_conversation = await self.database.retrieve_conversation(chat_id)

# messages = [
# {
# "role": "developer",
# "content": [
# {
# "type": "text",
# "text": "You are a helpful assistant that answers programming questions."
# }
# ]
# }
# ]

# # Append previous conversation messages
# for msg in past_conversation:
# messages.append({
# "role": "user",
# "content": [{"type": "text", "text": msg["user"]}]
# })
# messages.append({
# "role": "assistant",
# "content": [{"type": "text", "text": msg["assistant"]}]
# })

# # Append the new user message
# messages.append({
# "role": "user",
# "content": [{"type": "text", "text": user_message}]
# })

# print("sending message: ", messages)

# return messages

async def generate_prompt(self, chat_id: str, user_message: str) -> list:
# Retrieve dynamic context from your DB
chat_history = await self.database.retrieve_conversation(chat_id)
conversation_summary = await self.database.get_conversation_summary(chat_id)
personality_summary = await self.database.get_bot_personality_summary(chat_id)
user_details = await self.database.get_user_details(chat_id)

# Dynamic time context
now = datetime.datetime.now()
dynamic_time_context = f"Today is {now.strftime('%B %d, %Y')}. Current time is {now.strftime('%I:%M %p')}."

# Build system messages with context
messages = [
# Developer message (highest priority instructions)
{
"role": "developer",
"content": [
{
"type": "text",
"text": (
"You are Dave, a friendly, engaging language practice chatbot. Your goal is to help users practice "
"and improve their language skills through natural conversation. Always respond in short, human-like "
"messages. If asked, state: 'My name is Dave and I'm a chatbot.' Avoid overwhelming the user, gently "
"correct mistakes, and adapt to the user's language level. This service is a supplement to tutoring, not "
"a replacement."
)
}
]
},
# System message for dynamic time context and global dynamic context
{
"role": "system",
"content": [
{
"type": "text",
"text": (
f"{dynamic_time_context}\n"
f"Conversation Summary: {conversation_summary}\n"
f"Your personality Summary: {personality_summary}\n"
f"User Details: {user_details}\n"
"Note: These summaries and details are dynamically updated to help personalize your responses."
)
}
]
}
]

# Append previous conversation messages from chat history
for msg in chat_history:
messages.append({
"role": "user",
"content": [{"type": "text", "text": msg["user"]}]
})
messages.append({
"role": "assistant",
"content": [{"type": "text", "text": msg["assistant"]}]
})

# Append the new user message
messages.append({
"role": "user",
"content": [{"type": "text", "text": user_message}]
})

print("Sending message payload:", messages)
return messages


class HelperChatGPT:
def __init__(self, database, model="gpt-4o-mini-2024-07-18"):
self.model = model
self.database = database
async def generate_conversation_summary(self, conversation_history, current_summary) -> str:

message = [
{
"role": "developer",
"content": [
{
"type" : "text",
"text" : (
"Please create a summary of the convesation hisotry between an AI assistant and a bot user."
"You are given the convesations history"
)
}
]
}
]

async def generate_personality_summary(self, personality_summary, current_summary) -> str:
pass

async def generate_user_details(self, user_details, current_summary) -> str:
pass

54 changes: 54 additions & 0 deletions src/database.py → app/src/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ async def fetch_chat(self, chat_id: str):
return await conn.fetchrow("SELECT * FROM chatstable WHERE chatid = $1", uuid.UUID(chat_id))


# getting informatio from db
async def retrieve_conversation(self, chat_id: str) -> list:
await self.check_connection()

Expand Down Expand Up @@ -154,7 +155,45 @@ async def retrieve_conversation(self, chat_id: str) -> list:
# )

# return

async def get_conversation_summary(self, chat_id: str) -> str:
await self.check_connection()

async with self.pool.acquire() as conn:
async with conn.transaction():
row = await conn.fetchrow("SELECT conversation_summary FROM chatstable WHERE chatid = $1", chat_id)
if row and row["conversation_summary"]:
conversation_summary = row["conversation_summary"]
else:
conversation_summary = "there is not summary yet. This most likely means that the user is chatting with you for the first time."
return conversation_summary

async def get_bot_personality_summary(self, chat_id: str) -> str:
await self.check_connection()

async with self.pool.acquire() as conn:
async with conn.transaction():
row = await conn.fetchrow("SELECT personality_summary FROM chatstable WHERE chatid = $1", chat_id)
if row and row["personality_summary"]:
bot_personality_summary = row["personality_summary"]
else:
bot_personality_summary = "you have yet to create a personality."
return bot_personality_summary

async def get_user_details(self, chat_id: str) -> str:
await self.check_connection()

async with self.pool.acquire() as conn:
async with conn.transaction():
row = await conn.fetchrow("SELECT user_details FROM chatstable WHERE chatid = $1", chat_id)
if row and row["user_details"]:
user_details = row["user_details"]
else:
user_details = "the user has not provided any details about him/her self yet."
return user_details


# writing informatio to db
async def store_conversation(self, chat_id: str, user_message: str, assistant_response: str):
await self.check_connection()

Expand Down Expand Up @@ -186,3 +225,18 @@ async def store_conversation(self, chat_id: str, user_message: str, assistant_re
"UPDATE chatstable SET chatdailyconversation = $1 WHERE chatid = $2",
json.dumps(conversation), chat_id
)

async def store_conversation_summary(self, chat_id: str, new_summary: str) -> str:
await self.check_connection()

async with self.pool.acquire() as conn:
async with conn.transaction():
row = await conn.fetchrow(
"SELECT conversation_summary FROM chatstable WHERE chatid = $1", chat_id
)

# Store back into the database
await conn.execute(
"UPDATE chatstable SET conversation_summary = $1 WHERE chatid = $2",
new_summary, chat_id
)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
28 changes: 28 additions & 0 deletions app/src/manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import asyncpg
import asyncio
import uuid
import json

from chat import HelperChatGPT


class BackgroundUserChatProcess:
def __init__(self, database, gpt_helper):
self.database = database
self.gpt_helper = gpt_helper

async def run_conversation_summary(self, chat_id: str):
'''
Is in charge of getting/processisng/storing/updating the conversation_summary

Flow:
1. gets current conversation_history
2. if it exist - the process continues
3. gets current conversation_summary
4. calls HelperChatGPT to generate a summary
5. calls database to write the new summary
'''



pass
Loading