Python Code Example Of Gemini Chat+ History+ FunctionCalling with Interpeter

Since I could not find any example that would be easy to understand and build upon i created this little script that shows the work flow

# -*- coding: utf-8 -*-
import google.generativeai as genai
genai.configure(api_key=' key')
import  os
import  json
# Define color codes for terminal output


# Define color codes for terminal output
reset = "\033[0m"
black = "\033[30m"
red = "\033[31m"
green = "\033[32m"
yellow = "\033[33m"
blue = "\033[34m"
magenta = "\033[35m"
cyan = "\033[36m"
white = "\033[37m"
bright_black = "\033[90m"
bright_red = "\033[91m"
bright_green = "\033[92m"
bright_yellow = "\033[93m"
bright_blue = "\033[94m"
bright_magenta = "\033[95m"
bright_cyan = "\033[96m"
bright_white = "\033[97m"

messages = []

def multiply(a: float, b: float):
   """Returns the product of two numbers."""
   return a * b
# Example usage
multiply_description = {
   'function_declarations': [
       {
           'name': 'multiply',
           'description': 'Returns the product of two numbers.',
           'parameters': {
               'type_': 'OBJECT',
               'properties': {
                   'a': {'type_': 'NUMBER'},
                   'b': {'type_': 'NUMBER'}
               },
               'required': ['a', 'b']
           }
       }
   ]
}

def save_to_file(content: str = None, file_name: str = 'NoName', file_path: str = None):
   print(f"{blue}Entering: save_to_file(...)")
   """Saves content to a file and returns detailed execution result.

   Args:
       content (str): The content to save.
       file_name (str, optional): The name of the file. Defaults to 'NoName'.
       file_path (str, optional): The path to save the file. Defaults to the current working directory if not provided.

   Returns:
       dict: A dictionary containing the status, message, and file path if successful.
   """
   if content is None:
       content = ""
   if file_path is None:
       full_path = os.path.join(os.getcwd(), file_name)
   else:
       full_path = os.path.join(file_path, file_name)

   try:
       with open(full_path, 'w', encoding='utf-8') as f:
           f.write(content)

       success_message = f"File saved successfully at: {full_path}"
       print(f"{green}{success_message}")
       print(f"{blue}Exiting: save_to_file(...)")
       return {"status": "success", "message": success_message, "file_path": full_path}

   except Exception as e:
       error_message = f"Failed to save file: {str(e)}"
       print(f"{red}{error_message}")
       print(f"{blue}Exiting: save_to_file(...)")
       return {"status": "failure", "message": error_message}
save_to_file_description = {
   'function_declarations': [
       {
           'name': 'save_to_file',
           'description': 'Saves content to a file.',
           'parameters': {
               'type_': 'OBJECT',
               'properties': {
                   'content': {'type_': 'STRING'},
                   'file_name': {'type_': 'STRING', 'description': 'The name of the file. Defaults to "NoName".'},
                   'file_path': {'type_': 'STRING',
                                 'description': 'The path to save the file. Defaults to the current working directory if not provided.'}
               },
               'required': []
           }
       }
   ]
}




function_mapping = {
   'multiply': multiply,
   'save_to_file': save_to_file
}

model = genai.GenerativeModel(
   system_instruction="You are a helpful assistant. Never use emojis. You can save files using functions!",
   model_name='gemini-1.5-pro-latest',
   safety_settings={'HARASSMENT': 'block_none'},
   tools=[multiply_description, save_to_file_description]
)

def interpreter(response):
   print(f"{bright_yellow}----------------INTERPRETER START----------------------")
   """
   Interprets the model's response, extracts function details,
   and executes the appropriate function with the provided arguments.
   """
   Multiple_ResultsOfFunctions_From_interpreter = []

   if response.candidates:
       for part in response.candidates[0].content.parts:
           if hasattr(part, 'function_call'):
               function_call = part.function_call
               function_name = function_call.name
               function_args = function_call.args

               function_to_call = function_mapping.get(function_name)
               print(f"FUNCTION CALL: {function_name}({function_args}) ")

               try:
                   results = function_to_call(**function_args)
               except TypeError as e:
                   results = f"TypeError: {e}"
               except Exception as e:
                   results = f"Exception: {e}"

               print(f"{bright_blue}Function Call Exit: {function_name}")

               function_name_arguments = f"{function_name}({function_args})"

               modified_results = f"Result of Called function {function_name_arguments}: {results}"
               Multiple_ResultsOfFunctions_From_interpreter.append(modified_results)

   print(f"{bright_yellow}----------------INTERPRETER END------------------------")
   print()
   return Multiple_ResultsOfFunctions_From_interpreter



chat1 = model.start_chat(history=[])
while True:
   user_input = input(f"{bright_green}user input (type 'exit' to quit): {reset}")
   if user_input.lower() == 'exit':
       break

   try:
       response = chat1.send_message(user_input)
       print(f"{bright_magenta}Model Response: {response}")
       try:
           results = interpreter(response)
           print(f"{bright_cyan}Results of Interpreter and Called Functions: {results}")
       except Exception as e:
           print(f"{bright_red}Error with interpreter: {e}")
   except Exception as e:
       print(f"{bright_red}An error occurred: {e}")


print(f"{bright_yellow}Exiting the program.")
  1. Define the New Function:
  • Create a Python function with a descriptive name and parameters.

  • Include a docstring that clearly explains what the function does.

  1. Create a Description for the Function (Function Description Variable):
  • Create a separate variable with the function name appended with “_description” (e.g., multiply_description).

  • Define a dictionary within this variable that includes the following:

    • function_declarations: A list containing a dictionary representing the function’s details:

      • name: The name of the function.

      • description: A textual description of the function.

      • parameters: A dictionary defining the parameters:

        • type_: The data type of the parameter (e.g., “NUMBER”, “STRING”, “OBJECT”).

        • properties: (For object types) A dictionary defining the properties of the object.

        • required: A list of required parameters (if any).

  1. Add the Function to the Function Mapping (Function Mapping Dictionary):
  • Update the function_mapping dictionary, which maps function names to their corresponding implementations.

  • Add a new entry with the function name as the key and the function itself as the value.

  1. Update the Model with the New Function Description (LLM’s Tools):
  • Pass the function description variable (e.g., multiply_description) to the tools parameter when creating the genai.GenerativeModel instance.

  • The LLM will now be aware of the new function and its capabilities.

  1. Ensure the Function Provides Execution Results:
  • The function should return a meaningful result that can be used in subsequent interactions.

  • The interpreter handles function execution and returns the result to the LLM, making it available for further processing or inclusion in the next prompt.

Example:

def multiply(a: float, b: float):
    """Returns the product of two numbers."""
    return a * b

multiply_description = {
    'function_declarations': [
        {
            'name': 'multiply',
            'description': 'Returns the product of two numbers.',
            'parameters': {
                'type_': 'OBJECT',
                'properties': {
                    'a': {'type_': 'NUMBER'},
                    'b': {'type_': 'NUMBER'}
                },
                'required': ['a', 'b']
            }
        }
    ]
}

function_mapping = {
    'multiply': multiply,
    # ... other functions
}

model = genai.GenerativeModel(
    # ... other parameters
    tools=[multiply_description,  # ... other function descriptions]
)

if you have ideas about better workflow dont hesitate to speak!

Even simpler Description:

  1. Create a function:
  • Ensure the function returns the results of its execution.
  1. Create a description of the function as a variable:
  • Name the variable using a specific schema like functionName_description for maintenance purposes.
  1. Add the function to a mapping:
  • This step is essential for the interpreter to recognize and execute the function.
  1. Add the functionName_description to Tools:
  • Integrate the function description into the tools or environment for managing functions effectively.

Regarding the workflow:

  1. A chat history is created.
  2. Users input information.
  3. The model generates a response.
  4. The response is sent to an interpreter to check for any functions to execute.
  5. The interpreter executes the functions and gathers the results.
  6. The results of executed functions are printed.
  7. The loop continues, restarting the process.
    Note: Result of Executed functions is not included in history, you can do it manualy

If you have easier integration/implementaiton, dont by shy to speak out!

You guys are on the right track. One thing I don’t see you having taken care of is description text for each parameter of the tool function. In my testing, the per-parameter description text is even more relevant to what Gemini will do than the description of the function. Give it some examples of what goes into each parameter, and it will be better at using the tool functions you give it.