├── .gitignore ├── Home.py ├── README.md ├── components └── chat.py ├── database.py ├── models.py ├── pages ├── 1_Roadmap.py ├── 2_Quizzes.py └── 3_Resources.py ├── requirements.txt ├── runtime-data ├── agents.json ├── chroma.sqlite3 ├── context_variables.json ├── customers.json ├── evaluations.json ├── guideline_connections.json ├── guideline_tool_associations.json ├── guidelines.json ├── parlant.log ├── services.json ├── sessions.json └── tags.json ├── sample_schema ├── quiz.json ├── resource.json └── roadmap.json ├── service.py └── test_database.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | __pycache__ -------------------------------------------------------------------------------- /Home.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from components.chat import init_chat, show_chat 3 | 4 | def init_session_state(): 5 | """Initialize session state variables""" 6 | # Initialize chat with home page agent 7 | init_chat("yCkrWvHkgm") 8 | 9 | def main(): 10 | st.title("AI Coding Tutor") 11 | st.write("Welcome to your personalized AI Coding Tutor! I'm here to help you learn programming effectively.") 12 | 13 | # Initialize session state 14 | init_session_state() 15 | 16 | # Show features 17 | st.subheader("Features") 18 | st.write(""" 19 | - 📚 **Learning Roadmap**: Follow a structured learning path tailored to your goals 20 | - 🧪 **Interactive Quizzes**: Test your knowledge with quizzes and get instant feedback 21 | - 📖 **Learning Resources**: Access curated resources to support your learning journey 22 | - 💬 **AI Chat Support**: Get help from our AI tutor anytime 23 | """) 24 | 25 | # Show chat interface 26 | show_chat("Hi! I'm your AI Coding Tutor. How can I help you today?") 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Coding Tutor 2 | 3 | An interactive AI-powered coding tutor that helps users learn programming concepts through personalized instruction and real-time feedback. 4 | 5 | ## Prerequisites 6 | 7 | - Python 3.8 or higher 8 | - pip (Python package installer) 9 | 10 | ## Setup Instructions 11 | 12 | 1. Create a virtual environment: 13 | ```bash 14 | python -m venv venv 15 | ``` 16 | 17 | 2. Activate the virtual environment: 18 | ```bash 19 | # On Windows 20 | venv\Scripts\activate 21 | ``` 22 | ```bash 23 | # On Mac 24 | source venv/bin/activate 25 | ``` 26 | 27 | 3. Install the required dependencies: 28 | ```bash 29 | pip install -r requirements.txt 30 | ``` 31 | 32 | ## Running the Application 33 | 34 | The application consists of two main components that need to be run separately: 35 | 36 | 1. Start the Parlant server: 37 | ```bash 38 | parlant-server --module "service" 39 | ``` 40 | 41 | 2. In a new terminal window, activate the virtual environment again and run the Streamlit frontend: 42 | ```bash 43 | streamlit run Home.py 44 | ``` 45 | 46 | ## Support 47 | 48 | If you encounter any issues or have questions, please open an issue in the repository. 49 | -------------------------------------------------------------------------------- /components/chat.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from parlant.client import ParlantClient 3 | 4 | def init_chat(agent_id: str): 5 | """Initialize chat session state with specific agent ID""" 6 | # Always reinitialize messages when switching agents 7 | if "current_agent_id" not in st.session_state or st.session_state.current_agent_id != agent_id: 8 | st.session_state.messages = [] 9 | st.session_state.current_agent_id = agent_id 10 | 11 | if "parlant_session" not in st.session_state: 12 | client = ParlantClient(base_url="http://localhost:8800") 13 | agent = client.agents.retrieve(agent_id) 14 | session = client.sessions.create(agent_id=agent_id, allow_greeting=False) 15 | st.session_state.parlant_session = session 16 | st.session_state.parlant_client = client 17 | 18 | def show_chat(prompt_placeholder: str = "Ask me anything!", extra_info: str = ""): 19 | """Display chat interface with custom prompt placeholder""" 20 | st.markdown("---") 21 | st.subheader("Chat with AI Tutor") 22 | 23 | # Display chat messages 24 | for message in st.session_state.messages: 25 | with st.chat_message(message["role"]): 26 | st.markdown(message["content"].split("*&()")[0]) 27 | 28 | # Chat input 29 | if prompt := st.chat_input(prompt_placeholder): 30 | # Add user message to chat history 31 | st.session_state.messages.append({"role": "user", "content": prompt + f"*&() Use this extra information to provide better context and details: {extra_info}"}) 32 | with st.chat_message("user"): 33 | st.markdown(prompt) 34 | 35 | # Get AI response 36 | customer_event = st.session_state.parlant_client.sessions.create_event( 37 | session_id=st.session_state.parlant_session.id, 38 | source="customer", 39 | kind="message", 40 | message=prompt, 41 | ) 42 | 43 | agent_event, *_ = st.session_state.parlant_client.sessions.list_events( 44 | session_id=st.session_state.parlant_session.id, 45 | source="ai_agent", 46 | kinds="message", 47 | min_offset=customer_event.offset, 48 | ) 49 | 50 | # Display AI response 51 | assert agent_event.data 52 | agent_message = agent_event.data["message"] 53 | st.session_state.messages.append({"role": "assistant", "content": agent_message}) 54 | with st.chat_message("assistant"): 55 | st.markdown(agent_message) 56 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | from typing import Optional, List 3 | import os 4 | from dotenv import load_dotenv 5 | from models import Roadmap, Quiz, Resource 6 | from bson import ObjectId 7 | from pymongo.operations import SearchIndexModel 8 | from sentence_transformers import SentenceTransformer 9 | 10 | load_dotenv() 11 | 12 | # MongoDB connection string 13 | PWD = os.getenv("MONGO_PWD") 14 | USER = os.getenv("MONGO_USER") 15 | MONGO_URI = f"mongodb+srv://{USER}:{PWD}@database.0o8ty.mongodb.net/?retryWrites=true&w=majority&appName=database" 16 | 17 | # Initialize the model with specific configuration 18 | model = SentenceTransformer("BAAI/bge-large-en-v1.5", trust_remote_code=True) 19 | 20 | def get_embedding(data): 21 | """Generates vector embeddings for the given data.""" 22 | 23 | embedding = model.encode(data) 24 | return embedding.tolist() 25 | 26 | class Database: 27 | def __init__(self): 28 | self.client = MongoClient(MONGO_URI) 29 | self.db = self.client.ai_tutor_db 30 | 31 | def create_roadmap(self, roadmap: Roadmap) -> str: 32 | """Create a new roadmap""" 33 | data = roadmap.model_dump(exclude={"mongo_id"}) 34 | result = self.db.roadmaps.insert_one(data) 35 | return str(result.inserted_id) 36 | 37 | def update_roadmap(self, roadmap_id: str, roadmap: Roadmap) -> bool: 38 | """Update an existing roadmap""" 39 | data = roadmap.model_dump(exclude={"mongo_id"}) 40 | result = self.db.roadmaps.update_one( 41 | {'_id': ObjectId(roadmap_id)}, 42 | {'$set': data} 43 | ) 44 | return result.modified_count > 0 45 | 46 | def get_roadmap(self, roadmap_id: str) -> Optional[Roadmap]: 47 | """Get a roadmap by ID""" 48 | data = self.db.roadmaps.find_one({'_id': ObjectId(roadmap_id)}) 49 | if data: 50 | data['mongo_id'] = data.pop('_id') 51 | return Roadmap.model_validate(data) 52 | return None 53 | 54 | def get_roadmap_by_title(self, title: str) -> Optional[Roadmap]: 55 | """Get a roadmap by title""" 56 | data = self.db.roadmaps.find_one({'title': title}) 57 | if data: 58 | data['mongo_id'] = data.pop('_id') 59 | return Roadmap.model_validate(data) 60 | return None 61 | 62 | def get_all_roadmaps(self) -> List[Roadmap]: 63 | """Get all roadmaps""" 64 | data = list(self.db.roadmaps.find()) 65 | roadmaps = [] 66 | for item in data: 67 | item['mongo_id'] = item.pop('_id') 68 | roadmaps.append(Roadmap.model_validate(item)) 69 | return roadmaps 70 | 71 | def create_quiz(self, quiz: Quiz) -> str: 72 | """Create a new quiz""" 73 | data = quiz.model_dump(exclude={"mongo_id"}) 74 | result = self.db.quizzes.insert_one(data) 75 | return str(result.inserted_id) 76 | 77 | def get_quiz(self, quiz_id: str) -> Optional[Quiz]: 78 | """Get a quiz by ID 79 | 80 | Args: 81 | quiz_id: MongoDB ObjectId as string 82 | 83 | Returns: 84 | Quiz object if found, None otherwise 85 | """ 86 | data = self.db.quizzes.find_one({'_id': ObjectId(quiz_id)}) 87 | if data: 88 | data['mongo_id'] = str(data.pop('_id')) 89 | return Quiz.model_validate(data) 90 | return None 91 | 92 | def get_quiz_by_slug(self, slug: str) -> Optional[Quiz]: 93 | """Get a quiz by slug""" 94 | data = self.db.quizzes.find_one({'slug': slug}) 95 | if data: 96 | data['mongo_id'] = data.pop('_id') 97 | return Quiz.model_validate(data) 98 | return None 99 | 100 | def get_all_quizzes(self) -> List[Quiz]: 101 | """Get all available quizzes 102 | 103 | Returns: 104 | List of Quiz objects sorted by creation date 105 | """ 106 | quizzes = [] 107 | data = self.db.quizzes.find().sort("created_at", -1) 108 | 109 | for item in data: 110 | # Convert ObjectId to string for mongo_id 111 | item['mongo_id'] = str(item.pop('_id')) 112 | quizzes.append(Quiz.model_validate(item)) 113 | 114 | return quizzes 115 | 116 | def create_resource(self, resource: Resource) -> str: 117 | """Create a new resource""" 118 | data = resource.model_dump(exclude={"mongo_id"}) 119 | embedding = get_embedding(resource.description + resource.name) 120 | data["embedding"] = embedding 121 | result = self.db.resources.insert_one(data) 122 | return str(result.inserted_id) 123 | 124 | def get_resource(self, resource_id: str) -> Optional[Resource]: 125 | """Get a resource by ID""" 126 | data = self.db.resources.find_one({'_id': ObjectId(resource_id)}) 127 | if data: 128 | data['mongo_id'] = data.pop('_id') 129 | return Resource.model_validate(data) 130 | return None 131 | 132 | def get_resource_by_slug(self, slug: str) -> Optional[Resource]: 133 | """Get a resource by slug""" 134 | data = self.db.resources.find_one({'slug': slug}) 135 | if data: 136 | data['mongo_id'] = data.pop('_id') 137 | return Resource.model_validate(data) 138 | return None 139 | 140 | def get_all_resources(self) -> List[Resource]: 141 | """Get all resources""" 142 | data = list(self.db.resources.find()) 143 | resources = [] 144 | for item in data: 145 | item['mongo_id'] = item.pop('_id') 146 | resources.append(Resource.model_validate(item)) 147 | return resources 148 | 149 | def search_resources(self, query: str, limit: int = 2) -> list[Resource]: 150 | """Search resources using vector similarity 151 | 152 | Args: 153 | query: The search query text 154 | limit: Maximum number of results to return (default 5) 155 | 156 | Returns: 157 | List of Resource objects sorted by relevance 158 | """ 159 | query_embedding = get_embedding(query) 160 | 161 | pipeline = [ 162 | { 163 | "$vectorSearch": { 164 | "index": "vector_index", 165 | "queryVector": query_embedding, 166 | "path": "embedding", 167 | "numCandidates": limit * 10, # Internal limit for pre-filtering 168 | "limit": limit 169 | } 170 | }, 171 | { 172 | "$project": { 173 | "name": 1, 174 | "description": 1, 175 | "asset": 1, 176 | "resource_type": 1, 177 | "created_at": 1, 178 | "score": {"$meta": "vectorSearchScore"} 179 | } 180 | } 181 | ] 182 | 183 | results = list(self.db.resources.aggregate(pipeline)) 184 | resources = [] 185 | 186 | for item in results: 187 | # Convert ObjectId to string for mongo_id 188 | item['mongo_id'] = str(item.pop('_id')) 189 | # Remove score from the item before creating Resource object 190 | score = item.pop('score', 0) 191 | resources.append(Resource.model_validate(item)) 192 | 193 | return resources 194 | 195 | def create_index(self): 196 | collection = self.db.resources 197 | 198 | # List and drop all search indexes 199 | try: 200 | indexes = list(collection.list_search_indexes()) 201 | for index in indexes: 202 | 203 | index_name = index.get("name") 204 | if index_name: 205 | print(f"Dropping index: {index_name}") 206 | collection.drop_index(index_name) 207 | print("Dropped all existing search indexes") 208 | except Exception as e: 209 | print(f"Error handling existing indexes: {e}") 210 | 211 | # Create your index model, then create the search index 212 | search_index_model = SearchIndexModel( 213 | definition={ 214 | "mappings": { 215 | "dynamic": True, 216 | "fields": { 217 | "embedding": { 218 | "type": "knnVector", 219 | "dimensions": 1024, 220 | "similarity": "cosine" 221 | } 222 | } 223 | } 224 | }, 225 | name="vector_index" 226 | ) 227 | 228 | # Wait a bit before creating new index 229 | import time 230 | time.sleep(2) 231 | 232 | result = collection.create_search_index(model=search_index_model) 233 | print("New search index named " + result + " is building.") 234 | 235 | # Wait for initial sync to complete 236 | print("Polling to check if the index is ready. This may take up to a minute.") 237 | predicate = lambda index: index.get("queryable") is True 238 | while True: 239 | indices = list(collection.list_search_indexes(name="vector_index")) 240 | if len(indices) and predicate(indices[0]): 241 | break 242 | time.sleep(1) # Add small delay between checks 243 | 244 | print(result + " is ready for querying.") 245 | 246 | def update_all_embeddings(self): 247 | """Update embeddings for all existing resources""" 248 | print("Updating embeddings for all resources...") 249 | resources = self.get_all_resources() 250 | for resource in resources: 251 | # Generate new embedding 252 | embedding = get_embedding(resource.description + resource.name) 253 | # Update in database 254 | self.db.resources.update_one( 255 | {"_id": ObjectId(resource.mongo_id)}, 256 | {"$set": {"embedding": embedding}} 257 | ) 258 | print(f"Updated embedding for resource: {resource.name}") 259 | 260 | if __name__ == "__main__": 261 | db = Database() 262 | results = db.search_resources("python") 263 | print("\nSearch Results:") 264 | for resource in results: 265 | print(f"- {resource.name}: {resource.description}") -------------------------------------------------------------------------------- /models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional, Any 3 | from pydantic import BaseModel, Field, GetJsonSchemaHandler 4 | from pydantic.json_schema import JsonSchemaValue 5 | from pydantic_core import CoreSchema, core_schema 6 | from bson import ObjectId 7 | 8 | class PyObjectId(str): 9 | @classmethod 10 | def __get_pydantic_core_schema__( 11 | cls, 12 | _source_type: Any, 13 | _handler: GetJsonSchemaHandler, 14 | ) -> CoreSchema: 15 | return core_schema.json_or_python_schema( 16 | json_schema=core_schema.str_schema(), 17 | python_schema=core_schema.union_schema([ 18 | core_schema.str_schema(), 19 | core_schema.is_instance_schema(ObjectId), 20 | ]), 21 | serialization=core_schema.plain_serializer_function_ser_schema( 22 | lambda x: str(x) if isinstance(x, ObjectId) else x 23 | ), 24 | ) 25 | 26 | class SubTopic(BaseModel): 27 | name: str 28 | completed: bool = False 29 | 30 | class Topic(BaseModel): 31 | name: str 32 | subtopics: List[SubTopic] = [] 33 | completed: bool = False 34 | 35 | class Roadmap(BaseModel): 36 | title: str 37 | description: str = "" 38 | topics: List[Topic] 39 | created_at: datetime = Field(default_factory=datetime.now) 40 | mongo_id: Optional[PyObjectId] = None 41 | 42 | class Config: 43 | arbitrary_types_allowed = True 44 | json_encoders = { 45 | ObjectId: str 46 | } 47 | 48 | class QuizChoice(BaseModel): 49 | text: str 50 | is_correct: bool 51 | 52 | class QuizQuestion(BaseModel): 53 | question: str 54 | choices: List[QuizChoice] 55 | explanation: str = "" 56 | 57 | class Quiz(BaseModel): 58 | title: str 59 | description: str = "" 60 | questions: List[QuizQuestion] 61 | created_at: datetime = Field(default_factory=datetime.now) 62 | mongo_id: Optional[PyObjectId] = None 63 | 64 | class Config: 65 | arbitrary_types_allowed = True 66 | json_encoders = { 67 | ObjectId: str 68 | } 69 | 70 | class Resource(BaseModel): 71 | name: str 72 | description: str 73 | asset: str = "" # URL or file path 74 | resource_type: str # video, article, code_example, etc. 75 | created_at: datetime = Field(default_factory=datetime.now) 76 | mongo_id: Optional[PyObjectId] = None 77 | 78 | class Config: 79 | arbitrary_types_allowed = True 80 | json_encoders = { 81 | ObjectId: str 82 | } -------------------------------------------------------------------------------- /pages/1_Roadmap.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from database import Database 3 | from models import Roadmap, Topic, SubTopic 4 | from datetime import datetime 5 | from components.chat import init_chat, show_chat 6 | 7 | # Initialize database 8 | db = Database() 9 | 10 | def init_session_state(): 11 | """Initialize session state variables""" 12 | if "checkbox_states" not in st.session_state: 13 | st.session_state.checkbox_states = {} 14 | if "show_topic_creator" not in st.session_state: 15 | st.session_state.show_topic_creator = False 16 | if "show_subtopic_creator" not in st.session_state: 17 | st.session_state.show_subtopic_creator = None 18 | 19 | # Initialize chat with roadmap agent 20 | init_chat("w5HbpNTL14") 21 | 22 | def get_all_roadmaps(): 23 | """Get all available roadmaps""" 24 | return db.get_all_roadmaps() 25 | 26 | def save_progress(roadmap: Roadmap): 27 | """Save progress by updating the roadmap""" 28 | try: 29 | # Create a copy of the roadmap to avoid modifying the original 30 | roadmap_data = roadmap.model_dump() 31 | updated_roadmap = Roadmap.model_validate(roadmap_data) 32 | 33 | # Update subtopic completion status from session state 34 | for topic in updated_roadmap.topics: 35 | for subtopic in topic.subtopics: 36 | checkbox_key = f"checkbox_{subtopic.name}" 37 | if checkbox_key in st.session_state.checkbox_states: 38 | subtopic.completed = st.session_state.checkbox_states[checkbox_key] 39 | # Update topic completion based on subtopics 40 | topic.completed = all(subtopic.completed for subtopic in topic.subtopics) 41 | 42 | # Save updated roadmap 43 | return db.update_roadmap(str(roadmap.mongo_id), updated_roadmap) 44 | except Exception as e: 45 | st.error(f"Error saving progress: {str(e)}") 46 | return False 47 | 48 | def toggle_checkbox(key): 49 | """Toggle checkbox without triggering rerun""" 50 | st.session_state.checkbox_states[key] = not st.session_state.checkbox_states.get(key, False) 51 | 52 | def create_topic_form(roadmap: Roadmap): 53 | """Show form to create a new topic""" 54 | with st.form(key="new_topic_form"): 55 | st.subheader("Create New Topic") 56 | name = st.text_input("Topic Name") 57 | 58 | if st.form_submit_button("Create Topic"): 59 | if name: 60 | # Create new topic 61 | new_topic = Topic( 62 | name=name, 63 | subtopics=[], 64 | completed=False 65 | ) 66 | roadmap.topics.append(new_topic) 67 | 68 | # Save roadmap 69 | if db.update_roadmap(str(roadmap.mongo_id), roadmap): 70 | st.success("Topic created successfully!") 71 | st.session_state.show_topic_creator = False 72 | st.rerun() 73 | else: 74 | st.error("Failed to create topic. Please try again.") 75 | else: 76 | st.error("Please fill in all fields.") 77 | 78 | def create_subtopic_form(roadmap: Roadmap, parent_topic: Topic): 79 | """Show form to create a new subtopic""" 80 | with st.form(key=f"new_subtopic_form_{parent_topic.name}"): 81 | st.subheader(f"Create New Subtopic in {parent_topic.name}") 82 | name = st.text_input("Subtopic Name") 83 | 84 | if st.form_submit_button("Create Subtopic"): 85 | if name: 86 | # Create new subtopic 87 | new_subtopic = SubTopic( 88 | name=name, 89 | completed=False 90 | ) 91 | 92 | # Find parent topic and add subtopic 93 | for topic in roadmap.topics: 94 | if topic.name == parent_topic.name: 95 | topic.subtopics.append(new_subtopic) 96 | break 97 | 98 | # Save roadmap 99 | if db.update_roadmap(str(roadmap.mongo_id), roadmap): 100 | st.success("Subtopic created successfully!") 101 | st.session_state.show_subtopic_creator = None 102 | st.rerun() 103 | else: 104 | st.error("Failed to create subtopic. Please try again.") 105 | else: 106 | st.error("Please fill in all fields.") 107 | 108 | @st.fragment() 109 | def display_topic(topic: Topic, roadmap: Roadmap): 110 | """Display a single topic and its subtopics""" 111 | # Display subtopics 112 | for subtopic in topic.subtopics: 113 | checkbox_key = f"checkbox_{subtopic.name}" 114 | 115 | # Initialize checkbox state if not exists 116 | if checkbox_key not in st.session_state.checkbox_states: 117 | st.session_state.checkbox_states[checkbox_key] = subtopic.completed 118 | 119 | st.checkbox( 120 | label=subtopic.name, 121 | value=st.session_state.checkbox_states[checkbox_key], 122 | key=checkbox_key, 123 | on_change=toggle_checkbox, 124 | args=(checkbox_key,), 125 | ) 126 | 127 | if st.button("Add Subtopic", key=f"add_subtopic_{topic.name}", help="Add new subtopic"): 128 | st.session_state.show_subtopic_creator = topic.name 129 | 130 | if st.session_state.show_subtopic_creator == topic.name: 131 | create_subtopic_form(roadmap, topic) 132 | 133 | def show_roadmap(): 134 | st.title("Learning Roadmap") 135 | 136 | # Initialize session state 137 | init_session_state() 138 | 139 | roadmaps = get_all_roadmaps() 140 | 141 | if not roadmaps: 142 | st.warning("No roadmaps available. Please check your database connection.") 143 | return 144 | 145 | # If we have multiple roadmaps, let user select one 146 | if len(roadmaps) > 1: 147 | selected_roadmap = st.selectbox( 148 | "Select a Roadmap", 149 | roadmaps, 150 | format_func=lambda x: x.title 151 | ) 152 | else: 153 | selected_roadmap = roadmaps[0] 154 | 155 | # Display roadmap details 156 | st.header(selected_roadmap.title) 157 | if selected_roadmap.description: 158 | st.write(selected_roadmap.description) 159 | 160 | # Display topics 161 | for topic in selected_roadmap.topics: 162 | with st.expander(topic.name, expanded=True): 163 | display_topic(topic, selected_roadmap) 164 | 165 | if st.button("Add Topic", key="add_topic", help="Add new topic"): 166 | st.session_state.show_topic_creator = True 167 | 168 | if st.button("Save Progress", type="primary"): 169 | if save_progress(selected_roadmap): 170 | st.success("Progress saved successfully!") 171 | else: 172 | st.error("Failed to save progress. Please try again.") 173 | 174 | # Show topic creator if requested 175 | if st.session_state.show_topic_creator: 176 | create_topic_form(selected_roadmap) 177 | 178 | # Show chat interface at the bottom 179 | show_chat() 180 | 181 | if __name__ == "__main__": 182 | show_roadmap() 183 | -------------------------------------------------------------------------------- /pages/2_Quizzes.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from database import Database 3 | from models import Quiz 4 | from components.chat import init_chat, show_chat 5 | 6 | # Initialize database 7 | db = Database() 8 | 9 | def init_session_state(): 10 | """Initialize session state variables""" 11 | if "selected_quiz" not in st.session_state: 12 | st.session_state.selected_quiz = None 13 | if "show_quiz_creator" not in st.session_state: 14 | st.session_state.show_quiz_creator = False 15 | if "selected_roadmap" not in st.session_state: 16 | st.session_state.selected_roadmap = None 17 | 18 | # Initialize chat with quiz agent 19 | init_chat("dkJPs3GbXG") 20 | 21 | def format_roadmap_info(roadmap): 22 | """Format roadmap information into a detailed string""" 23 | if not roadmap: 24 | return "" 25 | 26 | # Format topics and subtopics 27 | topics_info = [] 28 | for topic in roadmap.topics: 29 | topic_info = f"\nTopic: {topic.name}\n" 30 | 31 | if topic.subtopics: 32 | topic_info += "Subtopics:\n" 33 | for subtopic in topic.subtopics: 34 | completion_status = "Yes" if subtopic.completed else "No" 35 | topic_info += f"- {subtopic.name} - Completed: {completion_status}\n" 36 | 37 | topics_info.append(topic_info) 38 | 39 | # Create formatted string 40 | info = f""" 41 | Roadmap: {roadmap.title} 42 | Description: {roadmap.description} 43 | 44 | Topics:{' '.join(topics_info)} 45 | """ 46 | return info 47 | 48 | def display_quiz(quiz: Quiz): 49 | """Display a quiz and handle responses""" 50 | st.subheader(quiz.title) 51 | st.write(quiz.description) 52 | 53 | with st.form(key=f"quiz_{quiz.mongo_id}"): 54 | score = 0 55 | user_answers = {} 56 | 57 | for i, question in enumerate(quiz.questions): 58 | st.write(f"\n**Question {i+1}:** {question.question}") 59 | 60 | # Create radio buttons for choices 61 | answer = st.radio( 62 | "Choose your answer:", 63 | [choice.text for choice in question.choices], 64 | key=f"q_{quiz.mongo_id}_{i}" 65 | ) 66 | user_answers[i] = answer 67 | 68 | submitted = st.form_submit_button("Submit Quiz") 69 | 70 | if submitted: 71 | score = 0 72 | feedback = [] 73 | 74 | for i, question in enumerate(quiz.questions): 75 | user_answer = user_answers[i] 76 | correct_choice = next( 77 | choice.text for choice in question.choices 78 | if choice.is_correct 79 | ) 80 | 81 | if user_answer == correct_choice: 82 | score += 1 83 | feedback.append(f"✅ Question {i+1}: Correct!") 84 | else: 85 | feedback.append( 86 | f"❌ Question {i+1}: Incorrect. " 87 | f"The correct answer was: {correct_choice}\n" 88 | f"Explanation: {question.explanation}" 89 | ) 90 | 91 | st.success(f"Your score: {score}/{len(quiz.questions)}") 92 | for fb in feedback: 93 | st.write(fb) 94 | 95 | def show_roadmap_selector(): 96 | """Show roadmap selection for quiz generation""" 97 | st.subheader("Generate Quiz from Roadmap") 98 | 99 | # Get all roadmaps 100 | roadmaps = db.get_all_roadmaps() 101 | 102 | if not roadmaps: 103 | st.warning("No roadmaps available. Create a roadmap first!") 104 | return 105 | 106 | # Create roadmap selection 107 | roadmap_titles = [roadmap.title for roadmap in roadmaps] 108 | selected_title = st.selectbox( 109 | "Select a Roadmap to Generate Quiz From:", 110 | roadmap_titles, 111 | key="roadmap_selector" 112 | ) 113 | 114 | if selected_title: 115 | selected_roadmap = next(r for r in roadmaps if r.title == selected_title) 116 | st.session_state.selected_roadmap = selected_roadmap 117 | 118 | def show_quizzes(): 119 | """Main function to display quizzes page""" 120 | st.title("Programming Quizzes") 121 | 122 | # Initialize session state 123 | init_session_state() 124 | 125 | # Create tabs for different sections 126 | quiz_tab, generate_tab = st.tabs([ 127 | "Available Quizzes", 128 | "Generate Quiz" 129 | ]) 130 | 131 | with quiz_tab: 132 | # Get all quizzes from database 133 | quizzes = db.get_all_quizzes() 134 | 135 | if not quizzes: 136 | st.info("No quizzes available yet. Generate one from a roadmap!") 137 | else: 138 | # Quiz selection 139 | quiz_titles = [quiz.title for quiz in quizzes] 140 | selected_title = st.selectbox( 141 | "Select Quiz", 142 | quiz_titles 143 | ) 144 | 145 | if selected_title: 146 | selected_quiz = next(q for q in quizzes if q.title == selected_title) 147 | display_quiz(selected_quiz) 148 | 149 | with generate_tab: 150 | # Show roadmap selector first 151 | show_roadmap_selector() 152 | 153 | # Get roadmap info if one is selected 154 | roadmap_info = format_roadmap_info(st.session_state.selected_roadmap) 155 | 156 | # Show chat with roadmap context 157 | show_chat( 158 | prompt_placeholder="Describe what you want to generate a quiz about!", 159 | extra_info=roadmap_info 160 | ) 161 | 162 | if __name__ == "__main__": 163 | show_quizzes() 164 | -------------------------------------------------------------------------------- /pages/3_Resources.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from database import Database 3 | from models import Resource 4 | from datetime import datetime 5 | from components.chat import init_chat, show_chat 6 | 7 | # Initialize database 8 | db = Database() 9 | 10 | def init_session_state(): 11 | # Initialize chat with resources agent 12 | init_chat("QWODNTNOhX") 13 | 14 | def display_resources(): 15 | """Display all resources""" 16 | resources = db.get_all_resources() 17 | 18 | if not resources: 19 | st.info("No resources available yet. Add some using the form below!") 20 | return 21 | 22 | # Group resources by type 23 | resources_by_type = {} 24 | for resource in resources: 25 | if resource.resource_type.lower() not in resources_by_type: 26 | resources_by_type[resource.resource_type.lower()] = [] 27 | resources_by_type[resource.resource_type.lower()].append(resource) 28 | 29 | # Display resources by type 30 | for resource_type, type_resources in resources_by_type.items(): 31 | st.header(resource_type.title()) 32 | for resource in type_resources: 33 | with st.expander(resource.name): 34 | st.write(resource.description) 35 | if resource.asset: 36 | st.write(f"Link: {resource.asset}") 37 | st.caption(f"Added on: {resource.created_at.strftime('%Y-%m-%d')}") 38 | 39 | def main(): 40 | st.title("Learning Resources") 41 | st.write("Explore our curated collection of learning resources") 42 | 43 | # Initialize session state 44 | init_session_state() 45 | 46 | # Display existing resources 47 | display_resources() 48 | 49 | # Show chat interface at the bottom 50 | show_chat("Ask me about learning resources!") 51 | 52 | if __name__ == "__main__": 53 | main() 54 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pymongo[srv] 2 | python-dotenv 3 | pydantic 4 | pydantic-core 5 | streamlit 6 | parlant-client 7 | sentence_transformers 8 | einops -------------------------------------------------------------------------------- /runtime-data/agents.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "agents": { 4 | "module_path": "parlant.core.agents", 5 | "model_path": "_AgentDocument" 6 | } 7 | }, 8 | "agents": [ 9 | { 10 | "id": "pWoazDUUzH", 11 | "version": "0.1.0", 12 | "creation_utc": "2024-12-27T09:21:15.010134+00:00", 13 | "name": "Default Agent", 14 | "description": null, 15 | "max_engine_iterations": 3 16 | }, 17 | { 18 | "id": "yCkrWvHkgm", 19 | "version": "0.1.0", 20 | "creation_utc": "2024-12-27T09:30:34.283118+00:00", 21 | "name": "Tutor", 22 | "description": "You are an AI coding assistant/tutor. Your goal is to help new programmers learn how to code and assis them in their journey. Always encourage problem solving, critical thinking and self discovery before just giving away an answer. Challenge the user and if they are still stuck help them.", 23 | "max_engine_iterations": 1 24 | }, 25 | { 26 | "id": "w5HbpNTL14", 27 | "version": "0.1.0", 28 | "creation_utc": "2024-12-27T10:57:26.992242+00:00", 29 | "name": "Roadmap", 30 | "description": "your role is to manage a users roadmap by creating one, updating it and answering questions based on it", 31 | "max_engine_iterations": 3 32 | }, 33 | { 34 | "id": "QWODNTNOhX", 35 | "version": "0.1.0", 36 | "creation_utc": "2024-12-27T14:04:41.977396+00:00", 37 | "name": "Resource", 38 | "description": "your job is to create and manage resources related to coding, programming and learning software development", 39 | "max_engine_iterations": 1 40 | }, 41 | { 42 | "id": "dkJPs3GbXG", 43 | "version": "0.1.0", 44 | "creation_utc": "2024-12-27T16:00:33.879345+00:00", 45 | "name": "Quiz", 46 | "description": "your role is to generarte quizzes for users based on their roadmap and preferences", 47 | "max_engine_iterations": 3 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /runtime-data/chroma.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/AI-Coding-Tutor/0ea349b8d7dacf44cd4cd7f33c3d3855b57af1f8/runtime-data/chroma.sqlite3 -------------------------------------------------------------------------------- /runtime-data/context_variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "variables": { 4 | "module_path": "parlant.core.context_variables", 5 | "model_path": "_ContextVariableDocument" 6 | }, 7 | "values": { 8 | "module_path": "parlant.core.context_variables", 9 | "model_path": "_ContextVariableValueDocument" 10 | } 11 | }, 12 | "variables": [], 13 | "values": [] 14 | } -------------------------------------------------------------------------------- /runtime-data/customers.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "customers": { 4 | "module_path": "parlant.core.customers", 5 | "model_path": "_CustomerDocument" 6 | }, 7 | "customer_tag_associations": { 8 | "module_path": "parlant.core.customers", 9 | "model_path": "_CustomerTagAssociationDocument" 10 | } 11 | }, 12 | "customers": [], 13 | "customer_tag_associations": [] 14 | } -------------------------------------------------------------------------------- /runtime-data/evaluations.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "evaluations": { 4 | "module_path": "parlant.core.evaluations", 5 | "model_path": "_EvaluationDocument" 6 | } 7 | }, 8 | "evaluations": [ 9 | { 10 | "id": "KcOmE7EmYs", 11 | "version": "0.1.0", 12 | "agent_id": "pWoazDUUzH", 13 | "creation_utc": "2024-12-27T09:25:59.833265+00:00", 14 | "status": "COMPLETED", 15 | "error": null, 16 | "invoices": [ 17 | { 18 | "kind": "GUIDELINE", 19 | "payload": { 20 | "content": { 21 | "condition": "the user greets you", 22 | "action": "encourage the user to use this tool for learning, be positive" 23 | }, 24 | "action": "add", 25 | "updated_id": null, 26 | "coherence_check": true, 27 | "connection_proposition": true 28 | }, 29 | "checksum": "79b2f07c8b7ab1cd9093b9544aa8c9e4", 30 | "state_version": "-2310704348297148185", 31 | "approved": true, 32 | "data": { 33 | "coherence_checks": [], 34 | "connection_propositions": null 35 | }, 36 | "error": null 37 | } 38 | ], 39 | "progress": 0.0 40 | }, 41 | { 42 | "id": "nc96OnYWeD", 43 | "version": "0.1.0", 44 | "agent_id": "yCkrWvHkgm", 45 | "creation_utc": "2024-12-27T09:45:45.119448+00:00", 46 | "status": "COMPLETED", 47 | "error": null, 48 | "invoices": [ 49 | { 50 | "kind": "GUIDELINE", 51 | "payload": { 52 | "content": { 53 | "condition": "the user pastes some kind of code", 54 | "action": "analayze the code and ask the user what they want help with. Is there a bug, do they want it to be explained, or do thet want other assistance." 55 | }, 56 | "action": "add", 57 | "updated_id": null, 58 | "coherence_check": true, 59 | "connection_proposition": true 60 | }, 61 | "checksum": "24095b0a1f3f1c6add8881c9bb9400b9", 62 | "state_version": "-2310704348297148185", 63 | "approved": true, 64 | "data": { 65 | "coherence_checks": [], 66 | "connection_propositions": null 67 | }, 68 | "error": null 69 | } 70 | ], 71 | "progress": 0.0 72 | }, 73 | { 74 | "id": "qJKO0YTG0U", 75 | "version": "0.1.0", 76 | "agent_id": "yCkrWvHkgm", 77 | "creation_utc": "2024-12-27T09:46:30.950274+00:00", 78 | "status": "COMPLETED", 79 | "error": null, 80 | "invoices": [ 81 | { 82 | "kind": "GUIDELINE", 83 | "payload": { 84 | "content": { 85 | "condition": "the user asks you for help with their code", 86 | "action": "first ask the user what they have tried to solve the problem" 87 | }, 88 | "action": "add", 89 | "updated_id": null, 90 | "coherence_check": true, 91 | "connection_proposition": true 92 | }, 93 | "checksum": "b5dbc525b1bc81aef99c6fb457d98929", 94 | "state_version": "-2310704348297148185", 95 | "approved": true, 96 | "data": { 97 | "coherence_checks": [], 98 | "connection_propositions": null 99 | }, 100 | "error": null 101 | } 102 | ], 103 | "progress": 100.0 104 | }, 105 | { 106 | "id": "DT8KpeN8RX", 107 | "version": "0.1.0", 108 | "agent_id": "yCkrWvHkgm", 109 | "creation_utc": "2024-12-27T10:11:08.472408+00:00", 110 | "status": "COMPLETED", 111 | "error": null, 112 | "invoices": [ 113 | { 114 | "kind": "GUIDELINE", 115 | "payload": { 116 | "content": { 117 | "condition": "the user wants to generate a random number", 118 | "action": "generate a random number for the user" 119 | }, 120 | "action": "add", 121 | "updated_id": null, 122 | "coherence_check": true, 123 | "connection_proposition": true 124 | }, 125 | "checksum": "e822782f82c9694a312ad7b7cdd1501d", 126 | "state_version": "-2310704348297148185", 127 | "approved": true, 128 | "data": { 129 | "coherence_checks": [], 130 | "connection_propositions": null 131 | }, 132 | "error": null 133 | } 134 | ], 135 | "progress": 100.0 136 | }, 137 | { 138 | "id": "GDEToAl7yk", 139 | "version": "0.1.0", 140 | "agent_id": "w5HbpNTL14", 141 | "creation_utc": "2024-12-27T11:35:28.207198+00:00", 142 | "status": "COMPLETED", 143 | "error": null, 144 | "invoices": [ 145 | { 146 | "kind": "GUIDELINE", 147 | "payload": { 148 | "content": { 149 | "condition": "user asks for a roadmap to be generated", 150 | "action": "generate a new roadmap for the user" 151 | }, 152 | "action": "add", 153 | "updated_id": null, 154 | "coherence_check": true, 155 | "connection_proposition": true 156 | }, 157 | "checksum": "3d4c6b35b365924d53d77cec50350347", 158 | "state_version": "-1193875770569460417", 159 | "approved": true, 160 | "data": { 161 | "coherence_checks": [], 162 | "connection_propositions": null 163 | }, 164 | "error": null 165 | } 166 | ], 167 | "progress": 0.0 168 | }, 169 | { 170 | "id": "NMGapFTHvH", 171 | "version": "0.1.0", 172 | "agent_id": "QWODNTNOhX", 173 | "creation_utc": "2024-12-27T14:07:01.646239+00:00", 174 | "status": "COMPLETED", 175 | "error": null, 176 | "invoices": [ 177 | { 178 | "kind": "GUIDELINE", 179 | "payload": { 180 | "content": { 181 | "condition": "user wants to create a new resource", 182 | "action": "ensure the user provides all of the approriate details like a name, some kind of asset like a link or book title, and the type of resource like Book, Tutorial etc" 183 | }, 184 | "action": "add", 185 | "updated_id": null, 186 | "coherence_check": true, 187 | "connection_proposition": true 188 | }, 189 | "checksum": "c924b557f0e0b5ce6f8f8fb3edfd2781", 190 | "state_version": "-7183025815427809231", 191 | "approved": true, 192 | "data": { 193 | "coherence_checks": [], 194 | "connection_propositions": null 195 | }, 196 | "error": null 197 | } 198 | ], 199 | "progress": 0.0 200 | }, 201 | { 202 | "id": "GrEdn9jy7j", 203 | "version": "0.1.0", 204 | "agent_id": "QWODNTNOhX", 205 | "creation_utc": "2024-12-27T14:07:33.722526+00:00", 206 | "status": "COMPLETED", 207 | "error": null, 208 | "invoices": [ 209 | { 210 | "kind": "GUIDELINE", 211 | "payload": { 212 | "content": { 213 | "condition": "user provides all of the needed details to create a resource", 214 | "action": "create a new resource" 215 | }, 216 | "action": "add", 217 | "updated_id": null, 218 | "coherence_check": true, 219 | "connection_proposition": true 220 | }, 221 | "checksum": "f858bc3bc898b325030966c583cf08d8", 222 | "state_version": "-7183025815427809231", 223 | "approved": true, 224 | "data": { 225 | "coherence_checks": [], 226 | "connection_propositions": null 227 | }, 228 | "error": null 229 | } 230 | ], 231 | "progress": 100.0 232 | }, 233 | { 234 | "id": "6KYY4MqBl1", 235 | "version": "0.1.0", 236 | "agent_id": "QWODNTNOhX", 237 | "creation_utc": "2024-12-27T14:49:27.177414+00:00", 238 | "status": "COMPLETED", 239 | "error": null, 240 | "invoices": [ 241 | { 242 | "kind": "GUIDELINE", 243 | "payload": { 244 | "content": { 245 | "condition": "user asks question about a resource", 246 | "action": "search for a resource and provide information about it" 247 | }, 248 | "action": "add", 249 | "updated_id": null, 250 | "coherence_check": true, 251 | "connection_proposition": true 252 | }, 253 | "checksum": "c1cb9d3761514afe7588cafe4c113866", 254 | "state_version": "2230514295236794539", 255 | "approved": true, 256 | "data": { 257 | "coherence_checks": [], 258 | "connection_propositions": null 259 | }, 260 | "error": null 261 | } 262 | ], 263 | "progress": 100.0 264 | }, 265 | { 266 | "id": "r-8YNClfIZ", 267 | "version": "0.1.0", 268 | "agent_id": "dkJPs3GbXG", 269 | "creation_utc": "2024-12-27T16:02:47.071309+00:00", 270 | "status": "COMPLETED", 271 | "error": null, 272 | "invoices": [ 273 | { 274 | "kind": "GUIDELINE", 275 | "payload": { 276 | "content": { 277 | "condition": "a user asks to generate a quiz but doesn't specify details", 278 | "action": "ask the user to be specific in what they want the quiz to be about, the difficulty level etc" 279 | }, 280 | "action": "add", 281 | "updated_id": null, 282 | "coherence_check": true, 283 | "connection_proposition": true 284 | }, 285 | "checksum": "b8224f25d46a928d4db110728c5fcaff", 286 | "state_version": "-2830481925790778641", 287 | "approved": true, 288 | "data": { 289 | "coherence_checks": [], 290 | "connection_propositions": null 291 | }, 292 | "error": null 293 | } 294 | ], 295 | "progress": 0.0 296 | }, 297 | { 298 | "id": "OgD38n8DxF", 299 | "version": "0.1.0", 300 | "agent_id": "dkJPs3GbXG", 301 | "creation_utc": "2024-12-27T16:03:34.244951+00:00", 302 | "status": "COMPLETED", 303 | "error": null, 304 | "invoices": [ 305 | { 306 | "kind": "GUIDELINE", 307 | "payload": { 308 | "content": { 309 | "condition": "a user asks to generate a quiz and provides enough details", 310 | "action": "generate a creative quiz for the user based on their completed items in their roadmap and the prefences they provided" 311 | }, 312 | "action": "add", 313 | "updated_id": null, 314 | "coherence_check": true, 315 | "connection_proposition": true 316 | }, 317 | "checksum": "09802b73f37a659b92749caa3658d458", 318 | "state_version": "-2830481925790778641", 319 | "approved": true, 320 | "data": { 321 | "coherence_checks": [], 322 | "connection_propositions": null 323 | }, 324 | "error": null 325 | } 326 | ], 327 | "progress": 100.0 328 | }, 329 | { 330 | "id": "qkzjV7BfvU", 331 | "version": "0.1.0", 332 | "agent_id": "QWODNTNOhX", 333 | "creation_utc": "2024-12-27T16:52:01.417501+00:00", 334 | "status": "COMPLETED", 335 | "error": null, 336 | "invoices": [ 337 | { 338 | "kind": "GUIDELINE", 339 | "payload": { 340 | "content": { 341 | "condition": "user provides all of the needed details to create a resource", 342 | "action": "dont create a new resource" 343 | }, 344 | "action": "add", 345 | "updated_id": null, 346 | "coherence_check": true, 347 | "connection_proposition": true 348 | }, 349 | "checksum": "16fb8513fdce8f07dc10bae20fe57284", 350 | "state_version": "-7744840516425832903", 351 | "approved": false, 352 | "data": { 353 | "coherence_checks": [ 354 | { 355 | "kind": "contradiction_with_existing_guideline", 356 | "first": { 357 | "condition": "user provides all of the needed details to create a resource", 358 | "action": "dont create a new resource" 359 | }, 360 | "second": { 361 | "condition": "user provides all of the needed details to create a resource", 362 | "action": "create a new resource" 363 | }, 364 | "issue": "The origin guideline explicitly states not to create a new resource when all details are provided, while the compared guideline explicitly instructs the agent to create a new resource in the same condition. These actions are directly contradictory.", 365 | "severity": 10 366 | } 367 | ], 368 | "connection_propositions": null 369 | }, 370 | "error": null 371 | } 372 | ], 373 | "progress": 100.0 374 | } 375 | ] 376 | } -------------------------------------------------------------------------------- /runtime-data/guideline_connections.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "guideline_connections": { 4 | "module_path": "parlant.core.guideline_connections", 5 | "model_path": "_GuidelineConnectionDocument" 6 | } 7 | }, 8 | "guideline_connections": [] 9 | } -------------------------------------------------------------------------------- /runtime-data/guideline_tool_associations.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "associations": { 4 | "module_path": "parlant.core.guideline_tool_associations", 5 | "model_path": "_GuidelineToolAssociationDocument" 6 | } 7 | }, 8 | "associations": [ 9 | { 10 | "id": "4rwvDo2ASa", 11 | "version": "0.1.0", 12 | "creation_utc": "2024-12-27T11:59:44.947619+00:00", 13 | "guideline_id": "6ygwwXecBe", 14 | "tool_id": "database:create_roadmap" 15 | }, 16 | { 17 | "id": "oAiq6cG0y9", 18 | "version": "0.1.0", 19 | "creation_utc": "2024-12-27T14:09:06.032768+00:00", 20 | "guideline_id": "QWYTEXsK9s", 21 | "tool_id": "database:create_resource" 22 | }, 23 | { 24 | "id": "69Fcquy8RV", 25 | "version": "0.1.0", 26 | "creation_utc": "2024-12-27T14:50:03.860652+00:00", 27 | "guideline_id": "JQcRQG6X2q", 28 | "tool_id": "database:search_resources" 29 | }, 30 | { 31 | "id": "23o2OoRJGd", 32 | "version": "0.1.0", 33 | "creation_utc": "2024-12-27T16:04:49.016682+00:00", 34 | "guideline_id": "jPzd114-LF", 35 | "tool_id": "database:create_quiz" 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /runtime-data/guidelines.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "guidelines": { 4 | "module_path": "parlant.core.guidelines", 5 | "model_path": "_GuidelineDocument" 6 | } 7 | }, 8 | "guidelines": [ 9 | { 10 | "id": "8yLc1Yhydh", 11 | "version": "0.1.0", 12 | "creation_utc": "2024-12-27T09:26:00.344001+00:00", 13 | "guideline_set": "pWoazDUUzH", 14 | "condition": "the user greets you", 15 | "action": "encourage the user to use this tool for learning, be positive" 16 | }, 17 | { 18 | "id": "NrndozOFW0", 19 | "version": "0.1.0", 20 | "creation_utc": "2024-12-27T09:45:45.631021+00:00", 21 | "guideline_set": "yCkrWvHkgm", 22 | "condition": "the user pastes some kind of code", 23 | "action": "analayze the code and ask the user what they want help with. Is there a bug, do they want it to be explained, or do thet want other assistance." 24 | }, 25 | { 26 | "id": "ocekNdf3ol", 27 | "version": "0.1.0", 28 | "creation_utc": "2024-12-27T09:46:35.988820+00:00", 29 | "guideline_set": "yCkrWvHkgm", 30 | "condition": "the user asks you for help with their code", 31 | "action": "first ask the user what they have tried to solve the problem" 32 | }, 33 | { 34 | "id": "6ygwwXecBe", 35 | "version": "0.1.0", 36 | "creation_utc": "2024-12-27T11:35:28.719446+00:00", 37 | "guideline_set": "w5HbpNTL14", 38 | "condition": "user asks for a roadmap to be generated", 39 | "action": "generate a new roadmap for the user" 40 | }, 41 | { 42 | "id": "yrEdC3OwaB", 43 | "version": "0.1.0", 44 | "creation_utc": "2024-12-27T14:07:02.160628+00:00", 45 | "guideline_set": "QWODNTNOhX", 46 | "condition": "user wants to create a new resource", 47 | "action": "ensure the user provides all of the approriate details like a name, some kind of asset like a link or book title, and the type of resource like Book, Tutorial etc" 48 | }, 49 | { 50 | "id": "QWYTEXsK9s", 51 | "version": "0.1.0", 52 | "creation_utc": "2024-12-27T14:07:42.288330+00:00", 53 | "guideline_set": "QWODNTNOhX", 54 | "condition": "user provides all of the needed details to create a resource", 55 | "action": "create a new resource" 56 | }, 57 | { 58 | "id": "JQcRQG6X2q", 59 | "version": "0.1.0", 60 | "creation_utc": "2024-12-27T14:49:37.745801+00:00", 61 | "guideline_set": "QWODNTNOhX", 62 | "condition": "user asks question about a resource", 63 | "action": "search for a resource and provide information about it" 64 | }, 65 | { 66 | "id": "kVgBxtzJdj", 67 | "version": "0.1.0", 68 | "creation_utc": "2024-12-27T16:02:47.582758+00:00", 69 | "guideline_set": "dkJPs3GbXG", 70 | "condition": "a user asks to generate a quiz but doesn't specify details", 71 | "action": "ask the user to be specific in what they want the quiz to be about, the difficulty level etc" 72 | }, 73 | { 74 | "id": "jPzd114-LF", 75 | "version": "0.1.0", 76 | "creation_utc": "2024-12-27T16:03:41.289468+00:00", 77 | "guideline_set": "dkJPs3GbXG", 78 | "condition": "a user asks to generate a quiz and provides enough details", 79 | "action": "generate a creative quiz for the user based on their completed items in their roadmap and the prefences they provided" 80 | } 81 | ] 82 | } -------------------------------------------------------------------------------- /runtime-data/parlant.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/AI-Coding-Tutor/0ea349b8d7dacf44cd4cd7f33c3d3855b57af1f8/runtime-data/parlant.log -------------------------------------------------------------------------------- /runtime-data/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "tool_services": { 4 | "module_path": "parlant.core.services.tools.service_registry", 5 | "model_path": "_ToolServiceDocument" 6 | } 7 | }, 8 | "tool_services": [] 9 | } -------------------------------------------------------------------------------- /runtime-data/sessions.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techwithtim/AI-Coding-Tutor/0ea349b8d7dacf44cd4cd7f33c3d3855b57af1f8/runtime-data/sessions.json -------------------------------------------------------------------------------- /runtime-data/tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "__schemas__": { 3 | "tags": { 4 | "module_path": "parlant.core.tags", 5 | "model_path": "_TagDocument" 6 | } 7 | }, 8 | "tags": [] 9 | } -------------------------------------------------------------------------------- /sample_schema/quiz.json: -------------------------------------------------------------------------------- 1 | { 2 | "questions": [ 3 | { 4 | "id": 1, 5 | "question": "Question 1", 6 | "answer": "Choice 1", 7 | "choices": ["Choice 1", "Choice 2", "Choice 3"] 8 | }, 9 | { 10 | "id": 2, 11 | "question": "Question 2", 12 | "answer": "Choice 2", 13 | "choices": ["Choice 1", "Choice 2", "Choice 3"] 14 | } 15 | ], 16 | "createdAt": "2020-01-01T00:00:00+00:00", 17 | "id": 2 18 | } 19 | -------------------------------------------------------------------------------- /sample_schema/resource.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "name": "Topic 1", 4 | "description": "description", 5 | "asset": "", 6 | "createdAt": "2020-01-01T00:00:00+00:00" 7 | } 8 | -------------------------------------------------------------------------------- /sample_schema/roadmap.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "topics": [ 4 | { 5 | "id": 2, 6 | "name": "Topic 1", 7 | "subtopics": [ 8 | { "id": 3, "name": "Topic 3", "notes": "", "completed": false }, 9 | { "id": 4, "name": "Topic 4", "notes": "", "completed": false } 10 | ], 11 | "notes": "" 12 | }, 13 | { 14 | "id": 5, 15 | "name": "Topic 2", 16 | "topics": [ 17 | { "id": 6, "name": "Topic 6", "notes": "", "completed": true }, 18 | { "id": 7, "name": "Topic 7", "notes": "", "completed": false } 19 | ], 20 | "notes": "" 21 | } 22 | ], 23 | "updatedAt": "2020-01-01T00:00:00+00:00" 24 | } 25 | -------------------------------------------------------------------------------- /service.py: -------------------------------------------------------------------------------- 1 | from lagom import Container 2 | 3 | from parlant.core.background_tasks import BackgroundTaskService 4 | from parlant.core.services.tools.plugins import PluginServer, tool 5 | from parlant.core.services.tools.service_registry import ServiceRegistry 6 | from parlant.core.tools import ToolContext, ToolResult 7 | 8 | from database import Database 9 | from models import ( 10 | Roadmap, Quiz, Resource, QuizQuestion, QuizChoice, 11 | Topic, SubTopic 12 | ) 13 | from datetime import datetime 14 | import json 15 | 16 | # Initialize database 17 | db = Database() 18 | 19 | server_instance: PluginServer | None = None 20 | 21 | @tool 22 | async def create_roadmap( 23 | context: ToolContext, 24 | title: str, 25 | description: str, 26 | topics_json: str # JSON string containing list of topics 27 | ) -> ToolResult: 28 | """Create a new roadmap 29 | 30 | The topics_json argument should be a JSON string representing a list of dictionaries with this structure: 31 | [ 32 | { 33 | "name": "Python Basics", 34 | "subtopics": [ 35 | {"name": "Variables and Data Types"}, 36 | {"name": "Basic Operators"}, 37 | {"name": "Control Flow (if/else)"} 38 | ] 39 | }, 40 | { 41 | "name": "Data Structures", 42 | "subtopics": [ 43 | {"name": "Lists and Arrays"}, 44 | {"name": "Dictionaries"}, 45 | {"name": "Sets and Tuples"} 46 | ] 47 | } 48 | ] 49 | """ 50 | # Parse JSON string to list of dicts 51 | topics_data = json.loads(topics_json) 52 | 53 | # Convert dictionaries to Topic models 54 | topic_models = [ 55 | Topic( 56 | name=topic["name"], 57 | subtopics=[SubTopic(name=subtopic["name"], completed=False) for subtopic in topic["subtopics"]], 58 | completed=False 59 | ) 60 | for topic in topics_data 61 | ] 62 | 63 | roadmap = Roadmap( 64 | title=title, 65 | description=description, 66 | topics=topic_models, 67 | created_at=datetime.now() 68 | ) 69 | roadmap_id = db.create_roadmap(roadmap) 70 | return ToolResult({"roadmap_id": roadmap_id}) 71 | 72 | @tool 73 | async def create_quiz( 74 | context: ToolContext, 75 | title: str, 76 | description: str, 77 | questions_json: str # JSON string containing questions data 78 | ) -> ToolResult: 79 | """Create a new quiz 80 | 81 | The questions_json argument should be a JSON string representing a list of questions with this structure: 82 | [ 83 | { 84 | "question": "What is a variable in Python?", 85 | "choices": [ 86 | { 87 | "text": "A container for storing data values", 88 | "is_correct": true 89 | }, 90 | { 91 | "text": "A loop statement", 92 | "is_correct": false 93 | }, 94 | { 95 | "text": "A function definition", 96 | "is_correct": false 97 | } 98 | ], 99 | "explanation": "A variable is a container that holds data values while a program is running" 100 | }, 101 | { 102 | "question": "Which of these is a valid variable name in Python?", 103 | "choices": [ 104 | { 105 | "text": "2myvar", 106 | "is_correct": false 107 | }, 108 | { 109 | "text": "my-var", 110 | "is_correct": false 111 | }, 112 | { 113 | "text": "my_var", 114 | "is_correct": true 115 | } 116 | ], 117 | "explanation": "my_var is valid because it starts with a letter and uses underscore for spacing" 118 | } 119 | ] 120 | """ 121 | # Parse JSON string to list of dicts 122 | questions_data = json.loads(questions_json) 123 | 124 | # Convert to QuizQuestion models manually 125 | question_models = [] 126 | for q in questions_data: 127 | # Create QuizChoice models for each choice 128 | choice_models = [ 129 | QuizChoice( 130 | text=choice["text"], 131 | is_correct=choice["is_correct"] 132 | ) 133 | for choice in q["choices"] 134 | ] 135 | 136 | # Create QuizQuestion model 137 | question_model = QuizQuestion( 138 | question=q["question"], 139 | choices=choice_models, 140 | explanation=q["explanation"] 141 | ) 142 | question_models.append(question_model) 143 | 144 | # Create Quiz model 145 | quiz = Quiz( 146 | title=title, 147 | description=description, 148 | questions=question_models, 149 | created_at=datetime.now() 150 | ) 151 | quiz_id = db.create_quiz(quiz) 152 | return ToolResult({"quiz_id": quiz_id}) 153 | 154 | 155 | @tool 156 | async def create_resource( 157 | context: ToolContext, 158 | name: str, 159 | description: str, 160 | asset: str, 161 | resource_type: str 162 | ) -> ToolResult: 163 | """Create a new resource""" 164 | resource = Resource( 165 | name=name, 166 | description=description, 167 | asset=asset, 168 | resource_type=resource_type, 169 | created_at=datetime.now() 170 | ) 171 | resource_id = db.create_resource(resource) 172 | return ToolResult({"resource_id": resource_id}) 173 | 174 | @tool 175 | async def search_resources( 176 | context: ToolContext, 177 | query: str, 178 | limit: int = 2 179 | ) -> ToolResult: 180 | """Search for resources using vector similarity 181 | 182 | Args: 183 | query: The search query text 184 | limit: Maximum number of results to return (default: 5) 185 | 186 | Returns: 187 | List of resources sorted by relevance to the query 188 | """ 189 | try: 190 | resources = db.search_resources(query, limit) 191 | return ToolResult( 192 | { 193 | "message":f"Found {len(resources)} relevant resources", 194 | "data":[resource.model_dump(exclude={"created_at"}) for resource in resources] 195 | } 196 | ) 197 | except Exception as e: 198 | return ToolResult( 199 | {"message": f"Failed to search resources: {str(e)}"} 200 | ) 201 | 202 | TOOLS = [ 203 | create_roadmap, 204 | create_quiz, 205 | create_resource, 206 | search_resources, 207 | ] 208 | 209 | async def initialize_module(container: Container) -> None: 210 | global server_instance 211 | _background_task_service = container[BackgroundTaskService] 212 | 213 | server = PluginServer( 214 | tools=TOOLS, 215 | port=8094, 216 | host="127.0.0.1", 217 | ) 218 | await _background_task_service.start( 219 | server.serve(), 220 | tag="Database Plugin", 221 | ) 222 | 223 | server_instance = server 224 | 225 | service_registry = container[ServiceRegistry] 226 | await service_registry.update_tool_service( 227 | name="database", 228 | kind="sdk", 229 | url="http://127.0.0.1:8094", 230 | transient=True, 231 | ) 232 | 233 | 234 | async def shutdown_module() -> None: 235 | global server_instance 236 | 237 | if server_instance is not None: 238 | await server_instance.shutdown() 239 | server_instance = None 240 | 241 | if __name__ == "__main__": 242 | asyncio.run(main()) -------------------------------------------------------------------------------- /test_database.py: -------------------------------------------------------------------------------- 1 | from database import Database 2 | from models import Roadmap, Topic, SubTopic, Quiz, QuizQuestion, QuizChoice, Resource 3 | from datetime import datetime 4 | 5 | def test_roadmap(): 6 | print("\n=== Testing Roadmap Operations ===") 7 | db = Database() 8 | 9 | # Create a roadmap 10 | roadmap = Roadmap( 11 | slug="python-beginner-roadmap", 12 | title="Python Beginner's Roadmap", 13 | description="A comprehensive guide to Python programming for beginners", 14 | topics=[ 15 | Topic( 16 | slug="python-fundamentals", 17 | name="Python Fundamentals", 18 | subtopics=[ 19 | SubTopic( 20 | slug="variables-and-types", 21 | name="Variables and Data Types", 22 | completed=False 23 | ), 24 | SubTopic( 25 | slug="control-flow", 26 | name="Control Flow", 27 | completed=True 28 | ) 29 | ] 30 | ), 31 | Topic( 32 | slug="python-oop", 33 | name="Object-Oriented Programming", 34 | subtopics=[ 35 | SubTopic( 36 | slug="classes-and-objects", 37 | name="Classes and Objects", 38 | completed=False 39 | ), 40 | SubTopic( 41 | slug="inheritance", 42 | name="Inheritance", 43 | completed=False 44 | ) 45 | ] 46 | ) 47 | ] 48 | ) 49 | 50 | # Save roadmap 51 | roadmap_id = db.create_roadmap(roadmap) 52 | print(f"Created roadmap with ID: {roadmap_id}") 53 | 54 | # Retrieve roadmap 55 | saved_roadmap = db.get_roadmap(roadmap_id) 56 | if saved_roadmap: 57 | print(f"Retrieved roadmap: {saved_roadmap.model_dump_json(indent=2)}") 58 | else: 59 | print("Failed to retrieve roadmap") 60 | 61 | return roadmap_id 62 | 63 | def test_quiz(): 64 | print("\n=== Testing Quiz Operations ===") 65 | db = Database() 66 | 67 | # Create a quiz 68 | quiz = Quiz( 69 | slug="python-basics-quiz", 70 | title="Python Basics Quiz", 71 | description="Test your Python fundamentals", 72 | questions=[ 73 | QuizQuestion( 74 | slug="type-function-output", 75 | question="What is the output of print(type(5))?", 76 | choices=[ 77 | QuizChoice(text="", is_correct=True), 78 | QuizChoice(text="", is_correct=False), 79 | QuizChoice(text="", is_correct=False) 80 | ], 81 | explanation="In Python, 5 is an integer literal" 82 | ), 83 | QuizQuestion( 84 | slug="valid-variable-name", 85 | question="Which of these is a valid variable name?", 86 | choices=[ 87 | QuizChoice(text="_variable", is_correct=True), 88 | QuizChoice(text="2variable", is_correct=False), 89 | QuizChoice(text="my-var", is_correct=False) 90 | ], 91 | explanation="Variable names can start with underscore but not with numbers or contain hyphens" 92 | ) 93 | ] 94 | ) 95 | 96 | # Save quiz 97 | quiz_id = db.create_quiz(quiz) 98 | print(f"Created quiz with ID: {quiz_id}") 99 | 100 | # Retrieve quiz 101 | saved_quiz = db.get_quiz(quiz_id) 102 | if saved_quiz: 103 | print(f"Retrieved quiz: {saved_quiz.model_dump_json(indent=2)}") 104 | else: 105 | print("Failed to retrieve quiz") 106 | 107 | return quiz_id 108 | 109 | def test_resource(): 110 | print("\n=== Testing Resource Operations ===") 111 | db = Database() 112 | 113 | # Create a resource 114 | resource = Resource( 115 | slug="python-variables-tutorial", 116 | name="Python Variables Tutorial", 117 | description="Learn about Python variables and data types", 118 | asset="https://example.com/python-variables", 119 | resource_type="video" 120 | ) 121 | 122 | # Save resource 123 | resource_id = db.create_resource(resource) 124 | print(f"Created resource with ID: {resource_id}") 125 | 126 | # Retrieve resource 127 | saved_resource = db.get_resource(resource_id) 128 | if saved_resource: 129 | print(f"Retrieved resource: {saved_resource.model_dump_json(indent=2)}") 130 | else: 131 | print("Failed to retrieve resource") 132 | 133 | return resource_id 134 | 135 | def main(): 136 | try: 137 | print("Starting database tests...") 138 | 139 | # Test all operations 140 | roadmap_id = test_roadmap() 141 | quiz_id = test_quiz() 142 | resource_id = test_resource() 143 | test_user_progress("python-beginner-roadmap") 144 | 145 | print("\nAll tests completed successfully!") 146 | except Exception as e: 147 | print(f"\nError during testing: {str(e)}") 148 | 149 | if __name__ == "__main__": 150 | main() 151 | --------------------------------------------------------------------------------