Skip to content

Commit f365663

Browse files
authored
feat(firebaseai): Add support for URL context (#17736)
1 parent 09d03aa commit f365663

File tree

10 files changed

+1088
-17
lines changed

10 files changed

+1088
-17
lines changed

packages/firebase_ai/firebase_ai/lib/firebase_ai.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,5 @@ export 'src/tool.dart'
108108
Tool,
109109
ToolConfig,
110110
GoogleSearch,
111-
CodeExecution;
111+
CodeExecution,
112+
UrlContext;

packages/firebase_ai/firebase_ai/lib/src/api.dart

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,16 @@ final class PromptFeedback {
182182
/// Metadata on the generation request's token usage.
183183
final class UsageMetadata {
184184
// ignore: public_member_api_docs
185-
UsageMetadata._(
186-
{this.promptTokenCount,
187-
this.candidatesTokenCount,
188-
this.totalTokenCount,
189-
this.thoughtsTokenCount,
190-
this.promptTokensDetails,
191-
this.candidatesTokensDetails});
185+
UsageMetadata._({
186+
this.promptTokenCount,
187+
this.candidatesTokenCount,
188+
this.totalTokenCount,
189+
this.thoughtsTokenCount,
190+
this.toolUsePromptTokenCount,
191+
this.promptTokensDetails,
192+
this.candidatesTokensDetails,
193+
this.toolUsePromptTokensDetails,
194+
});
192195

193196
/// Number of tokens in the prompt.
194197
final int? promptTokenCount;
@@ -202,19 +205,26 @@ final class UsageMetadata {
202205
/// Number of tokens present in thoughts output.
203206
final int? thoughtsTokenCount;
204207

208+
/// The number of tokens used by tools.
209+
final int? toolUsePromptTokenCount;
210+
205211
/// List of modalities that were processed in the request input.
206212
final List<ModalityTokenCount>? promptTokensDetails;
207213

208214
/// List of modalities that were returned in the response.
209215
final List<ModalityTokenCount>? candidatesTokensDetails;
216+
217+
/// A list of tokens used by tools whose usage was triggered from a prompt,
218+
/// broken down by modality.
219+
final List<ModalityTokenCount>? toolUsePromptTokensDetails;
210220
}
211221

212222
/// Response candidate generated from a [GenerativeModel].
213223
final class Candidate {
214224
// ignore: public_member_api_docs
215225
Candidate(this.content, this.safetyRatings, this.citationMetadata,
216226
this.finishReason, this.finishMessage,
217-
{this.groundingMetadata});
227+
{this.groundingMetadata, this.urlContextMetadata});
218228

219229
/// Generated content returned from the model.
220230
final Content content;
@@ -242,6 +252,9 @@ final class Candidate {
242252
/// Metadata returned to the client when grounding is enabled.
243253
final GroundingMetadata? groundingMetadata;
244254

255+
/// Metadata returned to the client when the [UrlContext] tool is enabled.
256+
final UrlContextMetadata? urlContextMetadata;
257+
245258
/// The concatenation of the text parts of [content], if any.
246259
///
247260
/// If this candidate was finished for a reason of [FinishReason.recitation]
@@ -417,6 +430,76 @@ final class GroundingMetadata {
417430
final List<String> webSearchQueries;
418431
}
419432

433+
/// The status of a URL retrieval.
434+
///
435+
/// > Warning: For Firebase AI Logic, URL Context
436+
/// is in Public Preview, which means that the feature is not subject to any SLA
437+
/// or deprecation policy and could change in backwards-incompatible ways.
438+
enum UrlRetrievalStatus {
439+
/// Unspecified retrieval status.
440+
unspecified('URL_RETRIEVAL_STATUS_UNSPECIFIED'),
441+
442+
/// The URL retrieval was successful.
443+
success('URL_RETRIEVAL_STATUS_SUCCESS'),
444+
445+
/// The URL retrieval failed due.
446+
error('URL_RETRIEVAL_STATUS_ERROR'),
447+
448+
/// The URL retrieval failed because the content is behind a paywall.
449+
paywall('URL_RETRIEVAL_STATUS_PAYWALL'),
450+
451+
/// The URL retrieval failed because the content is unsafe.
452+
unsafe('URL_RETRIEVAL_STATUS_UNSAFE');
453+
454+
const UrlRetrievalStatus(this._jsonString);
455+
final String _jsonString;
456+
457+
// ignore: public_member_api_docs
458+
String toJson() => _jsonString;
459+
460+
// ignore: unused_element
461+
static UrlRetrievalStatus _parseValue(Object jsonObject) {
462+
return switch (jsonObject) {
463+
'URL_RETRIEVAL_STATUS_UNSPECIFIED' => UrlRetrievalStatus.unspecified,
464+
'URL_RETRIEVAL_STATUS_SUCCESS' => UrlRetrievalStatus.success,
465+
'URL_RETRIEVAL_STATUS_ERROR' => UrlRetrievalStatus.error,
466+
'URL_RETRIEVAL_STATUS_PAYWALL' => UrlRetrievalStatus.paywall,
467+
'URL_RETRIEVAL_STATUS_UNSAFE' => UrlRetrievalStatus.unsafe,
468+
_ => UrlRetrievalStatus
469+
.unspecified, // Default to unspecified for unknown values.
470+
};
471+
}
472+
}
473+
474+
/// Metadata for a single URL retrieved by the [UrlContext] tool.
475+
///
476+
/// > Warning: For Firebase AI Logic, URL Context
477+
/// is in Public Preview, which means that the feature is not subject to any SLA
478+
/// or deprecation policy and could change in backwards-incompatible ways.
479+
final class UrlMetadata {
480+
// ignore: public_member_api_docs
481+
UrlMetadata({this.retrievedUrl, required this.urlRetrievalStatus});
482+
483+
/// The retrieved URL.
484+
final Uri? retrievedUrl;
485+
486+
/// The status of the URL retrieval.
487+
final UrlRetrievalStatus urlRetrievalStatus;
488+
}
489+
490+
/// Metadata related to the [UrlContext] tool.
491+
///
492+
/// > Warning: For Firebase AI Logic, URL Context
493+
/// is in Public Preview, which means that the feature is not subject to any SLA
494+
/// or deprecation policy and could change in backwards-incompatible ways.
495+
final class UrlContextMetadata {
496+
// ignore: public_member_api_docs
497+
UrlContextMetadata({required this.urlMetadata});
498+
499+
/// List of [UrlMetadata] used to provide context to the Gemini model.
500+
final List<UrlMetadata> urlMetadata;
501+
}
502+
420503
/// Safety rating for a piece of content.
421504
///
422505
/// The safety rating contains the category of harm and the harm probability
@@ -1280,6 +1363,11 @@ Candidate _parseCandidate(Object? jsonObject) {
12801363
{'groundingMetadata': final Object groundingMetadata} =>
12811364
parseGroundingMetadata(groundingMetadata),
12821365
_ => null
1366+
},
1367+
urlContextMetadata: switch (jsonObject) {
1368+
{'urlContextMetadata': final Object urlContextMetadata} =>
1369+
parseUrlContextMetadata(urlContextMetadata),
1370+
_ => null
12831371
});
12841372
}
12851373

@@ -1328,6 +1416,11 @@ UsageMetadata parseUsageMetadata(Object jsonObject) {
13281416
{'thoughtsTokenCount': final int thoughtsTokenCount} => thoughtsTokenCount,
13291417
_ => null,
13301418
};
1419+
final toolUsePromptTokenCount = switch (jsonObject) {
1420+
{'toolUsePromptTokenCount': final int toolUsePromptTokenCount} =>
1421+
toolUsePromptTokenCount,
1422+
_ => null,
1423+
};
13311424
final promptTokensDetails = switch (jsonObject) {
13321425
{'promptTokensDetails': final List<Object?> promptTokensDetails} =>
13331426
promptTokensDetails.map(_parseModalityTokenCount).toList(),
@@ -1338,13 +1431,23 @@ UsageMetadata parseUsageMetadata(Object jsonObject) {
13381431
candidatesTokensDetails.map(_parseModalityTokenCount).toList(),
13391432
_ => null,
13401433
};
1434+
final toolUsePromptTokensDetails = switch (jsonObject) {
1435+
{
1436+
'toolUsePromptTokensDetails': final List<Object?>
1437+
toolUsePromptTokensDetails
1438+
} =>
1439+
toolUsePromptTokensDetails.map(_parseModalityTokenCount).toList(),
1440+
_ => null,
1441+
};
13411442
return UsageMetadata._(
13421443
promptTokenCount: promptTokenCount,
13431444
candidatesTokenCount: candidatesTokenCount,
13441445
totalTokenCount: totalTokenCount,
13451446
thoughtsTokenCount: thoughtsTokenCount,
1447+
toolUsePromptTokenCount: toolUsePromptTokenCount,
13461448
promptTokensDetails: promptTokensDetails,
13471449
candidatesTokensDetails: candidatesTokensDetails,
1450+
toolUsePromptTokensDetails: toolUsePromptTokensDetails,
13481451
);
13491452
}
13501453

@@ -1523,6 +1626,33 @@ SearchEntryPoint _parseSearchEntryPoint(Object? jsonObject) {
15231626
);
15241627
}
15251628

1629+
UrlMetadata _parseUrlMetadata(Object? jsonObject) {
1630+
if (jsonObject is! Map) {
1631+
throw unhandledFormat('UrlMetadata', jsonObject);
1632+
}
1633+
final uriString = jsonObject['retrievedUrl'] as String?;
1634+
return UrlMetadata(
1635+
retrievedUrl: uriString != null ? Uri.parse(uriString) : null,
1636+
urlRetrievalStatus:
1637+
UrlRetrievalStatus._parseValue(jsonObject['urlRetrievalStatus']),
1638+
);
1639+
}
1640+
1641+
/// Parses a [UrlContextMetadata] from a JSON object.
1642+
///
1643+
/// This function is used internally to convert URL context metadata from the API
1644+
/// response.
1645+
UrlContextMetadata parseUrlContextMetadata(Object? jsonObject) {
1646+
if (jsonObject is! Map) {
1647+
throw unhandledFormat('UrlContextMetadata', jsonObject);
1648+
}
1649+
return UrlContextMetadata(
1650+
urlMetadata: (jsonObject['urlMetadata'] as List<Object?>? ?? [])
1651+
.map(_parseUrlMetadata)
1652+
.toList(),
1653+
);
1654+
}
1655+
15261656
/// Supported programming languages for the generated code.
15271657
enum CodeLanguage {
15281658
/// Unspecified status. This value should not be used.

packages/firebase_ai/firebase_ai/lib/src/developer/api.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import '../api.dart'
2929
SerializationStrategy,
3030
parseUsageMetadata,
3131
parseCitationMetadata,
32-
parseGroundingMetadata;
32+
parseGroundingMetadata,
33+
parseUrlContextMetadata;
3334
import '../content.dart' show Content, parseContent;
3435
import '../error.dart';
3536
import '../tool.dart' show Tool, ToolConfig;
@@ -175,6 +176,11 @@ Candidate _parseCandidate(Object? jsonObject) {
175176
parseGroundingMetadata(groundingMetadata),
176177
_ => null
177178
},
179+
urlContextMetadata: switch (jsonObject) {
180+
{'urlContextMetadata': final Object urlContextMetadata} =>
181+
parseUrlContextMetadata(urlContextMetadata),
182+
_ => null
183+
},
178184
);
179185
}
180186

packages/firebase_ai/firebase_ai/lib/src/tool.dart

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ import 'schema.dart';
2121
/// knowledge and scope of the model.
2222
final class Tool {
2323
// ignore: public_member_api_docs
24-
Tool._(this._functionDeclarations, this._googleSearch, this._codeExecution);
24+
Tool._(this._functionDeclarations, this._googleSearch, this._codeExecution,
25+
this._urlContext);
2526

2627
/// Returns a [Tool] instance with list of [FunctionDeclaration].
2728
static Tool functionDeclarations(
2829
List<FunctionDeclaration> functionDeclarations) {
29-
return Tool._(functionDeclarations, null, null);
30+
return Tool._(functionDeclarations, null, null, null);
3031
}
3132

3233
/// Creates a tool that allows the model to use Grounding with Google Search.
@@ -47,13 +48,30 @@ final class Tool {
4748
///
4849
/// Returns a `Tool` configured for Google Search.
4950
static Tool googleSearch({GoogleSearch googleSearch = const GoogleSearch()}) {
50-
return Tool._(null, googleSearch, null);
51+
return Tool._(null, googleSearch, null, null);
5152
}
5253

5354
/// Returns a [Tool] instance that enables the model to use Code Execution.
5455
static Tool codeExecution(
5556
{CodeExecution codeExecution = const CodeExecution()}) {
56-
return Tool._(null, null, codeExecution);
57+
return Tool._(null, null, codeExecution, null);
58+
}
59+
60+
/// Creates a tool that allows you to provide additional context to the models
61+
/// in the form of public web URLs.
62+
///
63+
/// By including URLs in your request, the Gemini model will access the
64+
/// content from those pages to inform and enhance its response.
65+
///
66+
/// - [urlContext]: Specifies the URL context configuration.
67+
///
68+
/// Returns a `Tool` configured for URL context.
69+
///
70+
/// > Warning: For Firebase AI Logic, URL Context
71+
/// is in Public Preview, which means that the feature is not subject to any SLA
72+
/// or deprecation policy and could change in backwards-incompatible ways.
73+
static Tool urlContext({UrlContext urlContext = const UrlContext()}) {
74+
return Tool._(null, null, null, urlContext);
5775
}
5876

5977
/// A list of `FunctionDeclarations` available to the model that can be used
@@ -74,6 +92,9 @@ final class Tool {
7492
/// A tool that allows the model to use Code Execution.
7593
final CodeExecution? _codeExecution;
7694

95+
/// A tool that allows providing URL context to the model.
96+
final UrlContext? _urlContext;
97+
7798
/// Convert to json object.
7899
Map<String, Object> toJson() => {
79100
if (_functionDeclarations case final _functionDeclarations?)
@@ -82,7 +103,9 @@ final class Tool {
82103
if (_googleSearch case final _googleSearch?)
83104
'googleSearch': _googleSearch.toJson(),
84105
if (_codeExecution case final _codeExecution?)
85-
'codeExecution': _codeExecution.toJson()
106+
'codeExecution': _codeExecution.toJson(),
107+
if (_urlContext case final _urlContext?)
108+
'urlContext': _urlContext.toJson(),
86109
};
87110
}
88111

@@ -104,6 +127,22 @@ final class GoogleSearch {
104127
Map<String, Object> toJson() => {};
105128
}
106129

130+
/// A tool that allows you to provide additional context to the models in the
131+
/// form of public web URLs. By including URLs in your request, the Gemini
132+
/// model will access the content from those pages to inform and enhance its
133+
/// response.
134+
///
135+
/// > Warning: For Firebase AI Logic, URL Context
136+
/// is in Public Preview, which means that the feature is not subject to any SLA
137+
/// or deprecation policy and could change in backwards-incompatible ways.
138+
final class UrlContext {
139+
// ignore: public_member_api_docs
140+
const UrlContext();
141+
142+
/// Convert to json object.
143+
Map<String, Object> toJson() => {};
144+
}
145+
107146
/// A tool that allows the model to use Code Execution.
108147
final class CodeExecution {
109148
// ignore: public_member_api_docs

0 commit comments

Comments
 (0)