fix(eu): drei Fehler beheben, die den Ertrag des Faktenchecks aufzehrten
Der Vergleichslauf zur Innenministerkonferenz (Lage 49) zeigte, dass der verbesserte Faktencheck zwar zehn Fakten lieferte, davon aber nur drei uebrig blieben. Die Ursachen lagen in drei nachgelagerten Stellen. 1. Quellenzuordnung stufte alle zehn Fakten herab. Durch den groesseren Belegblock stuetzt das Modell seine Fakten jetzt staerker auf die europaeische Suche als auf die gespeicherten Meldungen. Es zitiert dabei die Belegnummern statt vollstaendiger Adressen. Die Quellenzuordnung suchte daraufhin vergeblich nach passenden Meldungen und stufte jeden Fakt herab. Im Vergleich: Lauf 45 hatte null Herabstufungen, Lauf 44 eine, Lauf 49 zehn von zehn. Behoben an zwei Stellen. Die Belegsuche gibt die gefundenen Quellen jetzt einzeln zurueck, der Faktencheck reicht sie an die Zuordnung durch, und der Auftrag verlangt ausdruecklich die vollstaendige Adresse je Beleg. 2. Duplikatpruefung loeschte sieben von zehn Fakten. Das Clustering warf inhaltlich verschiedene Beschluesse in eine Gruppe, weil viele Behauptungen gleich beginnen. Es gab keinerlei Absicherung, bei kleinen Faktenmengen entschied das Modell sogar voellig allein. Zwei Sicherungen greifen jetzt. Eine Gruppe, die mehr als 40 Prozent aller Fakten umfasst, wird verworfen. Und jeder Loeschkandidat muss dem behaltenen Fakt auch rechnerisch aehnlich sein, Grenze 0,55 im selben Mass wie im Vorfilter. Beide Werte sind ueber Umgebungsvariablen einstellbar. 3. Leerrunden in der Recherche. War die Treffer-Obergrenze erreicht, brach nur die innere Schleife ab. Die Rundenschleife lief bis zum Ende weiter und befragte jedes Mal das teuerste Modell, ohne noch eine einzige Suche auszufuehren. Das kostete rund 0,17 USD je Durchgang, bei drei Durchgaengen etwa 0,50 USD pro Aktualisierung. Der Weg ueber die Anthropic-CLI bleibt unveraendert. Neu sind 16 Pruefungen, darunter der nachgebildete Fall aus Lage 49, insgesamt laufen jetzt 80 Pruefungen ohne Netzzugriff und ohne Kosten. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -80,10 +80,11 @@ plan_antwort["queries"] = [
|
||||
treffer_je_anfrage["Ceuta Opferzahl offiziell"] = treffer(2, "a")
|
||||
treffer_je_anfrage["Ceuta border deaths toll"] = treffer(2, "b")
|
||||
treffer_je_anfrage["Sanchez Ceuta declaracion"] = treffer(2, "c")
|
||||
block, usage, ausgefuehrt = asyncio.run(eur.plan_verification_searches(
|
||||
_r = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["57 Tote gemeldet", "Sanchez sprach von Angriff"],
|
||||
output_language="Deutsch", max_queries=7,
|
||||
))
|
||||
block, usage, ausgefuehrt = _r.block, _r.usage, _r.queries
|
||||
pruefe("A1 alle geplanten Anfragen ausgefuehrt", len(zustand["suchen"]) == 3, zustand["suchen"])
|
||||
pruefe("A2 ausgefuehrte Anfragen zurueckgemeldet", len(ausgefuehrt) == 3, ausgefuehrt)
|
||||
pruefe("A3 jeder Punkt steht im Planungsauftrag",
|
||||
@@ -106,9 +107,10 @@ plan_antwort["queries"] = [
|
||||
]
|
||||
treffer_je_anfrage["anfrage eins"] = treffer(3, "x")
|
||||
treffer_je_anfrage["anfrage zwei"] = treffer(3, "x") # identische URLs
|
||||
block_b, _, _ = asyncio.run(eur.plan_verification_searches(
|
||||
_rb = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=7,
|
||||
))
|
||||
block_b = _rb.block
|
||||
pruefe("B1 Dubletten entfernt", block_b.count("[S") == 3, block_b.count("[S"))
|
||||
pruefe("B2 Nummerierung bleibt luecklos",
|
||||
all(("[S" + str(i) + "]") in block_b for i in (1, 2, 3)))
|
||||
@@ -120,10 +122,11 @@ plan_antwort["queries"] = [
|
||||
{"q": "neue anfrage", "market": "de-de"},
|
||||
]
|
||||
treffer_je_anfrage["neue anfrage"] = treffer(2, "n")
|
||||
block_c, _, ausgefuehrt_c = asyncio.run(eur.plan_verification_searches(
|
||||
_rc = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=5,
|
||||
avoid_queries=["alte anfrage"], label="Nachhak-Suche",
|
||||
))
|
||||
block_c, ausgefuehrt_c = _rc.block, _rc.queries
|
||||
pruefe("C1 bereits gestellte Anfrage wird verworfen",
|
||||
[s["q"] for s in zustand["suchen"]] == ["neue anfrage"], zustand["suchen"])
|
||||
pruefe("C2 Sperrliste steht im Planungsauftrag", "alte anfrage" in zustand["plan_prompt"])
|
||||
@@ -134,9 +137,10 @@ neu()
|
||||
plan_antwort["queries"] = [{"q": "q" + str(i), "market": "de-de"} for i in range(1, 11)]
|
||||
for i in range(1, 11):
|
||||
treffer_je_anfrage["q" + str(i)] = treffer(20, "y" + str(i))
|
||||
block_d, _, ausgefuehrt_d = asyncio.run(eur.plan_verification_searches(
|
||||
_rd = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=4,
|
||||
))
|
||||
block_d, ausgefuehrt_d = _rd.block, _rd.queries
|
||||
pruefe("D1 hoechstens max_queries Anfragen", len(ausgefuehrt_d) == 4, ausgefuehrt_d)
|
||||
pruefe("D2 Treffer je Anfrage begrenzt",
|
||||
block_d.count("[S") == 4 * EU_VERIFY_HITS_PER_QUERY, block_d.count("[S"))
|
||||
@@ -163,10 +167,11 @@ async def staan_mit_fehler(q, **kw):
|
||||
|
||||
|
||||
eur.staan_search = staan_mit_fehler
|
||||
block_e, usage_e, ausgefuehrt_e = asyncio.run(eur.plan_verification_searches(
|
||||
_re = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=5,
|
||||
))
|
||||
eur.staan_search = fake_staan
|
||||
block_e, usage_e, ausgefuehrt_e = _re.block, _re.usage, _re.queries
|
||||
pruefe("E1 gescheiterte Suche uebersprungen", ausgefuehrt_e == ["heil"], ausgefuehrt_e)
|
||||
pruefe("E2 gefundene Belege bleiben erhalten", block_e.count("[S") == 2, block_e.count("[S"))
|
||||
pruefe("E3 nur bezahlte Suchen berechnet",
|
||||
@@ -174,15 +179,48 @@ pruefe("E3 nur bezahlte Suchen berechnet",
|
||||
|
||||
neu()
|
||||
plan_antwort["queries"] = []
|
||||
block_f, _, ausgefuehrt_f = asyncio.run(eur.plan_verification_searches(
|
||||
_rf = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=5,
|
||||
))
|
||||
block_f, ausgefuehrt_f = _rf.block, _rf.queries
|
||||
pruefe("E4 leere Planung liefert leeren Block", block_f == "" and ausgefuehrt_f == [])
|
||||
|
||||
block_g, _, _ = asyncio.run(eur.plan_verification_searches(
|
||||
_rg = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=[], max_queries=5,
|
||||
))
|
||||
block_g = _rg.block
|
||||
pruefe("E5 ohne Pruefpunkte keine Suche", block_g == "")
|
||||
|
||||
print("\nF) Gefundene Quellen werden einzeln zurueckgegeben")
|
||||
neu()
|
||||
plan_antwort["queries"] = [{"q": "eine anfrage", "market": "de-de"}]
|
||||
treffer_je_anfrage["eine anfrage"] = treffer(3, "q", host="tagesschau.de")
|
||||
_rh = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=5,
|
||||
))
|
||||
pruefe("F1 jede Quelle einzeln gemeldet", len(_rh.quellen) == 3, len(_rh.quellen))
|
||||
pruefe("F2 Quelle traegt Titel, Herkunft und Adresse",
|
||||
all(q.get("headline") and q.get("source") and q.get("source_url") for q in _rh.quellen),
|
||||
_rh.quellen[:1])
|
||||
pruefe("F3 Adressen stimmen mit dem Block ueberein",
|
||||
all(q["source_url"] in _rh.block for q in _rh.quellen))
|
||||
neu()
|
||||
plan_antwort["queries"] = [{"q": "a", "market": "de-de"}, {"q": "b", "market": "de-de"}]
|
||||
treffer_je_anfrage["a"] = treffer(2, "dup")
|
||||
treffer_je_anfrage["b"] = treffer(2, "dup")
|
||||
_ri = asyncio.run(eur.plan_verification_searches(
|
||||
title="Ceuta", search_items=["Punkt"], max_queries=5,
|
||||
))
|
||||
pruefe("F4 doppelte Quellen nur einmal gemeldet", len(_ri.quellen) == 2, len(_ri.quellen))
|
||||
|
||||
print("\nG) Der Auftrag verlangt vollstaendige Quellenadressen")
|
||||
from agents.eu_researcher import build_eu_prompt # noqa: E402
|
||||
|
||||
mit = build_eu_prompt("Auftrag", "\n\nBELEGE\n[S1] Titel | host | https://x.y/z")
|
||||
ohne = build_eu_prompt("Auftrag", "")
|
||||
pruefe("G1 Hinweis auf die Adresspflicht vorhanden",
|
||||
"vollständige" in mit and "URL" in mit)
|
||||
pruefe("G2 ohne Belege kein Adresshinweis", "Adresse (die URL" not in ohne)
|
||||
|
||||
print("\nErgebnis: " + str(ok) + " bestanden, " + str(fail) + " fehlgeschlagen")
|
||||
sys.exit(1 if fail else 0)
|
||||
|
||||
@@ -35,6 +35,13 @@ def pruefe(name, bedingung, extra=""):
|
||||
aufrufe = {"such": [], "modell": []}
|
||||
|
||||
|
||||
SUCHQUELLE = {
|
||||
"headline": "Innenministerkonferenz beschliesst Fahrplan",
|
||||
"source": "example.org",
|
||||
"source_url": "https://example.org/a",
|
||||
}
|
||||
|
||||
|
||||
async def fake_plan(*, title, search_items, output_language="Deutsch",
|
||||
max_queries=7, avoid_queries=None, label="Stuetzsuche"):
|
||||
aufrufe["such"].append({
|
||||
@@ -45,7 +52,7 @@ async def fake_plan(*, title, search_items, output_language="Deutsch",
|
||||
})
|
||||
u = ClaudeUsage(input_tokens=10, output_tokens=5, cost_usd=0.01)
|
||||
block = "\n\nBELEGE [" + label + "]\n[S1] Beleg | example.org | https://example.org/a"
|
||||
return block, u, [label + "-q1", label + "-q2"]
|
||||
return eur.Belegsuche(block, u, [label + "-q1", label + "-q2"], [dict(SUCHQUELLE)])
|
||||
|
||||
|
||||
antworten = []
|
||||
@@ -240,5 +247,33 @@ asyncio.run(fc._eu_pruefen(
|
||||
))
|
||||
pruefe("I1 followup_queries=0 unterbindet die Nachrunde", len(aufrufe["such"]) == 1, len(aufrufe["such"]))
|
||||
|
||||
print("\nJ) Suchbelege zaehlen bei der Quellenzuordnung")
|
||||
_ai_backend_var.set("bedrock")
|
||||
neu()
|
||||
# Behauptung, die zu KEINEM gespeicherten Artikel passt, wohl aber zur
|
||||
# Suchquelle. Vor der Korrektur wurde so ein Fakt herabgestuft.
|
||||
NUR_SUCHE = "Die Innenministerkonferenz beschliesst einen Fahrplan"
|
||||
antworten.append(json_fakten([
|
||||
(NUR_SUCHE, "established", "belegt durch die Suche"),
|
||||
(F_GRENZE, "confirmed", "belegt"),
|
||||
]))
|
||||
facts_j, _ = asyncio.run(fc.check("Ceuta", ARTS, "research", "Deutsch"))
|
||||
treffer_j = [f for f in facts_j if f["claim"].startswith("Die Innenministerkonferenz")]
|
||||
pruefe("J1 Fakt allein aus der Suche bleibt belegt",
|
||||
treffer_j and treffer_j[0]["status"] == "established",
|
||||
[(f["claim"][:35], f["status"]) for f in facts_j])
|
||||
pruefe("J2 Adresse der Suchquelle steht in der Evidenz",
|
||||
treffer_j and "https://example.org/a" in treffer_j[0]["evidence"],
|
||||
treffer_j[0]["evidence"][:160] if treffer_j else "")
|
||||
|
||||
print("\nK) Ohne Suchbelege bleibt die Zuordnung wie bisher")
|
||||
_ai_backend_var.set("cli")
|
||||
neu()
|
||||
antworten.append(json_fakten([(NUR_SUCHE, "established", "belegt")]))
|
||||
facts_k, _ = asyncio.run(fc.check("Ceuta", ARTS, "research", "Deutsch"))
|
||||
pruefe("K1 CLI stuft ohne zuordenbare Quelle weiterhin herab",
|
||||
facts_k and facts_k[0]["status"] != "established",
|
||||
[(f["claim"][:35], f["status"]) for f in facts_k])
|
||||
|
||||
print("\nErgebnis: " + str(ok) + " bestanden, " + str(fail) + " fehlgeschlagen")
|
||||
sys.exit(1 if fail else 0)
|
||||
|
||||
213
tests/test_qc_und_runden.py
Normale Datei
213
tests/test_qc_und_runden.py
Normale Datei
@@ -0,0 +1,213 @@
|
||||
"""Testet die Absicherung der Duplikatpruefung und den Abbruch der Suchrunden.
|
||||
|
||||
Laeuft ohne Netzzugriff und ohne Kosten, Datenbank, Modell und Suche sind
|
||||
durch Testdoubles ersetzt.
|
||||
|
||||
Aufruf aus dem Projektstamm:
|
||||
venv/bin/python tests/test_qc_und_runden.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src"))
|
||||
|
||||
import services.post_refresh_qc as qc # noqa: E402
|
||||
import agents.eu_researcher as eur # noqa: E402
|
||||
from agents.claude_client import ClaudeUsage # noqa: E402
|
||||
|
||||
ok = 0
|
||||
fail = 0
|
||||
|
||||
|
||||
def pruefe(name, bedingung, extra=""):
|
||||
global ok, fail
|
||||
if bedingung:
|
||||
ok += 1
|
||||
print(" OK " + name)
|
||||
else:
|
||||
fail += 1
|
||||
print(" FEHL " + name + " " + str(extra))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Testdouble fuer die Datenbank
|
||||
# ---------------------------------------------------------------------------
|
||||
class FakeCursor:
|
||||
def __init__(self, rows):
|
||||
self._rows = rows
|
||||
|
||||
async def fetchall(self):
|
||||
return self._rows
|
||||
|
||||
async def fetchone(self):
|
||||
return self._rows[0] if self._rows else None
|
||||
|
||||
|
||||
class FakeDB:
|
||||
"""Liefert eine feste Faktenliste und merkt sich die Loeschauftraege."""
|
||||
|
||||
def __init__(self, fakten):
|
||||
self.fakten = fakten
|
||||
self.geloescht = []
|
||||
|
||||
async def execute(self, sql, params=()):
|
||||
if sql.strip().upper().startswith("DELETE"):
|
||||
self.geloescht.extend(params)
|
||||
return FakeCursor([])
|
||||
return FakeCursor(list(self.fakten))
|
||||
|
||||
|
||||
def fakt(fid, claim, status="established", quellen=3):
|
||||
return {"id": fid, "claim": claim, "status": status, "sources_count": quellen,
|
||||
"evidence": "e", "checked_at": "2026-08-01 10:00:00"}
|
||||
|
||||
|
||||
FAHRPLAN = "Die IMK beschloss einen gemeinsamen Bund-Laender-Fahrplan zum Aufbau der zivilen Verteidigungsfaehigkeit bis 2029"
|
||||
FAHRPLAN_ANDERS = "Die IMK beschloss einen Bund-Laender-Fahrplan fuer die zivile Verteidigung bis zum Jahr 2029"
|
||||
BUNDESWEHR = "Die IMK beschloss die institutionelle Einbindung der Bundeswehr in die Innenministerkonferenz"
|
||||
SOZIAL = "Die IMK beschloss Massnahmen zur haerteren Bekaempfung von organisiertem Sozialmissbrauch"
|
||||
MILLIARDEN = "Die IMK forderte zusaetzliche Milliardeninvestitionen des Bundes in den Zivilschutz"
|
||||
ABSCHIEBUNG = "Die Innenminister waren sich einig, dass der Bund bei Abschiebungen mehr leisten muss"
|
||||
GDP = "Die Gewerkschaft der Polizei bewertete die Beschluesse als grundsaetzlich richtig"
|
||||
SYRER = "In der Frage der Rueckfuehrung von Syrern besteht ein Konflikt zwischen SPD und CDU"
|
||||
IMK_DATUM = "Die 225. Innenministerkonferenz fand im Juni 2026 in Hamburg statt"
|
||||
BESCHLUESSE = "Die IMK fasste insgesamt 66 Beschluesse"
|
||||
|
||||
geplante_cluster = []
|
||||
|
||||
|
||||
async def fake_cluster(facts, incident_title):
|
||||
return geplante_cluster
|
||||
|
||||
|
||||
qc._haiku_find_duplicate_clusters = fake_cluster
|
||||
|
||||
|
||||
def lauf_qc(fakten, cluster):
|
||||
global geplante_cluster
|
||||
geplante_cluster = cluster
|
||||
db = FakeDB(fakten)
|
||||
entfernt = asyncio.run(qc.check_fact_duplicates(db, 49, "Innenministerkonferenz 2026"))
|
||||
return entfernt, db.geloescht
|
||||
|
||||
|
||||
print("\nA) Der Fall aus Lage 49, sieben verschiedene Fakten in einer Gruppe")
|
||||
zehn = [
|
||||
fakt(469, FAHRPLAN), fakt(468, IMK_DATUM), fakt(470, BUNDESWEHR),
|
||||
fakt(471, SOZIAL), fakt(472, MILLIARDEN), fakt(473, ABSCHIEBUNG),
|
||||
fakt(474, SYRER, "disputed"), fakt(475, BESCHLUESSE), fakt(476, GDP),
|
||||
fakt(477, "Die IMK befasste sich mit der Umsetzung des europaeischen Asylsystems"),
|
||||
]
|
||||
entfernt, geloescht = lauf_qc(zehn, [[468, 469, 470, 471, 472, 473, 475, 476]])
|
||||
pruefe("A1 unplausible Gruppe wird komplett verworfen", entfernt == 0, entfernt)
|
||||
pruefe("A2 kein Fakt geloescht", geloescht == [], geloescht)
|
||||
|
||||
print("\nB) Echte Dubletten werden weiterhin entfernt")
|
||||
entfernt, geloescht = lauf_qc(
|
||||
[fakt(1, FAHRPLAN, quellen=8), fakt(2, FAHRPLAN_ANDERS, quellen=2),
|
||||
fakt(3, BUNDESWEHR), fakt(4, SOZIAL), fakt(5, MILLIARDEN)],
|
||||
[[1, 2]])
|
||||
pruefe("B1 echtes Duplikat entfernt", entfernt == 1, entfernt)
|
||||
pruefe("B2 der besser belegte Fakt bleibt", geloescht == [2], geloescht)
|
||||
|
||||
print("\nC) Kleine Gruppe mit unaehnlichen Fakten wird nicht geloescht")
|
||||
entfernt, geloescht = lauf_qc(
|
||||
[fakt(1, FAHRPLAN), fakt(2, SOZIAL), fakt(3, BUNDESWEHR), fakt(4, MILLIARDEN),
|
||||
fakt(5, ABSCHIEBUNG), fakt(6, GDP)],
|
||||
[[1, 2]])
|
||||
pruefe("C1 unaehnlicher Fakt bleibt erhalten", entfernt == 0, entfernt)
|
||||
|
||||
print("\nD) Gemischte Gruppe, nur der wirklich aehnliche Fakt faellt weg")
|
||||
entfernt, geloescht = lauf_qc(
|
||||
[fakt(1, FAHRPLAN, quellen=9), fakt(2, FAHRPLAN_ANDERS, quellen=1), fakt(3, SOZIAL),
|
||||
fakt(4, BUNDESWEHR), fakt(5, MILLIARDEN), fakt(6, ABSCHIEBUNG),
|
||||
fakt(7, GDP), fakt(8, SYRER), fakt(9, BESCHLUESSE), fakt(10, IMK_DATUM)],
|
||||
[[1, 2, 3]])
|
||||
pruefe("D1 genau ein Fakt entfernt", entfernt == 1, entfernt)
|
||||
pruefe("D2 der aehnliche wurde entfernt", geloescht == [2], geloescht)
|
||||
|
||||
print("\nE) Grenzwerte")
|
||||
pruefe("E1 Anteilsgrenze gesetzt", 0 < qc.DEDUP_MAX_CLUSTER_ANTEIL <= 1, qc.DEDUP_MAX_CLUSTER_ANTEIL)
|
||||
pruefe("E2 Aehnlichkeitsgrenze gesetzt", 0 < qc.DEDUP_MIN_AEHNLICHKEIT < 1, qc.DEDUP_MIN_AEHNLICHKEIT)
|
||||
pruefe("E3 verschiedene Sachverhalte liegen unter der Grenze",
|
||||
qc._aehnlichkeit(FAHRPLAN, SOZIAL) < qc.DEDUP_MIN_AEHNLICHKEIT,
|
||||
round(qc._aehnlichkeit(FAHRPLAN, SOZIAL), 2))
|
||||
pruefe("E4 echte Dublette liegt ueber der Grenze",
|
||||
qc._aehnlichkeit(FAHRPLAN, FAHRPLAN_ANDERS) >= qc.DEDUP_MIN_AEHNLICHKEIT,
|
||||
round(qc._aehnlichkeit(FAHRPLAN, FAHRPLAN_ANDERS), 2))
|
||||
pruefe("E5 bei zwei Fakten bleibt die Gruppengrenze nutzbar",
|
||||
max(2, int(2 * qc.DEDUP_MAX_CLUSTER_ANTEIL)) == 2)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Suchrunden
|
||||
# ---------------------------------------------------------------------------
|
||||
print("\nF) Die Suchphase endet, sobald die Treffergrenze erreicht ist")
|
||||
|
||||
runden = {"n": 0}
|
||||
|
||||
|
||||
async def fake_bedrock_research(prompt, model=None, raw_text=False, timeout=None):
|
||||
runden["n"] += 1
|
||||
# Jede Runde fordert vier Suchen an, das Modell will nie von selbst enden.
|
||||
antwort = {"action": "search", "reason": "weiter",
|
||||
"queries": [{"q": f"anfrage {runden['n']}-{i}", "market": "de-de",
|
||||
"full_content": False} for i in range(1, 5)]}
|
||||
return json.dumps(antwort), ClaudeUsage(input_tokens=100, output_tokens=50, cost_usd=0.05)
|
||||
|
||||
|
||||
async def fake_staan_research(q, market="de-de", extra_snippets=True, max_snippets=4,
|
||||
full_content=False, extra_exclude=None, timeout=None):
|
||||
# Jede Suche liefert 20 neue Treffer, nach zwei Runden ist die Grenze voll.
|
||||
basis = q.replace(" ", "")
|
||||
return [{
|
||||
"title": "Treffer " + basis + str(i),
|
||||
"hostname": "example.org",
|
||||
"url": "https://example.org/" + basis + "/" + str(i),
|
||||
"snippet": "Auszug",
|
||||
"extra_snippets": [],
|
||||
"full_text": "",
|
||||
} for i in range(1, 21)]
|
||||
|
||||
|
||||
letzter_prompt = {"text": ""}
|
||||
|
||||
|
||||
async def fake_bedrock_final(prompt, model=None, raw_text=False, timeout=None):
|
||||
"""Ab dem Abschlussauftrag wird eine Auswahl zurueckgegeben."""
|
||||
letzter_prompt["text"] = prompt
|
||||
return "[]", ClaudeUsage(input_tokens=100, output_tokens=50, cost_usd=0.05)
|
||||
|
||||
|
||||
async def bedrock_weiche(prompt, model=None, raw_text=False, timeout=None):
|
||||
# Der Rundenauftrag traegt die Rundennummer im Kopf, der Abschlussauftrag
|
||||
# nicht. Daran laesst sich beides sicher unterscheiden.
|
||||
if "Es ist Runde " in prompt:
|
||||
return await fake_bedrock_research(prompt, model, raw_text, timeout)
|
||||
return await fake_bedrock_final(prompt, model, raw_text, timeout)
|
||||
|
||||
|
||||
eur.call_bedrock = bedrock_weiche
|
||||
eur.staan_search = fake_staan_research
|
||||
|
||||
runden["n"] = 0
|
||||
text, usage = asyncio.run(eur.run_eu_research(
|
||||
title="Innenministerkonferenz 2026", description="", incident_type="research",
|
||||
lang_instruction="", existing_context="", preferred_sources_block="",
|
||||
output_language="Deutsch", excluded_sources=[], research_language_iso="de",
|
||||
))
|
||||
# Die erste Runde fuellt die Grenze von 60 Treffern, danach ist Schluss.
|
||||
# Ohne die Korrektur liefe die Schleife alle fuenf Runden durch und befragte
|
||||
# jedes Mal das teuerste Modell, ohne noch zu suchen.
|
||||
suchrunden = runden["n"]
|
||||
pruefe("F1 genau eine Suchrunde statt fuenf", suchrunden == 1, suchrunden)
|
||||
pruefe("F2 vier Leerrunden gespart", eur.EU_RESEARCH_MAX_ROUNDS_RESEARCH - suchrunden == 4,
|
||||
eur.EU_RESEARCH_MAX_ROUNDS_RESEARCH - suchrunden)
|
||||
pruefe("F3 Recherche liefert trotzdem ein Ergebnis", text == "[]", text[:40])
|
||||
pruefe("F4 Kosten nur fuer die tatsaechlich gelaufene Runde plus Abschluss",
|
||||
round(usage.cost_usd, 2) == round(0.05 * 2 + 4 * eur.STAAN_COST_PER_QUERY_USD, 2),
|
||||
round(usage.cost_usd, 4))
|
||||
|
||||
print("\nErgebnis: " + str(ok) + " bestanden, " + str(fail) + " fehlgeschlagen")
|
||||
sys.exit(1 if fail else 0)
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren