Spaces:
Sleeping
Sleeping
| import json | |
| import logging | |
| import asyncio | |
| from pathlib import Path | |
| from typing import Dict, Any, Optional | |
| # Настройка локального логгера для сервера | |
| logger = logging.getLogger("CyberAgentFactory") | |
| class AssistantFactory: | |
| def __init__(self, logger_agent=None, rules_path: str = 'api/coding_rules.json'): | |
| """ | |
| Фабрика для сборки контекста и системных инструкций ИИ-агента. | |
| Реализована поддержка неблокирующего чтения конфигураций. | |
| """ | |
| self.logger_agent = logger_agent | |
| self.rules_path = Path(rules_path) | |
| # Кэш в оперативной памяти для исключения избыточного дискового I/O | |
| self._cached_rules: Optional[Dict[str, Any]] = None | |
| # Блокировка для предотвращения Race Condition при горячей перезагрузке кэша | |
| self._lock = asyncio.Lock() | |
| def _read_file_sync(self) -> Dict[str, Any]: | |
| """Синхронный низкоуровневый запуск чтения (для выполнения в отдельном потоке)""" | |
| with open(self.rules_path, 'r', encoding='utf-8') as file: | |
| return json.load(file) | |
| async def _load_rules(self) -> Dict[str, Any]: | |
| """ | |
| Безопасный асинхронный метод загрузки и кэширования правил. | |
| Выносит дисковое чтение из основного event loop приложения FastAPI. | |
| """ | |
| if self._cached_rules is not None: | |
| return self._cached_rules | |
| async with self._lock: | |
| # Двойная проверка (Double-checked locking паттерн) | |
| if self._cached_rules is not None: | |
| return self._cached_rules | |
| if not self.rules_path.exists(): | |
| warn_msg = f"Файл гайдлайнов не найден по пути: {self.rules_path}. Используется дефолтный промпт." | |
| logger.warning(warn_msg) | |
| if self.logger_agent and hasattr(self.logger_agent, 'log'): | |
| self.logger_agent.log('factory_warning', warn_msg) | |
| self._cached_rules = {} | |
| return self._cached_rules | |
| try: | |
| # Делегируем блокирующий ввод-вывод в пул потоков операционной системы | |
| self._cached_rules = await asyncio.to_thread(self._read_file_sync) | |
| log_msg = f"Конфигурация правил успешно инициализирована из {self.rules_path} и кэширована." | |
| logger.info(log_msg) | |
| if self.logger_agent and hasattr(self.logger_agent, 'log'): | |
| self.logger_agent.log('factory_init', log_msg) | |
| return self._cached_rules | |
| except json.JSONDecodeError as e: | |
| err_msg = f"Критическая ошибка синтаксиса JSON в {self.rules_path}: {str(e)}. Активирован защитный режим." | |
| logger.error(err_msg) | |
| if self.logger_agent and hasattr(self.logger_agent, 'log'): | |
| self.logger_agent.log('factory_json_error', err_msg) | |
| self._cached_rules = {} | |
| return self._cached_rules | |
| except Exception as e: | |
| err_msg = f"Непредвиденный сбой ввода-вывода при чтении {self.rules_path}: {str(e)}" | |
| logger.error(err_msg) | |
| if self.logger_agent and hasattr(self.logger_agent, 'log'): | |
| self.logger_agent.log('factory_io_error', err_msg) | |
| self._cached_rules = {} | |
| return self._cached_rules | |
| def _format_rules_to_markdown(self, rules: Dict[str, Any], depth: int = 2) -> str: | |
| """ | |
| Рекурсивно преобразует JSON-словарь любой вложенности в валидный Markdown список. | |
| Максимизирует показатель Prompt Adherence для LLM-моделей. | |
| """ | |
| md_lines = [] | |
| prefix = "#" * min(depth, 6) | |
| if not isinstance(rules, dict): | |
| return str(rules) | |
| for key, val in rules.items(): | |
| display_key = key.replace("_", " ").capitalize() | |
| if isinstance(val, dict): | |
| md_lines.append(f"\n{prefix} {display_key}") | |
| md_lines.append(self._format_rules_to_markdown(val, depth + 1)) | |
| elif isinstance(val, list): | |
| md_lines.append(f"\n{prefix} {display_key}:") | |
| for item in val: | |
| md_lines.append(f" - {item}") | |
| else: | |
| # Базовый тип данных (строка, число, булево) | |
| if depth == 2: | |
| md_lines.append(f"**{display_key}**: {val}") | |
| else: | |
| md_lines.append(f" * **{key}**: {val}") | |
| return "\n".join(md_lines) | |
| async def create_assistant_prompt(self, user_prompt: str) -> str: | |
| """ | |
| Формирует структурированный системный промпт, изолируя правила безопасности | |
| в оптимизированные под LLM-инференс Markdown теги. | |
| """ | |
| rules = await self._load_rules() | |
| if not rules: | |
| return user_prompt | |
| # Трансляция древовидной JSON-структуры в читаемый ИИ-моделями Markdown-контекст | |
| formatted_rules = self._format_rules_to_markdown(rules) | |
| system_prompt = ( | |
| "### SYSTEM SECURITY GUARDRAILS AND OPERATIONAL RULES ###\n" | |
| f"{formatted_rules}\n" | |
| "### END OF GUARDRAILS ###\n\n" | |
| "Текущая операционная задача и контекст окружения:\n" | |
| f"{user_prompt}" | |
| ) | |
| return system_prompt | |
| async def hot_reload_rules(self) -> str: | |
| """ | |
| Метод 'горячей перезагрузки'. Атомарно обновляет правила в памяти | |
| на лету без блокирования асинхронного цикла обработки входящих вебхуков. | |
| """ | |
| async with self._lock: | |
| old_cache = self._cached_rules | |
| self._cached_rules = None | |
| # Принудительный вызов загрузки через пул потоков | |
| new_rules = await self._load_rules() | |
| if new_rules and len(new_rules) > 0: | |
| return "🔄 Контур управления: Кэш сброшен. Новые правила успешно развернуты в оперативной памяти!" | |
| # В случае повреждения структуры файла возвращаем стабильный старый кэш | |
| self._cached_rules = old_cache | |
| return "⚠️ Отказ перезагрузки: Файл поврежден или пуст. Ядро продолжает работу на старой конфигурации из кэша." | |