mirror of
https://github.com/Jeuners/astra-vision.git
synced 2026-09-09 15:02:35 +02:00
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
72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
import httpx
|
|
import pytest
|
|
|
|
from astra.articles import ArticleError, read_article
|
|
|
|
SAMPLE_HTML = b"""<!doctype html>
|
|
<html><head><title>Testartikel</title></head>
|
|
<body>
|
|
<nav>Navigation, sollte nicht im Ergebnis landen</nav>
|
|
<article>
|
|
<h1>Ein wichtiger Titel</h1>
|
|
<p>Dies ist der erste Absatz des eigentlichen Artikeltexts, lang genug fuer
|
|
eine sinnvolle Extraktion durch trafilatura, mit mehreren Saetzen.</p>
|
|
<p>Und hier folgt ein zweiter Absatz mit weiterem Inhalt, damit die
|
|
Extraktion genug Text zum Erkennen des Hauptinhalts hat.</p>
|
|
</article>
|
|
<footer>Footer-Boilerplate, sollte auch nicht im Ergebnis landen</footer>
|
|
</body></html>"""
|
|
|
|
|
|
def _patched_client(monkeypatch, handler):
|
|
real_async_client = httpx.AsyncClient
|
|
|
|
def factory(*args, **kwargs):
|
|
kwargs["transport"] = httpx.MockTransport(handler)
|
|
return real_async_client(**kwargs)
|
|
|
|
monkeypatch.setattr(httpx, "AsyncClient", factory)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_article_extracts_main_text(monkeypatch):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, content=SAMPLE_HTML)
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
text = await read_article("https://example.com/article")
|
|
|
|
assert "wichtiger Titel" in text or "erste Absatz" in text
|
|
assert "Navigation" not in text
|
|
assert "Footer-Boilerplate" not in text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_article_respects_max_chars(monkeypatch):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, content=SAMPLE_HTML)
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
text = await read_article("https://example.com/article", max_chars=20)
|
|
|
|
assert len(text) <= 20
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_article_raises_when_page_unreachable(monkeypatch):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
raise httpx.ConnectError("refused", request=request)
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
with pytest.raises(ArticleError):
|
|
await read_article("https://example.com/article")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_read_article_raises_when_no_text_extractable(monkeypatch):
|
|
def handler(request: httpx.Request) -> httpx.Response:
|
|
return httpx.Response(200, content=b"<html><body></body></html>")
|
|
|
|
_patched_client(monkeypatch, handler)
|
|
with pytest.raises(ArticleError):
|
|
await read_article("https://example.com/article")
|