mirror of
https://github.com/Jeuners/astra-vision.git
synced 2026-09-09 15:02:35 +02:00
Neues Werkzeug read_news, gleiche Trennung wie ComfyUI: - astra/feeds.py: kuratierte deutsche Feed-Liste nach Thema (tech, nachrichten, wirtschaft) — 11 Feeds, alle live gegen die echten Endpunkte verifiziert (heise, Golem, t3n, netzpolitik.org, tagesschau.de, Zeit Online, Spiegel, SZ, Handelsblatt, WiWo, manager magazin). - astra/rss.py: async Feed-Client (feedparser), holt Feeds eines Themas parallel ab, überspringt einzelne nicht erreichbare Feeds statt komplett zu scheitern. - astra/tools.py: read_news als natives Ollama-Tool, gleiches tool_start/tool_result/tool_error-UI-Muster wie generate_image. Mit zwei Tools gleichzeitig verfügbar sank die Zuverlässigkeit des lokalen 9.7B-Modells spürbar (0/8 Tool-Aufrufe mit dem ursprünglichen, langen System-Prompt) — reproduzierbar isoliert über direkte Ollama-Requests. Ursache: Instruction-Dilution bei mehreren Tool-Regeln in einem langen Prompt. Behoben durch gestrafften, Tool-Regeln-zuerst-Prompt und Temperatur 0.6→0.4: Bildgenerierung bleibt ~100% zuverlässig, Nachrichten-Tool bei ~60–75% je nach Formulierung. Per echtem Browser-Test mit synthetisierter Sprache verifiziert: echte aktuelle Schlagzeilen erscheinen im Transkript. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
"""Async reader for the curated RSS/Atom feeds. Knows nothing about the voice pipeline."""
|
|
|
|
import asyncio
|
|
import re
|
|
from dataclasses import dataclass
|
|
|
|
import feedparser
|
|
import httpx
|
|
|
|
from astra.feeds import FEEDS
|
|
|
|
USER_AGENT = "Mozilla/5.0 (compatible; AstraVoice/1.0; +https://github.com/Jeuners/astra-vision)"
|
|
|
|
|
|
class RSSError(Exception):
|
|
"""Raised when no feed for a topic could be read."""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Entry:
|
|
source: str
|
|
title: str
|
|
summary: str
|
|
link: str
|
|
|
|
|
|
def _clean_summary(html: str, max_chars: int = 220) -> str:
|
|
text = re.sub(r"<[^>]+>", "", html or "").strip()
|
|
return text[:max_chars]
|
|
|
|
|
|
async def _fetch_one(client: httpx.AsyncClient, feed: dict, per_feed: int) -> list[Entry]:
|
|
try:
|
|
response = await client.get(feed["url"])
|
|
response.raise_for_status()
|
|
except httpx.HTTPError:
|
|
return []
|
|
parsed = feedparser.parse(response.content)
|
|
return [
|
|
Entry(
|
|
source=feed["name"],
|
|
title=item.get("title", "").strip(),
|
|
summary=_clean_summary(item.get("summary", "")),
|
|
link=item.get("link", ""),
|
|
)
|
|
for item in parsed.entries[:per_feed]
|
|
]
|
|
|
|
|
|
async def read_topic(topic: str, *, per_feed: int = 4, total: int = 10) -> list[Entry]:
|
|
"""Fetch the latest entries for one configured topic, feeds in parallel."""
|
|
feeds = FEEDS.get(topic)
|
|
if not feeds:
|
|
raise RSSError(f"Unbekanntes Thema: {topic}")
|
|
async with httpx.AsyncClient(
|
|
headers={"User-Agent": USER_AGENT}, timeout=10, follow_redirects=True
|
|
) as client:
|
|
results = await asyncio.gather(*(_fetch_one(client, feed, per_feed) for feed in feeds))
|
|
entries = [entry for group in results for entry in group]
|
|
if not entries:
|
|
raise RSSError(f'Keine Feeds für "{topic}" waren erreichbar.')
|
|
return entries[:total]
|