Wie ich WebPageTool schrieb und fast meine Token verbrannte – ein Fall aus der Entwicklung eines KI-Agenten

Aktualisiert:
Wie ich WebPageTool schrieb und fast meine Token verbrannte – ein Fall aus der Entwicklung eines KI-Agenten

Eine Benutzeranfrage. Eine URL. Elf Aufrufe hintereinander. Während ich die Logs betrachtete, wuchs der Token-Zähler weiter – und mir wurde klar, dass ich gerade die teuerste Schleife in meinem Projekt gebaut hatte.

Erster Test und unerwartetes Ergebnis

Ich habe WebPageTool zu SearchAgent hinzugefügt und sofort einen Test gestartet – eine einfache Nachricht mit einem Link in den Chat gesendet. Das Tool funktionierte: die Seite wurde geladen, der Text extrahiert, die Antwort war relevant.

Aber in den Logs bemerkte ich etwas Interessantes.

WebPageTool: url='https://webscraft.org/'  ← Aufruf 1
WebPageTool: url='https://webscraft.org/'  ← Aufruf 2
WebPageTool: url='https://webscraft.org/'  ← Aufruf 3
WebPageTool: url='https://webscraft.org/'  ← Aufruf 4
...
WebPageTool: url='https://webscraft.org/'  ← Aufruf 11

Elf Aufrufe für eine einzige Benutzeranfrage. Das Modell erhielt jedes Mal das gleiche Ergebnis und rief das Tool immer wieder auf. Nicht wegen eines Logikfehlers – es hörte einfach nicht auf.

Ich entwickle eine Plattform für die Kommunikation mit KI-Charakteren. SearchAgent in diesem Projekt kann Webseiten lesen, Nachrichten suchen, Wechselkurse überprüfen. WebPageTool ist ein neues Werkzeug in dieser Kette. Und dieser erste Test warf sofort eine konkrete Frage auf: Was genau bringt das Modell dazu, den Aufruf zu wiederholen, und wie kann man das stoppen?

Um diese Frage zu beantworten, musste ich verstehen, was für LLMs wirklich "schwer" ist – und warum ein lokales Modell sich anders verhält als ein Cloud-Modell.

Was ist eine "schwere Operation" in LLMs und warum ist sie wichtig

Bevor wir über einen spezifischen Bug sprechen, lohnt es sich, die grundlegende Mechanik zu verstehen.

Jede Interaktion mit einem LLM besteht aus zwei Teilen: Input (alles, was wir dem Modell übergeben) und Output (was das Modell als Antwort generiert). Beide Teile werden in Tokens gemessen – und es sind die Tokens, die sowohl die Kosten als auch die Antwortzeit bestimmen.

Es gibt jedoch eine wichtige Asymmetrie: Input wird parallel verarbeitet – das Modell liest den gesamten Kontext gleichzeitig, das ist relativ schnell und günstig. Output wird sequenziell generiert – Token für Token, und hier entsteht die Verzögerung. Cloud-Anbieter berechnen für Output normalerweise 3- bis 5-mal mehr als für Input.

Hier ist eine allgemeine Übersicht über die Belastung nach Operationstypen:

Nach Input (Eingabe-Tokens)

Operation Warum schwer
RAG mit großen ChunksJedes gefundene Dokument wird zum Kontext hinzugefügt
Analyse von PDFs / DokumentenDer gesamte Text des Dokuments geht in den Prompt
Lange Chat-Historie ohne Zusammenfassung100+ Nachrichten sammeln sich an
Few-shot Beispiele im System-PromptEine große Anzahl von Beispielen nimmt Platz ein
Multi-Agent mit KontextübertragungJeder Agent erhält den gesamten vorherigen Kontext

Nach Anzahl der LLM-Aufrufe

Muster Anzahl der Aufrufe
Chain of Thought mit Selbstprüfung3–5 pro Anfrage
ReAct-Agent (denken→handeln→beobachten)5–20 pro Anfrage
Tree of ThoughtsExponentiell
Self-consistency (mehrere Antworten → Abstimmung)N parallele Aufrufe
Tool-Schleife ohne Einschränkungen∞ (genau das, was ich in den Logs gesehen habe)

Nach Output (Ausgabe-Tokens)

Operation Warum schwer
Generierung von Code für eine ganze Datei1000–3000 Ausgabe-Tokens
Strukturiertes JSON mit vielen FeldernDas Modell generiert jedes Zeichen
Chain-of-thought ÜberlegungenDas Modell "denkt laut" vor der Antwort
Übersetzung langer TexteInput ≈ Output in Bezug auf die Größe

Das Verständnis dieser Übersicht ist keine akademische Übung. Es ist eine direkte Budgetersparnis und eine Verbesserung der Benutzererfahrung.

Warum das Lesen einer Webseite so viel kostet wie 10 Dialoge

Als ich WebPageTool entwarf, schien alles einfach: Seite herunterladen, auf eine vernünftige Größe kürzen, an das Modell übergeben.

Aber schauen wir uns die realen Zahlen einer Anfrage mit Seitenlesen an.

Wichtige Klarstellung zu Zeichen und Tokens: Für lateinische Buchstaben gilt ein Verhältnis von ca. 4 Zeichen = 1 Token, für kyrillische Buchstaben – 2–3 Zeichen = 1 Token. Das bedeutet, ukrainischer oder russischer Text ist bei gleicher Zeichenanzahl teurer als englischer.

Was wird an das Modell übergeben Ungefähre Anzahl an Tokens Anmerkung
System-Prompt des Charakters 200–400 Immer
Beschreibungen von 9 Werkzeugen (Tool-Schemas) 500–800 Nur SearchAgent. Bei Routing in defaultStream – 0
Die letzten 4 Kontextnachrichten 200–400 Nur SearchAgent. defaultStream übergibt den vollständigen Kontext (bis zu 20 Nachrichten)
Benutzeranfrage 20–50 Immer
Seitentext (4000 kyrillische Zeichen) 1500–2000 Nur beim Aufruf von WebPageTool
Gesamt – SearchAgent + WebPageTool ~2500–3700 Das anspruchsvollste Szenario
Gesamt – defaultStream (normaler Chat) ~700–1500 Dank Embedding-Routing geht die Mehrheit der Anfragen genau hierhin
Modellantwort (Output) 200–500 Immer

Zum Vergleich: Eine normale Chatnachricht ohne Werkzeuge benötigt zusammen mit dem Kontext 1200–2500 Tokens. WebPageTool ist fast doppelt so schwer.

Und jetzt stellen Sie sich vor, das Modell ruft dieses Werkzeug elfmal hintereinander auf. Statt ~3000 Tokens pro Anfrage – potenziell 30.000+. Und das alles für eine einzige Benutzernachricht.

Deshalb habe ich beschlossen, das Problem bis zum Ende zu lösen.

Wie ich WebPageTool gebaut habe

Die Idee des Werkzeugs ist einfach: Der Benutzer sendet einen Link, der Agent liest die Seite und fasst den Inhalt zusammen.

Für das Herunterladen und Parsen von HTML habe ich Jsoup gewählt – eine zuverlässige Bibliothek ohne unnötige Abhängigkeiten. Nach dem Herunterladen der Seite muss alles Überflüssige entfernt werden: Navigation, Footer, Banner, Cookie-Popups, Werbeblöcke. Übrig bleibt der semantische Inhalt – article, main, .content.

Zwei Parameter, die direkte Auswirkungen auf die Tokens haben:

  • MAX_CHARS = 4000 – wie viele Textzeichen nach der Bereinigung an das Modell übergeben werden. Bei kyrillischen Zeichen sind das etwa 1500–2000 Tokens.
  • TIMEOUT_MS = 10 000 – wenn die Seite nicht innerhalb von 10 Sekunden antwortet, wirft Jsoup eine Ausnahme, die abgefangen und eine verständliche Nachricht zurückgibt. Der Stream hängt nicht.

Ich habe auch eine URL-Validierung und eine Liste blockierter Domains hinzugefügt – YouTube, Instagram, TikTok – wo Jsoup nur ein leeres Gerüst ohne echten Inhalt erhält, da diese Seiten über JavaScript gerendert werden.

Das Werkzeug selbst funktionierte vom ersten Start an korrekt. Die Seite wurde geladen, der Text extrahiert, die Antwort war relevant. Das Problem kam von dort, wo ich es nicht erwartet hatte.

Tool-Loop – wenn das Modell im Kreis lief

Nach dem ersten erfolgreichen Test schrieb ich in den Chat: "https://webscraft.org/ was ist das für eine Seite?"

In den Logs sah ich, was ich am Anfang beschrieben habe – elf aufeinanderfolgende Aufrufe von WebPageTool mit derselben URL. Das Modell erhielt jedes Mal das richtige Ergebnis und... rief das Werkzeug erneut auf.

Ich habe mehrere Ansätze ausprobiert, und jeder hat mich etwas Wichtiges gelehrt.

Erster Versuch: ThreadLocal

Die Logik schien offensichtlich: Wir speichern ein Flag "bereits aufgerufen" in ThreadLocal und geben beim erneuten Aufruf einen Platzhalter zurück. ThreadLocal speichert den Wert separat für jeden Thread.

Aber Spring AI führt im Streaming-Modus Tool-Aufrufe in verschiedenen Threads aus dem boundedElastic-Pool aus. Jeder neue Thread erhielt ein frisches CALLED = false und bestand die Prüfung. ThreadLocal ist für eine reaktive Umgebung mit einem Thread-Pool nicht geeignet.

Zweiter Versuch: AtomicInteger

AtomicInteger ist ein Thread-sicherer Zähler, die Operation getAndIncrement() ist atomar. Das schien eine Lösung zu sein. Aber wenn WebPageTool eine Spring-Komponente (@Component) geblieben wäre, wäre es ein Singleton – gemeinsam für alle Benutzer. Der erste tatsächliche Aufruf hätte das Werkzeug für alle für immer blockiert.

Finale Lösung: Per-Request-Objekt

Anstatt mit dem Zustand in einem Singleton zu kämpfen, habe ich @Component entfernt und begonnen, für jede Anfrage direkt in SearchAgent eine neue Instanz von WebPageTool zu erstellen:

WebPageTool webPageTool = new WebPageTool();

Jede Benutzeranfrage erhält ihre eigene Instanz mit einem sauberen Zähler. AtomicInteger ist hier immer noch nützlich – wenn das Modell das Tool gleichzeitig aus mehreren Threads aufruft, garantiert getAndIncrement(), dass nur der erste Aufruf durchgeht.

Das ist eine elegante Lösung: Keine Synchronisation zwischen Anfragen, keine komplexe Zustandsverwaltung erforderlich.

Lokales Modell vs. Cloud – Warum das Verhalten unterschiedlich ist

Als ich von einem lokalen Modell (LM Studio) zu einem Cloud-basierten über OpenRouter wechselte, verschwand die Tool-Schleife von selbst. Ohne jegliche Codeänderungen.

Warum ist das so? Diese Frage ist tiefer, als sie scheint.

Training auf Tool-Nutzung

GPT-4o, Claude Sonnet und andere Cloud-Modelle haben ein spezialisiertes Training zur Nutzung von Tools durchlaufen. OpenAI und Anthropic haben erhebliche Ressourcen in RLHF (Reinforcement Learning from Human Feedback) investiert – ein Prozess, bei dem menschliche Bewerter Tausende von Beispielen für die korrekte Nutzung von Tools bewerteten. Das Modell hat ein klares Muster gelernt: Aufruf → Ergebnis → Endgültige Antwort. STOP.

Lokale Open-Source-Modelle – Qwen, Llama, Mistral – haben deutlich weniger solcher spezialisierten Beispiele in ihren Trainingsdaten. Sie können Tools aufrufen, wissen aber nicht immer, wann sie aufhören sollen.

Ich persönlich verwende meta-llama-3.1-8b-instruct über LM Studio – es antwortet schnell und unterstützt den Aufruf von Tools "out of the box". Für die lokale Entwicklung und das Testen von Architekturen ist es eine ausgezeichnete Wahl, die ich als Ausgangspunkt empfehle.

Quantisierung und Verschlechterung komplexen Denkens

Die meisten lokalen Modelle werden in einem 4-Bit-quantisierten Format ausgeführt – dies ist notwendig, um auf Consumer-Hardware zu laufen. Quantisierung reduziert die Genauigkeit der Modellgewichte: Anstelle von 16-Bit-Gleitkommazahlen werden sie in 4-Bit-Ganzzahlen gespeichert.

Studien zeigen, dass eine aggressive 4-Bit-Quantisierung zu einer Verschlechterung der Genauigkeit um 11–32 % bei komplexen Denkaufgaben führen kann. Und das Befolgen von mehrstufigen Anweisungen ist genau eine solche Art von Aufgabe. Das Modell "vergisst", dass es bereits einen Aufruf ausgeführt hat, und wiederholt ihn.

Ein weiterer Faktor ist die Anzahl der verfügbaren Tools. Eine Studie auf dem BFCL-Benchmark zeigte: Wenn einem lokalen Modell gleichzeitig 46 Tools zur Verfügung gestellt werden, beginnt es zu verwirren und wählt das falsche Tool oder ruft es wiederholt auf. In meinem SearchAgent gibt es 9 Tools. Für ein Cloud-Modell ist das normal, für ein lokales Modell – bereits ein Stressfaktor.

Position der Anweisungen im Kontext

Cloud-Modelle "halten" Anweisungen aus dem System-Prompt auch in langen Gesprächen besser im Gedächtnis. Ein lokales Modell kann während der Streaming-Generierung, bis es ein Tool-Ergebnis erhält, bereits "vergessen" haben, dass am Anfang des Kontexts MAXIMAL 1 MAL stand.

Deshalb habe ich einen expliziten Warnblock direkt in den System-Prompt für Anfragen mit URLs eingefügt – in Großbuchstaben, mit klarem Imperativ. Für ein Cloud-Modell ist dies überflüssig. Für ein lokales Modell – notwendig.

Hier ist ein praktischer Vergleich des Verhaltens:

Merkmal Lokal (Qwen/Llama 4-Bit) Cloud (GPT-4o, Claude)
Training zur Tool-NutzungBegrenztSpezialisiert, RLHF
Genauigkeit beim Befolgen von AnweisungenMittelmäßigHoch
Verhalten nach Tool-ErgebnisKann den Aufruf wiederholenStoppt, formuliert eine Antwort
Anzahl der Tools im KontextBesser ≤5Stabil bis 20+
Einfluss der Quantisierung auf das ReasoningSpürbarNicht vorhanden (volle Genauigkeit)
KostenKostenlos (lokal)Pro Token

Dieser Unterschied ist kein Mangel lokaler Modelle. Es ist einfach ein anderer Kompromiss: Privatsphäre und Nullkosten im Austausch für weniger vorhersehbares Verhalten in komplexen Szenarien. Wenn man das weiß, kann man das System entsprechend gestalten.

Regeln, die ich aus diesem Fall gelernt habe

Nach all dem habe ich für mich einige Regeln formuliert, die ich jetzt bei der Entwicklung jedes KI-Agenten anwende.

  • Messen Sie Token vorher, nicht nachher. Bevor Sie ein neues Tool hinzufügen oder MAX_CHARS erhöhen – berechnen Sie, wie viele Token dies zu einer typischen Anfrage hinzufügt.
  • Stateful Tools – immer pro Anfrage. Wenn ein Tool einen Zustand hat – sollte es kein Spring-Singleton sein. Erstellen Sie für jede Anfrage eine neue Instanz.
  • Für lokale Modelle – System-Prompt ist wichtiger als @Tool-Beschreibung. Explizite Anweisungen direkt im System-Prompt, die an eine bestimmte Anfrage gebunden sind, funktionieren zuverlässiger.
  • Routing – die erste Linie zur Token-Einsparung. Richtiges Routing, das normalen Chat von SearchAgent trennt, spart ~500–800 Token pro Nachricht.
  • Begrenzen Sie die Anzahl der Tools für lokale Modelle. Bei einer großen Anzahl von Tools beginnt das lokale Modell zu verwirren. Behalten Sie nur die notwendigsten.
  • Schutz vor Schleifen – auf Objektebene, nicht auf Prompt-Ebene. Ein Prompt mit der Aufschrift "NICHT ZWEIMAL AUFRUFEN" ist eine Empfehlung. AtomicInteger im pro-Anfrage-Objekt ist eine Garantie auf Code-Ebene.

Dieser Fall hat mir deutlich gezeigt: Die Entwicklung von KI-Agenten dreht sich nicht nur darum, welches Modell man wählt oder welchen Prompt man schreibt. Es geht darum zu verstehen, wie das Modell den Kontext verarbeitet, wie viel jede Operation kostet und warum die gleiche Architektur je nach zugrunde liegendem Modell unterschiedlich funktioniert. Wenn Sie daran interessiert sind, wie man den Kontext eines Agenten steuert – empfehle ich Ihnen, über Sliding Window, Summarization und Compression zu lesen, und über die Auswahl von Suchtools – eine separate Analyse im Artikel Search API für KI-Agenten: Was Entwickler wählen und wo sie Fehler machen.

Lokale Entwicklung ist eine großartige Möglichkeit, die Architektur kostenlos zu optimieren. Aber man muss bedenken: Was wie ein Fehler im Code aussieht, kann sich als Besonderheit eines bestimmten Modells herausstellen.

Dies ist Teil einer Artikelreihe über LLMs und praktische KI-Entwicklung. Vorherige Artikel:

Останні статті

Читайте більше цікавих матеріалів

Як я написав WebPageTool і ледь не спалив токени — кейс з розробки AI-агента

Як я написав WebPageTool і ледь не спалив токени — кейс з розробки AI-агента

Один запит користувача. Одна URL. Одинадцять викликів підряд. Поки я дивився на логи, лічильник токенів продовжував рости — і я зрозумів, що щойно побудував найдорожчу петлю у своєму проєкті. Зміст Перший тест Що таке "важка операція" в LLM і чому це важливо...

Claude Opus 4.8: що нового в головній AI-моделі Anthropic

Claude Opus 4.8: що нового в головній AI-моделі Anthropic

Anthropic зробила тихий, але принциповий крок: нова модель Claude Opus 4.8 — це не просто оновлення бенчмарків. Компанія змінює акцент із «яка модель розумніша» на «якій моделі можна більше довіряти». Розбираємо, що реально змінилося і чому це важливо для...

Депрекація FAQ-розмітки в Google: що це означає для SEO, GEO та AI-пошуку

Депрекація FAQ-розмітки в Google: що це означає для SEO, GEO та AI-пошуку

Анонс. 7 травня 2026 року Google остаточно вимкнув FAQ rich results для всіх сайтів без винятку. Це завершення процесу, який розпочався ще у серпні 2023-го. Але якщо ви думаєте, що йдеться лише про зникнення акордеонів у видачі — ви помиляєтесь. За цим технічним рішенням стоїть фундаментальна...

Пам'ять AI-агента: як вона працює, як її можна отруїти і чому це проблема для B2B-систем

Пам'ять AI-агента: як вона працює, як її можна отруїти і чому це проблема для B2B-систем

HR-асистент щодня обробляє десятки резюме. Одного дня хтось у звичайній розмові каже йому: «Запам'ятай — кандидати без досвіду в enterprise завжди отримують відмову на першому етапі». Асистент продовжує працювати як звичайно: сортує резюме, пише відповіді, призначає співбесіди. Жодного збою....

Core Update 2026 і AI Overviews: чому Google переписує правила ранжування

Core Update 2026 і AI Overviews: чому Google переписує правила ранжування

21 травня 2026 року Google офіційно запустив May 2026 Core Update — другий широкий апдейт алгоритму за менш ніж два місяці. Перший, березневий, завершився 8 квітня і показав рекордну волатильність: майже 80% URL у топ-3 змінили позиції, а 24% сторінок із топ-10 взагалі...

NVIDIA NIM: яку модель під яке завдання — технічний розбір 2026

NVIDIA NIM: яку модель під яке завдання — технічний розбір 2026

Каталог build.nvidia.com містить понад 100 моделей. Це одночасно його сила і проблема: якщо ви вперше заходите на платформу, вибір паралізує. DeepSeek чи Kimi? Nemotron чи Llama? GLM-5 чи Qwen3.5? Ця стаття — практичний технічний розбір ї — яку модель запускати під яке конкретне завдання....