From ce49760e5af8a3ffc7958b97b468cd9ea612ecb9 Mon Sep 17 00:00:00 2001 From: busya Date: Wed, 27 May 2026 16:05:04 +0300 Subject: [PATCH] date fx --- ...add_missing_columns_policy_id_provider_.py | 38 ++++++++++++++----- .../src/api/routes/assistant/_admin_routes.py | 4 +- backend/src/api/routes/assistant/_history.py | 15 +++++--- backend/src/plugins/llm_analysis/service.py | 3 +- 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/backend/alembic/versions/2df63b7ce038_add_missing_columns_policy_id_provider_.py b/backend/alembic/versions/2df63b7ce038_add_missing_columns_policy_id_provider_.py index 28673a35..62e7b789 100644 --- a/backend/alembic/versions/2df63b7ce038_add_missing_columns_policy_id_provider_.py +++ b/backend/alembic/versions/2df63b7ce038_add_missing_columns_policy_id_provider_.py @@ -28,21 +28,41 @@ depends_on: str | Sequence[str] | None = None def upgrade() -> None: """Upgrade schema.""" + # Use IF NOT EXISTS — this is a catch-up migration for columns that + # may already exist (inserted by out-of-band schema changes). + conn = op.get_bind() + # Add is_multimodal to llm_providers (from 9f8e7d6c5b4a) - op.add_column("llm_providers", - sa.Column("is_multimodal", sa.Boolean(), nullable=False, server_default="false") - ) - op.alter_column("llm_providers", "is_multimodal", server_default=None) + if not _column_exists(conn, "llm_providers", "is_multimodal"): + op.add_column("llm_providers", + sa.Column("is_multimodal", sa.Boolean(), nullable=False, server_default="false") + ) + op.alter_column("llm_providers", "is_multimodal", server_default=None) # Add provider_id to validation_policies (from a7b1c2d3e4f5) - op.add_column("validation_policies", - sa.Column("provider_id", sa.String(), nullable=True) - ) + if not _column_exists(conn, "validation_policies", "provider_id"): + op.add_column("validation_policies", + sa.Column("provider_id", sa.String(), nullable=True) + ) # Add policy_id to llm_validation_results (from b1c2d3e4f5a6) - op.add_column("llm_validation_results", - sa.Column("policy_id", sa.String(), nullable=True, index=True) + if not _column_exists(conn, "llm_validation_results", "policy_id"): + op.add_column("llm_validation_results", + sa.Column("policy_id", sa.String(), nullable=True, index=True) + ) + + +def _column_exists(conn, table: str, column: str) -> bool: + """Check if a column exists in the given table.""" + from sqlalchemy import text + result = conn.execute( + text( + "SELECT 1 FROM information_schema.columns " + "WHERE table_name = :table AND column_name = :column" + ), + {"table": table, "column": column}, ) + return result.scalar() is not None def downgrade() -> None: diff --git a/backend/src/api/routes/assistant/_admin_routes.py b/backend/src/api/routes/assistant/_admin_routes.py index 9139ce8f..6db7dcbb 100644 --- a/backend/src/api/routes/assistant/_admin_routes.py +++ b/backend/src/api/routes/assistant/_admin_routes.py @@ -8,7 +8,7 @@ from __future__ import annotations -from datetime import UTC, datetime +from datetime import datetime from typing import Any from fastapi import Depends, HTTPException, Query @@ -69,7 +69,7 @@ async def list_conversations( conv_id = row.conversation_id if not conv_id: continue - created_at = row.created_at or datetime.now(UTC) + created_at = row.created_at or datetime.now() if conv_id not in summary: summary[conv_id] = { "conversation_id": conv_id, diff --git a/backend/src/api/routes/assistant/_history.py b/backend/src/api/routes/assistant/_history.py index 06350358..aaeb740e 100644 --- a/backend/src/api/routes/assistant/_history.py +++ b/backend/src/api/routes/assistant/_history.py @@ -301,7 +301,7 @@ def _resolve_or_create_conversation( # @PRE db session is available and user_id references current actor scope. # @POST Messages older than ASSISTANT_MESSAGE_TTL_DAYS are removed from persistence and memory mirrors. def _cleanup_history_ttl(db: Session, user_id: str): - cutoff = datetime.now(UTC) - timedelta(days=ASSISTANT_MESSAGE_TTL_DAYS) + cutoff = datetime.now() - timedelta(days=ASSISTANT_MESSAGE_TTL_DAYS) try: query = db.query(AssistantMessageRecord).filter( AssistantMessageRecord.user_id == user_id, @@ -323,8 +323,11 @@ def _cleanup_history_ttl(db: Session, user_id: str): kept = [] for item in items: created_at = item.get("created_at") - if isinstance(created_at, datetime) and created_at < cutoff: - continue + if isinstance(created_at, datetime): + # DB stores naive datetimes; in-memory items may be aware (from datetime.now(UTC)) + ref = created_at.replace(tzinfo=None) if created_at.tzinfo else created_at + if ref < cutoff: + continue kept.append(item) if kept: CONVERSATIONS[key] = kept @@ -344,8 +347,10 @@ def _cleanup_history_ttl(db: Session, user_id: str): def _is_conversation_archived(updated_at: datetime | None) -> bool: if not updated_at: return False - cutoff = datetime.now(UTC) - timedelta(days=ASSISTANT_ARCHIVE_AFTER_DAYS) - return updated_at < cutoff + cutoff = datetime.now() - timedelta(days=ASSISTANT_ARCHIVE_AFTER_DAYS) + # Ensure both operands are naive (DB stores naive datetimes) + ref = updated_at.replace(tzinfo=None) if updated_at.tzinfo else updated_at + return ref < cutoff # #endregion _is_conversation_archived diff --git a/backend/src/plugins/llm_analysis/service.py b/backend/src/plugins/llm_analysis/service.py index 21424e4a..750577fc 100644 --- a/backend/src/plugins/llm_analysis/service.py +++ b/backend/src/plugins/llm_analysis/service.py @@ -1122,7 +1122,8 @@ class LLMClient: return model_ids except Exception as e: logger.warning( - f"[LLMClient.fetch_models] Failed to fetch models: {e}", + f"[LLMClient.fetch_models] Failed to fetch models.\n" + f"{self._format_connection_error(e)}", ) raise # endregion LLMClient.fetch_models