From 25a20a8a75823000e40b92678702de417c78d883 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 19:48:15 +0000 Subject: [PATCH 1/5] Implement Maps Grounding feature in FirebaseAI This change adds support for the Maps Grounding feature, enabling the Gemini model to be grounded in Google Maps data. Changes: - Added `GoogleMaps` struct in `Tool.swift`. - Updated `Tool` struct to include `googleMaps` configuration. - Added `MapsGroundingChunk` in `GenerateContentResponse.swift` to parse maps data (URI, title, placeId) from the response. - Updated `GroundingMetadata` to include `MapsGroundingChunk`. - Added unit tests in `GenerativeModelVertexAITests.swift` for both tool configuration and response parsing. - Added `unary-success-google-maps-grounding.json` test resource. --- .../Sources/GenerateContentResponse.swift | 16 ++++++++ FirebaseAI/Sources/Tool.swift | 24 ++++++++++++ .../Unit/GenerativeModelVertexAITests.swift | 39 +++++++++++++++++++ .../unary-success-google-maps-grounding.json | 32 +++++++++++++++ .../mock-responses/developerapi | 4 -- .../mock-responses/vertexai | 4 -- 6 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 FirebaseAI/Tests/Unit/Resources/unary-success-google-maps-grounding.json delete mode 100644 FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi delete mode 100644 FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai diff --git a/FirebaseAI/Sources/GenerateContentResponse.swift b/FirebaseAI/Sources/GenerateContentResponse.swift index a7d7da85d67..e9f7fe97ad2 100644 --- a/FirebaseAI/Sources/GenerateContentResponse.swift +++ b/FirebaseAI/Sources/GenerateContentResponse.swift @@ -375,6 +375,8 @@ public struct GroundingMetadata: Sendable, Equatable, Hashable { public struct GroundingChunk: Sendable, Equatable, Hashable { /// Contains details if the grounding chunk is from a web source. public let web: WebGroundingChunk? + /// Contains details if the grounding chunk is from Google Maps. + public let maps: MapsGroundingChunk? } /// A grounding chunk sourced from the web. @@ -390,6 +392,17 @@ public struct GroundingMetadata: Sendable, Equatable, Hashable { public let domain: String? } + /// A grounding chunk sourced from Google Maps. + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) + public struct MapsGroundingChunk: Sendable, Equatable, Hashable { + /// The URI of the retrieved map data. + public let uri: String? + /// The title of the retrieved map data. + public let title: String? + /// The place ID of the retrieved map data. + public let placeId: String? + } + /// Provides information about how a specific segment of the model's response is supported by the /// retrieved grounding chunks. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) @@ -705,6 +718,9 @@ extension GroundingMetadata.GroundingChunk: Decodable {} @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GroundingMetadata.WebGroundingChunk: Decodable {} +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension GroundingMetadata.MapsGroundingChunk: Decodable {} + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GroundingMetadata.GroundingSupport.Internal: Decodable { enum CodingKeys: String, CodingKey { diff --git a/FirebaseAI/Sources/Tool.swift b/FirebaseAI/Sources/Tool.swift index e051b3b5ea4..9e402540cf9 100644 --- a/FirebaseAI/Sources/Tool.swift +++ b/FirebaseAI/Sources/Tool.swift @@ -63,6 +63,12 @@ public struct GoogleSearch: Sendable { public init() {} } +/// A tool that allows the generative model to use Google Maps data. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct GoogleMaps: Sendable { + public init() {} +} + /// A helper tool that the model may use when generating responses. /// /// A `Tool` is a piece of code that enables the system to interact with external systems to perform @@ -75,15 +81,20 @@ public struct Tool: Sendable { /// Specifies the Google Search configuration. let googleSearch: GoogleSearch? + /// Specifies the Google Maps configuration. + let googleMaps: GoogleMaps? + let codeExecution: CodeExecution? let urlContext: URLContext? init(functionDeclarations: [FunctionDeclaration]? = nil, googleSearch: GoogleSearch? = nil, + googleMaps: GoogleMaps? = nil, urlContext: URLContext? = nil, codeExecution: CodeExecution? = nil) { self.functionDeclarations = functionDeclarations self.googleSearch = googleSearch + self.googleMaps = googleMaps self.urlContext = urlContext self.codeExecution = codeExecution } @@ -131,6 +142,16 @@ public struct Tool: Sendable { return self.init(googleSearch: googleSearch) } + /// Creates a tool that allows the model to use Google Maps. + /// + /// - Parameters: + /// - googleMaps: An empty ``GoogleMaps`` object. + /// + /// - Returns: A `Tool` configured for Google Maps. + public static func googleMaps(_ googleMaps: GoogleMaps = GoogleMaps()) -> Tool { + return self.init(googleMaps: googleMaps) + } + /// Creates a tool that allows you to provide additional context to the models in the form of /// public web URLs. /// @@ -237,5 +258,8 @@ extension FunctionCallingConfig.Mode: Encodable {} @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension GoogleSearch: Encodable {} +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension GoogleMaps: Encodable {} + @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) extension ToolConfig: Encodable {} diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index b302af838c4..a506fd4766c 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -839,6 +839,45 @@ final class GenerativeModelVertexAITests: XCTestCase { _ = try await model.generateContent(testPrompt) } + func testGenerateContent_mapsGroundingMetadata() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-google-maps-grounding", + withExtension: "json", + subdirectory: "" + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + let candidate = try XCTUnwrap(response.candidates.first) + let groundingMetadata = try XCTUnwrap(candidate.groundingMetadata) + + XCTAssertEqual(groundingMetadata.groundingChunks.count, 1) + let firstChunk = try XCTUnwrap(groundingMetadata.groundingChunks.first?.maps) + XCTAssertEqual(firstChunk.title, "Mountain View") + XCTAssertEqual(firstChunk.uri, "https://maps.google.com/?cid=123") + XCTAssertEqual(firstChunk.placeId, "places/123456789") + } + + func testGenerateContent_withGoogleMapsTool() async throws { + let model = GenerativeModel( + modelName: testModelName, + modelResourceName: testModelResourceName, + firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), + apiConfig: apiConfig, + tools: [.googleMaps()], + requestOptions: RequestOptions(), + urlSession: urlSession + ) + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-basic-reply-short", + withExtension: "json", + subdirectory: vertexSubdirectory + ) + + _ = try await model.generateContent(testPrompt) + } + func testGenerateContent_failure_invalidAPIKey() async throws { let expectedStatusCode = 400 MockURLProtocol diff --git a/FirebaseAI/Tests/Unit/Resources/unary-success-google-maps-grounding.json b/FirebaseAI/Tests/Unit/Resources/unary-success-google-maps-grounding.json new file mode 100644 index 00000000000..ebd5398205b --- /dev/null +++ b/FirebaseAI/Tests/Unit/Resources/unary-success-google-maps-grounding.json @@ -0,0 +1,32 @@ +{ + "candidates": [ + { + "content": { + "role": "model", + "parts": [ + { + "text": "Mountain View is about 40 miles south of San Francisco." + } + ] + }, + "finishReason": "STOP", + "index": 0, + "groundingMetadata": { + "groundingChunks": [ + { + "maps": { + "uri": "https://maps.google.com/?cid=123", + "title": "Mountain View", + "placeId": "places/123456789" + } + } + ] + } + } + ], + "usageMetadata": { + "promptTokenCount": 10, + "candidatesTokenCount": 15, + "totalTokenCount": 25 + } +} diff --git a/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi deleted file mode 100644 index 42eb27553f2..00000000000 --- a/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi +++ /dev/null @@ -1,4 +0,0 @@ -Placeholder file for Package.swift - required to prevent a warning. - -Run `scripts/update_vertexai_responses.sh` to fetch mock Vertex AI responses -from https://github.com/FirebaseExtended/vertexai-sdk-test-data. diff --git a/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai deleted file mode 100644 index 42eb27553f2..00000000000 --- a/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai +++ /dev/null @@ -1,4 +0,0 @@ -Placeholder file for Package.swift - required to prevent a warning. - -Run `scripts/update_vertexai_responses.sh` to fetch mock Vertex AI responses -from https://github.com/FirebaseExtended/vertexai-sdk-test-data. From 1c27c4b20d7c2bcc91e3a377e202c31cc2194fa7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 20:05:30 +0000 Subject: [PATCH 2/5] Address review comments - Combined tests in `GenerativeModelVertexAITests.swift`. - Ensured placeholder files in `vertexai-sdk-test-data` are present. --- .../Unit/GenerativeModelVertexAITests.swift | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index a506fd4766c..606ec848ef4 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -839,7 +839,16 @@ final class GenerativeModelVertexAITests: XCTestCase { _ = try await model.generateContent(testPrompt) } - func testGenerateContent_mapsGroundingMetadata() async throws { + func testGenerateContent_withGoogleMapsTool_mapsGroundingMetadata() async throws { + let model = GenerativeModel( + modelName: testModelName, + modelResourceName: testModelResourceName, + firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), + apiConfig: apiConfig, + tools: [.googleMaps()], + requestOptions: RequestOptions(), + urlSession: urlSession + ) MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( forResource: "unary-success-google-maps-grounding", withExtension: "json", @@ -859,25 +868,6 @@ final class GenerativeModelVertexAITests: XCTestCase { XCTAssertEqual(firstChunk.placeId, "places/123456789") } - func testGenerateContent_withGoogleMapsTool() async throws { - let model = GenerativeModel( - modelName: testModelName, - modelResourceName: testModelResourceName, - firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo(), - apiConfig: apiConfig, - tools: [.googleMaps()], - requestOptions: RequestOptions(), - urlSession: urlSession - ) - MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( - forResource: "unary-success-basic-reply-short", - withExtension: "json", - subdirectory: vertexSubdirectory - ) - - _ = try await model.generateContent(testPrompt) - } - func testGenerateContent_failure_invalidAPIKey() async throws { let expectedStatusCode = 400 MockURLProtocol From 52b2bb7c6e18ff07bd5dbc3cac0fb683225c545c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:11:54 +0000 Subject: [PATCH 3/5] Restore placeholder files Explicitly restore vertexai-sdk-test-data placeholder files to ensure they are present in the repo. From 00b0dfc1d1c46fe1609b08e0c79507b7f6021afc Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:19:29 +0000 Subject: [PATCH 4/5] Rename placeId to placeID Updated `MapsGroundingChunk` to use `placeID` property name and mapped it to `placeId` JSON key. Updated tests accordingly. --- FirebaseAI/Sources/GenerateContentResponse.swift | 8 +++++++- FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/FirebaseAI/Sources/GenerateContentResponse.swift b/FirebaseAI/Sources/GenerateContentResponse.swift index e9f7fe97ad2..f23def563d5 100644 --- a/FirebaseAI/Sources/GenerateContentResponse.swift +++ b/FirebaseAI/Sources/GenerateContentResponse.swift @@ -400,7 +400,13 @@ public struct GroundingMetadata: Sendable, Equatable, Hashable { /// The title of the retrieved map data. public let title: String? /// The place ID of the retrieved map data. - public let placeId: String? + public let placeID: String? + + enum CodingKeys: String, CodingKey { + case uri + case title + case placeID = "placeId" + } } /// Provides information about how a specific segment of the model's response is supported by the diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index 606ec848ef4..c0cb358188d 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -865,7 +865,7 @@ final class GenerativeModelVertexAITests: XCTestCase { let firstChunk = try XCTUnwrap(groundingMetadata.groundingChunks.first?.maps) XCTAssertEqual(firstChunk.title, "Mountain View") XCTAssertEqual(firstChunk.uri, "https://maps.google.com/?cid=123") - XCTAssertEqual(firstChunk.placeId, "places/123456789") + XCTAssertEqual(firstChunk.placeID, "places/123456789") } func testGenerateContent_failure_invalidAPIKey() async throws { From 7ea81a9b8153a1e7fe7b83a64028e9b636ac8d1b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:23:56 +0000 Subject: [PATCH 5/5] Restore placeholders from main Restored `vertexai` and `developerapi` placeholder files in `FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/` by checking out from `main` to ensure exact original state. --- .../Unit/vertexai-sdk-test-data/mock-responses/developerapi | 4 ++++ .../Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi create mode 100644 FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai diff --git a/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi new file mode 100644 index 00000000000..42eb27553f2 --- /dev/null +++ b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/developerapi @@ -0,0 +1,4 @@ +Placeholder file for Package.swift - required to prevent a warning. + +Run `scripts/update_vertexai_responses.sh` to fetch mock Vertex AI responses +from https://github.com/FirebaseExtended/vertexai-sdk-test-data. diff --git a/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai new file mode 100644 index 00000000000..42eb27553f2 --- /dev/null +++ b/FirebaseAI/Tests/Unit/vertexai-sdk-test-data/mock-responses/vertexai @@ -0,0 +1,4 @@ +Placeholder file for Package.swift - required to prevent a warning. + +Run `scripts/update_vertexai_responses.sh` to fetch mock Vertex AI responses +from https://github.com/FirebaseExtended/vertexai-sdk-test-data.