EOL: Final release
This commit is contained in:
@@ -2,16 +2,16 @@ import os
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
class LLMAcousticBridge:
|
class LLMAcousticBridge:
|
||||||
def __init__(self, model_name="dolphin-llama3:8b"):
|
def __init__(self, model_name: str = "dolphin-llama3:8b"):
|
||||||
self.model_name = model_name
|
self.model_name = model_name
|
||||||
base_url = os.getenv("OLLAMA_API_URL", "http://emom_ollama:11434")
|
base_url = os.getenv("OLLAMA_API_URL", "http://emom_ollama:11434")
|
||||||
self.api_url = f"{base_url}/api/generate"
|
self.api_url = f"{base_url}/api/generate"
|
||||||
|
|
||||||
def get_acoustic_profile(self, valence, arousal, semantics):
|
def get_acoustic_profile(self, valence: float, arousal: float, semantics: List[str]) -> Dict[str, float]:
|
||||||
context_str = ", ".join(semantics) if semantics else "abstract scene"
|
context_str = ", ".join(semantics) if semantics else "abstract scene"
|
||||||
|
|
||||||
prompt = f"""
|
prompt = f"""
|
||||||
Analyze the visual context and emotions to determine the ideal background music properties.
|
Analyze the visual context and emotions to determine the ideal background music properties.
|
||||||
Emotions: Valence {valence:.1f}/9.0 (Positivity), Arousal {arousal:.1f}/9.0 (Energy).
|
Emotions: Valence {valence:.1f}/9.0 (Positivity), Arousal {arousal:.1f}/9.0 (Energy).
|
||||||
@@ -34,32 +34,42 @@ class LLMAcousticBridge:
|
|||||||
"model": self.model_name,
|
"model": self.model_name,
|
||||||
"prompt": prompt,
|
"prompt": prompt,
|
||||||
"stream": False,
|
"stream": False,
|
||||||
"format": "json" # Принудительный JSON-режим Ollama
|
"format": "json",
|
||||||
|
"options": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.9
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"Запрос акустического профиля к Ollama...")
|
print(f"Запрос акустического профиля к Ollama...")
|
||||||
response = requests.post(self.api_url, json=payload, timeout=120)
|
response = requests.post(self.api_url, json=payload, timeout=120)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
if response.status_code == 200:
|
|
||||||
data = response.json()
|
data = response.json()
|
||||||
response_text = data.get("response", "")
|
response_text = data.get("response", "")
|
||||||
|
|
||||||
|
profile = {}
|
||||||
try:
|
try:
|
||||||
# 1. Попытка прямой десериализации
|
|
||||||
profile = json.loads(response_text)
|
profile = json.loads(response_text)
|
||||||
return profile
|
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
# 2. Аварийное извлечение JSON из текста с помощью регулярного выражения
|
|
||||||
match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
match = re.search(r'\{.*\}', response_text, re.DOTALL)
|
||||||
if match:
|
if match:
|
||||||
return json.loads(match.group(0))
|
profile = json.loads(match.group(0))
|
||||||
|
|
||||||
print(f"Ошибка парсинга LLM ответа: {response_text}")
|
|
||||||
return {}
|
|
||||||
else:
|
else:
|
||||||
print(f"Ollama вернула ошибку HTTP: {response.status_code}")
|
print(f"[ERROR] Ошибка парсинга LLM ответа: {response_text}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
# 3. Жесткая валидация ключей (чтобы matcher.py не упал на демо)
|
||||||
|
required_keys = ['energy', 'flux', 'centroid', 'pitch', 'hnr', 'zcr']
|
||||||
|
if profile and all(k in profile for k in required_keys):
|
||||||
|
return profile
|
||||||
|
else:
|
||||||
|
print(f"[WARN] LLM вернула неполный JSON, активирован fallback: {profile}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"[ERROR] Ошибка соединения с Ollama: {str(e)}")
|
||||||
|
return {}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка соединения с Ollama: {str(e)}")
|
print(f"[ERROR] Внутренняя ошибка семантического моста: {str(e)}")
|
||||||
return {}
|
return {}
|
||||||
@@ -78,6 +78,6 @@ class MusicMatcher:
|
|||||||
self.norm_db['acoustic_distance'] = acoustic_penalty / len(self.acoustic_features)
|
self.norm_db['acoustic_distance'] = acoustic_penalty / len(self.acoustic_features)
|
||||||
|
|
||||||
# Вычисление интегральной метрики соответствия (мультимодальный скоринг)
|
# Вычисление интегральной метрики соответствия (мультимодальный скоринг)
|
||||||
self.norm_db['final_score'] = self.norm_db['emo_distance'] + (self.norm_db['acoustic_distance'] * 4.0)
|
self.norm_db['final_score'] = self.norm_db['emo_distance'] + (self.norm_db['acoustic_distance'] * 2.0)
|
||||||
|
|
||||||
return self.norm_db.sort_values(by='final_score').head(top_k)
|
return self.norm_db.sort_values(by='final_score').head(top_k)
|
||||||
@@ -264,9 +264,9 @@
|
|||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"kernelspec": {
|
"kernelspec": {
|
||||||
"display_name": "Python (thesis)",
|
"display_name": "Python (my-python-project)",
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"name": "thesis"
|
"name": "my-python-project"
|
||||||
},
|
},
|
||||||
"language_info": {
|
"language_info": {
|
||||||
"codemirror_mode": {
|
"codemirror_mode": {
|
||||||
|
|||||||
Reference in New Issue
Block a user