GitHub
Prototyppackages/ai-function-caller/src

AI Function Caller

@ralphschuler/ai-function-caller

Orchestriert Modellantworten, Gesprächsverlauf und lokal registrierte Funktionen – der vorhandene Stand führt die Tool-Schleife jedoch noch nicht aus.

tool callingconversationOpenAI

01 · Problem

Wofür braucht man das?

Ein Sprachmodell kann eine Funktion nur beschreiben und anfordern; Anwendungscode muss den Namen sicher auflösen, Argumente validieren, den Handler ausführen und das Ergebnis wieder in den Dialog einspeisen.

02 · Denkmodell

Das mentale Modell

Behandle das Modell als untrusted planner in einer begrenzten Schleife. Tool-Schema und ausführbarer Handler sind getrennte Verträge, verbunden über einen kontrollierten Registry-Lookup.

Im Repository

AIFunctionCaller sendet Nachrichten und Funktionsbeschreibungen an Chat Completions. Die private Ausführungsroutine ist nicht mit query verbunden, Nachrichtentypen passen nicht zum SDK und das Package kompiliert derzeit nicht.

03 · Kontrollfluss

Was passiert in welcher Reihenfolge?

  1. User-Nachricht an einen unveränderlich nachvollziehbaren Verlauf anhängen.
  2. Modell mit Nachrichten und ausschließlich öffentlichen Tool-Schemas aufrufen.
  3. Tool-Calls nach Name auflösen und JSON-Argumente gegen das jeweilige Schema prüfen.
  4. Handler ausführen, Ergebnis als Tool-Nachricht speichern und erneut anfragen.
  5. Bei normaler Antwort oder nach einem harten Schrittlimit beenden.

04 · Bauteile

Die entscheidenden Verträge

AIFunctionCaller.query(message) Startet im Repository genau einen Modellaufruf und gibt die Antwortnachricht zurück.
AIFunction Verbindet Funktionsbeschreibung, Parameter und lokalen Handler; diese Rollen sollten im Nachbau getrennt werden.
Message / MessageRole Eigene Verlaufstypen, die aktuell nicht deckungsgleich mit den SDK-Nachrichten sind.

05 · Build it yourself

Selbst implementieren

Baue zuerst eine modellunabhängige Tool-Schleife und injiziere anschließend einen Adapter zum gewünschten Provider.

  1. Definiere Tool-Definition und Handler getrennt und registriere beide unter einem eindeutigen Namen.
  2. Validiere jedes vom Modell gelieferte Argumentobjekt, bevor Anwendungscode es sieht.
  3. Begrenze Schritte, Laufzeit und Ergebnisgröße; leite ein AbortSignal durch alle Aufrufe.
minimal.ts · unabhängig vom Package
type Tool = { schema: object; run(input: unknown): Promise<unknown> };

async function runAgent(input: string, signal: AbortSignal) {
  const messages = [{ role: "user", content: input }];
  for (let step = 0; step < 6; step++) {
    const reply = await model.complete(messages, signal);
    messages.push(reply.message);
    if (!reply.toolCalls.length) return reply.message.content;
    for (const call of reply.toolCalls) {
      const tool = tools.get(call.name);
      if (!tool) throw new Error("Unknown tool: " + call.name);
      const args = validate(tool.schema, JSON.parse(call.arguments));
      const value = await tool.run(args);
      messages.push({ role: "tool", content: JSON.stringify(value) });
    }
  }
  throw new Error("Tool-call limit reached");
}

06 · Verifizieren

Was du testen solltest

  • Ein unbekannter Toolname wird abgelehnt und niemals per Property-Zugriff ausgeführt.
  • Ungültiges JSON, Schemafehler und Handlerfehler erscheinen als definierter Fehlerpfad.
  • Schrittlimit, Timeout und Abort beenden auch eine endlose Tool-Call-Kette.

07 · Grenzen

Kompromisse und Stolperfallen

  • Modellantworten sind untrusted input und dürfen keine direkte Codeausführung auslösen.
  • Gesprächsverläufe wachsen; Kontextfenster und sensible Daten benötigen eine eigene Strategie.
  • Provider-SDKs ändern Nachrichtentypen und Tool-Protokolle – der Adapter sollte klein bleiben.
Wichtig

API-Schlüssel gehören ausschließlich in eine Serverumgebung. Eine statische Browser-App darf diesen Ablauf nicht direkt mit einem geheimen Provider-Key ausführen.

08 · Weiterdenken

Quellcode und Nachbarn

Originalcode auf GitHub ansehen