14
14
15
15
import 'content.dart' ;
16
16
import 'error.dart' ;
17
- import 'function_calling .dart' show Tool, ToolConfig;
17
+ import 'tool .dart' show Tool, ToolConfig;
18
18
import 'schema.dart' ;
19
19
20
20
/// Response for Count Tokens
@@ -187,7 +187,8 @@ final class Candidate {
187
187
// TODO: token count?
188
188
// ignore: public_member_api_docs
189
189
Candidate (this .content, this .safetyRatings, this .citationMetadata,
190
- this .finishReason, this .finishMessage);
190
+ this .finishReason, this .finishMessage,
191
+ {this .groundingMetadata});
191
192
192
193
/// Generated content returned from the model.
193
194
final Content content;
@@ -212,6 +213,9 @@ final class Candidate {
212
213
/// Message for finish reason.
213
214
final String ? finishMessage;
214
215
216
+ /// Metadata returned to the client when grounding is enabled.
217
+ final GroundingMetadata ? groundingMetadata;
218
+
215
219
/// The concatenation of the text parts of [content] , if any.
216
220
///
217
221
/// If this candidate was finished for a reason of [FinishReason.recitation]
@@ -243,6 +247,144 @@ final class Candidate {
243
247
}
244
248
}
245
249
250
+ /// Represents a specific segment within a [Content] , often used to pinpoint
251
+ /// the exact location of text or data that grounding information refers to.
252
+ final class Segment {
253
+ Segment (
254
+ {required this .partIndex,
255
+ required this .startIndex,
256
+ required this .endIndex,
257
+ required this .text});
258
+
259
+ /// The zero-based index of the [Part] object within the `parts` array of its
260
+ /// parent [Content] object.
261
+ ///
262
+ /// This identifies which part of the content the segment belongs to.
263
+ final int partIndex;
264
+
265
+ /// The zero-based start index of the segment within the specified [Part] ,
266
+ /// measured in UTF-8 bytes.
267
+ ///
268
+ /// This offset is inclusive, starting from 0 at the beginning of the
269
+ /// part's content.
270
+ final int startIndex;
271
+
272
+ /// The zero-based end index of the segment within the specified [Part] ,
273
+ /// measured in UTF-8 bytes.
274
+ ///
275
+ /// This offset is exclusive, meaning the character at this index is not
276
+ /// included in the segment.
277
+ final int endIndex;
278
+
279
+ /// The text corresponding to the segment from the response.
280
+ final String text;
281
+ }
282
+
283
+ /// A grounding chunk sourced from the web.
284
+ final class WebGroundingChunk {
285
+ WebGroundingChunk ({this .uri, this .title, this .domain});
286
+
287
+ /// The URI of the retrieved web page.
288
+ final String ? uri;
289
+
290
+ /// The title of the retrieved web page.
291
+ final String ? title;
292
+
293
+ /// The domain of the original URI from which the content was retrieved.
294
+ ///
295
+ /// This field is only populated when using the Vertex AI Gemini API.
296
+ final String ? domain;
297
+ }
298
+
299
+ /// Represents a chunk of retrieved data that supports a claim in the model's
300
+ /// response.
301
+ ///
302
+ /// This is part of the grounding information provided when grounding is
303
+ /// enabled.
304
+ final class GroundingChunk {
305
+ GroundingChunk ({this .web});
306
+
307
+ /// Contains details if the grounding chunk is from a web source.
308
+ final WebGroundingChunk ? web;
309
+ }
310
+
311
+ /// Provides information about how a specific segment of the model's response
312
+ /// is supported by the retrieved grounding chunks.
313
+ final class GroundingSupport {
314
+ GroundingSupport (
315
+ {required this .segment, required this .groundingChunkIndices});
316
+
317
+ /// Specifies the segment of the model's response content that this
318
+ /// grounding support pertains to.
319
+ final Segment segment;
320
+
321
+ /// A list of indices that refer to specific [GroundingChunk] s within the
322
+ /// [GroundingMetadata.groundingChunks] array.
323
+ ///
324
+ /// These referenced chunks are the sources that
325
+ /// support the claim made in the associated `segment` of the response.
326
+ /// For example, an array `[1, 3, 4]`
327
+ /// means that `groundingChunks[1]` , `groundingChunks[3]` , and
328
+ /// `groundingChunks[4]` are the
329
+ /// retrieved content supporting this part of the response.
330
+ final List <int > groundingChunkIndices;
331
+ }
332
+
333
+ /// Google Search entry point for web searches.
334
+ final class SearchEntryPoint {
335
+ SearchEntryPoint ({required this .renderedContent});
336
+
337
+ /// An HTML/CSS snippet that **must** be embedded in an app to display a
338
+ /// Google Search entry point for follow-up web searches related to the
339
+ /// model's "Grounded Response".
340
+ ///
341
+ /// To ensure proper rendering, it's recommended to display this content
342
+ /// within a `WebView` .
343
+ final String renderedContent;
344
+ }
345
+
346
+ /// Metadata returned to the client when grounding is enabled.
347
+ ///
348
+ /// > Important: If using Grounding with Google Search, you are required to
349
+ /// comply with the "Grounding with Google Search" usage requirements for your
350
+ /// chosen API provider:
351
+ /// [Gemini Developer API] (https://ai.google.dev/gemini-api/terms#grounding-with-google-search)
352
+ /// or Vertex AI Gemini API (see [Service Terms] (https://cloud.google.com/terms/service-terms)
353
+ /// section within the Service Specific Terms).
354
+ final class GroundingMetadata {
355
+ GroundingMetadata (
356
+ {this .searchEntryPoint,
357
+ required this .groundingChunks,
358
+ required this .groundingSupport,
359
+ required this .webSearchQueries});
360
+
361
+ /// Google Search entry point for web searches.
362
+ ///
363
+ /// This contains an HTML/CSS snippet that **must** be embedded in an app to
364
+ // display a Google Search entry point for follow-up web searches related to
365
+ // the model's "Grounded Response".
366
+ final SearchEntryPoint ? searchEntryPoint;
367
+
368
+ /// A list of [GroundingChunk] s.
369
+ ///
370
+ /// Each chunk represents a piece of retrieved content (e.g., from a web
371
+ /// page) that the model used to ground its response.
372
+ final List <GroundingChunk > groundingChunks;
373
+
374
+ /// A list of [GroundingSupport] s.
375
+ ///
376
+ /// Each object details how specific segments of the
377
+ /// model's response are supported by the `groundingChunks` .
378
+ final List <GroundingSupport > groundingSupport;
379
+
380
+ /// A list of web search queries that the model performed to gather the
381
+ /// grounding information.
382
+ ///
383
+ /// These can be used to allow users to explore the search results
384
+ /// themselves.
385
+ final List <String > webSearchQueries;
386
+ }
387
+
246
388
/// Safety rating for a piece of content.
247
389
///
248
390
/// The safety rating contains the category of harm and the harm probability
@@ -1027,29 +1169,33 @@ Candidate _parseCandidate(Object? jsonObject) {
1027
1169
}
1028
1170
1029
1171
return Candidate (
1030
- jsonObject.containsKey ('content' )
1031
- ? parseContent (jsonObject['content' ] as Object )
1032
- : Content (null , []),
1033
- switch (jsonObject) {
1034
- {'safetyRatings' : final List <Object ?> safetyRatings} =>
1035
- safetyRatings.map (_parseSafetyRating).toList (),
1036
- _ => null
1037
- },
1038
- switch (jsonObject) {
1039
- {'citationMetadata' : final Object citationMetadata} =>
1040
- _parseCitationMetadata (citationMetadata),
1041
- _ => null
1042
- },
1043
- switch (jsonObject) {
1044
- {'finishReason' : final Object finishReason} =>
1045
- FinishReason ._parseValue (finishReason),
1046
- _ => null
1047
- },
1048
- switch (jsonObject) {
1049
- {'finishMessage' : final String finishMessage} => finishMessage,
1050
- _ => null
1051
- },
1052
- );
1172
+ jsonObject.containsKey ('content' )
1173
+ ? parseContent (jsonObject['content' ] as Object )
1174
+ : Content (null , []),
1175
+ switch (jsonObject) {
1176
+ {'safetyRatings' : final List <Object ?> safetyRatings} =>
1177
+ safetyRatings.map (_parseSafetyRating).toList (),
1178
+ _ => null
1179
+ },
1180
+ switch (jsonObject) {
1181
+ {'citationMetadata' : final Object citationMetadata} =>
1182
+ _parseCitationMetadata (citationMetadata),
1183
+ _ => null
1184
+ },
1185
+ switch (jsonObject) {
1186
+ {'finishReason' : final Object finishReason} =>
1187
+ FinishReason ._parseValue (finishReason),
1188
+ _ => null
1189
+ },
1190
+ switch (jsonObject) {
1191
+ {'finishMessage' : final String finishMessage} => finishMessage,
1192
+ _ => null
1193
+ },
1194
+ groundingMetadata: switch (jsonObject) {
1195
+ {'groundingMetadata' : final Object groundingMetadata} =>
1196
+ _parseGroundingMetadata (groundingMetadata),
1197
+ _ => null
1198
+ });
1053
1199
}
1054
1200
1055
1201
PromptFeedback _parsePromptFeedback (Object jsonObject) {
@@ -1163,3 +1309,114 @@ Citation _parseCitationSource(Object? jsonObject) {
1163
1309
jsonObject['license' ] as String ? ,
1164
1310
);
1165
1311
}
1312
+
1313
+ GroundingMetadata _parseGroundingMetadata (Object ? jsonObject) {
1314
+ if (jsonObject is ! Map ) {
1315
+ throw unhandledFormat ('GroundingMetadata' , jsonObject);
1316
+ }
1317
+
1318
+ final searchEntryPoint = switch (jsonObject) {
1319
+ {'searchEntryPoint' : final Object ? searchEntryPoint} =>
1320
+ _parseSearchEntryPoint (searchEntryPoint),
1321
+ _ => null ,
1322
+ };
1323
+ final groundingChunks = switch (jsonObject) {
1324
+ {'groundingChunks' : final List <Object ?> groundingChunks} =>
1325
+ groundingChunks.map (_parseGroundingChunk).toList (),
1326
+ _ => null ,
1327
+ } ??
1328
+ [];
1329
+ // Filters out null elements, which are returned from _parseGroundingSupport when
1330
+ // segment is null.
1331
+ final groundingSupport = switch (jsonObject) {
1332
+ {'groundingSupport' : final List <Object ?> groundingSupport} =>
1333
+ groundingSupport
1334
+ .map (_parseGroundingSupport)
1335
+ .whereType <GroundingSupport >()
1336
+ .toList (),
1337
+ _ => null ,
1338
+ } ??
1339
+ [];
1340
+ final webSearchQueries = switch (jsonObject) {
1341
+ {'webSearchQueries' : final List <String >? webSearchQueries} =>
1342
+ webSearchQueries,
1343
+ _ => null ,
1344
+ } ??
1345
+ [];
1346
+
1347
+ return GroundingMetadata (
1348
+ searchEntryPoint: searchEntryPoint,
1349
+ groundingChunks: groundingChunks,
1350
+ groundingSupport: groundingSupport,
1351
+ webSearchQueries: webSearchQueries);
1352
+ }
1353
+
1354
+ Segment _parseSegment (Object ? jsonObject) {
1355
+ if (jsonObject is ! Map ) {
1356
+ throw unhandledFormat ('Segment' , jsonObject);
1357
+ }
1358
+
1359
+ return Segment (
1360
+ partIndex: (jsonObject['partIndex' ] as int ? ) ?? 0 ,
1361
+ startIndex: (jsonObject['startIndex' ] as int ? ) ?? 0 ,
1362
+ endIndex: (jsonObject['endIndex' ] as int ? ) ?? 0 ,
1363
+ text: (jsonObject['text' ] as String ? ) ?? '' );
1364
+ }
1365
+
1366
+ WebGroundingChunk _parseWebGroundingChunk (Object ? jsonObject) {
1367
+ if (jsonObject is ! Map ) {
1368
+ throw unhandledFormat ('WebGroundingChunk' , jsonObject);
1369
+ }
1370
+
1371
+ return WebGroundingChunk (
1372
+ uri: jsonObject['uri' ] as String ? ,
1373
+ title: jsonObject['title' ] as String ? ,
1374
+ domain: jsonObject['domain' ] as String ? ,
1375
+ );
1376
+ }
1377
+
1378
+ GroundingChunk _parseGroundingChunk (Object ? jsonObject) {
1379
+ if (jsonObject is ! Map ) {
1380
+ throw unhandledFormat ('GroundingChunk' , jsonObject);
1381
+ }
1382
+
1383
+ return GroundingChunk (
1384
+ web: jsonObject['web' ] != null
1385
+ ? _parseWebGroundingChunk (jsonObject['web' ])
1386
+ : null ,
1387
+ );
1388
+ }
1389
+
1390
+ GroundingSupport ? _parseGroundingSupport (Object ? jsonObject) {
1391
+ if (jsonObject is ! Map ) {
1392
+ throw unhandledFormat ('GroundingSupport' , jsonObject);
1393
+ }
1394
+
1395
+ final segment = switch (jsonObject) {
1396
+ {'segment' : final Object ? segment} => _parseSegment (segment),
1397
+ _ => null ,
1398
+ };
1399
+ if (segment == null ) {
1400
+ return null ;
1401
+ }
1402
+
1403
+ return GroundingSupport (
1404
+ segment: segment,
1405
+ groundingChunkIndices:
1406
+ (jsonObject['groundingChunkIndices' ] as List <int >? ) ?? []);
1407
+ }
1408
+
1409
+ SearchEntryPoint _parseSearchEntryPoint (Object ? jsonObject) {
1410
+ if (jsonObject is ! Map ) {
1411
+ throw unhandledFormat ('SearchEntryPoint' , jsonObject);
1412
+ }
1413
+
1414
+ final renderedContent = jsonObject['renderedContent' ] as String ? ;
1415
+ if (renderedContent == null ) {
1416
+ throw unhandledFormat ('SearchEntryPoint' , jsonObject);
1417
+ }
1418
+
1419
+ return SearchEntryPoint (
1420
+ renderedContent: renderedContent,
1421
+ );
1422
+ }
0 commit comments