Skip to content
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
*.swp
tags
node_modules
__pycache__
__pycache__
*scratch*
*.vscode
33 changes: 0 additions & 33 deletions rag_service/api/test_api.py

This file was deleted.

Empty file removed rag_service/config/__init__.py
Empty file.
214 changes: 79 additions & 135 deletions rag_service/core/assignment_context_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ def __init__(self):
print("\nInitialized AssignmentContextManager")
print(f"Using API Endpoint: {self.settings.base_url.rstrip('/')}/{self.settings.assignment_context_endpoint.lstrip('/')}")

async def get_assignment_context(self, assignment_id: str) -> Dict:
async def get_assignment_context(self, assignment_id: str, student_id: str) -> Dict:
"""Get assignment context from cache or API"""
try:
print(f"\n=== Getting Assignment Context for: {assignment_id} ===")

context = None

# 1. Check cache if enabled
# Check cache if enabled
if self.settings.enable_caching:
cached_context = frappe.get_list(
"Assignment Context",
Expand All @@ -37,60 +39,108 @@ async def get_assignment_context(self, assignment_id: str) -> Dict:

if cached_context:
print("Found cached context")
return await self._format_cached_context(cached_context[0].name)

# 2. If not in cache or caching disabled, fetch from API
print("Fetching context from API...")
context = await self._fetch_from_api(assignment_id)

# 3. Save to cache if enabled
if self.settings.enable_caching:
print("Saving to cache...")
await self._save_to_cache(assignment_id, context)

# 4. Format and return
return self._format_context_for_llm(context)

cached_context = frappe.get_doc("Assignment Context", cached_context[0].name).as_dict()
# remove fields where value is a datatime object
for key in list(cached_context.keys()):
if isinstance(cached_context[key], datetime):
cached_context.pop(key, None)
context = {"assignment": cached_context,}

if not context:
# If not in cache or caching disabled, fetch from API
print("Fetching context from API...")
context = await self._fetch_assignment_from_api(assignment_id)

# Save to cache if enabled
if self.settings.enable_caching:
print("Saving to cache...")
await self._save_to_cache(assignment_id, context)

if context["assignment"]["rubrics"] is None:
print("Rubrics not found in assignment context.")
raise Exception("Rubrics missing in assignment context")

if student_id is None:
raise Exception("Student ID is required to fetch student context")

student_details = await self._fetch_student_from_api(student_id)
# student_details = {
# "student_id":"ST0001",
# "grade":"6",
# "level":"2",
# "language": "Hindi"
# }
context["student"] = {**student_details}

# print("Assignment context",context)
return context

except Exception as e:
error_msg = f"Error getting assignment context: {str(e)}"
print(f"\nError: {error_msg}")
frappe.log_error(error_msg, "Assignment Context Error")
raise

async def _fetch_from_api(self, assignment_id: str) -> Dict:
async def _fetch_assignment_from_api(self, assignment_id: str) -> Dict:
"""Fetch assignment context from TAP LMS API"""
try:
# Construct API URL properly
api_url = f"{self.settings.base_url.rstrip('/')}/{self.settings.assignment_context_endpoint.lstrip('/')}"
print(f"\nMaking API request to: {api_url}")

payload = {
"assignment_id": assignment_id
}
response = requests.post(
api_url,
headers=self.headers,
json=payload,
timeout=30
)

print("\nRequest Details:")
print(f"Headers: {json.dumps({k: v if k != 'Authorization' else '[REDACTED]' for k, v in self.headers.items()}, indent=2)}")
print(f"Payload: {json.dumps(payload, indent=2)}")
if response.status_code != 200:
error_msg = f"API request failed with status {response.status_code}: {response.text}"
print(f"Error: {error_msg}")
raise Exception(error_msg)

data = response.json()
if "message" not in data:
raise Exception("Invalid API response format")

print("API request successful")
return data["message"]

except requests.RequestException as e:
error_msg = f"API request failed: {str(e)}"
print(f"\nError: {error_msg}")
raise Exception(error_msg)

async def _fetch_student_from_api(self, student_id: str) -> Dict:
"""Fetch student details from TAP LMS API"""
try:
# Construct API URL properly
api_url = f"{self.settings.base_url.rstrip('/')}/{self.settings.student_context_endpoint.lstrip('/')}"

payload = {
"student_id": student_id
}
response = requests.post(
api_url,
headers=self.headers,
json=payload,
timeout=30
)

print(f"\nResponse Status: {response.status_code}")

if response.status_code != 200:
error_msg = f"API request failed with status {response.status_code}: {response.text}"
print(f"Error: {error_msg}")
raise Exception(error_msg)

data = response.json()
if "message" not in data:
print(f"Error: Invalid API response format: {data}")
raise Exception("Invalid API response format")

print("API request successful")
print("Student API request successful")
return data["message"]

except requests.RequestException as e:
Expand Down Expand Up @@ -157,7 +207,8 @@ async def _save_to_cache(self, assignment_id: str, context: Dict) -> None:
"last_updated": now_datetime(),
"cache_valid_till": cache_valid_till,
"last_sync_status": "Success",
"version": (doc.version or 0) + 1
"version": (doc.version or 0) + 1,
"rubrics": json.dumps(assignment.get("rubrics", {}))
})
doc.save()
print(f"Updated existing cache for assignment {assignment_id}")
Expand All @@ -177,7 +228,8 @@ async def _save_to_cache(self, assignment_id: str, context: Dict) -> None:
"last_updated": now_datetime(),
"cache_valid_till": cache_valid_till,
"last_sync_status": "Success",
"version": 1
"version": 1,
"rubrics": json.dumps(assignment.get("rubrics", {}))
})
doc.insert()
print(f"Created new cache for assignment {assignment_id}")
Expand All @@ -191,95 +243,13 @@ async def _save_to_cache(self, assignment_id: str, context: Dict) -> None:
frappe.db.rollback() # Rollback on error
raise Exception(error_msg)

async def _format_cached_context(self, context_name: str) -> Dict:
"""Format cached context for LLM"""
try:
context = frappe.get_doc("Assignment Context", context_name)

# Parse learning objectives safely
learning_objectives = []
try:
if context.learning_objectives:
learning_objectives = json.loads(context.learning_objectives)
except (json.JSONDecodeError, TypeError) as e:
print(f"Error parsing learning objectives: {str(e)}")
# Create an empty default if parsing fails
learning_objectives = []

return {
"assignment": {
"id": context.assignment_id,
"name": context.assignment_name,
"type": context.assignment_type,
"description": context.description,
"max_score": context.max_score,
"reference_image": context.reference_image
},
"learning_objectives": learning_objectives,
"course_vertical": context.course_vertical,
"difficulty_level": context.difficulty_level
}

except Exception as e:
error_msg = f"Error formatting cached context: {str(e)}"
print(f"\nError: {error_msg}")
raise Exception(error_msg)

def _format_context_for_llm(self, api_context: Dict) -> Dict:
"""Format API context for LLM"""
try:
assignment = api_context["assignment"]

# Determine course vertical
course_vertical = "General"
if "subject" in assignment and assignment["subject"]:
subject_parts = assignment["subject"].split("-")
if len(subject_parts) > 1:
course_vertical = subject_parts[-1].strip()

# Parse assignment type correctly
assignment_type = assignment.get("type", "Practical")
valid_types = ["Written", "Practical", "Performance", "Collaborative"]
if not assignment_type or assignment_type not in valid_types:
assignment_type = "Practical"

# Format learning objectives
learning_objectives = []
if "learning_objectives" in api_context and api_context["learning_objectives"]:
learning_objectives = [
{
"objective_id": obj.get("objective", "Unknown"),
"description": obj.get("description", "").strip()
}
for obj in api_context["learning_objectives"]
]

return {
"assignment": {
"id": assignment.get("name", ""), # Using name as ID
"name": assignment.get("name", ""),
"type": assignment_type,
"description": assignment.get("description", ""),
"max_score": assignment.get("max_score", "100"),
"reference_image": assignment.get("reference_image", "")
},
"learning_objectives": learning_objectives,
"course_vertical": course_vertical,
"difficulty_level": "Medium" # Default value
}

except Exception as e:
error_msg = f"Error formatting API context: {str(e)}"
print(f"\nError: {error_msg}")
raise Exception(error_msg)

async def refresh_cache(self, assignment_id: str) -> None:
"""Manually refresh cache for an assignment"""
try:
print(f"\n=== Refreshing Cache for Assignment: {assignment_id} ===")

# Force fetch from API
context = await self._fetch_from_api(assignment_id)
context = await self._fetch_assignment_from_api(assignment_id)

# Save to cache
await self._save_to_cache(assignment_id, context)
Expand All @@ -290,29 +260,3 @@ async def refresh_cache(self, assignment_id: str) -> None:
error_msg = f"Error refreshing cache: {str(e)}"
print(f"\nError: {error_msg}")
raise Exception(error_msg)

def verify_settings(self) -> Dict:
"""Verify RAG Settings configuration"""
try:
results = {
"base_url": bool(self.settings.base_url),
"api_key": bool(self.settings.api_key),
"api_secret": bool(self.settings.get_password('api_secret')),
"endpoints": bool(self.settings.assignment_context_endpoint),
"cache_config": bool(self.settings.cache_duration_days is not None)
}

missing = [k for k, v in results.items() if not v]

return {
"status": "Valid" if not missing else "Invalid",
"missing_settings": missing,
"cache_enabled": self.settings.enable_caching,
"cache_duration": self.settings.cache_duration_days
}

except Exception as e:
return {
"status": "Error",
"error": str(e)
}
Loading