[update] added gemma2:9b model to example.env, refactored prompt generation with stricter JSON rules, adjusted context size, integrated system prompt for better summaries, and improved error handling in backend services
This commit is contained in:
@@ -8,7 +8,7 @@ MIN_CRON_HOURS = float(os.getenv("MIN_CRON_HOURS", 0.5))
|
||||
DEFAULT_CRON_HOURS = float(os.getenv("CRON_HOURS", MIN_CRON_HOURS))
|
||||
CRON_HOURS = max(MIN_CRON_HOURS, DEFAULT_CRON_HOURS)
|
||||
SYNC_COOLDOWN_MINUTES = int(os.getenv("SYNC_COOLDOWN_MINUTES", 30))
|
||||
LLM_MODEL = os.getenv("LLM_MODEL", "phi3:3.8b-mini-128k-instruct-q4_0")
|
||||
LLM_MODEL = os.getenv("LLM_MODEL", "gemma2:9b")
|
||||
LLM_TIMEOUT_SECONDS = int(os.getenv("LLM_TIMEOUT_SECONDS", 180))
|
||||
OLLAMA_API_TIMEOUT_SECONDS = int(os.getenv("OLLAMA_API_TIMEOUT_SECONDS", 10))
|
||||
ARTICLE_FETCH_TIMEOUT = int(os.getenv("ARTICLE_FETCH_TIMEOUT", 30))
|
||||
|
||||
@@ -119,12 +119,11 @@ class NewsFetcher:
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"❌ Error fetching article content from {url}: {
|
||||
type(e).__name__}: {e}")
|
||||
type(e).__name__}: {e}")
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def build_prompt(
|
||||
url: str,
|
||||
title: str = "",
|
||||
summary: str = "",
|
||||
content: str = "") -> str:
|
||||
@@ -132,14 +131,13 @@ class NewsFetcher:
|
||||
Generate a prompt for the LLM to summarize an article.
|
||||
|
||||
Args:
|
||||
url: Public URL of the article to summarize
|
||||
title: Article title from RSS feed (optional)
|
||||
summary: Article summary from RSS feed (optional)
|
||||
content: Extracted article content (optional)
|
||||
|
||||
Returns:
|
||||
A formatted prompt string that instructs the LLM to generate
|
||||
a JSON response with title and summaries in German and English
|
||||
a JSON response with title, summary and tags in German
|
||||
"""
|
||||
context_info = []
|
||||
if title:
|
||||
@@ -148,28 +146,42 @@ class NewsFetcher:
|
||||
context_info.append(f"RSS-Beschreibung: {summary}")
|
||||
if content:
|
||||
content_preview = content[:500] + \
|
||||
"..." if len(content) > 500 else content
|
||||
"..." if len(content) > 500 else content
|
||||
context_info.append(f"Artikel-Inhalt: {content_preview}")
|
||||
|
||||
context = "\n".join(
|
||||
context_info) if context_info else "Keine zusätzlichen Informationen verfügbar."
|
||||
|
||||
return (
|
||||
"### Aufgabe\n"
|
||||
f"Du sollst eine Nachricht basierend auf der URL und den verfügbaren Informationen zusammenfassen.\n"
|
||||
f"URL: {url}\n"
|
||||
f"Verfügbare Informationen:\n{context}\n\n"
|
||||
"### Vorliegende Informationen\n"
|
||||
f"{context}\n\n"
|
||||
"### Längenbegrenzungen\n"
|
||||
"title: max 100 Zeichen\n"
|
||||
"summary: 100–160 Wörter\n"
|
||||
"tags: bis zu 6 Schlüsselwörter, durch Komma getrennt, alles Kleinbuchstaben.\n\n"
|
||||
"### Regeln\n"
|
||||
"1. Nutze VORRANGIG den Artikel-Inhalt falls verfügbar, ergänze mit RSS-Informationen\n"
|
||||
"2. Falls kein Artikel-Inhalt verfügbar ist, nutze RSS-Titel und -Beschreibung\n"
|
||||
"3. Falls keine ausreichenden Informationen vorliegen, erstelle eine plausible Zusammenfassung basierend auf der URL\n"
|
||||
"4. Gib ausschließlich **gültiges minifiziertes JSON** zurück – kein Markdown, keine Kommentare\n"
|
||||
"5. Struktur: {\"title\":\"…\",\"summary\":\"…\"}\n"
|
||||
"6. title: Aussagekräftiger deutscher Titel (max 100 Zeichen)\n"
|
||||
"7. summary: Deutsche Zusammenfassung (zwischen 100 und 160 Wörter)\n"
|
||||
"8. Kein Text vor oder nach dem JSON\n\n"
|
||||
"1. Nutze ausschließlich Informationen, die im bereitgestellten Material eindeutig vorkommen. Externes Wissen ist untersagt.\n"
|
||||
"2. Liegt sowohl Artikel-Text als auch RSS-Metadaten vor, hat der Artikel-Text Vorrang; verwende RSS nur ergänzend.\n"
|
||||
"3. Liegt nur RSS-Titel und/oder -Beschreibung vor, stütze dich ausschließlich darauf.\n"
|
||||
"4. Sind die Informationen unzureichend, gib exakt {\"title\":\"\",\"summary\":\"\",\"tags\":\"\"} zurück.\n"
|
||||
"5. Gib nur gültiges, minifiziertes JSON zurück – keine Zeilenumbrüche, kein Markdown, keine Kommentare.\n"
|
||||
"6. Verwende keine hypothetischen Formulierungen (\"könnte\", \"möglicherweise\" etc.).\n"
|
||||
"7. Wörtliche Zitate dürfen höchstens 15 % des Summary-Texts ausmachen.\n"
|
||||
"8. Kein Text vor oder nach dem JSON.\n\n"
|
||||
"### Ausgabe\n"
|
||||
"Jetzt antworte mit dem JSON:"
|
||||
"Antworte jetzt ausschließlich mit dem JSON:\n"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def build_system_prompt():
|
||||
return (
|
||||
"Du bist ein hochpräziser JSON-Summarizer und Experte für die Zusammenfassung von Artikeln.\n\n"
|
||||
"### Vorgehen\n"
|
||||
"Schritt 1: Identifiziere Hauptthema und Zweck.\n"
|
||||
"Schritt 2: Extrahiere die wichtigsten Fakten und Ergebnisse.\n"
|
||||
"Schritt 3: Erkenne die zentralen Argumente und Standpunkte.\n"
|
||||
"Schritt 4: Ordne die Informationen nach Wichtigkeit.\n"
|
||||
"Schritt 5: Erstelle eine prägnante, klare und sachliche Zusammenfassung.\n\n"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@@ -199,17 +211,39 @@ class NewsFetcher:
|
||||
logger.warning(
|
||||
f"⚠️ Could not fetch article content, using RSS data only")
|
||||
|
||||
prompt = NewsFetcher.build_prompt(
|
||||
url, title, summary, article_content)
|
||||
prompt = NewsFetcher.build_prompt(title, summary, article_content)
|
||||
system_prompt = NewsFetcher.build_system_prompt()
|
||||
payload = {
|
||||
"model": LLM_MODEL,
|
||||
"prompt": prompt,
|
||||
"system": system_prompt,
|
||||
"stream": False,
|
||||
"temperature": 0.1,
|
||||
"format": "json",
|
||||
"format": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"summary",
|
||||
"tags"
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"num_gpu": 1, # Force GPU usage
|
||||
"num_ctx": 64_000, # Context size
|
||||
"num_ctx": 8192, # Context size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +297,7 @@ class NewsFetcher:
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"❌ Unexpected error summarizing {url}: {
|
||||
type(e).__name__}: {e}")
|
||||
type(e).__name__}: {e}")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@@ -300,7 +334,7 @@ class NewsFetcher:
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"❌ Critical error during harvest: {
|
||||
type(e).__name__}: {e}")
|
||||
type(e).__name__}: {e}")
|
||||
raise
|
||||
|
||||
@staticmethod
|
||||
@@ -327,18 +361,18 @@ class NewsFetcher:
|
||||
if hasattr(feed_data, 'bozo') and feed_data.bozo:
|
||||
logger.warning(
|
||||
f"⚠️ Feed has parsing issues: {
|
||||
feed_row['url']}")
|
||||
feed_row['url']}")
|
||||
if hasattr(feed_data, 'bozo_exception'):
|
||||
logger.warning(
|
||||
f"⚠️ Feed exception: {
|
||||
feed_data.bozo_exception}")
|
||||
feed_data.bozo_exception}")
|
||||
|
||||
total_entries = len(feed_data.entries)
|
||||
|
||||
if total_entries == 0:
|
||||
logger.warning(
|
||||
f"⚠️ No entries found in feed: {
|
||||
feed_row['url']}")
|
||||
feed_row['url']}")
|
||||
return stats
|
||||
|
||||
for i, entry in enumerate(feed_data.entries, 1):
|
||||
@@ -403,8 +437,9 @@ class NewsFetcher:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT
|
||||
OR IGNORE INTO news
|
||||
(title, summary, url, published, country)
|
||||
OR IGNORE
|
||||
INTO news
|
||||
(title, summary, url, published, country)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
@@ -429,7 +464,7 @@ class NewsFetcher:
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"❌ Error processing feed {
|
||||
feed_row['url']}: {
|
||||
type(e).__name__}: {e}")
|
||||
feed_row['url']}: {
|
||||
type(e).__name__}: {e}")
|
||||
|
||||
return stats
|
||||
|
||||
@@ -16,6 +16,7 @@ LLM_MODEL=phi3:3.8b-mini-128k-instruct-q4_0 # ca 6-8GB (langer kontext)
|
||||
LLM_MODEL=mistral-nemo:12b # ca 16-24+GB
|
||||
LLM_MODEL=cnjack/mistral-samll-3.1:24b-it-q4_K_S # ca 22GB
|
||||
LLM_MODEL=yarn-mistral:7b-64k-q4_K_M # ca 11GB
|
||||
LLM_MODEL=gemma2:9b # ca 8GB
|
||||
|
||||
# Timeout in seconds for LLM requests
|
||||
LLM_TIMEOUT_SECONDS=180
|
||||
|
||||
Reference in New Issue
Block a user