Skip to main content
Ein Modell allein kann dir nicht sagen, was gerade auf der Startseite von Hacker News steht. Dafür braucht es Werkzeuge, und jemand muss diese Werkzeuge bauen und pflegen. Apify hat das bereits getan: Es hostet Tausende Actors, die Websites scrapen, Dokumentation crawlen und strukturierte Daten abrufen, und stellt sie über das Model Context Protocol bereit. Diese Kombination passt gut zu Venice. Venice liefert OpenAI-kompatibles Function Calling ohne Datenspeicherung, Apify liefert die Werkzeuge, und MCP ist das Übertragungsformat dazwischen. Du schreibst keinen Scraper pro Website — du verbindest dich einmal und lässt das Modell den Actor auswählen. In diesem Tutorial bauen wir einen Terminal-Agenten in Python, der genau das tut. Am Ende hast du eine CLI, die zur Laufzeit ein Venice-Modell mit Function Calling entdeckt, den Apify-Werkzeugkatalog über MCP lädt, Antworten in dein Terminal streamt und nachfragt, bevor sie Geld für einen Actor-Lauf ausgibt. Interessiert an der vollständigen Code-Implementierung? Schau dir das GitHub-Repo an. Bevor wir weitermachen, brauchst du einen Venice-API-Schlüssel:

Was wir bauen

Die Referenzimplementierung ist ein kleines Python-Paket mit einer Aufgabe pro Modul: Eine einzelne Frage fließt so hindurch:
  1. Frag Venice nach dem aktuellen Function-Calling-Modell, es sei denn, du hast eines gepinnt.
  2. Verbinde dich mit dem Apify-MCP-Server und liste seine Werkzeuge auf.
  3. Schreibe diese MCP-Werkzeuge als OpenAI-kompatible Funktionsdefinitionen um.
  4. Sende die Frage mit angehängter Werkzeugliste.
  5. Wenn das Modell tool_calls zurückgibt, führe sie gegen Apify aus und hänge die Ergebnisse als tool-Nachrichten an.
  6. Wiederhole, bis das Modell mit Text statt mit einem Werkzeugaufruf antwortet.
Die Schritte 4 bis 6 sind der ganze Agent. Alles andere existiert, um diese drei Schritte sicher und angenehm nutzbar zu machen.
Dieser Agent kann Apify-Compute auf deinem Konto verbrauchen. Starte ohne APIFY_TOKEN, wenn du nur Such- und Dokumentationswerkzeuge willst, und lass --yes weg, bis du tatsächlich Actors ausführen willst.

Das Projekt aufsetzen

Das Referenzprojekt nutzt Python 3.12+ und uv. Erstelle ein neues Projekt:
Installiere die Abhängigkeiten:
Das ist httpx2, die 2.x-Linie von httpx, von der sowohl openai als auch mcp bereits abhängen. Sie direkt zu installieren vermeidet, dass am Ende zwei HTTP-Clients in derselben Umgebung landen. Erstelle dann eine .env-Datei:
VENICE_API_KEY kommt aus den Venice-API-Einstellungen. APIFY_TOKEN kommt aus der Apify Console und ist optional — was du ohne ihn bekommst, klären wir gleich.

Konfiguration laden

Die Einstellungen kommen zuerst, weil jedes andere Modul sie als Argument entgegennimmt. Wir verwenden pydantic-settings, damit Umgebungsvariablen, .env und CLI-Flags alle in einem validierten Objekt landen. In src/venice_terminal_agent/config.py trägt eine Settings(BaseSettings)-Klasse die Felder, auf die es ankommt:
Zwei davon tragen Entscheidungen statt Standardwerte. venice_model ist None statt einer Modell-ID, worauf wir im nächsten Abschnitt zurückkommen. Und max_rounds mit max_tool_result_chars sind die Grenzen, die einen davonlaufenden Agenten stoppen: Die erste deckelt, wie viele Werkzeugrunden eine Frage in Anspruch nehmen darf, die zweite deckelt, wie viel einer gescrapten Seite zurück in den Kontext gespeist wird. Die interessante Funktion in diesem Modul ist der URL-Builder:
Der gehostete Apify-MCP-Server nimmt einen tools-Query-Parameter entgegen, der entscheidet, welche Werkzeuge er anbietet. Ohne APIFY_TOKEN fragen wir nach den vier anonymen Werkzeugen, die ohne Authentifizierung funktionieren — Actor-Suche, Actor-Details, Dokumentationssuche und Dokumentationsabruf. Das bedeutet, jemand kann das Projekt klonen, nur einen Venice-Schlüssel hinzufügen und bekommt trotzdem einen funktionierenden Agenten, der Apify Actors recherchieren kann. Er kann nur keinen ausführen.

Mit Venice sprechen

Venice ist OpenAI-kompatibel, also können wir das OpenAI-SDK für Chat Completions verwenden und schlichtes httpx für den Modell-Discovery-Aufruf. Erstelle src/venice_terminal_agent/venice.py:
Zwei Clients für eine API sieht redundant aus, aber sie machen unterschiedliche Jobs. AsyncOpenAI gibt uns den Streaming-Helper und typisierte tool_calls gratis dazu. Der rohe httpx-Client ist für Venice-Endpunkte da, die das OpenAI-SDK nicht kennt, was in diesem Projekt /models/traits bedeutet. Das Chat-Timeout ist absichtlich viel länger als das Discovery-Timeout. Eine Frage, die einen Web-Crawl auslöst, kann legitimerweise ein paar Minuten dauern.

Ein Modell zur Laufzeit entdecken

Venice-Modell-IDs rotieren, und eine hartzucodieren ist der schnellste Weg, einen Agenten auszuliefern, der in einem Monat kaputtgeht. GET /models/traits bildet stabile Trait-Namen auf das Modell ab, das diese Rolle gerade ausfüllt, also fragen wir nach function_calling_default, statt ein Modell zu benennen:
Die Rangfolge hier ist wichtig: Ein explizites --model-Flag gewinnt, dann VENICE_MODEL aus der Umgebung, dann die Trait-Abfrage. Der Standardpfad braucht also gar keine Konfiguration, aber du kannst trotzdem ein Modell pinnen, wenn du das Verhalten zweier Modelle vergleichst.
Nicht jedes Textmodell unterstützt Function Calling. Nach dem Trait function_calling_default zu fragen bedeutet, dass du eines bekommst, das es kann, ohne selbst eine Liste zu pflegen. Siehe Deprecations dazu, wie oft sich die zugrunde liegenden IDs ändern.

Completions streamen

Füge nun den Completion-Aufruf hinzu:
Wir streamen, damit die Nutzerin oder der Nutzer Text erscheinen sieht, während er generiert wird, aber wir wollen danach trotzdem die zusammengesetzte Nachricht — Werkzeugaufrufe kommen in Fragmenten über viele Chunks an, und sie von Hand wieder zusammenzusetzen ist mühsam. Der stream()-Context-Manager des SDK erledigt beides: content.delta-Events treiben die Terminalausgabe an, und get_final_completion() liefert eine vollständige Nachricht mit bereits zusammengefügten tool_calls. Die Anfrage selbst wird von einer separaten Funktion gebaut, damit sie leicht testbar bleibt:
extra_body ist der Weg, über den das OpenAI-SDK Felder durchreicht, die es nicht modelliert, und dort landet venice_parameters. include_venice_system_prompt auf false zu setzen hält den Standard-Assistenten-Prompt von Venice aus der Konversation heraus, sodass unser eigener System-Prompt die einzige Anweisung ist, die das Modell bekommt. Für einen Agenten mit strengen Werkzeugregeln ist das genau das, was du willst. Hänge tools und tool_choice nur an, wenn es mindestens ein Werkzeug gibt. Ein leeres tools-Array zu senden ist eine unnötige Art, ein Modell zu verwirren. Das Modul hat außerdem einen format_http_error()-Helfer, der einen APIStatusError oder einen httpx2.HTTPStatusError in einen Einzeiler mit Statuscode und Response-Body verwandelt. Agenten scheitern an der API-Grenze öfter als irgendwo sonst, und eine lesbare Meldung dort erspart viel Rätselraten.

MCP-Werkzeuge in Venice-Werkzeuge umwandeln

MCP-Werkzeuge und Function-Tools im OpenAI-Stil beschreiben dasselbe in unterschiedlichen Formen. Beide haben einen Namen, eine Beschreibung und ein JSON-Schema für Argumente. Die Übersetzung ist größtenteils mechanisch, mit einem Haken: Apify-Werkzeugnamen enthalten Zeichen, die Funktionsnamen nicht erlauben. Ein Actor-Werkzeug kann apify/rag-web-browser heißen, und dieser Schrägstrich ist nicht gültig. Also bereinigen wir die Namen auf dem Hinweg und behalten eine Zuordnung, damit wir sie auf dem Rückweg wiederherstellen können. In src/venice_terminal_agent/tools.py erledigt ein ToolCatalog die Übersetzung und hält die Zuordnung:
Drei kleine Helfer erledigen die unglamouröse Arbeit. sanitize_tool_name() ersetzt unzulässige Zeichen durch Bindestriche, stellt Namen, die mit einer Ziffer beginnen, ein Präfix voran und kürzt auf 64 Zeichen. unique_name() hängt dann ein numerisches Suffix an, falls diese Kürzung zwei Actors kollidieren lässt — das erspart dir einen wirklich verwirrenden Bug, bei dem das Modell einen Actor aufruft und ein anderer läuft. tool_input_schema() kommt damit zurecht, dass MCP-Server ein dict, ein Pydantic-Modell oder gar nichts zurückgeben.

Ergebnisse zurück in den Kontext formatieren

Werkzeugergebnisse gehen direkt in die Konversation, also müssen sie ein String sein, und sie brauchen ein Größenlimit. Das Scrapen einer Dokumentationsseite kann leicht mehr Text zurückgeben, als das Kontextfenster fasst. format_tool_result() bevorzugt structured_content, wenn der Server es liefert, und flacht andernfalls die Content-Blöcke zu Text ab, wobei es auch mit Blöcken zurechtkommt, die kein TextContent sind. Es endet mit den zwei Zeilen, auf die es ankommt:
Der Kürzungshinweis ist für das Modell geschrieben, nicht für dich. Ihm zu sagen, dass Inhalt abgeschnitten wurde, und Filter, Limits oder Offsets vorzuschlagen, reicht meist aus, damit es einen enger gefassten zweiten Aufruf macht, statt anzunehmen, es hätte alles gesehen. Fehler werden als {"error": "..."} verpackt statt geworfen. Ein fehlgeschlagener Werkzeugaufruf ist Information, auf die das Modell reagieren kann — es kann einen anderen Actor wählen oder seine Argumente korrigieren — und das kann es nur, wenn der Fehlschlag es als normales Werkzeugergebnis erreicht.

Die Werkzeuge markieren, die Geld kosten

Apify-Werkzeuge teilen sich sauber in zwei Gruppen: solche, die Metadaten und Dokumentation lesen, und solche, die Compute starten. Für die zweite Gruppe wollen wir eine Bestätigung, also setzen wir die erste auf eine Allowlist:
Eine Allowlist statt einer Blocklist ist die wichtige Entscheidung. Apify fügt laufend Werkzeuge und Actors hinzu, und alles, was der Agent noch nicht gesehen hat, fragt standardmäßig erst nach. Mach es andersherum, und jeder neue Actor wird automatisch genehmigt.

Über MCP mit Apify verbinden

Apify bietet zwei Zugänge. Der gehostete Server unter https://mcp.apify.com spricht Streamable HTTP, und @apify/actors-mcp-server läuft lokal über stdio via npx. Wir unterstützen beide, da sie zu unterschiedlichen Situationen passen: Gehostet braucht kein Node.js, und stdio hält die Verbindung auf deiner eigenen Maschine. In src/venice_terminal_agent/apify_mcp.py umschließt eine ApifyMcp-Klasse die verbundene Session. Ihr call_tool() ist die Stelle, an der der bereinigte Name zurückübersetzt wird — Venice sendet apify-rag-web-browser, Apify empfängt apify/rag-web-browser:
Das Bauen des Katalogs braucht eine Cursor-Schleife über client.list_tools(), da ein Token mit Zugriff auf viele Actors eine paginierte Liste erzeugt.

Den Transport besitzen

Eine MCP-Verbindung ist eine langlebige asynchrone Ressource, und der HTTP-Client darunter ebenso. Ein ApifyMcpSession-Async-Context-Manager hält beide in einem AsyncExitStack, wählt anhand der Einstellungen einen Transport und lädt den Katalog. Das Detail, das es zu kopieren lohnt, ist das Aufräumen:
Dieses except BaseException ist wichtiger, als es aussieht. Wenn das Auflisten der Werkzeuge fehlschlägt, nachdem der Transport steht, leckst du ohne es bei jedem Fehlstart des Agenten einen Subprozess oder einen offenen Socket. Hier sind die beiden Transporte:
Beachte das Read-Timeout von 300 Sekunden auf dem HTTP-Transport. Actor-Läufe sind langsam, und das Standard-Timeout von 30 Sekunden schneidet völlig gesunde Crawls ab. Beachte auch, dass der stdio-Subprozess nur APIFY_TOKEN in seiner Umgebung bekommt, nicht deine gesamte Shell-Umgebung — einschließlich deines Venice-Schlüssels.

Einen Werkzeugaufruf ausführen

Das letzte Stück dieses Moduls, execute_venice_tool_call(), verwandelt einen Venice-Werkzeugaufruf in ein String-Ergebnis. Es verpackt beide Fehlerklassen — nicht parsbare Argumente und einen fehlgeschlagenen Apify-Aufruf — als {"error": "..."}, statt sie zu werfen:
Fehlerhafte JSON-Argumente kommen vor. Wenn es passiert, bringt dir das Übergeben von {"error": "invalid arguments: ..."} an das Modell einen korrigierten Aufruf in der nächsten Runde, während ein geworfener Fehler die Session beendet und die Konversation verliert.

Die Werkzeugschleife ausführen

Nun zum Agenten selbst, in src/venice_terminal_agent/agent.py. Beginne mit dem System-Prompt:
Jede Regel dort entspricht einem konkreten Fehlschlag, den wir vermeiden wollen. „Prefer search-actors and fetch-actor-details before calling an unfamiliar Actor” existiert, weil ein Modell, das das Eingabeschema eines Actors errät, einen kostenpflichtigen Lauf verschwendet. Die Zeile über abgelehnte Werkzeuge existiert, weil das Modell eine Ablehnung sonst als vorübergehenden Fehler behandelt und es sofort erneut versucht. Die Agent-Klasse nimmt die beiden Clients, ein Modell, ein Rundenlimit und drei Callbacks entgegen:
Diese Callbacks halten den Agenten unabhängig vom Terminal. on_tool meldet einen Werkzeugaufruf, on_text empfängt gestreamte Tokens, und approve_tool beantwortet die Bestätigungsfrage. Tausche sie aus, und derselbe Agent funktioniert hinter einer Web-App oder einem Chatbot. Hier ist die Schleife:
Das ist der gesamte Agent: Ruf das Modell auf, und wenn es Werkzeuge angefragt hat, führe sie aus und ruf erneut auf. Der start-Index und das del im Exception-Handler verdienen einen genaueren Blick. Wenn eine Frage auf halbem Weg scheitert — Netzwerkfehler, Ctrl+C, Rundenlimit — bleibt in der Konversation ein Assistant-Turn zurück, der Werkzeuge anfordert, die nie Ergebnisse geliefert haben. Venice wird die nächste Anfrage ablehnen, denn auf einen tool_calls-Turn müssen passende tool-Nachrichten folgen. Zum Anfang der Frage zurückzurollen bedeutet, dass eine gescheiterte Frage keine Spur hinterlässt und die REPL nutzbar bleibt.

Den Assistant-Turn zurückspiegeln

Die nächste Funktion ist klein und leicht falsch zu machen:
Die naheliegende Implementierung ist message.model_dump(exclude_none=True), und sie zerstört das Tool Calling. Ein Tool-Call-Turn hat content: null, und diesen Schlüssel wegzulassen verändert die Form der Nachricht, die du zurücksendest. exclude_unset=True ist die Variante, die du willst: Sie behält null-Werte, die das Modell tatsächlich gesetzt hat, und lässt Felder weg, die es nie gesendet hat. Sie bewahrt außerdem Felder, die das OpenAI-Schema nicht kennt. Reasoning-Modelle geben reasoning_content und reasoning_details zurück, und diese müssen den Rundtrip überleben, damit das Modell seine eigene Gedankenkette über Werkzeugrunden hinweg behält.

Aufrufe ausführen und absichern

Modelle können mehrere Werkzeuge in einem Turn anfordern, und es gibt keinen Grund, sie nacheinander auszuführen. Aber die Genehmigungen wollen wir sequenziell abfragen, da verschachtelte Bestätigungsabfragen unlesbar wären. Also planen wir erst und führen dann nebenläufig aus:
Abgelehnte Werkzeuge bekommen trotzdem eine tool-Nachricht. Jede tool_call_id braucht eine Antwort, und eine auszulassen hinterlässt eine fehlerhafte Konversation. Die Antwort erklärt eben nur, dass die Nutzerin oder der Nutzer Nein gesagt hat. Die Genehmigungsprüfung selbst konsultiert beide Namen, da das Modell mit bereinigten Namen arbeitet und unsere Allowlist MCP-Namen verwendet:

Die CLI hinzufügen

Die CLI in src/venice_terminal_agent/cli.py ist Typer plus eine REPL, und sie ist die uninteressanteste Datei im Projekt — aber drei Details darin lohnen das Kopieren. Das erste ist, dass die Typer-Optionen als optional typisiert sind und standardmäßig None sind, damit der Settings-Loader „nicht übergeben” von „ein falsy Wert übergeben” unterscheiden kann:
Diese None-Defaults machen die Übergabe an load_settings() sicher, da ein Flag, das du nicht genutzt hast, niemals die Umgebung überschreibt:
Das yes or None ist dieselbe Idee, angewandt auf ein boolesches Flag: --yes setzt es, und es wegzulassen übergibt None statt False, sodass AUTO_APPROVE_TOOLS aus der Umgebung erhalten bleibt. Das zweite ist die Startreihenfolge. Löse das Modell auf, öffne dann die MCP-Session, baue dann den Agenten — und schließe den Venice-Client in einem finally, da die MCP-Session und die HTTP-Clients beide abgewickelt werden müssen, ob die Frage nun erfolgreich war oder nicht:
Das dritte ist der Approver, das eine Stück des Agenten, das allein dazu existiert, deine Apify-Rechnung zu schützen:
Die isatty()-Prüfung ist der Teil, den viele vergessen. Führe den Agenten aus cron oder CI aus, und es ist niemand da, der die Abfrage beantwortet — eine naive Implementierung hängt also entweder für immer oder genehmigt stillschweigend. Hier lehnt sie ab, sagt warum, und lässt das Modell mit den Nur-Lese-Werkzeugen weitermachen. default=False bedeutet, dass ein versehentliches Enter keinen kostenpflichtigen Lauf startet, und das Unterbrechen der Abfrage zählt als Nein. Der Rest des Moduls ist gewöhnliche Terminalarbeit, es lohnt sich also eher zu wissen, was drin ist, als es zu lesen: eine prompt_toolkit-REPL-Schleife, ein _handle_command()-Lookup für die Slash-Befehle, eine render.py mit Rich-Helfern und ein _settings_error(), das einen fehlenden VENICE_API_KEY in eine lesbare Meldung statt eines Pydantic-Tracebacks verwandelt. Drei davon tragen eine Entscheidung: Die Slash-Befehle sind /help, /clear, /quit und zwei, die sich bezahlt machen: /tools gibt den geladenen Katalog aus, was meist erklärt, warum der Agent ein merkwürdiges Werkzeug gewählt hat, und /reload nimmt Actors auf, die du mitten in der Session zu deinem Apify-Konto hinzugefügt hast. Verdrahte zum Schluss den Einstiegspunkt in pyproject.toml, damit uv run venice-agent funktioniert:

Den Agenten ausführen

Starte eine interaktive Session:
Oder stell eine Frage und beende danach:
Du siehst das Banner und dann die Werkzeugaufrufe, während sie passieren:
Lies die Apify-Zeile auf diesem Banner vor allem anderen. Wenn dort „anonymous Apify tools only” steht, wurde dein APIFY_TOKEN nicht geladen, und das jetzt zu bemerken ist deutlich besser als nach zehn Minuten Rätselns, warum der Agent sich weigert, einen Actor auszuführen. Beschränke den Werkzeugkatalog, wenn du weißt, was du brauchst:
Ein kleinerer Katalog ist nicht nur eine Kostenfrage. Modelle wählen generell besser, wenn es weniger, dafür relevantere Werkzeuge zur Auswahl gibt, und --tools ist der günstigste Weg, die Auswahl einzugrenzen. Führe den MCP-Server lokal aus, statt den gehosteten zu nutzen:
Diese Variante braucht Node.js in deinem PATH, da sie @apify/actors-mcp-server über npx startet, und sie braucht ein APIFY_TOKEN — für den lokalen Server gibt es keinen anonymen Modus. Und wenn du wirklich unbeaufsichtigte Actor-Läufe willst:

Die Bausteine testen

Nichts von der interessanten Logik hier braucht ein Netzwerk. Ein FakeVenice, das aus einer geskripteten Liste von Antworten poppt, plus ein FakeApify, das einen echten ToolCatalog aus SimpleNamespace-Werkzeugen baut, reicht aus, um eine volle Werkzeugrunde zu treiben:
Auf die Abfolge der Rollen zu asserten ist eine gute Angewohnheit für Agent-Code. Es fängt die Bugs mit fehlerhaften Konversationen, die sonst unsichtbar bleiben, bis Venice einen 400 zurückgibt. Drei weitere Tests lohnen sich, und alle asserten auf dieselbe Weise auf agent.messages. Dass ein fehlgeschlagener Lauf den Verlauf auf nur ["system"] zurückrollt, egal ob er an einem Venice-Fehler oder am Erschöpfen von max_rounds gescheitert ist. Dass ein Nur-Lese-Werkzeug trotzdem läuft, wenn der Approver False zurückgibt. Und dass ein abgelehntes kostenpflichtiges Werkzeug eine tool-Nachricht mit declined hinterlässt, während apify.calls leer bleibt. Führe die Suite aus mit:

Hinweise zu Privatsphäre und Kosten

Bei einem Agenten, der zwei APIs erreicht, lohnt sich Präzision: Venices Zero Data Retention deckt die Modellseite ab. Sie deckt nicht Apify ab, und ein Actor-Lauf schreibt Ergebnisse in dein Apify-Konto. Wenn das für eine bestimmte Aufgabe wichtig ist, lauf ohne APIFY_TOKEN und bleib bei den anonymen Discovery-Werkzeugen. Bei den Kosten bringen drei Gewohnheiten viel:
  • Lass --yes während der Entwicklung weg. Zu beobachten, welche Actors das Modell ausführen will, ist an sich schon aufschlussreich.
  • Nutze --tools, um den Katalog auf Actors einzugrenzen, die du tatsächlich geprüft hast.
  • Halte max_rounds moderat. Zwölf Runden sind für Recherche-Aufgaben reichlich, und eine niedrigere Obergrenze begrenzt den Schaden, wenn ein Modell in einer Schleife feststeckt.

Dieses Beispiel erweitern

Die Schleife ist das Fundament. Sobald sie funktioniert, sind nützliche Richtungen unter anderem:
  • Füge einen zweiten MCP-Server hinzu. Nichts in Agent ist Apify-spezifisch, das Zusammenführen von Katalogen mehrerer Server bedeutet also hauptsächlich Namespacing der Werkzeugnamen.
  • Persistiere Konversationen in SQLite, damit du eine Session fortsetzen oder auditieren kannst, was ein Actor zurückgegeben hat.
  • Füge Budgets pro Werkzeug hinzu, die Actor-Läufe verfolgen und an einer Obergrenze stoppen, statt jeden einzelnen zu bestätigen.
  • Cache Werkzeugergebnisse nach Name und Argumenten, damit wiederholte Dokumentationsabfragen nicht erneut crawlen.
  • Pinne ein Modell mit --model und vergleiche die Qualität der Werkzeugauswahl mit function_calling_default.
  • Tausche den Approver gegen eine Policy-Funktion, die bestimmte Actors mit bestimmten Argumenten automatisch genehmigt und bei allem anderen nachfragt.
Für einen kleineren Startpunkt ohne MCP behandelt Einen Tool-nutzenden Agenten bauen dieselbe Schleife mit drei lokalen Python-Funktionen.

Zum Abschluss

Danke fürs Lesen! Hoffentlich hat dir das geholfen, einen Terminal-Agenten zu bauen, der mit Venice denkt und über Apify handelt. Das Muster, das es mitzunehmen lohnt, ist, wie wenig von diesem Code mit Intelligenz zu tun hat. Das Modell gibt Werkzeugaufrufe zurück, und dein Code entscheidet, welche davon laufen dürfen, wie ihre Ergebnisse zurückkommen und was passiert, wenn etwas schiefgeht. Sobald diese Entscheidungen explizit sind, ist das Hinzufügen von Fähigkeiten hauptsächlich eine Frage davon, den Agenten auf mehr Werkzeuge zu richten.