From ac8aaaa3b432710b410c7ac0ee7e4bfaaf3d4bd2 Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 16:20:48 +0100 Subject: [PATCH 01/13] feat: convert markdown sample for function calling to python --- .../declare_function_from_function.py | 74 +++++++++++++++++++ .../function_calling/test_function_calling.py | 12 +++ 2 files changed, 86 insertions(+) create mode 100644 generative_ai/function_calling/declare_function_from_function.py diff --git a/generative_ai/function_calling/declare_function_from_function.py b/generative_ai/function_calling/declare_function_from_function.py new file mode 100644 index 00000000000..5726af3abc0 --- /dev/null +++ b/generative_ai/function_calling/declare_function_from_function.py @@ -0,0 +1,74 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from typing import List + +from vertexai.generative_models import FunctionDeclaration + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") + + +def prototype(): + # [START TBD] + # Define a function. Could be a local function or you can import the requests library to call an API + def multiply_numbers(numbers: List[int]) -> int: + """ + Calculates the product of all numbers in an array. + + Args: + numbers: An array of numbers to be multiplied. + + Returns: + The product of all the numbers. If the array is empty, returns 1. + """ + + if not numbers: # Handle empty array + return 1 + + product = 1 + for num in numbers: + product *= num + + return product + + multiply_number_func = FunctionDeclaration.from_func(multiply_numbers) + + ''' + multiply_number_func contains the following schema: + + name: "multiply_numbers" + description: "Calculates the product of all numbers in an array." + parameters { + type_: OBJECT + properties { + key: "numbers" + value { + description: "An array of numbers to be multiplied." + title: "Numbers" + } + } + required: "numbers" + description: "Calculates the product of all numbers in an array." + title: "multiply_numbers" + } + ''' + # [END TBD] + return multiply_number_func + + +if __name__ == "__main__": + prototype() diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index 9dc8c22cd5d..2d883fd24b7 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -15,6 +15,7 @@ import backoff from google.api_core.exceptions import ResourceExhausted +from vertexai.generative_models import GenerativeModel, Tool import advanced_example import basic_example @@ -22,6 +23,7 @@ import chat_function_calling_basic import chat_function_calling_config import parallel_function_calling_example +import declare_function_from_function @backoff.on_exception(backoff.expo, ResourceExhausted, max_time=10) @@ -85,3 +87,13 @@ def test_function_calling_chat() -> None: def test_parallel_function_calling() -> None: response = parallel_function_calling_example.parallel_function_calling_example() assert response is not None + + +def test_prototype() -> None: + func_declaration = declare_function_from_function.prototype() + tools = Tool(function_declarations=[func_declaration]) + model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) + chat_session = model.start_chat() + response = chat_session.send_message("What will be 1 multiplied by 2?") + + assert response is not None From 913441275cac93fc7772deebcbe679d6795f0476 Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 16:22:34 +0100 Subject: [PATCH 02/13] fix region tag name --- .../function_calling/declare_function_from_function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generative_ai/function_calling/declare_function_from_function.py b/generative_ai/function_calling/declare_function_from_function.py index 5726af3abc0..5d12b3a972d 100644 --- a/generative_ai/function_calling/declare_function_from_function.py +++ b/generative_ai/function_calling/declare_function_from_function.py @@ -23,7 +23,7 @@ def prototype(): - # [START TBD] + # [START generativeaionvertexai_gemini_function_calling_declare_from_function] # Define a function. Could be a local function or you can import the requests library to call an API def multiply_numbers(numbers: List[int]) -> int: """ @@ -66,7 +66,7 @@ def multiply_numbers(numbers: List[int]) -> int: title: "multiply_numbers" } ''' - # [END TBD] + # [END generativeaionvertexai_gemini_function_calling_declare_from_function] return multiply_number_func From b0788f51ea84a760aa510c579d8fd864fb71658f Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 17:13:38 +0100 Subject: [PATCH 03/13] fix? basic example is no longer working with gemini-flash --- generative_ai/function_calling/basic_example.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generative_ai/function_calling/basic_example.py b/generative_ai/function_calling/basic_example.py index ce108337bbb..ce6a1c0e829 100644 --- a/generative_ai/function_calling/basic_example.py +++ b/generative_ai/function_calling/basic_example.py @@ -39,7 +39,7 @@ def generate_function_call() -> GenerationResponse: vertexai.init(project=PROJECT_ID, location="us-central1") # Initialize Gemini model - model = GenerativeModel("gemini-1.5-flash-002") + model = GenerativeModel("gemini-1.5-pro-002") # Define the user's prompt in a Content object that we can reuse in model calls user_prompt_content = Content( From 693f9140e55bb19998420358fa4f70d15d722a99 Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 19:50:27 +0100 Subject: [PATCH 04/13] mark broken openai samples as skip --- ...on_from_function.py => function_declaration_from_func.py} | 4 ++-- generative_ai/function_calling/test_function_calling.py | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) rename generative_ai/function_calling/{declare_function_from_function.py => function_declaration_from_func.py} (96%) diff --git a/generative_ai/function_calling/declare_function_from_function.py b/generative_ai/function_calling/function_declaration_from_func.py similarity index 96% rename from generative_ai/function_calling/declare_function_from_function.py rename to generative_ai/function_calling/function_declaration_from_func.py index 5d12b3a972d..8138eca6f5d 100644 --- a/generative_ai/function_calling/declare_function_from_function.py +++ b/generative_ai/function_calling/function_declaration_from_func.py @@ -22,7 +22,7 @@ PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") -def prototype(): +def function_declaration_from_func(): # [START generativeaionvertexai_gemini_function_calling_declare_from_function] # Define a function. Could be a local function or you can import the requests library to call an API def multiply_numbers(numbers: List[int]) -> int: @@ -71,4 +71,4 @@ def multiply_numbers(numbers: List[int]) -> int: if __name__ == "__main__": - prototype() + function_declaration_from_func() diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index 2d883fd24b7..a882956a0c5 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -13,6 +13,7 @@ # limitations under the License. import backoff +import pytest from google.api_core.exceptions import ResourceExhausted from vertexai.generative_models import GenerativeModel, Tool @@ -54,12 +55,14 @@ def test_function_calling_advanced_function_selection() -> None: ) +@pytest.mark.skip(reason="Blocked on b/... ") @backoff.on_exception(backoff.expo, ResourceExhausted, max_time=10) def test_function_calling_basic() -> None: response = chat_function_calling_basic.generate_text() assert "get_current_weather" in response.choices[0].message.tool_calls[0].id +@pytest.mark.skip(reason="Blocked on b/... ") @backoff.on_exception(backoff.expo, ResourceExhausted, max_time=10) def test_function_calling_config() -> None: response = chat_function_calling_config.generate_text() @@ -90,7 +93,7 @@ def test_parallel_function_calling() -> None: def test_prototype() -> None: - func_declaration = declare_function_from_function.prototype() + func_declaration = declare_function_from_function.function_declaration_as_func() tools = Tool(function_declarations=[func_declaration]) model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) chat_session = model.start_chat() From ade95c774554498999ed0537501ca36514cc4b6a Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 19:56:07 +0100 Subject: [PATCH 05/13] fix test name --- generative_ai/function_calling/test_function_calling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index a882956a0c5..e3bdac5d897 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -92,7 +92,7 @@ def test_parallel_function_calling() -> None: assert response is not None -def test_prototype() -> None: +def test_function_declaration_as_func() -> None: func_declaration = declare_function_from_function.function_declaration_as_func() tools = Tool(function_declarations=[func_declaration]) model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) From d7f744b2aad08fc9248e9a01c9ae4eac0ceb9321 Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 20:13:38 +0100 Subject: [PATCH 06/13] fix test name --- generative_ai/function_calling/test_function_calling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index e3bdac5d897..1c615d42295 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -92,8 +92,8 @@ def test_parallel_function_calling() -> None: assert response is not None -def test_function_declaration_as_func() -> None: - func_declaration = declare_function_from_function.function_declaration_as_func() +def test_function_declaration_from_func() -> None: + func_declaration = declare_function_from_function.function_declaration_from_func() tools = Tool(function_declarations=[func_declaration]) model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) chat_session = model.start_chat() From 5b9e49b6d37b320cf7242f5192d2cb9d9e714cfe Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 20:29:35 +0100 Subject: [PATCH 07/13] fix checks; add experimental 2-tag sample --- .../function_declaration_from_dict.py | 70 +++++++++++++++++++ .../function_declaration_from_func.py | 2 +- .../function_calling/test_function_calling.py | 15 +++- 3 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 generative_ai/function_calling/function_declaration_from_dict.py diff --git a/generative_ai/function_calling/function_declaration_from_dict.py b/generative_ai/function_calling/function_declaration_from_dict.py new file mode 100644 index 00000000000..077d7a19fcb --- /dev/null +++ b/generative_ai/function_calling/function_declaration_from_dict.py @@ -0,0 +1,70 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +from vertexai.generative_models import FunctionDeclaration + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") + + +def function_declaration_from_dict() -> object: + # [START generativeaionvertexai_gemini_function_calling_declare_from_dict1] + function_name = "get_current_weather" + get_current_weather_func = FunctionDeclaration( + name=function_name, + description="Get the current weather in a given location", + # Function parameters are specified in JSON schema format + parameters={ + "type": "object", + "properties": { + "location": {"type": "string", "description": "The city name of the location for which to get the weather."} + }, + }, + ) + # [END generativeaionvertexai_gemini_function_calling_declare_from_dict] + # [START generativeaionvertexai_gemini_function_calling_declare_from_dict2] + extract_sale_records_func = FunctionDeclaration( + name="extract_sale_records", + description="Extract sale records from a document.", + parameters={ + "type": "object", + "properties": { + "records": { + "type": "array", + "description": "A list of sale records", + "items": { + "description": "Data for a sale record", + "type": "object", + "properties": { + "id": {"type": "integer", "description": "The unique id of the sale."}, + "date": {"type": "string", "description": "Date of the sale, in the format of MMDDYY, e.g., 031023"}, + "total_amount": {"type": "number", "description": "The total amount of the sale."}, + "customer_name": {"type": "string", "description": "The name of the customer, including first name and last name."}, + "customer_contact": {"type": "string", "description": "The phone number of the customer, e.g., 650-123-4567."}, + }, + "required": ["id", "date", "total_amount"], + }, + }, + }, + "required": ["records"], + }, + ) + # [END generativeaionvertexai_gemini_function_calling_declare_from_dic2] + return [get_current_weather_func, extract_sale_records_func] + + +if __name__ == "__main__": + function_declaration_from_dict() diff --git a/generative_ai/function_calling/function_declaration_from_func.py b/generative_ai/function_calling/function_declaration_from_func.py index 8138eca6f5d..9b79e741cd3 100644 --- a/generative_ai/function_calling/function_declaration_from_func.py +++ b/generative_ai/function_calling/function_declaration_from_func.py @@ -22,7 +22,7 @@ PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") -def function_declaration_from_func(): +def function_declaration_from_func() -> object: # [START generativeaionvertexai_gemini_function_calling_declare_from_function] # Define a function. Could be a local function or you can import the requests library to call an API def multiply_numbers(numbers: List[int]) -> int: diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index 1c615d42295..e2b180cc467 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -23,8 +23,9 @@ import chat_example import chat_function_calling_basic import chat_function_calling_config +import function_declaration_from_func +import function_declaration_from_dict import parallel_function_calling_example -import declare_function_from_function @backoff.on_exception(backoff.expo, ResourceExhausted, max_time=10) @@ -93,10 +94,20 @@ def test_parallel_function_calling() -> None: def test_function_declaration_from_func() -> None: - func_declaration = declare_function_from_function.function_declaration_from_func() + func_declaration = function_declaration_from_func.function_declaration_from_func() tools = Tool(function_declarations=[func_declaration]) model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) chat_session = model.start_chat() response = chat_session.send_message("What will be 1 multiplied by 2?") assert response is not None + + +def test_function_declaration_as_func() -> None: + function_declarations = function_declaration_from_dict.function_declaration_from_dict() + tools = Tool(function_declarations=function_declarations) + model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) + chat_session = model.start_chat() + response = chat_session.send_message("What is the weather in Boston?") + + assert response is not None From 27727473408ef3a9b850704778034e1241b690ee Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 20:41:02 +0100 Subject: [PATCH 08/13] fix import order --- generative_ai/function_calling/test_function_calling.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index e2b180cc467..c131673b45f 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -13,6 +13,7 @@ # limitations under the License. import backoff + import pytest from google.api_core.exceptions import ResourceExhausted @@ -23,8 +24,8 @@ import chat_example import chat_function_calling_basic import chat_function_calling_config -import function_declaration_from_func import function_declaration_from_dict +import function_declaration_from_func import parallel_function_calling_example From 1f8c008bcdf33c2fe896889c3475f6dc491a2f6d Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 22:08:30 +0100 Subject: [PATCH 09/13] fix import order --- generative_ai/function_calling/test_function_calling.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index c131673b45f..351d9595128 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -14,9 +14,10 @@ import backoff +from google.api_core.exceptions import ResourceExhausted + import pytest -from google.api_core.exceptions import ResourceExhausted from vertexai.generative_models import GenerativeModel, Tool import advanced_example From 6e34636e4bf2ea04ab9c85930e0b7cd1ee0db49b Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Mon, 2 Dec 2024 23:17:14 +0100 Subject: [PATCH 10/13] fix typo in region tag --- .../function_calling/function_declaration_from_dict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generative_ai/function_calling/function_declaration_from_dict.py b/generative_ai/function_calling/function_declaration_from_dict.py index 077d7a19fcb..bec0d4f0496 100644 --- a/generative_ai/function_calling/function_declaration_from_dict.py +++ b/generative_ai/function_calling/function_declaration_from_dict.py @@ -34,7 +34,7 @@ def function_declaration_from_dict() -> object: }, }, ) - # [END generativeaionvertexai_gemini_function_calling_declare_from_dict] + # [END generativeaionvertexai_gemini_function_calling_declare_from_dict1] # [START generativeaionvertexai_gemini_function_calling_declare_from_dict2] extract_sale_records_func = FunctionDeclaration( name="extract_sale_records", @@ -62,7 +62,7 @@ def function_declaration_from_dict() -> object: "required": ["records"], }, ) - # [END generativeaionvertexai_gemini_function_calling_declare_from_dic2] + # [END generativeaionvertexai_gemini_function_calling_declare_from_dict2] return [get_current_weather_func, extract_sale_records_func] From 98df51c93c66b6b6502eca5e2b7d7ffdcc2072dc Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Fri, 6 Dec 2024 15:44:56 +0100 Subject: [PATCH 11/13] wip: re-work markdown 'how to create...' section into python --- .../function_calling_application.py | 210 ++++++++++++++++++ .../function_declaration_from_dict.py | 70 ------ .../function_declaration_from_func.py | 74 ------ .../function_calling/test_function_calling.py | 27 +-- 4 files changed, 220 insertions(+), 161 deletions(-) create mode 100644 generative_ai/function_calling/function_calling_application.py delete mode 100644 generative_ai/function_calling/function_declaration_from_dict.py delete mode 100644 generative_ai/function_calling/function_declaration_from_func.py diff --git a/generative_ai/function_calling/function_calling_application.py b/generative_ai/function_calling/function_calling_application.py new file mode 100644 index 00000000000..faffdf5bd4c --- /dev/null +++ b/generative_ai/function_calling/function_calling_application.py @@ -0,0 +1,210 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") + + +def create_app() -> object: + # [START generativeaionvertexai_gemini_function_calling_app_setup] + from typing import List + + import vertexai + + from vertexai.generative_models import ( + Content, + FunctionDeclaration, + GenerationConfig, + GenerativeModel, + Part, + Tool, + ToolConfig, + ) + + # Initialize Vertex AI + # TODO(developer): Update and un-comment below lines + # PROJECT_ID = 'your-project-id' + vertexai.init(project=PROJECT_ID, location="us-central1") + + # Initialize Gemini model + model = GenerativeModel(model_name="gemini-1.5-flash-002") + # [END generativeaionvertexai_gemini_function_calling_app_setup] + + # [START generativeaionvertexai_gemini_function_calling_app_declare_1] + function_name = "get_current_weather" + get_current_weather_func = FunctionDeclaration( + name=function_name, + description="Get the current weather in a given location", + # Function parameters are specified in JSON schema format + parameters={ + "type": "object", + "properties": { + "location": {"type": "string", "description": "The city name of the location for which to get the weather."} + }, + }, + ) + # [END generativeaionvertexai_gemini_function_calling_app_declare_1] + + # [START generativeaionvertexai_gemini_function_calling_app_declare_2] + extract_sale_records_func = FunctionDeclaration( + name="extract_sale_records", + description="Extract sale records from a document.", + parameters={ + "type": "object", + "properties": { + "records": { + "type": "array", + "description": "A list of sale records", + "items": { + "description": "Data for a sale record", + "type": "object", + "properties": { + "id": {"type": "integer", "description": "The unique id of the sale."}, + "date": {"type": "string", "description": "Date of the sale, in the format of MMDDYY, e.g., 031023"}, + "total_amount": {"type": "number", "description": "The total amount of the sale."}, + "customer_name": {"type": "string", "description": "The name of the customer, including first name and last name."}, + "customer_contact": {"type": "string", "description": "The phone number of the customer, e.g., 650-123-4567."}, + }, + "required": ["id", "date", "total_amount"], + }, + }, + }, + "required": ["records"], + }, + ) + # [END generativeaionvertexai_gemini_function_calling_app_declare_2] + + # [START generativeaionvertexai_gemini_function_calling_app_declare_3] + # Define a function. Could be a local function or you can import the requests library to call an API + def multiply_numbers(numbers: List[int]) -> int: + """ + Calculates the product of all numbers in an array. + + Args: + numbers: An array of numbers to be multiplied. + + Returns: + The product of all the numbers. If the array is empty, returns 1. + """ + + if not numbers: # Handle empty array + return 1 + + product = 1 + for num in numbers: + product *= num + + return product + + multiply_number_func = FunctionDeclaration.from_func(multiply_numbers) + + ''' + multiply_number_func contains the following schema: + + name: "multiply_numbers" + description: "Calculates the product of all numbers in an array." + parameters { + type_: OBJECT + properties { + key: "numbers" + value { + description: "An array of numbers to be multiplied." + title: "Numbers" + } + } + required: "numbers" + description: "Calculates the product of all numbers in an array." + title: "multiply_numbers" + } + ''' + # [END generativeaionvertexai_gemini_function_calling_app_declare_3] + + # [START generativeaionvertexai_gemini_function_calling_app_prompt] + # Define the user's prompt in a Content object that we can reuse in model calls + user_prompt_content = Content( + role="user", + parts=[ + Part.from_text("What is the weather like in Boston?"), + ], + ) + # [END generativeaionvertexai_gemini_function_calling_app_prompt] + + # [START generativeaionvertexai_gemini_function_calling_app_submit] + # Define a tool that includes some of the functions that we declared earlier + tool = Tool( + function_declarations=[get_current_weather_func, extract_sale_records_func, multiply_number_func], + ) + + # Send the prompt and instruct the model to generate content using the Tool object that you just created + response = model.generate_content( + user_prompt_content, + generation_config=GenerationConfig(temperature=0), + tools=[tool], + tool_config=ToolConfig( + function_calling_config=ToolConfig.FunctionCallingConfig( + # ANY mode forces the model to predict only function calls + mode=ToolConfig.FunctionCallingConfig.Mode.ANY, + # Allowed function calls to predict when the mode is ANY. If empty, any of + # the provided function calls will be predicted. + allowed_function_names=["get_current_weather"], + ) + ) + ) + # [END generativeaionvertexai_gemini_function_calling_app_submit] + + # flake8: noqa=F841 # Intentionally unused variable `location` in sample code + # [START generativeaionvertexai_gemini_function_calling_app_invoke] + # Check the function name that the model responded with, and make an API call to an external system + if (response.candidates[0].function_calls[0].name == "get_current_weather"): + # Extract the arguments to use in your API call + location = response.candidates[0].function_calls[0].args["location"] + + # Here you can use your preferred method to make an API request to fetch the current weather, for example: + # api_response = requests.post(weather_api_url, data={"location": location}) + + # In this example, we'll use synthetic data to simulate a response payload from an external API + api_response = """{ "location": "Boston, MA", "temperature": 38, "description": "Partly Cloudy", + "icon": "partly-cloudy", "humidity": 65, "wind": { "speed": 10, "direction": "NW" } }""" + # [END generativeaionvertexai_gemini_function_calling_app_invoke] + # flake8: qa=F841 + + # [START generativeaionvertexai_gemini_function_calling_app_generate] + response = model.generate_content( + [ + user_prompt_content, # User prompt + response.candidates[0].content, # Function call response + Content( + parts=[ + Part.from_function_response( + name="get_current_weather", + response={ + "content": api_response, # Return the API response to Gemini + }, + ) + ], + ), + ], + tools=[tool], + ) + # Get the model summary response + summary = response.text + # [END generativeaionvertexai_gemini_function_calling_app_generate] + + return { "weather_response": summary, "tool": tool } + + +if __name__ == "__main__": + create_app() diff --git a/generative_ai/function_calling/function_declaration_from_dict.py b/generative_ai/function_calling/function_declaration_from_dict.py deleted file mode 100644 index bec0d4f0496..00000000000 --- a/generative_ai/function_calling/function_declaration_from_dict.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -from vertexai.generative_models import FunctionDeclaration - - -PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") - - -def function_declaration_from_dict() -> object: - # [START generativeaionvertexai_gemini_function_calling_declare_from_dict1] - function_name = "get_current_weather" - get_current_weather_func = FunctionDeclaration( - name=function_name, - description="Get the current weather in a given location", - # Function parameters are specified in JSON schema format - parameters={ - "type": "object", - "properties": { - "location": {"type": "string", "description": "The city name of the location for which to get the weather."} - }, - }, - ) - # [END generativeaionvertexai_gemini_function_calling_declare_from_dict1] - # [START generativeaionvertexai_gemini_function_calling_declare_from_dict2] - extract_sale_records_func = FunctionDeclaration( - name="extract_sale_records", - description="Extract sale records from a document.", - parameters={ - "type": "object", - "properties": { - "records": { - "type": "array", - "description": "A list of sale records", - "items": { - "description": "Data for a sale record", - "type": "object", - "properties": { - "id": {"type": "integer", "description": "The unique id of the sale."}, - "date": {"type": "string", "description": "Date of the sale, in the format of MMDDYY, e.g., 031023"}, - "total_amount": {"type": "number", "description": "The total amount of the sale."}, - "customer_name": {"type": "string", "description": "The name of the customer, including first name and last name."}, - "customer_contact": {"type": "string", "description": "The phone number of the customer, e.g., 650-123-4567."}, - }, - "required": ["id", "date", "total_amount"], - }, - }, - }, - "required": ["records"], - }, - ) - # [END generativeaionvertexai_gemini_function_calling_declare_from_dict2] - return [get_current_weather_func, extract_sale_records_func] - - -if __name__ == "__main__": - function_declaration_from_dict() diff --git a/generative_ai/function_calling/function_declaration_from_func.py b/generative_ai/function_calling/function_declaration_from_func.py deleted file mode 100644 index 9b79e741cd3..00000000000 --- a/generative_ai/function_calling/function_declaration_from_func.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os - -from typing import List - -from vertexai.generative_models import FunctionDeclaration - - -PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") - - -def function_declaration_from_func() -> object: - # [START generativeaionvertexai_gemini_function_calling_declare_from_function] - # Define a function. Could be a local function or you can import the requests library to call an API - def multiply_numbers(numbers: List[int]) -> int: - """ - Calculates the product of all numbers in an array. - - Args: - numbers: An array of numbers to be multiplied. - - Returns: - The product of all the numbers. If the array is empty, returns 1. - """ - - if not numbers: # Handle empty array - return 1 - - product = 1 - for num in numbers: - product *= num - - return product - - multiply_number_func = FunctionDeclaration.from_func(multiply_numbers) - - ''' - multiply_number_func contains the following schema: - - name: "multiply_numbers" - description: "Calculates the product of all numbers in an array." - parameters { - type_: OBJECT - properties { - key: "numbers" - value { - description: "An array of numbers to be multiplied." - title: "Numbers" - } - } - required: "numbers" - description: "Calculates the product of all numbers in an array." - title: "multiply_numbers" - } - ''' - # [END generativeaionvertexai_gemini_function_calling_declare_from_function] - return multiply_number_func - - -if __name__ == "__main__": - function_declaration_from_func() diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index 351d9595128..734981895f9 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -18,15 +18,14 @@ import pytest -from vertexai.generative_models import GenerativeModel, Tool +from vertexai.generative_models import GenerativeModel import advanced_example import basic_example import chat_example import chat_function_calling_basic import chat_function_calling_config -import function_declaration_from_dict -import function_declaration_from_func +import function_calling_application import parallel_function_calling_example @@ -95,21 +94,15 @@ def test_parallel_function_calling() -> None: assert response is not None -def test_function_declaration_from_func() -> None: - func_declaration = function_declaration_from_func.function_declaration_from_func() - tools = Tool(function_declarations=[func_declaration]) - model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) - chat_session = model.start_chat() - response = chat_session.send_message("What will be 1 multiplied by 2?") - - assert response is not None +def test_function_calling_app() -> None: + result = function_calling_application.create_app() + assert result["weather_response"] is not None - -def test_function_declaration_as_func() -> None: - function_declarations = function_declaration_from_dict.function_declaration_from_dict() - tools = Tool(function_declarations=function_declarations) - model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tools]) + tool = result["tool"] + model = GenerativeModel(model_name="gemini-1.5-pro-002", tools=[tool]) chat_session = model.start_chat() - response = chat_session.send_message("What is the weather in Boston?") + response = chat_session.send_message("What will be 1 multiplied by 2?") assert response is not None + + response = chat_session.send_message("I have a PDF document with a series of sale transactions from our store, but I need to parse it for our accounting system. Each transaction includes information like sale ID numbers, dates in MMDDYY format, monetary amounts, and sometimes customer details. What's the best way to extract this structured data from the document? I need to maintain the relationships between IDs, dates, and amounts for each sale.") From 87aa2a41e4d65f696a83252d7f2fdbd946a33f43 Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Fri, 6 Dec 2024 16:12:33 +0100 Subject: [PATCH 12/13] update test prompt to execute the 'sales' func declaration --- .../function_calling/test_function_calling.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index 734981895f9..8f4ca4f84e2 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -105,4 +105,14 @@ def test_function_calling_app() -> None: response = chat_session.send_message("What will be 1 multiplied by 2?") assert response is not None - response = chat_session.send_message("I have a PDF document with a series of sale transactions from our store, but I need to parse it for our accounting system. Each transaction includes information like sale ID numbers, dates in MMDDYY format, monetary amounts, and sometimes customer details. What's the best way to extract this structured data from the document? I need to maintain the relationships between IDs, dates, and amounts for each sale.") + extract_sales_prompt = """ + I need to parse a series of sale transactions written down in a text editor and extract + full sales records for each transaction. + 1 / 031023 / $123,02 + 2 / 031123 / $12,99 + 3 / 031123 / $12,99 + 4 / 031223 / $15,99 + 5 / 031223 / $2,20 + """ + response = chat_session.send_message(extract_sales_prompt) + assert response From 26f725a539bc860064e4cd688c9ef35ee359ab5b Mon Sep 17 00:00:00 2001 From: Valeriy Burlaka Date: Tue, 10 Dec 2024 13:30:08 +0100 Subject: [PATCH 13/13] feat(vertex-ai): function-calling api: 'example syntax' --- .../function_calling/example_syntax.py | 52 +++++++++++++++++++ .../function_calling/test_function_calling.py | 6 +++ 2 files changed, 58 insertions(+) create mode 100644 generative_ai/function_calling/example_syntax.py diff --git a/generative_ai/function_calling/example_syntax.py b/generative_ai/function_calling/example_syntax.py new file mode 100644 index 00000000000..d8b8d44eb46 --- /dev/null +++ b/generative_ai/function_calling/example_syntax.py @@ -0,0 +1,52 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os + +import vertexai + +from vertexai.generative_models import GenerativeModel + +PROJECT_ID = os.getenv("GOOGLE_CLOUD_PROJECT") + +vertexai.init(project=PROJECT_ID, location="us-central1") + + +def create_model_with_toolbox() -> GenerativeModel: + # [START generativeaionvertexai_function_calling_example_syntax] + from vertexai.generative_models import FunctionDeclaration, GenerationConfig, GenerativeModel, Tool + + gemini_model = GenerativeModel( + "gemini-1.5-flash-002", + generation_config=GenerationConfig(temperature=0), + tools=[ + Tool( + function_declarations=[ + FunctionDeclaration( + name="get_current_weather", + description="Get the current weather in a given location", + parameters={ + "type": "object", + "properties": {"location": {"type": "string", "description": "Location"}}, + }, + ) + ] + ) + ], + ) + # [END generativeaionvertexai_function_calling_example_syntax] + return gemini_model + + +if __name__ == "__main__": + create_model_with_toolbox() diff --git a/generative_ai/function_calling/test_function_calling.py b/generative_ai/function_calling/test_function_calling.py index 8f4ca4f84e2..23b5d60063b 100644 --- a/generative_ai/function_calling/test_function_calling.py +++ b/generative_ai/function_calling/test_function_calling.py @@ -25,6 +25,7 @@ import chat_example import chat_function_calling_basic import chat_function_calling_config +import example_syntax import function_calling_application import parallel_function_calling_example @@ -116,3 +117,8 @@ def test_function_calling_app() -> None: """ response = chat_session.send_message(extract_sales_prompt) assert response + + +def test_example_syntax() -> None: + model = example_syntax.create_model_with_toolbox() + assert model is not None