forked from cardosofelipe/fast-next-template
feat: enhance database transactions, add Makefiles, and improve Docker setup
- Refactored database batch operations to ensure transaction atomicity and simplify nested structure. - Added `Makefile` for `knowledge-base` and `llm-gateway` modules to streamline development workflows. - Simplified `Dockerfile` for `llm-gateway` by removing multi-stage builds and optimizing dependencies. - Improved code readability in `collection_manager` and `failover` modules with refined logic. - Minor fixes in `test_server` and Redis health check handling for better diagnostics.
This commit is contained in:
79
mcp-servers/knowledge-base/Makefile
Normal file
79
mcp-servers/knowledge-base/Makefile
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
.PHONY: help install install-dev lint lint-fix format type-check test test-cov validate clean run
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "Knowledge Base MCP Server - Development Commands"
|
||||||
|
@echo ""
|
||||||
|
@echo "Setup:"
|
||||||
|
@echo " make install - Install production dependencies"
|
||||||
|
@echo " make install-dev - Install development dependencies"
|
||||||
|
@echo ""
|
||||||
|
@echo "Quality Checks:"
|
||||||
|
@echo " make lint - Run Ruff linter"
|
||||||
|
@echo " make lint-fix - Run Ruff linter with auto-fix"
|
||||||
|
@echo " make format - Format code with Ruff"
|
||||||
|
@echo " make type-check - Run mypy type checker"
|
||||||
|
@echo ""
|
||||||
|
@echo "Testing:"
|
||||||
|
@echo " make test - Run pytest"
|
||||||
|
@echo " make test-cov - Run pytest with coverage"
|
||||||
|
@echo ""
|
||||||
|
@echo "All-in-one:"
|
||||||
|
@echo " make validate - Run lint, type-check, and tests"
|
||||||
|
@echo ""
|
||||||
|
@echo "Running:"
|
||||||
|
@echo " make run - Run the server locally"
|
||||||
|
@echo ""
|
||||||
|
@echo "Cleanup:"
|
||||||
|
@echo " make clean - Remove cache and build artifacts"
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
install:
|
||||||
|
@echo "Installing production dependencies..."
|
||||||
|
@uv pip install -e .
|
||||||
|
|
||||||
|
install-dev:
|
||||||
|
@echo "Installing development dependencies..."
|
||||||
|
@uv pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# Quality checks
|
||||||
|
lint:
|
||||||
|
@echo "Running Ruff linter..."
|
||||||
|
@uv run ruff check .
|
||||||
|
|
||||||
|
lint-fix:
|
||||||
|
@echo "Running Ruff linter with auto-fix..."
|
||||||
|
@uv run ruff check --fix .
|
||||||
|
|
||||||
|
format:
|
||||||
|
@echo "Formatting code..."
|
||||||
|
@uv run ruff format .
|
||||||
|
|
||||||
|
type-check:
|
||||||
|
@echo "Running mypy..."
|
||||||
|
@uv run mypy . --ignore-missing-imports
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
test:
|
||||||
|
@echo "Running tests..."
|
||||||
|
@uv run pytest tests/ -v
|
||||||
|
|
||||||
|
test-cov:
|
||||||
|
@echo "Running tests with coverage..."
|
||||||
|
@uv run pytest tests/ -v --cov=. --cov-report=term-missing --cov-report=html
|
||||||
|
|
||||||
|
# All-in-one validation
|
||||||
|
validate: lint type-check test
|
||||||
|
@echo "All validations passed!"
|
||||||
|
|
||||||
|
# Running
|
||||||
|
run:
|
||||||
|
@echo "Starting Knowledge Base server..."
|
||||||
|
@uv run python server.py
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning up..."
|
||||||
|
@rm -rf __pycache__ .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov
|
||||||
|
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
@find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||||
@@ -328,7 +328,7 @@ class CollectionManager:
|
|||||||
"source_path": chunk.source_path or source_path,
|
"source_path": chunk.source_path or source_path,
|
||||||
"start_line": chunk.start_line,
|
"start_line": chunk.start_line,
|
||||||
"end_line": chunk.end_line,
|
"end_line": chunk.end_line,
|
||||||
"file_type": (chunk.file_type or file_type).value if (chunk.file_type or file_type) else None,
|
"file_type": effective_file_type.value if (effective_file_type := chunk.file_type or file_type) else None,
|
||||||
}
|
}
|
||||||
embeddings_data.append((
|
embeddings_data.append((
|
||||||
chunk.content,
|
chunk.content,
|
||||||
|
|||||||
@@ -284,9 +284,8 @@ class DatabaseManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with self.acquire() as conn:
|
async with self.acquire() as conn, conn.transaction():
|
||||||
# Wrap in transaction for all-or-nothing batch semantics
|
# Wrap in transaction for all-or-nothing batch semantics
|
||||||
async with conn.transaction():
|
|
||||||
for project_id, collection, content, embedding, chunk_type, metadata in embeddings:
|
for project_id, collection, content, embedding, chunk_type, metadata in embeddings:
|
||||||
content_hash = self.compute_content_hash(content)
|
content_hash = self.compute_content_hash(content)
|
||||||
source_path = metadata.get("source_path")
|
source_path = metadata.get("source_path")
|
||||||
@@ -566,9 +565,8 @@ class DatabaseManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with self.acquire() as conn:
|
async with self.acquire() as conn, conn.transaction():
|
||||||
# Use transaction for atomic replace
|
# Use transaction for atomic replace
|
||||||
async with conn.transaction():
|
|
||||||
# First, delete existing embeddings for this source
|
# First, delete existing embeddings for this source
|
||||||
delete_result = await conn.execute(
|
delete_result = await conn.execute(
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ async def health_check() -> dict[str, Any]:
|
|||||||
# Check Redis cache (non-critical - degraded without it)
|
# Check Redis cache (non-critical - degraded without it)
|
||||||
try:
|
try:
|
||||||
if _embeddings and _embeddings._redis:
|
if _embeddings and _embeddings._redis:
|
||||||
await _embeddings._redis.ping()
|
await _embeddings._redis.ping() # type: ignore[misc]
|
||||||
status["dependencies"]["redis"] = "connected"
|
status["dependencies"]["redis"] = "connected"
|
||||||
else:
|
else:
|
||||||
status["dependencies"]["redis"] = "not initialized"
|
status["dependencies"]["redis"] = "not initialized"
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
"""Tests for server module and MCP tools."""
|
"""Tests for server module and MCP tools."""
|
||||||
|
|
||||||
import json
|
|
||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|||||||
@@ -1,39 +1,25 @@
|
|||||||
# Syndarix LLM Gateway MCP Server
|
# Syndarix LLM Gateway MCP Server
|
||||||
# Multi-stage build for minimal image size
|
FROM python:3.12-slim
|
||||||
|
|
||||||
# Build stage
|
WORKDIR /app
|
||||||
FROM python:3.12-slim AS builder
|
|
||||||
|
# Install system dependencies (needed for tiktoken regex compilation)
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
build-essential \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install uv for fast package management
|
# Install uv for fast package management
|
||||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||||
|
|
||||||
WORKDIR /app
|
# Copy project files
|
||||||
|
|
||||||
# Copy dependency files
|
|
||||||
COPY pyproject.toml ./
|
COPY pyproject.toml ./
|
||||||
|
COPY *.py ./
|
||||||
|
|
||||||
# Create virtual environment and install dependencies
|
# Install dependencies to system Python
|
||||||
RUN uv venv /app/.venv
|
RUN uv pip install --system --no-cache .
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
RUN uv pip install -e .
|
|
||||||
|
|
||||||
# Runtime stage
|
|
||||||
FROM python:3.12-slim AS runtime
|
|
||||||
|
|
||||||
# Create non-root user for security
|
# Create non-root user for security
|
||||||
RUN groupadd --gid 1000 appgroup && \
|
RUN useradd --create-home --shell /bin/bash appuser
|
||||||
useradd --uid 1000 --gid appgroup --shell /bin/bash --create-home appuser
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy virtual environment from builder
|
|
||||||
COPY --from=builder /app/.venv /app/.venv
|
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
|
||||||
|
|
||||||
# Copy application code
|
|
||||||
COPY --chown=appuser:appgroup . .
|
|
||||||
|
|
||||||
# Switch to non-root user
|
|
||||||
USER appuser
|
USER appuser
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
@@ -47,7 +33,7 @@ EXPOSE 8001
|
|||||||
|
|
||||||
# Health check
|
# Health check
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||||
CMD python -c "import httpx; httpx.get('http://localhost:8001/health').raise_for_status()" || exit 1
|
CMD python -c "import httpx; httpx.get('http://localhost:8001/health').raise_for_status()"
|
||||||
|
|
||||||
# Run the server
|
# Run the server
|
||||||
CMD ["python", "server.py"]
|
CMD ["python", "server.py"]
|
||||||
|
|||||||
79
mcp-servers/llm-gateway/Makefile
Normal file
79
mcp-servers/llm-gateway/Makefile
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
.PHONY: help install install-dev lint lint-fix format type-check test test-cov validate clean run
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
help:
|
||||||
|
@echo "LLM Gateway MCP Server - Development Commands"
|
||||||
|
@echo ""
|
||||||
|
@echo "Setup:"
|
||||||
|
@echo " make install - Install production dependencies"
|
||||||
|
@echo " make install-dev - Install development dependencies"
|
||||||
|
@echo ""
|
||||||
|
@echo "Quality Checks:"
|
||||||
|
@echo " make lint - Run Ruff linter"
|
||||||
|
@echo " make lint-fix - Run Ruff linter with auto-fix"
|
||||||
|
@echo " make format - Format code with Ruff"
|
||||||
|
@echo " make type-check - Run mypy type checker"
|
||||||
|
@echo ""
|
||||||
|
@echo "Testing:"
|
||||||
|
@echo " make test - Run pytest"
|
||||||
|
@echo " make test-cov - Run pytest with coverage"
|
||||||
|
@echo ""
|
||||||
|
@echo "All-in-one:"
|
||||||
|
@echo " make validate - Run lint, type-check, and tests"
|
||||||
|
@echo ""
|
||||||
|
@echo "Running:"
|
||||||
|
@echo " make run - Run the server locally"
|
||||||
|
@echo ""
|
||||||
|
@echo "Cleanup:"
|
||||||
|
@echo " make clean - Remove cache and build artifacts"
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
install:
|
||||||
|
@echo "Installing production dependencies..."
|
||||||
|
@uv pip install -e .
|
||||||
|
|
||||||
|
install-dev:
|
||||||
|
@echo "Installing development dependencies..."
|
||||||
|
@uv pip install -e ".[dev]"
|
||||||
|
|
||||||
|
# Quality checks
|
||||||
|
lint:
|
||||||
|
@echo "Running Ruff linter..."
|
||||||
|
@uv run ruff check .
|
||||||
|
|
||||||
|
lint-fix:
|
||||||
|
@echo "Running Ruff linter with auto-fix..."
|
||||||
|
@uv run ruff check --fix .
|
||||||
|
|
||||||
|
format:
|
||||||
|
@echo "Formatting code..."
|
||||||
|
@uv run ruff format .
|
||||||
|
|
||||||
|
type-check:
|
||||||
|
@echo "Running mypy..."
|
||||||
|
@uv run mypy . --ignore-missing-imports
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
test:
|
||||||
|
@echo "Running tests..."
|
||||||
|
@uv run pytest tests/ -v
|
||||||
|
|
||||||
|
test-cov:
|
||||||
|
@echo "Running tests with coverage..."
|
||||||
|
@uv run pytest tests/ -v --cov=. --cov-report=term-missing --cov-report=html
|
||||||
|
|
||||||
|
# All-in-one validation
|
||||||
|
validate: lint type-check test
|
||||||
|
@echo "All validations passed!"
|
||||||
|
|
||||||
|
# Running
|
||||||
|
run:
|
||||||
|
@echo "Starting LLM Gateway server..."
|
||||||
|
@uv run python server.py
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
clean:
|
||||||
|
@echo "Cleaning up..."
|
||||||
|
@rm -rf __pycache__ .pytest_cache .mypy_cache .ruff_cache .coverage htmlcov
|
||||||
|
@find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
|
||||||
|
@find . -type f -name "*.pyc" -delete 2>/dev/null || true
|
||||||
@@ -110,9 +110,8 @@ class CircuitBreaker:
|
|||||||
"""
|
"""
|
||||||
if self._state == CircuitState.OPEN:
|
if self._state == CircuitState.OPEN:
|
||||||
time_in_open = time.time() - self._stats.state_changed_at
|
time_in_open = time.time() - self._stats.state_changed_at
|
||||||
if time_in_open >= self.recovery_timeout:
|
# Double-check state after time calculation (for thread safety)
|
||||||
# Only transition if still in OPEN state (double-check)
|
if time_in_open >= self.recovery_timeout and self._state == CircuitState.OPEN:
|
||||||
if self._state == CircuitState.OPEN:
|
|
||||||
self._transition_to(CircuitState.HALF_OPEN)
|
self._transition_to(CircuitState.HALF_OPEN)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Circuit {self.name} transitioned to HALF_OPEN "
|
f"Circuit {self.name} transitioned to HALF_OPEN "
|
||||||
|
|||||||
Reference in New Issue
Block a user