initial commit
Build and Push Docker Images / build-and-push (push) Failing after 3m33s

This commit is contained in:
admin committed 2026-11-07 16:39:43 +03:00
commit 46af290db3
100 files changed
+16663

No files matched your search

+134
View File
@@ -0,0 +1,134 @@
import json
import re
from typing import Any, Dict, List, Optional
from models.bible import StoryBible
from services.llm_router import llm_router
class EntityExtractor:
def __init__(self):
self.llm_router = llm_router
def _build_extraction_prompt(
self, text: str, existing_bible: Optional[StoryBible]
) -> str:
"""Формирует промпт для извлечения сущностей"""
existing_chars = ""
existing_locs = ""
existing_events = ""
if existing_bible:
if existing_bible.characters:
existing_chars = "Известные персонажи: " + ", ".join(
[c.name for c in existing_bible.characters]
)
if existing_bible.locations:
existing_locs = "Известные локации: " + ", ".join(
[l.name for l in existing_bible.locations]
)
if existing_bible.events:
existing_events = "Известные события: " + ", ".join(
[e.title for e in existing_bible.events]
)
prompt = f"""Ты — эксперт по анализу художественных текстов. Твоя задача — извлечь из текста новых персонажей, локации и события.
ВАЖНО: Отвечай СТРОГО НА РУССКОМ ЯЗЫКЕ. Все имена, описания и названия должны быть на русском языке.
{existing_chars}
{existing_locs}
{existing_events}
ТЕКСТ ДЛЯ АНАЛИЗА:
---
{text}
---
ИНСТРУКЦИИ:
1. Извлеки ТОЛЬКО НОВЫЕ сущности, которых еще нет в списке известных
2. Для персонажей укажи: name, description (краткое), traits (список черт), appearance (если описана), role (protagonist/antagonist/supporting/minor)
3. Для локаций укажи: name, description, atmosphere (если есть), type (city/building/nature/interior/other)
4. Для событий укажи: title, description, importance (major/minor/background)
5. Если обнаружены новые черты для УЖЕ ИЗВЕСТНЫХ персонажей, укажи их в updated_traits
6. НЕ выдумывай информацию, которой нет в тексте
7. Если новых сущностей нет, верни пустые списки
8. Все ответы СТРОГО НА РУССКОМ ЯЗЫКЕ
ОТВЕТЬ СТРОГО В ФОРМАТЕ JSON (без markdown, без пояснений, только чистый JSON):
{{
"new_characters": [
{{"name": "...", "description": "...", "traits": ["...", "..."], "appearance": "...", "role": "..."}}
],
"new_locations": [
{{"name": "...", "description": "...", "atmosphere": "...", "type": "..."}}
],
"new_events": [
{{"title": "...", "description": "...", "importance": "..."}}
],
"updated_traits": [
{{"character_name": "...", "new_traits": ["...", "..."]}}
]
}}"""
return prompt
def _parse_json_response(self, response_text: str) -> Dict[str, Any]:
"""Парсит JSON из ответа модели, обрабатывая возможные markdown обертки"""
# Убираем возможные markdown обертки
cleaned = response_text.strip()
# Убираем ```json и ```
if cleaned.startswith("```"):
cleaned = re.sub(r"^```(?:json)?\s*", "", cleaned)
cleaned = re.sub(r"\s*```$", "", cleaned)
# Пытаемся найти JSON в тексте
json_match = re.search(r"\{[\s\S]*\}", cleaned)
if json_match:
cleaned = json_match.group(0)
try:
return json.loads(cleaned)
except json.JSONDecodeError as e:
raise ValueError(
f"Failed to parse JSON from LLM response: {e}\n"
f"Raw response: {response_text[:500]}"
)
async def extract_entities(
self,
text: str,
existing_bible: Optional[StoryBible] = None,
provider: str = "openrouter",
model: Optional[str] = None,
) -> Dict[str, Any]:
"""Извлекает сущности из текста"""
prompt = self._build_extraction_prompt(text, existing_bible)
# Используем пресет editor для точности (низкая температура)
generated_text = await self.llm_router.generate(
prompt=prompt,
provider=provider,
model=model,
preset="editor",
)
parsed = self._parse_json_response(generated_text)
# Валидация и нормализация ответа
result = {
"new_characters": parsed.get("new_characters", []),
"new_locations": parsed.get("new_locations", []),
"new_events": parsed.get("new_events", []),
"updated_traits": parsed.get("updated_traits", []),
}
return result
# Singleton instance
entity_extractor = EntityExtractor()