Skip to content

Commit 4bab0b3

Browse files
andrewheardgoogle-labs-jules[bot]cynthiajoan
authored
fix(firebaseai): Added token details parsing for Dev API (#17609)
* feat(firebase_ai): Add thoughtsTokenCount to _parseUsageMetadata - Added `thoughtsTokenCount` to `_parseUsageMetadata` in `packages/firebase_ai/firebase_ai/lib/src/api.dart` to align with the implementation in the developer API. - Added unit tests for `thoughtsTokenCount` parsing in `packages/firebase_ai/firebase_ai/test/api_test.dart`. * Fix formatting * Remove extraneous test * Revert Jules mistakes * I have added parsing for `promptTokensDetails` and `candidatesTokensDetails` in the `_parseUsageMetadata` function. * Move helper methods to align with Vertex `api.dart` * Add token details testing to existing tests instead * Add test for missing token details * Use `parseUsageMetadata` for both backends * Remove unused `show` types * fix the error --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: Cynthia J <[email protected]>
1 parent 5faec2c commit 4bab0b3

File tree

3 files changed

+51
-58
lines changed

3 files changed

+51
-58
lines changed

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

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -209,25 +209,6 @@ final class UsageMetadata {
209209
final List<ModalityTokenCount>? candidatesTokensDetails;
210210
}
211211

212-
/// Constructe a UsageMetadata with all it's fields.
213-
///
214-
/// Expose access to the private constructor for use within the package..
215-
UsageMetadata createUsageMetadata({
216-
required int? promptTokenCount,
217-
required int? candidatesTokenCount,
218-
required int? totalTokenCount,
219-
required int? thoughtsTokenCount,
220-
required List<ModalityTokenCount>? promptTokensDetails,
221-
required List<ModalityTokenCount>? candidatesTokensDetails,
222-
}) =>
223-
UsageMetadata._(
224-
promptTokenCount: promptTokenCount,
225-
candidatesTokenCount: candidatesTokenCount,
226-
totalTokenCount: totalTokenCount,
227-
thoughtsTokenCount: thoughtsTokenCount,
228-
promptTokensDetails: promptTokensDetails,
229-
candidatesTokensDetails: candidatesTokensDetails);
230-
231212
/// Response candidate generated from a [GenerativeModel].
232213
final class Candidate {
233214
// TODO: token count?
@@ -1194,7 +1175,7 @@ final class VertexSerialization implements SerializationStrategy {
11941175
};
11951176
final usageMedata = switch (jsonObject) {
11961177
{'usageMetadata': final usageMetadata?} =>
1197-
_parseUsageMetadata(usageMetadata),
1178+
parseUsageMetadata(usageMetadata),
11981179
{'totalTokens': final int totalTokens} =>
11991180
UsageMetadata._(totalTokenCount: totalTokens),
12001181
_ => null,
@@ -1324,7 +1305,10 @@ PromptFeedback _parsePromptFeedback(Object jsonObject) {
13241305
};
13251306
}
13261307

1327-
UsageMetadata _parseUsageMetadata(Object jsonObject) {
1308+
/// Parses a UsageMetadata from a JSON object.
1309+
///
1310+
/// Expose access to the private helper for use within the package.
1311+
UsageMetadata parseUsageMetadata(Object jsonObject) {
13281312
if (jsonObject is! Map<String, Object?>) {
13291313
throw unhandledFormat('UsageMetadata', jsonObject);
13301314
}
@@ -1355,7 +1339,7 @@ UsageMetadata _parseUsageMetadata(Object jsonObject) {
13551339
candidatesTokensDetails.map(_parseModalityTokenCount).toList(),
13561340
_ => null,
13571341
};
1358-
return createUsageMetadata(
1342+
return UsageMetadata._(
13591343
promptTokenCount: promptTokenCount,
13601344
candidatesTokenCount: candidatesTokenCount,
13611345
totalTokenCount: totalTokenCount,

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

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ import '../api.dart'
3434
SearchEntryPoint,
3535
Segment,
3636
SerializationStrategy,
37-
UsageMetadata,
3837
WebGroundingChunk,
39-
createUsageMetadata;
38+
parseUsageMetadata;
4039
import '../content.dart' show Content, parseContent;
4140
import '../error.dart';
4241
import '../tool.dart' show Tool, ToolConfig;
@@ -117,13 +116,13 @@ final class DeveloperSerialization implements SerializationStrategy {
117116
_parsePromptFeedback(promptFeedback),
118117
_ => null,
119118
};
120-
final usageMedata = switch (jsonObject) {
119+
final usageMetadata = switch (jsonObject) {
121120
{'usageMetadata': final usageMetadata?} =>
122-
_parseUsageMetadata(usageMetadata),
121+
parseUsageMetadata(usageMetadata),
123122
_ => null,
124123
};
125124
return GenerateContentResponse(candidates, promptFeedback,
126-
usageMetadata: usageMedata);
125+
usageMetadata: usageMetadata);
127126
}
128127

129128
@override
@@ -238,37 +237,6 @@ PromptFeedback _parsePromptFeedback(Object jsonObject) {
238237
};
239238
}
240239

241-
UsageMetadata _parseUsageMetadata(Object jsonObject) {
242-
if (jsonObject is! Map<String, Object?>) {
243-
throw unhandledFormat('UsageMetadata', jsonObject);
244-
}
245-
final promptTokenCount = switch (jsonObject) {
246-
{'promptTokenCount': final int promptTokenCount} => promptTokenCount,
247-
_ => null,
248-
};
249-
final candidatesTokenCount = switch (jsonObject) {
250-
{'candidatesTokenCount': final int candidatesTokenCount} =>
251-
candidatesTokenCount,
252-
_ => null,
253-
};
254-
final totalTokenCount = switch (jsonObject) {
255-
{'totalTokenCount': final int totalTokenCount} => totalTokenCount,
256-
_ => null,
257-
};
258-
final thoughtsTokenCount = switch (jsonObject) {
259-
{'thoughtsTokenCount': final int thoughtsTokenCount} => thoughtsTokenCount,
260-
_ => null,
261-
};
262-
return createUsageMetadata(
263-
promptTokenCount: promptTokenCount,
264-
candidatesTokenCount: candidatesTokenCount,
265-
totalTokenCount: totalTokenCount,
266-
thoughtsTokenCount: thoughtsTokenCount,
267-
promptTokensDetails: null,
268-
candidatesTokensDetails: null,
269-
);
270-
}
271-
272240
SafetyRating _parseSafetyRating(Object? jsonObject) {
273241
return switch (jsonObject) {
274242
{

packages/firebase_ai/firebase_ai/test/developer_api_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ void main() {
4040
'candidatesTokenCount': 5,
4141
'totalTokenCount': 15,
4242
'thoughtsTokenCount': 3,
43+
'promptTokensDetails': [
44+
{'modality': 'TEXT', 'tokenCount': 10}
45+
],
46+
'candidatesTokensDetails': [
47+
{'modality': 'TEXT', 'tokenCount': 25}
48+
],
4349
}
4450
};
4551
final response =
@@ -49,6 +55,15 @@ void main() {
4955
expect(response.usageMetadata!.candidatesTokenCount, 5);
5056
expect(response.usageMetadata!.totalTokenCount, 15);
5157
expect(response.usageMetadata!.thoughtsTokenCount, 3);
58+
expect(response.usageMetadata!.promptTokensDetails, isNotNull);
59+
expect(response.usageMetadata!.promptTokensDetails, hasLength(1));
60+
expect(
61+
response.usageMetadata!.promptTokensDetails!.first.tokenCount, 10);
62+
expect(response.usageMetadata!.candidatesTokensDetails, isNotNull);
63+
expect(response.usageMetadata!.candidatesTokensDetails, hasLength(1));
64+
expect(
65+
response.usageMetadata!.candidatesTokensDetails!.first.tokenCount,
66+
25);
5267
});
5368

5469
test('parses usageMetadata when thoughtsTokenCount is missing', () {
@@ -69,6 +84,12 @@ void main() {
6984
'candidatesTokenCount': 5,
7085
'totalTokenCount': 15,
7186
// thoughtsTokenCount is missing
87+
'promptTokensDetails': [
88+
{'modality': 'TEXT', 'tokenCount': 10}
89+
],
90+
'candidatesTokensDetails': [
91+
{'modality': 'TEXT', 'tokenCount': 25}
92+
],
7293
}
7394
};
7495
final response =
@@ -303,6 +324,26 @@ void main() {
303324
});
304325
});
305326

327+
test('parses usageMetadata when token details are missing', () {
328+
final jsonResponse = {
329+
'usageMetadata': {
330+
'promptTokenCount': 10,
331+
'candidatesTokenCount': 25,
332+
'totalTokenCount': 35,
333+
}
334+
};
335+
336+
final response =
337+
DeveloperSerialization().parseGenerateContentResponse(jsonResponse);
338+
339+
expect(response.usageMetadata, isNotNull);
340+
expect(response.usageMetadata!.promptTokenCount, 10);
341+
expect(response.usageMetadata!.candidatesTokenCount, 25);
342+
expect(response.usageMetadata!.totalTokenCount, 35);
343+
expect(response.usageMetadata!.promptTokensDetails, isNull);
344+
expect(response.usageMetadata!.candidatesTokensDetails, isNull);
345+
});
346+
306347
test('parses inlineData part correctly', () {
307348
final inlineData = Uint8List.fromList([1, 2, 3, 4]);
308349
final jsonResponse = {

0 commit comments

Comments
 (0)