Tool calling con Ollama

Autore

Joshua Noble

Data Scientist

Tool calling con Ollama

Il tool calling nei modelli linguistici di grandi dimensioni (LLM) è la capacità dell'LLM di interagire con strumenti, servizi o API esterni per eseguire attività. Ciò consente agli LLM di estendere le loro funzionalità, migliorando la loro capacità di gestire attività reali che potrebbero richiedere l'accesso a dati esterni, informazioni in tempo reale o applicazioni specifiche. Quando un LLM utilizza uno strumento di ricerca web, può richiamare il web per recuperare dati in tempo reale che non sono disponibili nei dati di addestramento del modello. Altri tipi di strumenti potrebbero includere Python per i calcoli, l'analisi o la visualizzazione dei dati o la chiamata a un endpoint di servizio per i dati. Il tool calling possono rendere un chatbot più dinamico e adattabile, consentendogli di fornire risposte più accurate, pertinenti e dettagliate sulla base di dati in tempo reale o attività specializzate al di fuori della sua base di conoscenza immediata. I framework più diffusi per il tool calling includono Langchain e ora Ollama.

Ollama è una piattaforma che offre modelli AI open source da utilizzare su dispositivi personali in modo che gli utenti possano eseguire gli LLM direttamente sui propri computer. A differenza di un servizio come l'API OpenAI, non è necessario un account poiché il modello si trova sul computer locale. Ollama si concentra sulla privacy, sulle prestazioni e sulla facilità d'uso, consentendo agli utenti di accedere e interagire con i modelli AI senza inviare dati a server esterni. Questo può essere particolarmente interessante per chi è preoccupato per la privacy dei dati o che vuole evitare di fare affidamento su API esterne. La piattaforma di Ollama è progettata per essere facile da configurare e utilizzare e supporta vari modelli, offrendo agli utenti una gamma di strumenti per l'elaborazione del linguaggio naturale, la generazione di codice e altre attività di AI direttamente sul proprio hardware. È adatto a un'architettura di tool calling perché può accedere a tutte le funzionalità di un ambiente locale, inclusi dati, programmi e software personalizzato.

In questo tutorial imparerai come impostare il tool calling utilizzando Ollama per esaminare un file di sistema locale, un'attività che sarebbe difficile da eseguire con un LLM remoto. Molti modelli Ollama sono disponibili per il tool calling e la creazione di agenti AI come Mistral e Llama 3.2, un elenco completo è disponibile sul sito web Ollama. In questo caso utilizzeremo IBM Granite 3.2 Dense che supporta gli strumenti. I modelli 2B e 8B sono LLM ad alta densità di testo addestrati su cui sono stati addestrati e progettati per supportare casi d'uso basati su strumenti e per la retrieval-augmented generation (RAG), che semplificano la generazione di codice, la traduzione e la correzione dei bug.

Il notebook per questo tutorial può essere scaricato da Github qui.

Passaggio 1: installare Ollama

Per prima cosa scarica ollama da https://ollama.com/download e installalo sul tuo sistema operativo. Su OSX questo avviene tramite un file .dmg, su Linux tramite un singolo comando shell e su Windows con un programma di installazione. Per eseguire il programma di installazione, potrebbe essere necessario l'accesso come amministratore al computer.

Puoi verificare che ollama sia installato correttamente aprendo un terminale o un prompt dei comandi e inserendo:

ollama -v 

 

Passaggio 2: installare le librerie

Aggiungi quindi le importazioni iniziali. Questa demo utilizza la libreria python ollama per comunicare con ollama e la libreria pymupdf per leggere i file PDF nel file system.

!pip install pymupdf

import ollama
import os
import pymupdf

 

Estrai quindi il modello da utilizzare in questo tutorial. Questa operazione scarica i pesi del modello da ollama sul tuo computer locale e li memorizza senza dover effettuare chiamate API remote in un momento successivo.

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

Passaggio 3: definire gli strumenti

Ora definisci gli strumenti a cui avrà accesso l'istanza degli strumenti ollama. Poiché l'intento degli strumenti è quello di leggere i file e sfogliare le immagini nel file system locale, dovrai creare due funzioni Python per ciascuno di questi strumenti. Il primo si chiamasearch_text_files e richiede una parola chiave per la ricerca nei file locali. Ai fini di questa demo, il codice cerca solo i file in una cartella specifica, ma potrebbe essere esteso per includere un secondo parametro che imposta la cartella in cui lo strumento eseguirà la ricerca.

Potrebbe usare una semplice corrispondenza di stringhe per vedere se la parola chiave è contenuta nel documento, ma poiché ollama consente di chiamare facilmente gli LLM locali,search_text_files utilizzerà Granite 3.2 per determinare se la parola chiave descrive il testo del documento. Ciò avviene leggendo il documento in una stringa chiamatadocument_text La funzione chiama quindi ollama.chat ed emette il seguente prompt per il modello:

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

Se il modello risponde "yes", la funzione restituisce il nome del file che contiene la parola chiave indicata dall'utente nel prompt. Se nessuno dei file sembra contenere le informazioni, la funzione restituisce "None" come stringa.

Questa funzione potrebbe essere lenta la prima volta perché ollama scarica Granite 3.2 Dense. 

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"

Il secondo strumento si chiama search_image_files  e serve una parola chiave per la ricerca nelle foto locali. La ricerca viene effettuata utilizzando il modello di descrizione delle immagini Granite 3.2 Vision tramite ollama. Questo modello restituisce una descrizione testuale di ogni file immagine nella cartella e cerca la parola chiave nella descrizione. Uno dei punti di forza dell'utilizzo di ollama è che si possono facilmente costruire sistemi multi-agente per chiamare un modello con un altro.

La funzione restituisce una stringa, che è il nome del file la cui descrizione contiene la parola chiave indicata dall'utente nel prompt.

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"

Passaggio 4: definire gli strumenti per Ollama

Ora che le funzioni da chiamare per ollama sono state definite, bisogna configurare le informazioni sullo strumento per ollama stesso. Il primo passo consiste nel creare un oggetto che mappa il nome dello strumento alle funzioni per la chiamata della funzione ollama:

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

Configura quindi un array di strumenti per dire a ollama a quali strumenti avrà accesso e cosa richiedono tali strumenti. Si tratta di un array con uno schema di oggetti per strumento che indica al framework di chiamata dello strumento ollama come chiamare lo strumento e cosa esso restituisce.

Nel caso degli strumenti creati in precedenza, si tratta di funzioni che richiedono parola chiave parametro. Al momento sono supportate solo le funzioni, sebbene questo potrebbe cambiare in futuro. La descrizione della funzione e del parametro aiuta il modello a chiamare correttamente lo strumento. Il Descrizione campo per la funzione di ogni strumento viene passato all'LLM quando seleziona lo strumento da utilizzare. Il Descrizione della parola chiave viene passato al modello quando genera i parametri che verranno passati allo strumento. In entrambi i casi puoi cercare di mettere a punto i prompt quando crei le tue applicazioni di chiamata degli strumenti con ollama.

# 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'],
        },
      },
    },
  ]


Utilizzerai questa definizione degli strumenti per chiamare ollama con l'input dell'utente.

Passaggio 5: passare l'input dell'utente a Ollama

Ora è il momento di passare l'input dell'utente a ollama e fare in modo che restituisca i risultati delle chiamate allo strumento. Innanzitutto, assicurati che ollama sia in esecuzione sul tuo sistema:

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

Se Ollama è in esecuzione restituirà:

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

Ora chiedi l'input all'utente. Puoi anche codificare l'input o recuperarlo da un'interfaccia di chat a seconda della configurazione dell'applicazione. La funzione input  attenderà l'input dell'utente prima di continuare.

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

Ad esempio, se l'utente inserisce "Information about dogs" questa cella stamperà:

Information about dogs

Ora la query dell'utente viene passata a ollama stesso. I messaggi richiedono un ruolo per l'utente e il contenuto immesso dall'utente. Questo viene passato a ollama usando la funzione di chat. Il primo parametro è il modello che vuoi utilizzare, in questo caso Granite 3.2 Dense, quindi il messaggio con l'input dell'utente e infine l'array di strumenti che hai configurato in precedenza.

La funzione funzione di genererà un output selezionando quale strumento utilizzare e quali parametri devono essere passati a esso nelle successive chiamate allo strumento.

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
)

Ora che il modello ha generato chiamate allo strumento nell'output, esegui tutte le chiamate allo strumento con i parametri generati dal modello e controlla l'output. In questa applicazione Granite 3.2 Dense viene utilizzato anche per generare l'output finale, quindi i risultati del tool calling vengono aggiunti all'input iniziale dell'utente e quindi passati al modello.

Chiamate multiple possono restituire corrispondenze tra file, quindi le risposte vengono raccolte in un array che viene poi passato a Granite 3.2 per generare una risposta. Il prompt che precede i dati indica al modello come rispondere:

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: 

L'output finale viene quindi generato utilizzando i nomi di file restituiti o 

# 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')

Utilizzando i file forniti per questo tutorial, il prompt "Information about dogs" ritornerà:

    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.

Come si può vedere, Granite 3.2 ha selezionato la parola chiave corretta dall'input, "dogs", e ha effettuato una ricerca nei file della cartella, trovando la parola chiave in un file PDF. Poiché i risultati LLM non sono puramente deterministici, potresti ottenere risultati leggermente diversi con lo stesso prompt o con prompt molto simili.

Soluzioni correlate
Agenti AI per il Business

Crea, implementa e gestisci assistenti e agenti AI potenti che automatizzano workflow e processi con l'AI generativa.

    Scopri watsonx Orchestrate
    Soluzioni per agenti AI IBM

    Costruisci il futuro della tua azienda con soluzioni AI di cui puoi fidarti.

    Esplora le soluzioni basate su agenti AI
    Servizi AI di IBM Consulting

    I servizi di AI di IBM Consulting aiutano a reinventare il modo in cui le aziende lavorano con l'AI per la trasformazione.

    Esplora i servizi di intelligenza artificiale
    Prossimi passi

    Sia che tu scelga di personalizzare app e competenze precostituite o di creare e implementare servizi di agenti personalizzati utilizzando uno studio di AI, la piattaforma IBM watsonx è la soluzione che fa per te.

    Scopri watsonx Orchestrate Esplora watsonx.ai