- Dem Modell eine Datenbank und drei Tools geben, die sie lesen
- Diese Tools beschreiben, damit das Modell weiß, wann es zu welchem greifen soll
- Die Schleife ausführen, die Tool-Aufrufe in Tool-Ergebnisse verwandelt
- Beobachten, wie es mehrere Tools gleichzeitig anfordert
- Fehler an das Modell zurückgeben, statt sie zu werfen
- Die Grenze zwischen dem ziehen, was das Modell nicht tun wird, und dem, was es nicht tun kann
Setup
Du brauchst Python 3.9 oder neuer, dasrequests-Paket und einen Venice-API-Schlüssel. Siehe API-Schlüssel erzeugen, falls du noch keinen hast. Alles andere ist in der Standardbibliothek.
agent.py mit den Imports und dem Header-Block, den jeder Aufruf wiederverwendet:
GET /models/traits bildet stabile Trait-Namen auf das Modell ab, das gerade diese Rolle ausfüllt. Wenn du function_calling_default beim Start ausliest, funktioniert dein Agent weiter, wenn das zugrunde liegende Modell ersetzt wird. Siehe Models für die vollständige Trait-Liste.1. Eine Datenbank, die es wert ist, befragt zu werden
Jede SQLite-Datei tut es. Diese hier ist ein kleiner Shop mit Kunden, Produkten und den Bestellungen, die sie verbinden — genug, dass eine echte Frage einen Join und eine Aggregation braucht:2. Drei Tools, nach denen das Modell greifen kann
Die Tools spiegeln wider, wie ein Mensch einer unbekannten Datenbank begegnet: herausfinden, was drin ist, sich eine Tabelle genauer ansehen und sie dann abfragen.description ist kein Kommentar. Sie ist das Einzige, was das Modell liest, wenn es entscheidet, welches Tool es aufruft und was es hineinlegt:
3. Die Schleife
Function Calling ist ein Gespräch, keine Anfrage. Das Modell antwortet mit Tool-Aufrufen, du führst sie aus, hängst die Ergebnisse an und fragst erneut. Es endet, wenn das Modell mit Inhalt statt mit Aufrufen antwortet.messages. Sie trägt die tool_calls, auf die die Ergebnisse antworten, und bei einem Reasoning-Modell trägt sie zusätzlich ein Feld reasoning_content. Die Nachricht von Hand nachzubauen und Felder wegzulassen, mit denen du nicht gerechnet hast, ist der häufigste Weg, die zweite Runde kaputtzumachen.
Jedes Ergebnis wird über tool_call_id seinem Aufruf zugeordnet. Sonst identifiziert es nichts.
max_rounds ist eine echte Grenze, keine Formalität. Ein Modell, das immer weiter abfragt, ohne zu einem Schluss zu kommen, würde sonst schleifen, bis dir die Geduld oder das Guthaben ausgeht.
4. Was er tatsächlich tut
Verdrahte einen Main-Block und führ ihn aus:stderr ausgegeben, während sie passieren, sodass du bei der Arbeit zusehen kannst:
Runde 4 ist der Teil, den ein einzelner Funktionsaufruf nicht leisten kann. Das Modell konnte diese Abfrage nicht schreiben, bevor es die Antwort auf die davor gesehen hatte.
Dein Lauf wird nicht Aufruf für Aufruf zu diesem passen. Das Modell beschreibt manchmal alle drei Tabellen auf einmal und manchmal eine nach der anderen, und gelegentlich überspringt es
list_tables und rät einen Namen. Die Zahlen sind stabil, weil sie aus der Datenbank kommen; der Weg zu ihnen ist es nicht.
Runde 2 lieferte drei Tool-Aufrufe in einer Antwort, und die obige Schleife führt sie nacheinander aus. Sie sind unabhängig, sodass sich ein
ThreadPoolExecutor hier lohnt, sobald deine Tools echtes I/O machen. Halte die tool-Nachrichten in derselben Reihenfolge wie die Aufrufe, die sie erzeugt haben.usage-Block zeigt, wie sich das auszahlt:
5. Lass Fehler das Modell erreichen
Der Instinkt ist, bei einer fehlerhaften Abfrage eine Exception zu werfen. Widersteh ihm. Ein Fehler ist Information, und das Modell kann darauf reagieren. Frag nach einer Tabelle, die nicht existiert:run_query {"error": "OperationalError: no such table: purchases"} als gewöhnliches Tool-Ergebnis zurückgab, statt eine Exception zu werfen, hat das Modell es gelesen, list_tables aufgerufen, um herauszufinden, was tatsächlich existiert, und sich korrigiert. Hätte sich die Exception fortgepflanzt, wäre das Skript an einem Tippfehler gestorben.
Deshalb gibt jedes Tool auch im Fehlerfall JSON zurück. Die Regel ist einfach: Wenn ein Mensch, der dein Tool debuggt, die Nachricht sehen möchte, dann möchte das Modell sie auch.
6. Was er nicht tun wird und was er nicht tun kann
Bitte den Agenten, etwas zu zerstören:SELECT für spanische Kunden ausgeführt, keine gefunden, weil die Spalte ES statt Spain speichert, und stattdessen das gemeldet:
run_query ist der Teil, der nicht von einer Entscheidung abhängt:
Diese zweite Zeile ist der Grund, warum
run_query neben sqlite3.Error auch sqlite3.Warning abfängt. Pythons Treiber lehnt gestapelte Anweisungen ab, wirft für sie aber Warning, und Warning ist keine Unterklasse von Error. Nur sqlite3.Error abzufangen lässt eine gestapelte Anweisung dem Handler entkommen und tötet die Schleife, statt eine Nachricht zurückzugeben, die das Modell lesen kann.Steuern, wann Tools benutzt werden
tool_choice entscheidet, wie viel Mitsprache das Modell hat:
"required" ist plumper, als es aussieht. Frag diesen Agenten What is 2 + 2? mit tool_choice auf "required", und er ruft list_tables auf, sieht sich eine Datenbank an, für die er keine Verwendung hat, und antwortet dann in der nächsten Runde 4. Mit "auto" antwortet er sofort mit 4 und ruft nichts auf. Greif zu "required", wenn ein Tool wirklich ausgeführt werden muss, etwa um eine Anfrage zu protokollieren, und lass es ansonsten sein.
Den Agenten justieren
Nächste Schritte
Die Schleife, die du jetzt hast, ist dieselbe, die hinter den meisten Agenten steckt. Nur die Tools ändern sich.- Tausch die SQL-Tools gegen HTTP-Aufrufe und es wird ein API-Agent.
- Füg Websuche und Scraping als Tool hinzu, und er kann während der Antwort das Live-Web prüfen.
- Frag mit Strukturierten Antworten nach einem typisierten Ergebnis statt Prosa.
- Sieh dir eine größere Version dieses Musters im Private Research Agent an.
Function Calling
Referenz für das tools-Array und tool_choice.
Strukturierte Antworten
Beschränke die finale Antwort auf ein JSON-Schema.
Prompt Caching
Halte das wachsende Gespräch günstig.
Private Research Agent
Dieselbe Schleife mit Web-Tools und einem Planer.