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
65 changes: 65 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
compose_service = backend

add-package:
@poetry add $(pkg)
@poetry install
@docker compose build

remove-package:
@poetry remove $(pkg)
@poetry install
@docker compose build

add-dev-package:
@poetry add --group dev $(pkg)
@poetry install
@docker compose build

up:
@docker compose up

bash:
@docker compose run --rm ${compose_service} bash

build:
@docker compose build

build-up:
@docker compose up --build

down:
@docker compose down --remove-orphans

format:
@docker compose run --rm ${compose_service} poetry run ruff format ./app
@docker compose run --rm ${compose_service} poetry run ruff check ./app --fix --select I

install:
@poetry install

lint:
@docker compose run --rm ${compose_service} poetry run ruff check .
@docker compose run --rm ${compose_service} poetry run ruff format --check .

lint-full:
@docker compose run --rm ${compose_service} poetry run ruff check .
@docker compose run --rm ${compose_service} poetry run ruff format --check .
@docker compose run --rm ${compose_service} poetry run mypy .

run-command:
@docker compose run --rm ${compose_service} $(command)

shell:
@docker compose run --rm ${compose_service} python manage.py shell

stop:
@docker compose stop

test:
@docker compose run --rm ${compose_service} poetry run pytest .

testcase:
@docker compose run --rm ${compose_service} poetry run pytest $(t)

up-d:
@docker compose up -d
21 changes: 19 additions & 2 deletions app/agents/apiconf_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import logging
import hashlib
from typing import Dict, Any, Optional
from google.adk.agents import LlmAgent
from google.adk.runners import Runner
Expand All @@ -13,6 +14,7 @@
from app.services.message_processor import MessageProcessor
from app.services.response_processor import ResponseProcessor
from app.services.agent_config import AgentConfig
from app.services.redis_service import RedisService

logger = logging.getLogger(__name__)

Expand All @@ -24,6 +26,7 @@ def __init__(self):
# Initialize services
self.tool_manager = ToolManager()
self.session_manager = SessionManager()
self.redis_service = RedisService.get_instance()

# Create the agent using LlmAgent
self.agent = LlmAgent(
Expand Down Expand Up @@ -54,6 +57,15 @@ async def chat(self, message: str, user_id: str = None, session_id: str = None,
timestamp: int = None, timezone_offset: int = None) -> Dict[str, Any]:
"""Process a chat message and return a response."""
try:
# Create a cache key for the message
cache_key = f"chat:{hashlib.md5(message.encode()).hexdigest()}"

# Check cache first
cached_response = self.redis_service.get(cache_key)
if cached_response:
logger.info(f"Returning cached response for message: {message}")
return cached_response

# Set defaults
user_id = user_id or "default_user"
session_id = session_id or f"session_{user_id}"
Expand Down Expand Up @@ -93,12 +105,17 @@ async def chat(self, message: str, user_id: str = None, session_id: str = None,
if final_response is None:
final_response = "I apologize, but I couldn't generate a response. Please try again."

return {
response = {
"success": True,
"response": final_response,
"user_id": user_id,
"session_id": session_id
}

# Cache the response
self.redis_service.set(cache_key, response, ttl=3600)

return response
except Exception as e:
logger.error(f"Error in chat: {e}", exc_info=True)
return {
Expand All @@ -116,4 +133,4 @@ def get_status(self) -> Dict[str, Any]:
"dates": "July 18-19, 2025",
"speakers_announced": True,
"total_speakers": 44
}
}
11 changes: 10 additions & 1 deletion app/agents/tools/csv_schedule_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@

from app.config.logger import Logger
from app.config.settings import settings
from app.services.redis_service import RedisService

logger = Logger.get_logger(__name__)
redis_service = RedisService.get_instance()

def _load_csv_data() -> List[Dict[str, Any]]:
"""Load session and speaker data from CSV file."""
cache_key = "csv_data"
cached_data = redis_service.get(cache_key)
if cached_data:
logger.info("Returning cached CSV data")
return cached_data

try:
csv_path = Path("data/api-conf-lagos-2025 flattened accepted sessions - exported 2025-07-05 - Accepted sessions and speakers.csv")
if not csv_path.exists():
Expand Down Expand Up @@ -50,6 +58,7 @@ def _load_csv_data() -> List[Dict[str, Any]]:
sessions.append(session)

logger.info(f"Loaded {len(sessions)} sessions from CSV")
redis_service.set(cache_key, sessions, ttl=86400) # Cache for 24 hours
return sessions

except Exception as e:
Expand Down Expand Up @@ -276,4 +285,4 @@ def get_csv_schedule_tools() -> List[FunctionTool]:
FunctionTool(search_speakers_csv),
FunctionTool(get_full_schedule_csv),
FunctionTool(get_keynote_speakers_csv)
]
]
67 changes: 65 additions & 2 deletions app/api/v1/agents_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
FastAPI router for agent endpoints.
"""

from fastapi import APIRouter, HTTPException, status, Request, Query, Body
from fastapi import APIRouter, HTTPException, status, Request, Query, Body, Header
from fastapi.responses import JSONResponse
from typing import Optional
import time
Expand All @@ -12,6 +12,7 @@
from app.schemas.agents import AgentRequest, AgentResponse, AgentStatus
from app.agents.agent_api import process_user_message, get_agent_status
from app.config.settings import settings
from app.services.redis_service import RedisService

router = APIRouter()
logger = Logger.get_logger(__name__)
Expand Down Expand Up @@ -186,4 +187,66 @@ async def get_conference_info():
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get conference information"
)
)

@router.post(
"/cache/invalidate",
response_model=SuccessResponseSchema[dict],
status_code=status.HTTP_200_OK,
summary="Invalidate Redis cache",
description="Clear the entire Redis cache. Requires a valid secret key."
)
async def invalidate_cache(
x_secret_key: str = Header(..., description="Secret key for authorization")
):
"""Invalidate the Redis cache."""
if x_secret_key != settings.secret_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid secret key"
)

try:
redis_service = RedisService.get_instance()
result = redis_service.clear_cache()
return SuccessResponseSchema(
data=result,
message="Cache invalidated successfully"
)
except Exception as e:
logger.error(f"Error invalidating cache: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to invalidate cache"
)

@router.get(
"/cache/stats",
response_model=SuccessResponseSchema[dict],
status_code=status.HTTP_200_OK,
summary="Get Redis cache statistics",
description="Get statistics about the Redis cache. Requires a valid secret key."
)
async def get_cache_stats(
x_secret_key: str = Header(..., description="Secret key for authorization")
):
"""Get Redis cache statistics."""
if x_secret_key != settings.secret_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid secret key"
)

try:
redis_service = RedisService.get_instance()
stats = redis_service.get_stats()
return SuccessResponseSchema(
data=stats,
message="Cache stats retrieved successfully"
)
except Exception as e:
logger.error(f"Error getting cache stats: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get cache stats"
)
79 changes: 79 additions & 0 deletions app/api/v1/redis_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
FastAPI router for agent endpoints.
"""

from fastapi import APIRouter, HTTPException, status, Header
import time

from app.config.logger import Logger
from app.schemas.base import SuccessResponseSchema
from app.config.settings import settings
from app.services.redis_service import RedisService

router = APIRouter()
logger = Logger.get_logger(__name__)

# Track agent startup time
startup_time = time.time()

@router.post(
"/cache/invalidate",
response_model=SuccessResponseSchema[dict],
status_code=status.HTTP_200_OK,
summary="Invalidate Redis cache",
description="Clear the entire Redis cache. Requires a valid secret key."
)
async def invalidate_cache(
x_secret_key: str = Header(..., description="Secret key for authorization")
):
"""Invalidate the Redis cache."""
if x_secret_key != settings.secret_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid secret key"
)

try:
redis_service = RedisService.get_instance()
result = redis_service.clear_cache()
return SuccessResponseSchema(
data=result,
message="Cache invalidated successfully"
)
except Exception as e:
logger.error(f"Error invalidating cache: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to invalidate cache"
)

@router.get(
"/cache/stats",
response_model=SuccessResponseSchema[dict],
status_code=status.HTTP_200_OK,
summary="Get Redis cache statistics",
description="Get statistics about the Redis cache. Requires a valid secret key."
)
async def get_cache_stats(
x_secret_key: str = Header(..., description="Secret key for authorization")
):
"""Get Redis cache statistics."""
if x_secret_key != settings.secret_key:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid secret key"
)

try:
redis_service = RedisService.get_instance()
stats = redis_service.get_stats()
return SuccessResponseSchema(
data=stats,
message="Cache stats retrieved successfully"
)
except Exception as e:
logger.error(f"Error getting cache stats: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get cache stats"
)
Loading