Dein Agent braucht nicht jedes Tool in seinem Kontext
DynoTables KI-Agent kann 38 Tools erreichen. Alle auf einmal sieht er selten. Wo wir die Grenze gezogen haben zwischen dem, was das Modell sofort bekommt, und dem, wonach es erst suchen muss, war die folgenreichste Entscheidung am gesamten Toolkit — und die Grenze liegt am Ende weit von der Stelle entfernt, an die wir sie zuerst gesetzt hatten.
Wir haben einen Discovery-Mechanismus gebaut, damit das Modell mit einem kleinen Satz startet und den Rest sucht. Dann haben wir günstigen Modellen beim Benutzen zugesehen und 27 der 38 Tools zurück in den immer sichtbaren Kern verschoben. Der Mechanismus hat überlebt. Unsere Theorie darüber, wer ihn braucht, nicht.
Das haben wir gelernt, als wir eine Tool-Oberfläche für Modelle gebaut haben, denen man nicht zutrauen kann, dass sie von selbst suchen.
Ein Tool, das das Modell nie aufruft, kostet dich trotzdem
Jedes Tool, das du bereitstellst, ist sein Name, seine Beschreibung und sein vollständiges Input-Schema, serialisiert in den Request, bevor der Nutzer irgendetwas getippt hat. 38 davon sind nicht umsonst.
Tokens sind die kleinere Hälfte der Rechnung. Der eigentliche Preis ist die
Trefferquote bei der Auswahl: Je mehr nahezu identische Optionen ein Modell
durchgeht, desto häufiger greift es zur falschen. Unser Katalog ist mit
Absicht voll von nahezu identischen Optionen. Fünf unserer Tools gibt es
doppelt — openTable und proposeOpenTable, openWorkbench und
proposeOpenWorkbench, und so weiter. Jedes Paar tut dasselbe; das eine sofort,
das andere gibt einen Chip aus, den der Nutzer erst anklickt. Dieser Unterschied
ist tragend für die Sicherheit des und in einer flachen
Namensliste praktisch unsichtbar.
Die Client-Leitlinien des führen genau mit diesem Punkt: Alle Tool-Definitionen vorab zu laden verschwendet Tokens, erhöht die Latenz und verschlechtert die Leistung des Modells. Dem zuzustimmen ist leicht. Interessant wird es bei der Frage, welche Tools ihren Platz verlieren.
Wir haben ein Such-Tool gebaut. Das Untergrenzen-Modell hat es nicht aufgerufen.
Der Mechanismus ist zweistufig. Ein Satz Tools ist ab dem ersten Schritt aktiv.
Der Rest ist unsichtbar, bis das Modell searchTools(query) aufruft — das
bewertet den Katalog anhand von Namen, Beschreibungen und Keywords, gibt die
Treffer zurück und fügt sie dem Satz an Tools hinzu, die das Modell in den
folgenden Schritten aufrufen darf.
Dann haben wir das gegen unser Untergrenzen-Modell laufen lassen. Wir tunen diesen Agenten nicht gegen ein Frontier-Modell — er läuft auf deinen eigenen Bedrock-Zugangsdaten, also wählen Leute günstige Modelle, und wir optimieren auf das günstigste. Auf eine Frage zu einer angehängten Datei hin durchforstete dieses Modell stattdessen die Liste der offenen Tabs. Es rief das Such-Tool so gut wie nie auf. Was nicht direkt sichtbar war, existierte für es nicht.
Dieses Ergebnis erledigt den naheliegenden Entwurf. Wenn Discovery der einzige Weg zu einem Tool ist, hängt jede Anfrage, die dieses Tool braucht, daran, dass das Modell sich fürs Suchen entscheidet — und die Modelle, die am ehesten Hilfe brauchen, fragen am wenigsten danach.
Damit war die Aufteilung nicht mehr „kleiner Kern, großer Rest“, sondern eine Frage an die Anfrage, nicht an das Tool: Benennt die Formulierung des Nutzers das Tool? Die 11 Tools, die wir auffindbar gelassen haben, sind die, bei denen die Antwort Ja lautet. „Exportier das als CSV“ bringt ein Modell dazu, nach export zu suchen. „Zeig mir die Bestellungen vom letzten Monat“ bringt es nicht dazu, nach einem Tool zum Setzen von Filtern zu suchen, also bleibt dieses inline. Index-Statistiken, gespeicherte Specs, Beziehungs-Introspektion und die Oberflächen für Änderungen im Staging sind allesamt Dinge, die ein Nutzer beim Namen nennt, wenn er sie will — und nie implizit.
27 inline ist keine Zahl, die wir vorab verteidigt hätten. Es ist die Zahl, die den Kontakt mit dem Modell überlebt hat, gegen das wir tatsächlich ausliefern.
Das Race, das Discovery stillschweigend nutzlos gemacht hätte
Discovery hat eine Timing-Bedingung, die man leicht falsch macht und schwer bemerkt.
Wenn das Such-Tool Treffer zurückgibt, müssen diese Namen dem erlaubten Satz beitreten, bevor der nächste Schritt des Modells vorbereitet wird. Der naheliegende Ort dafür ist der Hook, der beim Abschluss eines Schritts feuert. Für diesen Hook ist dokumentiert, dass er in manchen SDK-Versionen nach der Vorbereitung des nächsten Schritts feuert — die Mutation landet also einen Schritt zu spät.
Der Fehlerfall ist fies. Das Modell sucht. Es bekommt ein korrektes Ergebnis, das genau das Tool nennt, das es braucht. Es ruft dieses Tool im unmittelbar nächsten Schritt auf und bekommt gesagt, das Tool existiere nicht. Sporadisch, abhängig davon, welche SDK-Version aufgelöst wurde, und es liest sich wie ein dummes Modell statt wie ein kaputtes Harness.
Die Lösung ist, den erlaubten Satz innerhalb der Ausführung des Such-Tools selbst zu verändern — die ist garantiert abgeschlossen, bevor die Schleife weiterläuft. Das ist ein Unterschied von einer Zeile, wo eine Anweisung steht, und es ist der Unterschied zwischen einem funktionierenden Discovery-Mechanismus und einem, der in einem Bruchteil der Fälle scheitert, aus Gründen, die niemand richtig zuordnen wird.
Drei Suchen, dann Schluss
Die Suche ist auf 3 Aufrufe pro Agenten-Turn gedeckelt. Der vierte gibt das hier zurück, statt zu laufen:
{"error": "search-budget-exhausted", "budgetCap": 3}Der Deckel existiert wegen einer bestimmten Schleife. Das Modell sucht, findet nicht, was es sich vorgestellt hat, sucht erneut mit einem Synonym, findet auch das nicht und verbrennt sein gesamtes Schritt-Budget im Such-Tool, ohne die Datenbank je anzufassen. Ihn zu deckeln erzwingt eine Entscheidung — sich auf eines der bereits gefundenen Tools festlegen oder den Nutzer fragen — genau an dem Punkt, an dem weiteres Suchen aufgehört hat, sich zu lohnen.
Die Fehlermeldung, wenn ein Modell ein Tool aufruft, das es nicht entdeckt hat, folgt demselben Prinzip wie jeder Validator im Agenten:
Tool 'startExport' not in active set. Call searchTools(query='startExport')
to discover it, or use one of: <inline tool names>Eine Ablehnung, die den rettenden Schritt benennt, kostet einen zusätzlichen Schritt. Eine Ablehnung, die nur Nein sagt, kostet den ganzen Turn.
Eine Zeile pro Tool, alles andere abgeleitet
Jedes Tool wird genau einmal deklariert, in einer einzigen flachen Liste, und die Zeile trägt die gesamte Identität des Tools: Name und Beschreibung, die Keywords, auf die die Suche matcht, ob es inline oder auffindbar startet, in welcher Stufe es läuft und wie es über MCP bereitgestellt wird.
Diese Stufen zählen genauso viel wie die Aufteilung nach Sichtbarkeit. 21 Tools sind silent — Lesevorgänge, die laufen, ohne jemanden zu unterbrechen. 16 sind hinter der Autorisierungs-Leiter gated. Genau eines gehört zu keinem von beiden, denn das Such-Tool ist keine Fähigkeit, die der Agent auf deine Daten anwendet; es ist Teil der Schleife selbst. Die MCP-Bereitstellung ist eine dritte Achse in derselben Zeile: read-only, Staging, full oder komplett ausgeschlossen, was auf drei Tools zutrifft.
Die Regel, die das ehrlich hält, lautet: Jede andere Liste im System wird aus diesen Zeilen abgeleitet — die Menge der Silent-Tier-Tools, die MCP-Scope-Stufen, die Menge mit Schreib-Scope — und keine davon wird von Hand gepflegt. Eine handgepflegte Silent-Liste neben einer handgepflegten MCP-Liste ist genau der Weg, auf dem ein Tool im Chat korrekt gated endet und gegenüber einem externen Client still und leise ungated.
Die Bedingung, die wir nicht kommen sahen: Die Deklarationsliste darf null Laufzeit-Imports enthalten. Sie wird von der Desktop-UI und vom Backend geteilt, und ein einziger Import erreicht transitiv — über die Implementierung eines einzelnen Tools — eine Node-only-Krypto-Abhängigkeit. Zieh die ins Browser-Bundle, und die App scheitert beim Laden des Moduls. Weder der Typechecker noch die Unit-Tests fangen das ab — beide lösen den Import bereitwillig auf. Was es abfängt, ist ein Test, der die Datei als Text liest und bei jeder Import-Anweisung überhaupt fehlschlägt. Das wirkt grob — genau bis zu dem Moment, in dem es dich das erste Mal rettet.
Was sich übertragen lässt, wenn du selbst einen baust
- Zähl deine Tools, bevor du deine Architektur verteidigst. Die richtige Aufteilung ist eine Messung, kein Prinzip.
- Teste Discovery gegen dein schwächstes Modell. Ein Frontier-Modell sucht, wenn es soll; das sagt nichts über das Modell aus, das deine Nutzer wählen.
- Entscheide über Sichtbarkeit danach, ob die Formulierung des Nutzers das Tool benennt. Implizit aufgerufene Tools gehören inline; Tools, nach denen Leute namentlich fragen, dürfen gesucht werden.
- Prüf nach, wann die Schritt-Hooks deines Frameworks tatsächlich feuern, bevor du etwas Reihenfolge-Empfindliches hineinlegst.
- Deckle die Meta-Tools. Alles, was sich wiederholt aufrufen lässt, ohne echten Zustand anzufassen, wird auch wiederholt aufgerufen — und ein Schritt-Budget, das in die Suche fließt, ist ein verlorener Turn.
- Lass Fehler zu nicht entdeckten Tools den rettenden Aufruf benennen, genau wie jeden anderen Validator-Fehler.
- Deklariere jedes Tool einmal und leite jede andere Liste daraus ab. Zwei handgepflegte Listen derselben Tools weichen irgendwann voneinander ab, und die Abweichung zeigt sich an einer Sicherheitsgrenze.
- Wenn ein Modul eine tragende Bedingung hat, die dein Compiler nicht ausdrücken kann, schreib den groben Test, der sie auf Textebene durchsetzt.
Wo das läuft
Das alles wird im Tool-Katalog von DynoTable ausgeliefert — schema-bewusstes Abfragen auf deinen eigenen Zugangsdaten für , mit Schreibvorgängen, die immer nur in einem prüfbaren Staging-Bereich landen. Dieselben Deklarationen treiben den MCP-Server an, mit dem sich externe Agenten verbinden; dort wird die Bereitstellungsstufe jeder Zeile zum Scope, den ein externer Client bekommt. Wie wir das abgesichert haben — OAuth, Consent, Isolation der Zugangsdaten — ist eine eigene Geschichte.
Und die Schicht unter alldem — die Validatoren, die jedes dieser Tools für ein günstiges Modell überlebbar machen — ist ein eigener Beitrag.