Instructivo: Llama a funciones con la API de Gemini


Ver en ai.google.dev Ejecutar en Google Colab Ver código fuente en GitHub

Usar llamadas a funciones para definir funciones personalizadas y pasarlas a Gemini El modelo no invoca directamente estas funciones, sino que genera una salida de datos estructurados que especifica el nombre de la función y los argumentos sugeridos. Este resultado habilita la llamada a APIs externas y el resultado de la API resultante puede incorporarse de nuevo al modelo, lo que permite respuestas a consultas más integrales. La llamada de funciones permite a los LLM interactuar con información en tiempo real y diversos servicios, como bases de datos, sistemas de administración de relaciones con clientes y repositorios de documentos, lo que mejora su capacidad para brindar respuestas relevantes y contextuales. Puedes proporcionar descripciones de funciones a los modelos de Gemini. El modelo puede pedirte que llames a una función y envíes el resultado para ayudar al modelo a manejar tu consulta.

Si aún no lo has hecho, consulta la Introducción a las llamadas a funciones para obtener más información.

Configuración

Instala el SDK de Python

El SDK de Python para la API de Gemini se encuentra en el paquete google-generativeai. Instala la dependencia con pip:

pip install -U -q google-generativeai

Importa paquetes

Importa los paquetes necesarios.

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

Cómo configurar tu clave de API

Para poder usar la API de Gemini, primero debes obtener una clave de API. Si aún no tienes una, crea una con un clic en Google AI Studio.

Obtén una clave de API.

En Colab, agrega la clave al administrador de Secrets en la "automated" del panel izquierdo. Asígnale el nombre API_KEY.

Una vez que tengas la clave de API, pásala al SDK. Puedes hacerlo de dos maneras:

  • Coloca la clave en la variable de entorno GOOGLE_API_KEY (el SDK la recogerá automáticamente desde allí).
  • Pasa la llave 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)

Conceptos básicos de la llamada a funciones

Para usar la llamada de funciones, pasa una lista de funciones al parámetro tools cuando crees un GenerativeModel. El modelo usa el nombre de la función, la docstring, los parámetros y las anotaciones de tipo de parámetro para decidir si necesita que la función responda mejor a una instrucción.

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>,
)

Se recomienda usar llamadas a funciones a través de la interfaz de chat. Esto se debe a que las llamadas a funciones se ajustan naturalmente a los chats de varios turnos, ya que capturan la interacción de ida y vuelta entre el usuario y el modelo. El ChatSession del SDK de Python es una gran interfaz para chats porque controla el historial de conversaciones por ti, y el uso del parámetro enable_automatic_function_calling simplifica aún más las llamadas a funciones:

chat = model.start_chat(enable_automatic_function_calling=True)

Con la llamada automática a funciones habilitada, chat.send_message llama automáticamente a tu función si el modelo lo solicita.

Parece que solo muestra una respuesta de texto que contiene la respuesta correcta:

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

Examina el historial de chat para ver el flujo de la conversación y cómo se integran las llamadas a funciones en ella.

La propiedad ChatSession.history almacena un registro cronológico de la conversación entre el usuario y el modelo Gemini. Cada turno de la conversación se representa con un objeto glm.Content, que contiene la siguiente información:

  • Rol: Identifica si el contenido se originó del “usuario” o el “modelo”.
  • Partes: Una lista de objetos glm.Part que representan componentes individuales del mensaje Con un modelo de solo texto, estas partes pueden ser las siguientes:
    • Texto: Mensajes de texto sin formato.
    • Llamada a función (glm.FunctionCall): Es una solicitud del modelo para ejecutar una función específica con los argumentos proporcionados.
    • Respuesta de función (glm.FunctionResponse): Es el resultado que muestra el usuario después de ejecutar la función solicitada.

En el ejemplo anterior con el cálculo de guantes, el historial muestra la siguiente secuencia:

  1. Usuario: Hace la pregunta sobre la cantidad total de guantes.
  2. Model: Determina que la función de multiplicación es útil y envía una solicitud de FunctionCall al usuario.
  3. Usuario: ChatSession ejecuta automáticamente la función (debido a que se establece enable_automatic_function_calling) y devuelve un objeto FunctionResponse con el resultado calculado.
  4. Modelo: Usa el resultado de la función para formular la respuesta final y la presenta como una respuesta de texto.
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.'}
--------------------------------------------------------------------------------

En general, el diagrama de estados es el siguiente:

El modelo siempre puede responder con texto o una FunctionCall. Si el modelo envía una FunctionCall, el usuario debe responder con una FunctionResponse

El modelo puede responder con múltiples llamadas a función antes de mostrar una respuesta de texto, y las llamadas a funciones vienen antes de la respuesta de texto.

Aunque todo esto se controló automáticamente, si necesitas más control, puedes hacer lo siguiente:

  • Deja el enable_automatic_function_calling=False predeterminado y procesa las respuestas de la glm.FunctionCall por tu cuenta.
  • También puedes usar GenerativeModel.generate_content, donde también debes administrar el historial de chat.

Llamada de funciones paralelas

Además de la llamada a funciones básica descrita anteriormente, también puedes llamar a varias funciones en un solo turno. En esta sección, se muestra un ejemplo de cómo puedes usar la llamada de funciones paralelas.

Definir las herramientas

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

Ahora llama al modelo con una instrucción que pueda usar todas las herramientas especificadas.

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

Cada uno de los resultados impresos refleja una única llamada a función que el modelo solicitó. Para devolver los resultados, incluye las respuestas en el mismo orden en que se solicitaron.

# 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! 🕺💃

Acceso de bajo nivel (opcional)

La extracción automática del esquema de las funciones de Python no funciona en todos los casos. Por ejemplo: no se manejan casos en los que describes los campos de un objeto de diccionario anidado, pero la API sí lo admite. La API puede describir cualquiera de los siguientes tipos:

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

La biblioteca cliente de google.ai.generativelanguage proporciona acceso a los tipos de nivel inferior con el control total.

import google.ai.generativelanguage as glm

Primero, revisa el atributo _tools del modelo. Puedes ver cómo describe las funciones que le pasaste al modelo:

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"
   }
 }]

Esto muestra la lista de objetos glm.Tool que se enviarían a la API. Si el formato impreso no te resulta conocido, es porque estas son clases de protobuf de Google. Cada glm.Tool (1 en este caso) contiene una lista de glm.FunctionDeclarations, que describen una función y sus argumentos.

A continuación, se muestra una declaración para la misma función de multiplicación escrita con las clases glm.

Ten en cuenta que estas clases solo describen la función para la API, no incluyen una implementación de ella. Por lo tanto, el uso de esto no funciona con las llamadas automáticas a funciones, pero las funciones no siempre necesitan una implementación.

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

De forma equivalente, puedes describirlo como un objeto compatible 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"
  }
}

De cualquier manera, pasas una representación de un glm.Tool o una lista de herramientas a

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

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

Al igual que antes, el modelo muestra un glm.FunctionCall invocando la función multiply de la calculadora:

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
]

Ejecuta la función tú mismo:

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

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

Envía el resultado al modelo para continuar la conversación:

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

Resumen

El SDK admite llamadas a funciones básicas. Recuerda que es más fácil administrarlo con el modo chat debido a la estructura natural de ida y vuelta. Estás a cargo de llamar a las funciones y enviar los resultados al modelo para que pueda producir una respuesta de texto.