Tutorial: chiamate di funzioni con l'API Gemini


Visualizza su ai.google.dev Esegui in Google Colab Visualizza il codice sorgente su GitHub

Utilizza le chiamate di funzione per definire funzioni personalizzate e passarle a Gemini. Il modello non richiama direttamente queste funzioni, ma genera invece un output di dati strutturati che specifica il nome della funzione e gli argomenti suggeriti. Questo output consente la chiamata di API esterne e l'output API risultante può essere incorporato di nuovo nel modello, consentendo risposte più complete alle query. Le chiamate di funzione consentono agli LLM di interagire con informazioni in tempo reale e vari servizi, come database, sistemi di gestione delle relazioni con i clienti e repository di documenti, migliorando la loro capacità di fornire risposte pertinenti e contestuali. Puoi fornire ai modelli Gemini le descrizioni delle funzioni. Il modello potrebbe chiederti di chiamare una funzione e inviare il risultato per aiutarlo a gestire la query.

Se non l'hai ancora fatto, consulta l'introduzione alle chiamate di funzione per saperne di più.

Configurazione

Installa l'SDK Python

L'SDK Python per l'API Gemini è contenuto nel pacchetto google-generativeai. Installa la dipendenza utilizzando pip:

pip install -U -q google-generativeai

Importa pacchetti

Importa i pacchetti necessari.

import pathlib
import textwrap
import time

import google.generativeai as genai

from IPython import display
from IPython.display import Markdown

def to_markdown(text):
  text = text.replace('•', '  *')
  return Markdown(textwrap.indent(text, '> ', predicate=lambda _: True))

Configura la chiave API

Prima di poter utilizzare l'API Gemini, devi ottenere una chiave API. Se non ne hai già una, crea una chiave con un clic in Google AI Studio.

Ottenere una chiave API

In Colab, aggiungi la chiave al gestore dei secret nella sezione "☁" del riquadro a sinistra. Assegna il nome API_KEY.

Una volta ottenuta la chiave API, passala all'SDK. A tale scopo, puoi procedere in uno dei due seguenti modi:

  • Inserisci la chiave nella variabile di ambiente GOOGLE_API_KEY (l'SDK la acquisirà automaticamente da lì).
  • Passa la chiave a genai.configure(api_key=...)
try:
    # Used to securely store your API key
    from google.colab import userdata

    # Or use `os.getenv('API_KEY')` to fetch an environment variable.
    GOOGLE_API_KEY=userdata.get('GOOGLE_API_KEY')
except ImportError:
    import os
    GOOGLE_API_KEY = os.environ['GOOGLE_API_KEY']

genai.configure(api_key=GOOGLE_API_KEY)

Nozioni di base sulle chiamate di funzione

Per utilizzare le chiamate di funzione, passa un elenco di funzioni al parametro tools durante la creazione di GenerativeModel. Il modello utilizza il nome della funzione, docstring, i parametri e le annotazioni del tipo di parametro per decidere se ha bisogno della funzione per rispondere al meglio a un prompt.

def multiply(a:float, b:float):
    """returns a * b."""
    return a*b

model = genai.GenerativeModel(model_name='gemini-1.0-pro',
                              tools=[multiply])

model
genai.GenerativeModel(
    model_name='models/gemini-1.0-pro',
    generation_config={},
    safety_settings={},
    tools=<google.generativeai.types.content_types.FunctionLibrary object at 0x10e73fe90>,
)

Ti consigliamo di utilizzare le chiamate alle funzioni tramite l'interfaccia della chat. Questo perché le chiamate di funzione si inseriscono naturalmente nelle chat multi-turno poiché acquisiscono l'interazione tra l'utente e il modello. ChatSession dell'SDK Python è un'ottima interfaccia per le chat perché gestisce la cronologia delle conversazioni per conto tuo e l'uso del parametro enable_automatic_function_calling semplifica ulteriormente le chiamate alle funzioni:

chat = model.start_chat(enable_automatic_function_calling=True)

Quando le chiamate di funzione automatiche sono attivate, chat.send_message chiama automaticamente la tua funzione se il modello lo richiede.

Sembra che restituisca semplicemente un messaggio di risposta contenente la risposta corretta:

response = chat.send_message('I have 57 cats, each owns 44 mittens, how many mittens is that in total?')
response.text
'The total number of mittens is 2508.'
57*44
2508

Esamina la cronologia chat per vedere il flusso della conversazione e in che modo le chiamate di funzione sono integrate al suo interno.

La proprietà ChatSession.history archivia un record cronologico della conversazione tra l'utente e il modello Gemini. Ogni turno della conversazione è rappresentato da un oggetto glm.Content, che contiene le seguenti informazioni:

  • Ruolo: identifica se il contenuto proviene da "utente" o "modello".
  • Parts: un elenco di oggetti glm.Part che rappresentano i singoli componenti del messaggio. Con un modello di solo testo, queste parti possono essere:
    • Testo: messaggi di testo normali.
    • Chiamata funzione (glm.FunctionCall): una richiesta del modello di eseguire una funzione specifica con gli argomenti forniti.
    • Risposta della funzione (glm.FunctionResponse): il risultato restituito dall'utente dopo l'esecuzione della funzione richiesta.

Nell'esempio precedente, relativo al calcolo dei guanti, la cronologia mostra la seguente sequenza:

  1. Utente: pone una domanda sul numero totale di guanti.
  2. Modello: determina l'utilità della funzione di moltiplicazione e invia una richiesta FunctionCall all'utente.
  3. Utente: l'ChatSession esegue automaticamente la funzione (perché enable_automatic_function_calling è stato impostato) e invia un FunctionResponse con il risultato calcolato.
  4. Modello: utilizza l'output della funzione per formulare la risposta finale e la presenta come una risposta di testo.
for content in chat.history:
    part = content.parts[0]
    print(content.role, "->", type(part).to_dict(part))
    print('-'*80)
user -> {'text': 'I have 57 cats, each owns 44 mittens, how many mittens is that in total?'}
--------------------------------------------------------------------------------
model -> {'function_call': {'name': 'multiply', 'args': {'a': 57.0, 'b': 44.0} } }
--------------------------------------------------------------------------------
user -> {'function_response': {'name': 'multiply', 'response': {'result': 2508.0} } }
--------------------------------------------------------------------------------
model -> {'text': 'The total number of mittens is 2508.'}
--------------------------------------------------------------------------------

In generale, il diagramma di stato è:

Il modello può sempre rispondere con un testo o una funzione FunctionCall. Se il modello invia una funzione FunctionCall, l&#39;utente deve rispondere con una FunctionResponse

Il modello può rispondere con più chiamate di funzione prima di restituire una risposta testuale, e le chiamate di funzione precedeno la risposta testuale.

Sebbene il tutto sia stato gestito automaticamente, se hai bisogno di un maggiore controllo puoi:

  • Lascia il valore predefinito enable_automatic_function_calling=False ed elabora autonomamente le risposte glm.FunctionCall.
  • In alternativa, utilizza GenerativeModel.generate_content, dove devi gestire anche la cronologia chat.

Chiamate di funzione parallele

Oltre alle chiamate di funzione di base descritte sopra, puoi anche chiamare più funzioni in un unico turno. Questa sezione mostra un esempio di come utilizzare le chiamate di funzione parallele.

Definire gli strumenti.

def power_disco_ball(power: bool) -> bool:
    """Powers the spinning disco ball."""
    print(f"Disco ball is {'spinning!' if power else 'stopped.'}")
    return True


def start_music(energetic: bool, loud: bool, bpm: int) -> str:
    """Play some music matching the specified parameters.

    Args:
      energetic: Whether the music is energetic or not.
      loud: Whether the music is loud or not.
      bpm: The beats per minute of the music.

    Returns: The name of the song being played.
    """
    print(f"Starting music! {energetic=} {loud=}, {bpm=}")
    return "Never gonna give you up."


def dim_lights(brightness: float) -> bool:
    """Dim the lights.

    Args:
      brightness: The brightness of the lights, 0.0 is off, 1.0 is full.
    """
    print(f"Lights are now set to {brightness:.0%}")
    return True

Ora chiama il modello con un'istruzione che potrebbe utilizzare tutti gli strumenti specificati.

# Set the model up with tools.
house_fns = [power_disco_ball, start_music, dim_lights]

model = genai.GenerativeModel(model_name="gemini-1.5-pro-latest", tools=house_fns)

# Call the API.
chat = model.start_chat()
response = chat.send_message("Turn this place into a party!")

# Print out each of the function calls requested from this single call.
for part in response.parts:
    if fn := part.function_call:
        args = ", ".join(f"{key}={val}" for key, val in fn.args.items())
        print(f"{fn.name}({args})")
power_disco_ball(power=True)
start_music(energetic=True, loud=True, bpm=120.0)
dim_lights(brightness=0.3)

Ciascuno dei risultati stampati riflette una singola chiamata di funzione richiesta dal modello. Per inviare i risultati, includi le risposte nello stesso ordine in cui sono state richieste.

# Simulate the responses from the specified tools.
responses = {
    "power_disco_ball": True,
    "start_music": "Never gonna give you up.",
    "dim_lights": True,
}

# Build the response parts.
response_parts = [
    glm.Part(function_response=glm.FunctionResponse(name=fn, response={"result": val}))
    for fn, val in responses.items()
]

response = chat.send_message(response_parts)
print(response.text)
Let's get this party started! I've turned on the disco ball, started playing some upbeat music, and dimmed the lights. 🎶✨  Get ready to dance! 🕺💃

(Facoltativo) Accesso di basso livello

L'estrazione automatica dello schema dalle funzioni Python non funziona in tutti i casi. Ad esempio, non gestisce i casi in cui descrivi i campi di un oggetto dizionario nidificato, ma l'API supporta questa operazione. L'API è in grado di descrivere i seguenti tipi:

AllowedType = (int | float | bool | str | list['AllowedType'] | dict[str, AllowedType]

La libreria client di google.ai.generativelanguage consente di accedere ai tipi di basso livello, dandoti il pieno controllo.

import google.ai.generativelanguage as glm

Prima di tutto all'interno dell'attributo _tools del modello, puoi vedere come descrive la funzione o le funzioni che hai trasmesso al modello:

def multiply(a:float, b:float):
    """returns a * b."""
    return a*b

model = genai.GenerativeModel(model_name='gemini-1.0-pro',
                             tools=[multiply])

model._tools.to_proto()
[function_declarations {
   name: "multiply"
   description: "returns a * b."
   parameters {
     type_: OBJECT
     properties {
       key: "b"
       value {
         type_: NUMBER
       }
     }
     properties {
       key: "a"
       value {
         type_: NUMBER
       }
     }
     required: "a"
     required: "b"
   }
 }]

Questo restituisce l'elenco di glm.Tool oggetti che verranno inviati all'API. Se il formato stampato non conosce il formato, si tratta di classi protobuf di Google. Ogni glm.Tool (1 in questo caso) contiene un elenco di glm.FunctionDeclarations, che descrivono una funzione e i relativi argomenti.

Ecco una dichiarazione per la stessa funzione di moltiplicazione scritta utilizzando le classi glm.

Tieni presente che queste classi descrivono solo la funzione per l'API e non ne includono un'implementazione. Quindi, l'utilizzo di questa funzione non funziona con le chiamate di funzione automatiche, ma non sempre le funzioni richiedono un'implementazione.

calculator = glm.Tool(
    function_declarations=[
      glm.FunctionDeclaration(
        name='multiply',
        description="Returns the product of two numbers.",
        parameters=glm.Schema(
            type=glm.Type.OBJECT,
            properties={
                'a':glm.Schema(type=glm.Type.NUMBER),
                'b':glm.Schema(type=glm.Type.NUMBER)
            },
            required=['a','b']
        )
      )
    ])

In modo equivalente, puoi descrivere questo oggetto come un oggetto compatibile con JSON:

calculator = {'function_declarations': [
      {'name': 'multiply',
       'description': 'Returns the product of two numbers.',
       'parameters': {'type_': 'OBJECT',
       'properties': {
         'a': {'type_': 'NUMBER'},
         'b': {'type_': 'NUMBER'} },
       'required': ['a', 'b']} }]}
glm.Tool(calculator)
function_declarations {
  name: "multiply"
  description: "Returns the product of two numbers."
  parameters {
    type_: OBJECT
    properties {
      key: "b"
      value {
        type_: NUMBER
      }
    }
    properties {
      key: "a"
      value {
        type_: NUMBER
      }
    }
    required: "a"
    required: "b"
  }
}

In ogni caso, passi una rappresentazione di glm.Tool o di un elenco di strumenti a

model = genai.GenerativeModel('gemini-pro', tools=calculator)
chat = model.start_chat()

response = chat.send_message(
    f"What's 234551 X 325552 ?",
)

Come prima che il modello restituisca un glm.FunctionCall che richiama la funzione multiply della calcolatrice:

response.candidates
[index: 0
content {
  parts {
    function_call {
      name: "multiply"
      args {
        fields {
          key: "b"
          value {
            number_value: 325552
          }
        }
        fields {
          key: "a"
          value {
            number_value: 234551
          }
        }
      }
    }
  }
  role: "model"
}
finish_reason: STOP
]

Esegui la funzione autonomamente:

fc = response.candidates[0].content.parts[0].function_call
assert fc.name == 'multiply'

result = fc.args['a'] * fc.args['b']
result
76358547152.0

Invia il risultato al modello per continuare la conversazione:

response = chat.send_message(
    glm.Content(
    parts=[glm.Part(
        function_response = glm.FunctionResponse(
          name='multiply',
          response={'result': result}))]))

Riepilogo

Le chiamate di funzione di base sono supportate nell'SDK. Ricorda che è più facile da gestire con la modalità chat, a causa della naturale struttura degli scambi. Sta a te chiamare le funzioni e inviare i risultati al modello in modo che possa produrre una risposta testuale.