Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Location {

class _FunctionCallingPageState extends State<FunctionCallingPage> {
late final GenerativeModel _functionCallModel;
late final GenerativeModel _codeExecutionModel;
final List<MessageData> _messages = <MessageData>[];
bool _loading = false;

Expand All @@ -49,19 +50,31 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
if (widget.useVertexBackend) {
var vertexAI = FirebaseAI.vertexAI(auth: FirebaseAuth.instance);
_functionCallModel = vertexAI.generativeModel(
model: 'gemini-2.0-flash',
model: 'gemini-2.5-flash',
tools: [
Tool.functionDeclarations([fetchWeatherTool]),
],
);
_codeExecutionModel = vertexAI.generativeModel(
model: 'gemini-2.5-flash',
tools: [
Tool.codeExecution(),
],
);
} else {
var googleAI = FirebaseAI.googleAI(auth: FirebaseAuth.instance);
_functionCallModel = googleAI.generativeModel(
model: 'gemini-2.0-flash',
model: 'gemini-2.5-flash',
tools: [
Tool.functionDeclarations([fetchWeatherTool]),
],
);
_codeExecutionModel = googleAI.generativeModel(
model: 'gemini-2.5-flash',
tools: [
Tool.codeExecution(),
],
);
}
}

Expand Down Expand Up @@ -146,6 +159,17 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
child: const Text('Test Function Calling'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: !_loading
? () async {
await _testCodeExecution();
}
: null,
child: const Text('Test Code Execution'),
),
),
],
),
),
Expand Down Expand Up @@ -197,4 +221,42 @@ class _FunctionCallingPageState extends State<FunctionCallingPage> {
});
}
}

Future<void> _testCodeExecution() async {
setState(() {
_loading = true;
});
final codeExecutionChat = _codeExecutionModel.startChat();
const prompt = 'What is the sum of the first 50 prime numbers? '
'Generate and run code for the calculation, and make sure you get all 50.';

_messages.add(MessageData(text: prompt, fromUser: true));

final response = await codeExecutionChat.sendMessage(Content.text(prompt));

final buffer = StringBuffer();
for (final part in response.candidates.first.content.parts) {
if (part is ExecutableCodePart) {
buffer.writeln('Executable Code:');
buffer.writeln('Language: ${part.language}');
buffer.writeln('Code:');
buffer.writeln(part.code);
} else if (part is CodeExecutionResultPart) {
buffer.writeln('Code Execution Result:');
buffer.writeln('Outcome: ${part.outcome}');
buffer.writeln('Output:');
buffer.writeln(part.output);
} else if (part is TextPart) {
buffer.writeln(part.text);
}
}

if (buffer.isNotEmpty) {
_messages.add(MessageData(text: buffer.toString()));
}

setState(() {
_loading = false;
});
}
}
8 changes: 6 additions & 2 deletions packages/firebase_ai/firebase_ai/lib/firebase_ai.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export 'src/content.dart'
FunctionCall,
FunctionResponse,
Part,
TextPart;
TextPart,
ExecutableCodePart,
CodeExecutionResultPart;
export 'src/error.dart'
show
FirebaseAIException,
Expand Down Expand Up @@ -103,4 +105,6 @@ export 'src/tool.dart'
FunctionCallingMode,
FunctionDeclaration,
Tool,
ToolConfig;
ToolConfig,
GoogleSearch,
CodeExecution;
41 changes: 38 additions & 3 deletions packages/firebase_ai/firebase_ai/lib/src/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ final class UsageMetadata {
final List<ModalityTokenCount>? candidatesTokensDetails;
}

/// Constructe a UsageMetadata with all it's fields.
/// Construct a UsageMetadata with all it's fields.
///
/// Expose access to the private constructor for use within the package..
UsageMetadata createUsageMetadata({
Expand Down Expand Up @@ -1150,15 +1150,15 @@ final class VertexSerialization implements SerializationStrategy {
_parsePromptFeedback(promptFeedback),
_ => null,
};
final usageMedata = switch (jsonObject) {
final usageMetadata = switch (jsonObject) {
{'usageMetadata': final usageMetadata?} =>
_parseUsageMetadata(usageMetadata),
{'totalTokens': final int totalTokens} =>
UsageMetadata._(totalTokenCount: totalTokens),
_ => null,
};
return GenerateContentResponse(candidates, promptFeedback,
usageMetadata: usageMedata);
usageMetadata: usageMetadata);
}

/// Parse the json to [CountTokensResponse]
Expand Down Expand Up @@ -1489,3 +1489,38 @@ SearchEntryPoint _parseSearchEntryPoint(Object? jsonObject) {
renderedContent: renderedContent,
);
}

/// Represents the result of the code execution.
enum Outcome {
/// Unspecified status. This value should not be used.
unspecified('OUTCOME_UNSPECIFIED'),

/// Code execution completed successfully.
ok('OUTCOME_OK'),

/// Code execution finished but with a failure. `stderr` should contain the
/// reason.
failed('OUTCOME_FAILED'),

/// Code execution ran for too long, and was cancelled. There may or may not
/// be a partial output present.
deadlineExceeded('OUTCOME_DEADLINE_EXCEEDED');

const Outcome(this._jsonString);

final String _jsonString;

/// Convert to json format.
String toJson() => _jsonString;

/// Parse the json string to [Outcome].
static Outcome parseValue(String jsonObject) {
return switch (jsonObject) {
'OUTCOME_UNSPECIFIED' => Outcome.unspecified,
'OUTCOME_OK' => Outcome.ok,
'OUTCOME_FAILED' => Outcome.failed,
'OUTCOME_DEADLINE_EXCEEDED' => Outcome.deadlineExceeded,
_ => throw FormatException('Unhandled Outcome format', jsonObject),
};
}
}
64 changes: 64 additions & 0 deletions packages/firebase_ai/firebase_ai/lib/src/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'dart:convert';
import 'dart:developer';
import 'dart:typed_data';

import 'api.dart';
import 'error.dart';

/// The base structured datatype containing multi-part content of a message.
Expand Down Expand Up @@ -105,6 +106,32 @@ Part parsePart(Object? jsonObject) {
throw unhandledFormat('functionCall', functionCall);
}
}
if (jsonObject.containsKey('executableCode')) {
final executableCode = jsonObject['executableCode'];
if (executableCode is Map &&
executableCode.containsKey('language') &&
executableCode.containsKey('code')) {
return ExecutableCodePart(
executableCode['language'] as String,
executableCode['code'] as String,
);
} else {
throw unhandledFormat('executableCode', executableCode);
}
}
if (jsonObject.containsKey('codeExecutionResult')) {
final codeExecutionResult = jsonObject['codeExecutionResult'];
if (codeExecutionResult is Map &&
codeExecutionResult.containsKey('outcome') &&
codeExecutionResult.containsKey('output')) {
return CodeExecutionResultPart(
Outcome.parseValue(codeExecutionResult['outcome'] as String),
codeExecutionResult['output'] as String,
);
} else {
throw unhandledFormat('codeExecutionResult', codeExecutionResult);
}
}
return switch (jsonObject) {
{'text': final String text} => TextPart(text),
{
Expand Down Expand Up @@ -258,3 +285,40 @@ final class FileData implements Part {
'file_data': {'file_uri': fileUri, 'mime_type': mimeType}
};
}

/// A `Part` that represents the code that is executed by the model.
final class ExecutableCodePart implements Part {
/// The programming language of the code.
final String language;

/// The source code to be executed.
final String code;

// ignore: public_member_api_docs
ExecutableCodePart(this.language, this.code);

@override
Object toJson() => {
'executableCode': {'language': language, 'code': code}
};
}

/// A `Part` that represents the code execution result from the model.
final class CodeExecutionResultPart implements Part {
/// The result of the execution.
final Outcome outcome;

/// The stdout from the code execution, or an error message if it failed.
final String output;

// ignore: public_member_api_docs
CodeExecutionResultPart(this.outcome, this.output);

@override
Object toJson() => {
'codeExecutionResult': {
'outcome': outcome.toJson(),
'output': output
}
};
}
28 changes: 24 additions & 4 deletions packages/firebase_ai/firebase_ai/lib/src/tool.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ import 'schema.dart';
/// knowledge and scope of the model.
final class Tool {
// ignore: public_member_api_docs
Tool._(this._functionDeclarations, this._googleSearch);
Tool._(this._functionDeclarations, this._googleSearch, this._codeExecution);

/// Returns a [Tool] instance with list of [FunctionDeclaration].
static Tool functionDeclarations(
List<FunctionDeclaration> functionDeclarations) {
return Tool._(functionDeclarations, null);
return Tool._(functionDeclarations, null, null);
}

/// Creates a tool that allows the model to use Grounding with Google Search.
Expand All @@ -47,7 +47,13 @@ final class Tool {
///
/// Returns a `Tool` configured for Google Search.
static Tool googleSearch({GoogleSearch googleSearch = const GoogleSearch()}) {
return Tool._(null, googleSearch);
return Tool._(null, googleSearch, null);
}

/// Returns a [Tool] instance that enables the model to use Code Execution.
static Tool codeExecution(
{CodeExecution codeExecution = const CodeExecution()}) {
return Tool._(null, null, codeExecution);
}

/// A list of `FunctionDeclarations` available to the model that can be used
Expand All @@ -65,13 +71,18 @@ final class Tool {
/// responses.
final GoogleSearch? _googleSearch;

/// A tool that allows the model to use Code Execution.
final CodeExecution? _codeExecution;

/// Convert to json object.
Map<String, Object> toJson() => {
if (_functionDeclarations case final _functionDeclarations?)
'functionDeclarations':
_functionDeclarations.map((f) => f.toJson()).toList(),
if (_googleSearch case final _googleSearch?)
'googleSearch': _googleSearch.toJson()
'googleSearch': _googleSearch.toJson(),
if (_codeExecution case final _codeExecution?)
'codeExecution': _codeExecution.toJson()
};
}

Expand All @@ -93,6 +104,15 @@ final class GoogleSearch {
Map<String, Object> toJson() => {};
}

/// A tool that allows the model to use Code Execution.
final class CodeExecution {
// ignore: public_member_api_docs
const CodeExecution();

/// Convert to json object.
Map<String, Object> toJson() => {};
}

/// Structured representation of a function declaration as defined by the
/// [OpenAPI 3.03 specification](https://spec.openapis.org/oas/v3.0.3).
///
Expand Down
Loading