File size: 7,662 Bytes
0f261a1
97407fe
2d8971f
52a8efb
97407fe
dd9371d
97407fe
 
c556336
0f261a1
2d8971f
97407fe
 
2d8971f
97407fe
dd9371d
52a8efb
2d8971f
52a8efb
97407fe
2d8971f
 
 
 
 
 
 
97407fe
2d8971f
97407fe
2d8971f
 
97407fe
 
 
 
2d8971f
 
 
 
c556336
2d8971f
 
 
 
 
 
 
 
 
 
 
97407fe
 
 
2d8971f
97407fe
 
 
0f261a1
2d8971f
 
 
 
 
 
 
97407fe
2d8971f
 
 
 
 
 
 
52a8efb
2d8971f
52a8efb
2d8971f
 
52a8efb
 
2d8971f
 
 
 
 
 
 
 
 
 
 
 
 
 
52a8efb
 
2d8971f
 
 
 
 
 
52a8efb
0f261a1
2d8971f
c556336
2d8971f
52a8efb
c556336
2d8971f
97407fe
 
 
2d8971f
52a8efb
c556336
97407fe
2d8971f
 
97407fe
2d8971f
 
97407fe
 
 
2d8971f
c556336
2d8971f
 
c556336
2d8971f
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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 "⚠️ Отказ перезагрузки: Файл поврежден или пуст. Ядро продолжает работу на старой конфигурации из кэша."