Files
fckbot/rag/utils/config_loader.py
Markov Andrey 67d86d6c1d Update 5 files
- /rag/config_models.py
- /rag/utils/config_loader.py
- /rag/prompt_builder.py
- /rag/query_processor.py
- /template_bot_profile/data/fewshot_examples.json
2026-06-30 14:57:03 +00:00

155 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
Загрузчик конфигурации с использованием Pydantic.
Читает YAML-файлы (bot.conf и rag.conf), подставляет переменные окружения,
создаёт экземпляр AppConfig.
"""
import os
import yaml
import logging
from pathlib import Path
from typing import Dict, Any
from dotenv import load_dotenv
from ..config_models import AppConfig
logger = logging.getLogger(__name__)
# Загружаем переменные окружения из .env
load_dotenv()
def load_config(profile_dir: str) -> AppConfig:
"""
Загружает конфигурацию из профиля.
Аргументы:
profile_dir: путь к каталогу профиля бота (например, /usr/local/etc/bots/metabot)
Возвращает:
AppConfig: экземпляр модели конфигурации.
"""
profile_path = Path(profile_dir).resolve()
bot_config_path = profile_path / "bot.conf"
if not bot_config_path.exists():
raise FileNotFoundError(f"Файл конфигурации бота не найден: {bot_config_path}")
# Загружаем bot.conf
with open(bot_config_path, 'r', encoding='utf-8') as f:
bot_raw = yaml.safe_load(f) or {}
# Загружаем rag.conf (общий для всех ботов) ищем в родительском каталоге или рядом
rag_config_path = profile_path.parent / "rag" / "rag.conf"
if not rag_config_path.exists():
# fallback: возможно, rag.conf лежит в той же директории, что и profile
rag_config_path = profile_path / "rag.conf"
if not rag_config_path.exists():
raise FileNotFoundError(f"Файл rag.conf не найден: {rag_config_path}")
with open(rag_config_path, 'r', encoding='utf-8') as f:
rag_raw = yaml.safe_load(f) or {}
# Глубокое слияние: bot.conf имеет приоритет над rag.conf
merged = deep_merge(rag_raw, bot_raw)
# Извлекаем переменные окружения
env_vars = {
'XMPP_PASSWORD': os.getenv('XMPP_PASSWORD'),
'DB_PASSWORD': os.getenv('DB_PASSWORD'),
'GIGACHAT_API_KEY': os.getenv('GIGACHAT_API_KEY'),
'SALUTE_SPEECH_AUTH': os.getenv('SALUTE_SPEECH_AUTH'),
}
# Подставляем пароли и ключи в секции
if 'database' in merged and 'password_env' in merged['database']:
env_name = merged['database']['password_env']
merged['database']['password'] = env_vars.get(env_name)
if not merged['database']['password']:
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
del merged['database']['password_env']
if 'gigachat' in merged and 'credentials_env' in merged['gigachat']:
env_name = merged['gigachat']['credentials_env']
merged['gigachat']['api_key'] = env_vars.get(env_name)
if not merged['gigachat']['api_key']:
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
del merged['gigachat']['credentials_env']
if 'password_env' in merged:
env_name = merged['password_env']
merged['xmpp_password'] = env_vars.get(env_name)
if not merged['xmpp_password']:
raise ValueError(f"Переменная окружения {env_name} не задана или пуста")
del merged['password_env']
merged['salute_speech_auth'] = env_vars.get('SALUTE_SPEECH_AUTH')
# Преобразуем пути в абсолютные с учётом profile_dir
paths_section = merged.get('paths', {})
for key in ['data_dir', 'temp_dir', 'prompts_dir', 'upload_dir', 'log_dir']:
if key in paths_section:
p = Path(paths_section[key])
if not p.is_absolute():
paths_section[key] = str(profile_path / p)
# Добавляем profile_dir
merged['profile_dir'] = str(profile_path)
# Добавляем web_scraper (если отсутствует)
if 'web_scraper' not in merged:
merged['web_scraper'] = {'max_pages': 5, 'max_depth': 1}
# Добавляем секцию auth, если её нет
if 'auth' not in merged:
merged['auth'] = {}
# Добавляем секцию fewshot, если её нет
if 'fewshot' not in merged:
merged['fewshot'] = {}
# Создаём экземпляр AppConfig
try:
config = AppConfig(**merged)
except Exception as e:
raise ValueError(f"Ошибка валидации конфигурации: {e}")
# Загружаем API-ключ для RAG-сервера
api_key_env = merged.get('auth', {}).get('api_key_env', 'RAG_API_KEY')
config._rag_api_key = os.getenv(api_key_env)
if not config._rag_api_key:
logger.warning(f"Переменная окружения {api_key_env} не задана. Аутентификация на RAG-сервере отключена (НЕ БЕЗОПАСНО!)")
# Загружаем промпты из файлов
prompts_dir = config.prompts_dir_path
if prompts_dir and prompts_dir.exists():
prompts_content = {}
for prompt_name, filename in config.ai.prompts.items():
file_path = prompts_dir / filename
if file_path.exists():
try:
with open(file_path, 'r', encoding='utf-8') as f:
prompts_content[prompt_name] = f.read()
except Exception as e:
logger.warning(f"Не удалось загрузить промпт {prompt_name} из {file_path}: {e}")
else:
logger.warning(f"Файл промпта {filename} не найден в {prompts_dir}")
config.prompts_content = prompts_content
else:
logger.warning(f"Каталог промптов не существует: {prompts_dir}")
return config
def deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
"""
Рекурсивное слияние словарей: override имеет приоритет над base.
"""
result = base.copy()
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = value
return result