API de Gemini: Llamadas a funciones con Python

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

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.

Instalar

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 función

Puedes pasar una lista de funciones al argumento tools cuando creas un genai.GenerativeModel.

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

La forma recomendada de usar la llamada a funciones es a través de la interfaz del chat. El motivo principal es que FunctionCalls se adapta bien a la estructura de varios turnos del chat.

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

Si consultas en ChatSession.history, puedes ver la secuencia de eventos:

  1. Enviaste la pregunta.
  2. El modelo respondió con glm.FunctionCall.
  3. genai.ChatSession ejecutó la función de manera local y envió al modelo un glm.FunctionResponse.
  4. El modelo usó la salida de la función en su respuesta.
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.

[Opcional] Acceso de bajo nivel

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.