Tutorial: chamada de funções com a API Gemini


Ver em ai.google.dev Executar no Google Colab Consulte o código-fonte no GitHub

Use a chamada de funções para definir funções personalizadas e transmiti-las para o Gemini. O modelo não invoca diretamente essas funções, mas gera uma saída de dados estruturados que especifica o nome da função e os argumentos sugeridos. Essa saída permite chamar APIs externas, e a saída de API resultante pode ser incorporada de volta ao modelo, permitindo respostas de consulta mais abrangentes. As chamadas de função permitem que os LLMs interajam com informações em tempo real e diversos serviços, como bancos de dados, sistemas de gestão de relacionamento com o cliente e repositórios de documentos, melhorando a capacidade de fornecer respostas relevantes e contextuais. É possível fornecer modelos do Gemini com descrições de funções. O modelo pode pedir que você chame uma função e envie o resultado de volta para ajudar o modelo a processar a consulta.

Se você ainda não fez isso, confira a Introdução à chamada de função para saber mais.

Configuração

Instalar o SDK do Python

O SDK do Python para a API Gemini está incluído no pacote google-generativeai. Instale a dependência usando pip:

pip install -U -q google-generativeai

Importar pacotes

Importe os pacotes necessários.

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

Configurar sua chave de API

Antes de usar a API Gemini, é necessário ter uma chave de API. Se você ainda não tiver uma, crie uma chave com um clique no Google AI Studio.

Gerar uma chave de API

No Colab, adicione a chave ao gerenciador de secrets abaixo do "", no painel à esquerda. Dê o nome API_KEY a ela.

Quando você tiver a chave de API, transmita-a ao SDK. Faça isso de duas maneiras:

  • Coloque a chave na variável de ambiente GOOGLE_API_KEY. O SDK vai selecioná-la automaticamente de lá.
  • Transmita a chave para 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)

Noções básicas da chamada de funções

Para usar a chamada de funções, transmita uma lista de funções para o parâmetro tools ao criar um GenerativeModel. O modelo usa o nome da função, docstring, parâmetros e anotações de tipo de parâmetro para decidir se ele precisa da função para responder melhor a um comando.

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

Recomendamos usar chamadas de função na interface de chat. Isso ocorre porque as chamadas de função se encaixam naturalmente em chats com várias interações, porque capturam a interação entre o usuário e o modelo. O ChatSession do SDK para Python é uma ótima interface para chats porque gerencia o histórico de conversas para você, e o uso do parâmetro enable_automatic_function_calling simplifica ainda mais a chamada de funções:

chat = model.start_chat(enable_automatic_function_calling=True)

Com a chamada automática de funções ativada, o chat.send_message vai chamar sua função automaticamente se o modelo pedir.

Ele parece simplesmente retornar uma resposta de texto, contendo a resposta correta:

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

Analise o histórico de chat para conferir o fluxo da conversa e como as chamadas de função são integradas a ela.

A propriedade ChatSession.history armazena um registro cronológico da conversa entre o usuário e o modelo do Gemini. Cada turno da conversa é representado por um objeto glm.Content, que contém as seguintes informações:

  • Papel: identifica se o conteúdo teve origem no "usuário" ou no "modelo".
  • Partes: uma lista de objetos glm.Part que representam componentes individuais da mensagem. Com um modelo somente de texto, essas partes podem ser:
    • Texto: mensagens de texto simples.
    • Chamada de função (glm.FunctionCall): uma solicitação do modelo para executar uma função específica com argumentos fornecidos.
    • Resposta de função (glm.FunctionResponse): o resultado retornado pelo usuário após a execução da função solicitada.

No exemplo anterior com o cálculo de luvas, o histórico mostra a seguinte sequência:

  1. Usuário: faz a pergunta sobre o número total de luvas.
  2. Modelo: determina que a função de multiplicação é útil e envia uma solicitação FunctionCall ao usuário.
  3. Usuário: o ChatSession executa automaticamente a função (devido que enable_automatic_function_calling está definido) e envia um FunctionResponse com o resultado calculado.
  4. Modelo: usa a saída da função para formular a resposta final e a apresenta como uma resposta 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.'}
--------------------------------------------------------------------------------

Em geral, o diagrama de estado é:

O modelo sempre pode responder com texto ou uma FunctionCall. Se o modelo enviar uma FunctionCall, o usuário deverá responder com um FunctionResponse.

O modelo pode responder com várias chamadas de função antes de retornar uma resposta de texto, e as chamadas de função vêm antes da resposta de texto.

Embora tudo isso seja automático, você pode fazer o seguinte para ter mais controle:

  • Deixe o valor padrão enable_automatic_function_calling=False e processe as respostas glm.FunctionCall por conta própria.
  • Ou use o GenerativeModel.generate_content, onde você também precisa gerenciar o histórico de chat.

Chamada de função paralela

Além da chamada de função básica descrita acima, você também pode chamar várias funções em um único turno. Nesta seção, mostramos um exemplo de como usar a chamada de funções paralelas.

Definir as ferramentas.

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

Agora, chame o modelo com uma instrução que possa usar todas as ferramentas 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 um dos resultados impressos reflete uma única chamada de função solicitada pelo modelo. Para enviar os resultados de volta, inclua as respostas na mesma ordem em que foram solicitadas.

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

(Opcional) Acesso de baixo nível

A extração automática do esquema de funções do Python não funciona em todos os casos. Por exemplo: ela não processa casos em que você descreve os campos de um objeto de dicionário aninhado, mas a API suporta isso. A API é capaz de descrever qualquer um dos seguintes tipos:

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

A biblioteca de cliente google.ai.generativelanguage fornece acesso aos tipos de nível inferior, proporcionando controle total.

import google.ai.generativelanguage as glm

Primeiro, confira o atributo _tools do modelo para saber como ele descreve as funções que você transmitiu ao 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"
   }
 }]

Isso retorna a lista de objetos glm.Tool que seriam enviados à API. Se o formato impresso não for familiar, é porque essas são classes protobuf do Google. Cada glm.Tool (1, neste caso) contém uma lista de glm.FunctionDeclarations, que descreve uma função e os argumentos dela.

Veja a seguir uma declaração para a mesma função de multiplicação escrita usando as classes glm.

Essas classes apenas descrevem a função da API, e não incluem uma implementação dela. Portanto, usar isso não funciona com a chamada automática de funções, mas as funções nem sempre precisam de uma implementação.

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

Da mesma forma, é possível descrever esse objeto como um objeto compatível com 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 qualquer forma, você transmite uma representação de um glm.Tool ou uma lista de ferramentas para

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

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

Como antes, o modelo retorna um glm.FunctionCall invocando a função multiply da 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
]

Execute a função:

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

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

Para continuar a conversa, envie o resultado ao modelo:

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

Resumo

A chamada de função básica é compatível com o SDK. Lembre-se de que é mais fácil gerenciar usando o modo de chat devido à estrutura natural de idas e vindas. Você é responsável por chamar as funções e enviar os resultados de volta ao modelo para que ele produza uma resposta de texto.