astra-vision/astra/tools.py
Jeuner e7760688f0 feat: read German news from curated RSS feeds
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
2026-09-07 17:55:24 +02:00

102 lines
4.1 KiB
Python

"""LLM-callable tools. Each tool owns its handler; server.py only wires in
per-session dependencies (notify, media_store) and hands the result to pipecat.
"""
import uuid
from collections.abc import Callable
from pipecat.adapters.schemas.function_schema import FunctionSchema
from pipecat.adapters.schemas.tools_schema import ToolsSchema
from astra.comfyui import ComfyUIError, generate_image
from astra.core import Settings
from astra.feeds import TOPICS
from astra.rss import RSSError, read_topic
def build_tools(config: Settings, notify: Callable[[dict], None], media_store: dict[str, bytes]) -> ToolsSchema:
"""Assemble the tool set available to the LLM for one session."""
async def handle_generate_image(params):
prompt = params.arguments.get("prompt", "")
notify({"type": "activity", "text": "Astra erzeugt ein Bild …"})
notify({"type": "tool_start", "text": f'Anfrage an ComfyUI: "{prompt}"'})
try:
image = await generate_image(config.comfyui_url, prompt)
except ComfyUIError as exc:
notify({"type": "activity", "text": "Bilderzeugung fehlgeschlagen."})
notify({"type": "tool_error", "text": f"ComfyUI-Anfrage fehlgeschlagen: {exc}"})
await params.result_callback({"error": str(exc)})
return
image_id = uuid.uuid4().hex
media_store[image_id] = image
notify({"type": "image", "url": f"/api/media/{image_id}"})
await params.result_callback(
{"status": "ok", "message": "Bild wurde erzeugt und dem Nutzer angezeigt."}
)
async def handle_read_news(params):
topic = params.arguments.get("topic", "")
notify({"type": "activity", "text": "Astra liest Nachrichten …"})
notify({"type": "tool_start", "text": f'Lese RSS-Feeds: "{topic}"'})
try:
entries = await read_topic(topic)
except RSSError as exc:
notify({"type": "activity", "text": "Nachrichten konnten nicht geladen werden."})
notify({"type": "tool_error", "text": str(exc)})
await params.result_callback({"error": str(exc)})
return
headlines = "\n".join(f"- ({entry.source}) {entry.title}" for entry in entries)
notify({"type": "tool_result", "text": headlines})
await params.result_callback(
{
"status": "ok",
"headlines": [
{"source": e.source, "title": e.title, "summary": e.summary, "link": e.link}
for e in entries
],
}
)
read_news_schema = FunctionSchema(
name="read_news",
description=(
"Lies aktuelle Schlagzeilen aus kuratierten deutschen RSS-Feeds zu einem "
"Themenbereich vor. Nutze dieses Werkzeug, wenn der Nutzer nach aktuellen "
"Nachrichten, News oder was gerade in einem Themenbereich passiert, fragt."
),
properties={
"topic": {
"type": "string",
"enum": list(TOPICS),
"description": (
"'tech' für Technologie & KI, 'nachrichten' für allgemeine "
"Tagesnachrichten, 'wirtschaft' für Wirtschaftsnews."
),
},
},
required=["topic"],
handler=handle_read_news,
)
generate_image_schema = FunctionSchema(
name="generate_image",
description=(
"Erzeuge ein Bild anhand einer Beschreibung und zeige es dem Nutzer an. "
"Nutze dieses Werkzeug, wenn der Nutzer explizit ein Bild, eine Grafik "
"oder eine Illustration wünscht."
),
properties={
"prompt": {
"type": "string",
"description": (
"Konkrete, szenenorientierte Bildbeschreibung auf Englisch, "
"mit Hinweisen zu Licht und Kamera, z.B. "
"'close-up, cinematic light, warm tones'."
),
},
},
required=["prompt"],
handler=handle_generate_image,
)
return ToolsSchema(standard_tools=[generate_image_schema, read_news_schema])