Tool-Calling mit Ollama

Autor

Joshua Noble

Data Scientist

Tool-Calling mit Ollama

Tool-Calling in großen Sprachmodellen (LLMs) ist die Fähigkeit des LLM, mit externen Tools, Diensten oder APIs zu interagieren, um Aufgaben auszuführen. Dadurch kann die Funktionalität von LLMs erweitert und ihre Fähigkeit verbessert werden, reale Aufgaben zu bewältigen, die den Zugriff auf externe Daten, Echtzeitinformationen oder bestimmte Anwendungen erfordern. Wenn ein LLM ein Web-Suchtool verwendet, kann es das Web aufrufen, um Echtzeitdaten abzurufen, die in den Trainingsdaten des Modells nicht verfügbar sind. Andere Arten von Tools können Python für Berechnungen, Datenanalysen oder Visualisierung oder das Aufrufen eines Endgeräts für Daten enthalten. Durch Tool-Calling kann ein Chatbot dynamischer und anpassungsfähiger werden, sodass er genauere, relevantere und detailliertere Antworten auf der Grundlage von Live-Daten oder speziellen Aufgaben außerhalb seiner unmittelbaren Wissensbasis geben kann. Beliebte Frameworks für den Tool-Aufruf sind Langchain und jetzt auch ollama.

Ollama ist eine Plattform, die lokale Open-Source-KI-Modelle für den Einsatz auf persönlichen Geräten bereitstellt, sodass Benutzer LLMs direkt auf ihren Computern ausführen können. Im Gegensatz zu einem Dienst wie der OpenAI-API ist kein Konto erforderlich, da sich das Modell auf Ihrem lokalen Computer befindet. Ollama konzentriert sich auf Datenschutz, Leistung und Benutzerfreundlichkeit und ermöglicht Benutzern den Zugriff auf und die Interaktion mit KI-Modellen, ohne Daten an externe Server zu senden. Dies kann besonders für diejenigen attraktiv sein, die sich Gedanken über den Datenschutz machen oder die Abhängigkeit von externen APIs vermeiden möchten. Die Plattform von Ollama ist so konzipiert, dass sie einfach einzurichten und zu bedienen ist. Sie unterstützt verschiedene Modelle, um den Benutzern eine Reihe von Tools für die Verarbeitung natürlicher Sprache, die Generierung von Code und andere KI-Aufgaben direkt auf ihrer eigenen Hardware zur Verfügung zu stellen. Es eignet sich gut für eine Tool-Calling-Architektur, da es auf alle Funktionen einer lokalen Umgebung zugreifen kann, einschließlich Daten, Programme und benutzerdefinierte Software.

In diesem Tutorial erfahren Sie, wie Sie einen Tool-Aufruf einrichten, indem Sie mit ollama ein lokales Dateisystem durchsuchen – eine Aufgabe, die mit einem Remote-LLM nur schwer zu bewerkstelligen wäre. Viele Ollama-Modelle sind für den Aufruf von Tools und die Erstellung von KI-Agenten wie Mistral und Llama 3.2 verfügbar. Eine vollständige Liste finden Sie auf der Ollama-Website. In diesem Fall verwenden wir IBM Granite 3.2 Dense mit Tool-Unterstützung. Bei den 2B- und 8B-Modellen handelt es sich um dichte reine Text-LLMs, die auf toolbasierten Anwendungsfällen und für die Retrieval Augmented Generation (RAG) trainiert wurden, um die Codegenerierung, Übersetzung und Fehlerbehebung zu optimieren.

Das Notebook für dieses Tutorial kann hier von Github heruntergeladen werden.

Schritt 1: Ollama installieren

Zuerst laden Sie ollama von https://ollama.com/download herunter und installieren es für Ihr Betriebssystem. Unter OSX erfolgt dies über eine .dmg-Datei, unter Linux über einen einzelnen Shell-Befehl und unter Windows mit einem Installationsprogramm. Möglicherweise benötigen Sie Administratorzugriff auf Ihren Computer, um das Installationsprogramm ausführen zu können.

Sie können testen, ob ollama korrekt installiert ist, indem Sie ein Terminal oder einen Prompt öffnen und Folgendes eingeben:

ollama -v 

 

Schritt 2: Bibliotheken installieren

Als Nächstes fügen Sie die ersten Importe hinzu. Diese Demo verwendet die Python-Bibliothek ollama, um mit ollama zu kommunizieren, und die PyMUpdf-Bibliothek, um PDF-Dateien im Dateisystem zu lesen.

!pip install pymupdf

import ollama
import os
import pymupdf

 

Als Nächstes rufen Sie das Modell ab, das Sie in diesem Tutorial verwenden werden. Dadurch werden die Modellgewichtungen von ollama auf Ihren lokalen Computer heruntergeladen und gespeichert, ohne dass spätere Remote-API-Aufrufe erforderlich sind.

!ollama pull granite3.2
!ollama pull granite3.2-vision

Schritt 3: Definieren Sie die Werkzeuge

Jetzt definieren Sie die Tools, auf die die ollama-Tool-Instanz zugreifen kann. Da die Tools dazu gedacht sind, Dateien zu lesen und Bilder im lokalen Dateisystem zu durchsuchen, erstellen Sie für jedes dieser Tools zwei Python-Funktionen. Der erste heißtsearch_text_files und es wird ein Schlüsselwort benötigt, nach dem in den lokalen Dateien gesucht wird. Für die Zwecke dieser Demo sucht der Code nur nach Dateien in einem bestimmten Ordner, aber er kann um einen zweiten Parameter erweitert werden, der festlegt, in welchem Ordner das Tool sucht.

Sie könnten einen einfachen Zeichenfolgenabgleich verwenden, um zu sehen, ob das Schlüsselwort im Dokument enthalten ist, aber da ollama das Aufrufen lokaler llms vereinfacht, verwendetsearch_text_files Granite 3.2, um festzustellen, ob das Schlüsselwort den Dokumenttext beschreibt. Dies geschieht durch Einlesen des Dokuments in die Zeichenfolgedocument_text . Die Funktion ruft dann ollama.chat auf und fordert das Modell mit folgendem Prompt auf:

"Respond only 'yes' or 'no', do not add any additional information. Is the following text about " + keyword + "? " + document_text 

Wenn das Modell mit „Ja“ antwortet, gibt die Funktion den Namen der Datei zurück, die das Schlüsselwort enthält, das der Benutzer im Prompt angegeben hat. Wenn keine der Dateien die Informationen zu enthalten scheint, gibt die Funktion „Keine“ als Zeichenfolge zurück.

Diese Funktion wird beim ersten Mal möglicherweise langsam ausgeführt, da ollama Granite 3.2 Dense herunterlädt. 

def search_text_files(keyword: str) -> str:
  
  directory = os.listdir("./files/")
  for fname in directory:
    
    # look through all the files in our directory that aren't hidden files
    if os.path.isfile("./files/" + fname) and not fname.startswith('.'):

        if(fname.endswith(".pdf")):
           
           document_text = ""
           doc = pymupdf.open("./files/" + fname)

           for page in doc: # iterate the document pages
               document_text += page.get_text() # get plain text (is in UTF-8)
               
           doc.close()

           prompt = "Respond only 'yes' or 'no', do not add any additional information. Is the following text about " + keyword + "? " + document_text 

           res = ollama.chat(
                model="granite3.2:8b",
                messages=[{'role': 'user', 'content': prompt}]
            )

           if 'Yes' in res['message']['content']:
                return "./files/" + fname

        elif(fname.endswith(".txt")):

            f = open("./files/" + fname, 'r')
            file_content = f.read()
            
            prompt = "Respond only 'yes' or 'no', do not add any additional information. Is the following text about " + keyword + "? " + file_content 

            res = ollama.chat(
                model="granite3.2:8b",
                messages=[{'role': 'user', 'content': prompt}]
            )
           
            if 'Yes' in res['message']['content']:
                f.close()
                return "./files/" + fname

  return "None"

Das zweite Tool heißt search_image_files  und es ist ein Schlüsselwort erforderlich, nach dem in den lokalen Fotos gesucht werden soll. Die Suche erfolgt mit dem Bildbeschreibungsmodell Granite 3.2 Vision via ollama. Dieses Modell gibt eine Textbeschreibung jeder Bilddatei im Ordner zurück und sucht in der Beschreibung nach dem Schlüsselwort. Eine der Stärken von ollama ist, dass Multi-Agent-Systeme einfach erstellt werden können, um ein Modell mit einem anderen aufzurufen.

Die Funktion gibt eine Zeichenfolge zurück. Dies ist der Name der Datei, deren Beschreibung das Schlüsselwort enthält, das der Benutzer im Prompt angegeben hat.

def search_image_files(keyword:str) -> str:

    directory = os.listdir("./files/")
    image_file_types = ("jpg", "png", "jpeg")

    for fname in directory:

        if os.path.isfile("./files/" + fname) and not fname.startswith('.') and fname.endswith(image_file_types):
            res = ollama.chat(
                model="granite3.2-vision",
                messages=[
                    {
                        'role': 'user',
                        'content': 'Describe this image in short sentences. Use simple phrases first and then describe it more fully.',
                        'images': ["./files/" + fname]
                    }
                ]
            )

            if keyword in res['message']['content']:
                return "./files/" + fname
    
    return "None"

Schritt 4: Definieren Sie die Werkzeuge für Ollama

Nachdem die Funktionen, die ollama aufrufen soll, definiert wurden, konfigurieren Sie die Toolinformationen für ollama selbst. Der erste Schritt besteht darin, ein Objekt zu erstellen, das den Namen des Tools den Funktionen für den Aufruf der Ollama-Funktion zuordnet:

available_functions = {
  'Search inside text files':search_text_files,
  'Search inside image files':search_image_files
}

Als Nächstes konfigurieren Sie ein Tools-Array, um ollama mitzuteilen, auf welche Tools es Zugriff hat und was diese Tools benötigen. Dabei handelt es sich um ein Array mit einem Objektschema pro Tool, das dem ollama-Toolaufruf-Framework mitteilt, wie das Tool aufgerufen werden soll und was es zurückgibt.

Bei den beiden Tools, die Sie zuvor erstellt haben, handelt es sich um Funktionen, die einen Schlüsselwortparameter erfordern. Derzeit werden nur Funktionen unterstützt, dies kann sich jedoch in Zukunft ändern. Die Beschreibung der Funktion und des Parameters helfen dem Modell, das Tools korrekt aufzurufen. Das Beschreibung -Feld für die Funktion jedes Tools wird an das LLM weitergegeben, wenn es auswählt, welches Tool verwendet werden soll. Der Beschreibung des Schlüsselworts wird an das Modell weitergeben, wenn es die Parameter generiert, die an das Tool weitergeben werden. An beiden Stellen können Sie die Prompts feinabstimmen, wenn Sie Ihre eigenen Anwendungen zum Aufrufen von Tools mit ollama erstellen.

# tools don't need to be defined as an object but this helps pass the correct parameters
# to the tool call itself by giving the model a prompt of how the tool is to be used
ollama_tools=[
     {
      'type': 'function',
      'function': {
        'name': 'Search inside text files',
        'description': 'This tool searches in PDF or plaintext or text files in the local file system for descriptions or mentions of the keyword.',
        'parameters': {
          'type': 'object',
          'properties': {
            'keyword': {
              'type': 'string',
              'description': 'Generate one keyword from the user request to search for in text files',
            },
          },
          'required': ['keyword'],
        },
      },
    },
    {
      'type': 'function',
      'function': {
        'name': 'Search inside image files',
        'description': 'This tool searches for photos or image files in the local file system for the keyword.',
        'parameters': {
          'type': 'object',
          'properties': {
            'keyword': {
              'type': 'string',
              'description': 'Generate one keyword from the user request to search for in image files',
            },
          },
          'required': ['keyword'],
        },
      },
    },
  ]


Sie verwenden diese Tooldefinition, wenn Sie ollama mit einer Benutzereingabe aufrufen.

Schritt 5: Übergeben Sie die Eingabe an ollama

Jetzt ist es an der Zeit, die Benutzereingabe an ollama weiterzuleiten und es die Ergebnisse der Toolaufrufe zurückgeben zu lassen. Stellen Sie zunächst sicher, dass ollama auf Ihrem System läuft:

# if ollama is not currently running, start it
import subprocess
subprocess.Popen(["ollama","serve"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)

Wenn Ollama läuft, wird Folgendes zurückgegeben:

<Popen: returncode: None args: ['ollama', 'serve']>

Bitten Sie den Benutzer nun um eine Eingabe. Sie können die Eingabe auch fest codieren oder von einer Chat-Oberfläche abrufen, je nachdem, wie Sie Ihre Anwendung konfigurieren. Die Funktion Eingabe  wartet auf die Eingabe des Benutzers, bevor fortgefahren wird.

# input
user_input = input("What would you like to search for?")
print(user_input)

Wenn der Benutzer beispielsweise „Informationen über Hunde“ eingibt, wird in dieser Zelle Folgendes angezeigt:

Information about dogs

Nun wird die Benutzerabfrage an ollama selbst weitergeben. Die Nachrichten benötigen eine Rolle für den Benutzer und den vom Benutzer eingegebenen Inhalt. Dies wird über die Chat-Funktion an ollama weitergegeben. Der erste Parameter ist das Modell, das Sie verwenden möchten, in diesem Fall Granite 3.2 Dense, dann die Nachricht mit der Benutzereingabe und schließlich das Tools-Array, das Sie zuvor konfiguriert haben.

Die Chat-Funktion -Funktion generiert eine Ausgabe, die auswählt, welches Tool verwendet werden soll und welche Parameter in den nachfolgenden Toolaufrufen an sie weitergeben werden sollen.

messages = [{'role': 'user', 'content':user_input}]

response: ollama.ChatResponse = ollama.chat(
   
  # set which model we're using
  'granite3.2:8b',

  # use the message from the user
  messages=messages,

  tools=ollama_tools
)

Nachdem das Modell nun Toolaufrufe in der Ausgabe generiert hat, führen Sie alle Toolaufrufe mit den Parametern aus, die das Modell generiert hat, und überprüfen Sie die Ausgabe. In dieser Anwendung wird Granite 3.2 Dense auch verwendet, um die endgültige Ausgabe zu generieren, sodass die Ergebnisse der Toolaufrufe zur anfänglichen Benutzereingabe hinzugefügt und dann an das Modell weitergegeben werden.

Mehrere Toolaufrufe können Dateiübereinstimmungen zurückgeben, daher werden die Antworten in einem Array gesammelt, das dann an Granite 3.2 weitergeleitet wird, um eine Antwort zu generieren. Der Prompt, der den Daten vorangeht, weist das Modell an, wie es reagieren soll:

If the tool output contains one or more file names, then give the user only the filename found. Do not add additional details. 
If the tool output is empty ask the user to try again. Here is the tool output: 

Die endgültige Ausgabe wird dann entweder mit den zurückgegebenen Dateinamen generiert oder 

# this is a place holder that to use to see whether the tools return anything 
output = []

if response.message.tool_calls:
  
  # There may be multiple tool calls in the response
  for tool_call in response.message.tool_calls:

    # Ensure the function is available, and then call it
    if function_to_call := available_functions.get(tool_call.function.name):
      print('Calling tool: ', tool_call.function.name, ' \n with arguments: ', tool_call.function.arguments)
      tool_res = function_to_call(**tool_call.function.arguments)

      print(" Tool response is " + str(tool_res))

      if(str(tool_res) != "None"):
        output.append(str(tool_res))
        print(tool_call.function.name, ' has output: ', output)
    else:
      print('Could not find ', tool_call.function.name)

  # Now chat with the model using the tool call results
  # Add the function response to messages for the model to use
  messages.append(response.message)

  prompt = '''
    If the tool output contains one or more file names, 
    then give the user only the filename found. Do not add additional details. 
    If the tool output is empty ask the user to try again. Here is the tool output: 
  '''

  messages.append({'role': 'tool', 'content': prompt + " " + ", ".join(str(x) for x in output)})
  
  # Get a response from model with function outputs
  final_response = ollama.chat('granite3.2:8b', messages=messages)
  print('Final response:', final_response.message.content)

else:

  # the model wasn't able to pick the correct tool from the prompt
  print('No tool calls returned from model')

Mit den bereitgestellten Dateien für dieses Tutorial gibt der Prompt „Informationen über Hunde“ Folgendes zurück:

    Calling tool:  Search inside text files  
     with arguments:  {'keyword': 'dogs'}
     Tool response is ./files/File4.pdf
    Search inside text files  has output:  ['./files/File4.pdf']
    Calling tool:  Search inside image files  
     with arguments:  {'keyword': 'dogs'}
     Tool response is None
    Final response: The keyword "dogs" was found in File4.pdf.

Sie können sehen, dass Granite 3.2 das richtige Schlüsselwort aus der Eingabe ausgewählt hat, „Hunde“, die Dateien im Ordner durchsucht und das Schlüsselwort in einer PDF-Datei gefunden hat. Da die LLM-Ergebnisse nicht rein deterministisch sind, können Sie bei demselben Prompt oder sehr ähnlichen Prompts leicht unterschiedliche Ergebnisse erhalten.

Weiterführende Lösungen
KI-Agenten für Unternehmen

Entwickeln, implementieren und verwalten Sie leistungsstarke KI-Assistenten und -Agenten, die Workflows und Prozesse mit generativer KI automatisieren.

    Entdecken Sie watsonx Orchestrate
    IBM KI-Agenten-Lösungen

    Gestalten Sie die Zukunft Ihres Unternehmens mit KI-Lösungen, denen Sie vertrauen können.

    KI-Agenten-Lösungen erkunden
    IBM Consulting KI-Dienstleistungen

    Die KI-Services von IBM Consulting unterstützen Sie dabei, die Art und Weise, wie Unternehmen mit KI arbeiten, neu zu denken.

    Erkunden Sie die Services im Bereich der künstlichen Intelligenz
    Machen Sie den nächsten Schritt

    Ganz gleich, ob Sie vorgefertigte Apps und Skills anpassen oder mithilfe eines KI-Studios eigene Agentenservices erstellen und bereitstellen möchten, die IBM watsonx-Plattform bietet Ihnen alles, was Sie brauchen.

    Entdecken Sie watsonx Orchestrate watsonx.ai erkunden