Commit graph

9 commits

Author SHA1 Message Date
Jeuner
1656d26ea6 feat: numbered headlines + article-reference trigger ("Artikel 2")
Schlagzeilen aus read_news sind jetzt nummeriert (1., 2., 3. ...) statt
einer reinen Aufzählung, per <ol> im Frontend. astra/triggers.py bekommt
einen zweiten deterministischen Trigger, detect_article_reference():
erkennt "Artikel 2" / "Artikel Nummer drei" / "zweiter Artikel" /
"Detail zu Schlagzeile 4" (Ziffern und deutsche Zahlwörter 1-10) und
löst die Nummer gegen die zuletzt in dieser Session gezeigte
Schlagzeilenliste auf.

trigger_tool() gibt jetzt das Tool-Ergebnis zurück statt es nur in den
Kontext zu injizieren, damit server.py die read_news-Headlines (mit
Nummer + Link) für den Session-lokalen last_headlines-Zustand behalten
kann. Trifft der Artikel-Trigger, wird derselbe Mechanismus wie beim
News-Trigger genutzt, um read_article direkt mit dem passenden Link
aufzurufen.

Live per echtem Zwei-Turn-Dialog mit synthetisierter Sprache verifiziert
("Gib mir bitte die aktuellen News Hilden." → "Hole mir Detail zu
Artikel zwei."): vier nummerierte, klickbare Hilden-Schlagzeilen,
danach korrekt zu Artikel 2 aufgelöst und der echte volle Artikeltext
(Pedelec-Kurs-Details, Helmpflicht, Termine) abgerufen und angezeigt —
kein einziger erfundener Inhalt.

NICHT gepusht — weiterhin nur lokal, wie angewiesen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 18:56:20 +02:00
Jeuner
b0282900d2 feat: show RSS headlines as clickable links in the transcript
read_news bisher zeigte nur Klartext-Titel in der Karte an — man
konnte im Chat selbst nicht auf den Artikel klicken (nur das Modell
kannte den Link, für ein mögliches read_article danach). Jetzt trägt
der tool_result-Event zusätzlich strukturierte 'items' (source, title,
link), das Frontend rendert daraus eine Liste mit echten <a>-Links
statt reinem Text. read_article bleibt unverändert bei reinem Text
(items bleibt leer, kein Link zum Verlinken vorhanden).

Verifiziert: die Rendering-Logik direkt per simuliertem Datenkanal-
Event getestet (unabhängig von der Modell-Zuverlässigkeit, die bei
read_news aktuell bei ~15-20% liegt) — zwei Links korrekt mit href,
target=_blank, rel=noopener und Quelle gerendert.

NICHT gepusht — weiterhin nur lokal, wie angewiesen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 18:36:09 +02:00
Jeuner
0d2e826dd3 feat: read full article text via a new read_article tool
Löst die RSS-Beschränkung: Feed-Einträge liefern nur eine kurze
Teaser-Beschreibung, nie den vollen Artikeltext.

- astra/articles.py: lädt eine URL, extrahiert den reinen Fließtext via
  trafilatura (ohne Navigation/Werbung/Boilerplate). Live gegen einen
  echten RP-ONLINE-Artikel verifiziert (1682 Zeichen sauberer Text).
- read_article-Tool in tools.py, gleiches tool_start/tool_result/
  tool_error-Muster wie die anderen beiden Tools. Beschreibung nennt
  explizit den erwarteten Anwendungsfall (Nachfrage zu einer schon
  genannten Schlagzeile) — konsequent nach der zuletzt beschlossenen
  Regel, Tool-Verhalten nur in der FunctionSchema, nie im SYSTEM_PROMPT.

Gemessen: mit drei Tools sinkt read_news weiter auf ca. 15-20%
(vorher mit zwei Tools 20-50%). read_article selbst liegt bei einer
konkreten Nachfrage zu einer schon im Kontext stehenden Schlagzeile
bei ~65% (2/3 in einem Live-Lauf gegen echtes Ollama, echter
Artikeltext erfolgreich extrahiert; der eine Fehlschlag gab ehrlich
zu, den Volltext nicht zu kennen, statt zu erfinden).

NICHT gepusht — auf Anweisung bis auf Weiteres nur lokal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 18:26:02 +02:00
Jeuner
51f6824a5d feat: add a local Hilden news topic to read_news
Neues Thema 'hilden' via RP ONLINE (rp-online.de/nrw/staedte/hilden),
verifiziert live erreichbar mit echten aktuellen Artikeln. Anderer
RP-ONLINE-Pfad (kreis-mettmann/hilden) und der WZ-Feed lieferten beide
nur leere Channel-Shells, deshalb nicht mit aufgenommen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 18:11:19 +02:00
Jeuner
9f1e87434c refactor: move tool behavior out of SYSTEM_PROMPT into each tool's own description
Auf Wunsch: wie ein Tool funktioniert (wann aufrufen, wie mit dem
Ergebnis umgehen) gehört ausschließlich in seine FunctionSchema.description,
nie in den globalen SYSTEM_PROMPT. SYSTEM_PROMPT enthält jetzt nur noch
Astras Persona, Sprachausgabe-Formatierung und eine generische
"nutze Werkzeuge statt zu erfinden"-Zeile, die kein einzelnes Tool nennt.

Gemessener Preis dieser saubereren Trennung: read_news-Zuverlässigkeit
sinkt von ca. 60-75% (Tool-Regeln zusätzlich im System-Prompt dupliziert)
auf ca. 20-50% (Regeln nur noch in der Tool-Description). generate_image
bleibt bei ~100%, vermutlich weil es die einzige naheliegende Aktion für
Bild-Anfragen ist, während read_news mit generate_image um die
Modell-Aufmerksamkeit konkurriert. Bewusst so gebaut trotz der Zahlen —
Architekturentscheidung, keine Regression, die ich für einen Fehler halte.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 18:01:31 +02:00
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
Jeuner
76a5d4e499 feat: show the ComfyUI job itself in the transcript, not just a status line
Ein Statustext über dem Orb war zu unauffällig. Ein Tool-Aufruf legt
jetzt eine eigene Karte im Gesprächsverlauf an, mit Spinner und dem
tatsächlichen Prompt, den das LLM an ComfyUI schickt ("Anfrage an
ComfyUI: ..."). Sobald das Bild fertig ist, wird dieselbe Karte
in-place durch das Ergebnis ersetzt statt eine zweite anzuhängen;
schlägt ComfyUI fehl, zeigt sie stattdessen die Fehlermeldung.

Der Prompt-Text landet über sichere DOM-Erstellung (createTextNode),
nicht per innerHTML, weil er vom LLM erzeugt und damit indirekt
nutzerbeeinflusst ist.

Per Browser-Test mit echter synthetisierter Sprache verifiziert: Karte
mit Spinner + Prompt erscheint sofort, wird sauber (kein Leerlauf-id im
DOM) durch das Bild ersetzt, gefolgt von Astras Textantwort.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 16:48:17 +02:00
Jeuner
6854e80703 feat: show what Astra is doing while it works
Der Orb pulsierte bisher nur bei "listening"/"speaking", nicht während
"responding" — während des LLM-Aufrufs sah die UI eingefroren aus, obwohl
im Hintergrund gearbeitet wurde. Jetzt pulsiert er (und der Status-Punkt
leuchtet) auch während "responding".

Neuer "activity"-Nachrichtentyp überschreibt zusätzlich den Statustext
mit konkreten Zwischenschritten, ohne den Animationszustand zu brechen —
astra/tools.py meldet jetzt "Astra erzeugt ein Bild …" während ComfyUI
läuft, statt dass der Nutzer nur ein generisches "Einen Moment …" sieht,
das über die volle Dauer (LLM-Entscheidung, ComfyUI-Generierung,
LLM-Folgeantwort) unverändert bleibt.

Per Browser-Test mit echter synthetisierter Sprache verifiziert: die
Statuszeile durchläuft jetzt sichtbar "Einen Moment …" → "Astra erzeugt
ein Bild …" → "Einen Moment …" → "Astra spricht." statt derselben Zeile
über die ganze Anfrage hinweg.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 16:35:26 +02:00
Jeuner
16786fd3fd feat: image generation via ComfyUI and PDF/image upload
Zwei neue, sauber getrennte Fähigkeiten, jede in ihrem eigenen Modul:

- astra/comfyui.py: async HTTP-Client für einen lokalen ComfyUI-Server
  (z-image-turbo-Workflow), kennt nichts von der Pipeline.
- astra/documents.py: PDF-Textextraktion via pypdf, keine Netzwerkzugriffe.
- astra/tools.py: verdrahtet generate_image als natives Ollama-Tool-Call —
  Qwen 3.5 unterstützt Tools und Vision bereits nativ laut `ollama show`.

Dafür wurde astra/services.py so erweitert, dass NativeOllamaService
Ollamas native tool_calls im Streaming-Response erkennt und als
ChatCompletionChunk-Deltas an Pipecats bereits vorhandene, generische
Function-Calling-Maschinerie (_process_context/run_function_calls)
weiterreicht — die musste dafür nicht angefasst werden. trim_messages in
core.py bewahrt jetzt Tool-Roundtrips und Bild-Anhänge vollständig statt
sie auf role/content zu reduzieren.

Neuer Upload-Button im UI (Bild oder PDF, während eines laufenden
Gesprächs): PDFs gehen als Text, Bilder als Base64 über Qwens Vision in
den Gesprächskontext ein. Generierte Bilder werden über /api/media/<id>
ausgeliefert und per Datenkanal im Transkript angezeigt.

Kompletter Function-Calling-Roundtrip end-to-end gegen echtes Ollama und
echtes ComfyUI verifiziert (Modell ruft generate_image korrekt auf,
Bild wird erzeugt und im media_store abgelegt).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LVgSHNHdRx3UNTBodFmhRA
2026-09-07 15:56:18 +02:00