diff --git a/backend/.flake8 b/backend/.flake8 new file mode 100644 index 0000000..c7a72a9 --- /dev/null +++ b/backend/.flake8 @@ -0,0 +1,32 @@ +[flake8] +max-line-length = 88 +extend-ignore = + # E203: whitespace before ':' (conflicts with black) + E203, + # E501: line too long (handled by black) + E501, + # W503: line break before binary operator (conflicts with black) + W503, + # F401: module imported but unused (handled by isort) + F401 +exclude = + .git, + __pycache__, + .venv, + venv, + env, + .tox, + build, + dist, + *.egg-info, + .pytest_cache, + migrations, + alembic/versions +per-file-ignores = + # Allow unused imports in __init__.py files + __init__.py:F401,F403 + # Allow long lines in test files for readability + tests/*.py:E501 + # Allow specific test patterns + test_*.py:E501,F401,F811 +max-complexity = 12 \ No newline at end of file diff --git a/backend/Dockerfile.migrate b/backend/Dockerfile.migrate new file mode 100644 index 0000000..9847266 --- /dev/null +++ b/backend/Dockerfile.migrate @@ -0,0 +1,22 @@ +# Migration container for running Alembic migrations +# Reuses the backend image but runs only database migrations + +# Build from the backend image to reuse dependencies +FROM enclava-backend:latest + +# Install PostgreSQL client for database health checks +USER root +RUN apt-get update && apt-get install -y \ + postgresql-client \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Copy migration script (if not already present) +COPY scripts/migrate.sh /usr/local/bin/migrate.sh +RUN chmod +x /usr/local/bin/migrate.sh + +# Switch back to app user +USER enclava + +# Set default command to run migrations +CMD ["/usr/local/bin/migrate.sh"] \ No newline at end of file diff --git a/backend/app/services/rag_service.py b/backend/app/services/rag_service.py index dc0cf2d..119cb26 100644 --- a/backend/app/services/rag_service.py +++ b/backend/app/services/rag_service.py @@ -405,7 +405,7 @@ class RAGService: if os.path.exists(document.file_path): os.remove(document.file_path) except Exception as e: - print(f"Warning: Could not delete file {document.file_path}: {e}") + logger.warning(f"Could not delete file {document.file_path}: {e}") return True diff --git a/backend/modules/chatbot/main.py b/backend/modules/chatbot/main.py index 75d3418..38ee9ec 100644 --- a/backend/modules/chatbot/main.py +++ b/backend/modules/chatbot/main.py @@ -9,7 +9,6 @@ Provides AI chatbot capabilities with: - UI-configurable settings """ -import asyncio import json from pprint import pprint import uuid @@ -295,7 +294,9 @@ class ChatbotModule(BaseModule): # Force the session to see the committed changes db.expire_all() - # Get conversation history for context - INCLUDING the message we just created + # Get conversation history for context - includes the current message we just created + # Fetch up to memory_length pairs of messages (user + assistant) + # The +1 ensures we include the current message if we're at the limit messages = db.query(DBMessage).filter( DBMessage.conversation_id == conversation.id ).order_by(DBMessage.timestamp.desc()).limit(chatbot_config.memory_length * 2 + 1).all() @@ -424,16 +425,11 @@ class ChatbotModule(BaseModule): import traceback logger.warning(f"RAG search traceback: {traceback.format_exc()}") - # Build conversation context + # Build conversation context (includes the current message from db_messages) messages = self._build_conversation_messages(db_messages, config, rag_context, context) - # CRITICAL: Add the current user message to the messages array - # This ensures the LLM knows what the user is asking, not just the history - messages.append({ - "role": "user", - "content": message - }) - logger.info(f"Added current user message to messages array") + # Note: Current user message is already included in db_messages from the query + logger.info(f"Built conversation context with {len(messages)} messages") # LLM completion logger.info(f"Attempting LLM completion with model: {config.model}") @@ -546,9 +542,7 @@ class ChatbotModule(BaseModule): else: logger.info(f"Skipped message with role {msg.role}") - logger.info(f"Final messages array has {len(messages)} messages") - from pprint import pprint - pprint(messages) # For debugging, can be removed in production + logger.info(f"Final messages array has {len(messages)} messages") # For debugging, can be removed in production return messages async def _get_or_create_conversation(self, conversation_id: Optional[str], diff --git a/backend/pyproject.toml b/backend/pyproject.toml new file mode 100644 index 0000000..e8c45dd --- /dev/null +++ b/backend/pyproject.toml @@ -0,0 +1,111 @@ +[tool.black] +line-length = 88 +target-version = ['py311'] +include = '\.pyi?$' +extend-exclude = ''' +/( + # directories + \.eggs + | \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist + | alembic/versions +)/ +''' + +[tool.isort] +profile = "black" +line_length = 88 +known_first_party = ["app", "tests"] +known_third_party = ["fastapi", "sqlalchemy", "pydantic", "pytest", "httpx"] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +skip = [ + ".git", + "__pycache__", + ".venv", + "venv", + "env", + ".tox", + "build", + "dist", + "*.egg-info", + ".pytest_cache", + "alembic/versions" +] + +[tool.mypy] +python_version = "3.11" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +strict_equality = true + +[[tool.mypy.overrides]] +module = "tests.*" +disallow_untyped_defs = false +disallow_incomplete_defs = false +disallow_untyped_decorators = false + +[[tool.mypy.overrides]] +module = [ + "alembic.*", + "qdrant_client.*", + "redis.*", + "uvicorn.*", + "factory.*", + "faker.*", + "locust.*", + "responses.*", + "aioresponses.*" +] +ignore_missing_imports = true + +[tool.pytest.ini_options] +minversion = "7.0" +testpaths = ["tests"] +python_files = "test_*.py" +python_classes = "Test*" +python_functions = "test_*" +asyncio_mode = "auto" +addopts = """ + -v + --strict-markers + --strict-config + --tb=short + --cov=app + --cov-report=term-missing + --cov-report=html + --cov-report=xml + --cov-fail-under=80 +""" +markers = [ + "unit: Unit tests", + "integration: Integration tests", + "e2e: End-to-end tests", + "slow: Slow tests", + "db: Tests that require database", + "redis: Tests that require redis", + "qdrant: Tests that require qdrant", +] + +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file