Flutter集成Vertex AI服务插件firebase_vertexai的使用
Flutter集成Vertex AI服务插件firebase_vertexai的使用
Vertex AI in Firebase Flutter
这是一个用于在Flutter应用中使用Vertex AI的插件。要了解更多关于Vertex AI的信息,请访问官方网站。
正式可用(GA):Firebase中的Vertex AI现已正式可用,可以在生产应用程序中使用。
开始使用
要开始在Flutter项目中使用Vertex AI,请参考官方文档。
使用方法
要开始使用此插件,请访问文本提示文档。
问题与反馈
请在我们的issue tracker中提交特定于FlutterFire的问题、错误或功能请求。
非特定于FlutterFire的插件问题可以提交到Flutter issue tracker。
要为本插件贡献代码,请阅读我们的贡献指南,并提交pull request。
示例代码
以下是一个完整的示例,展示了如何在Flutter应用中集成和使用firebase_vertexai
插件。
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_vertexai/firebase_vertexai.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
// REQUIRED if you want to run on Web
const FirebaseOptions? options = null;
void main() {
runApp(const GenerativeAISample());
}
class GenerativeAISample extends StatelessWidget {
const GenerativeAISample({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter + Vertex AI',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.dark,
seedColor: const Color.fromARGB(255, 171, 222, 244),
),
useMaterial3: true,
),
home: const ChatScreen(title: 'Flutter + Vertex AI'),
);
}
}
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key, required this.title});
final String title;
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
late final GenerativeModel _model;
late final GenerativeModel _functionCallModel;
ChatSession? _chat;
final ScrollController _scrollController = ScrollController();
final TextEditingController _textController = TextEditingController();
final FocusNode _textFieldFocus = FocusNode();
final List<({Image? image, String? text, bool fromUser})> _generatedContent = [];
bool _loading = false;
@override
void initState() {
super.initState();
initFirebase().then((value) {
var vertex_instance = FirebaseVertexAI.instanceFor(auth: FirebaseAuth.instance);
_model = vertex_instance.generativeModel(
model: 'gemini-1.5-flash',
);
_functionCallModel = vertex_instance.generativeModel(
model: 'gemini-1.5-flash',
tools: [
Tool.functionDeclarations([fetchWeatherTool]),
],
);
_chat = _model.startChat();
});
}
Future<Map<String, Object?>> fetchWeather(Location location, String date) async {
final apiResponse = {
'temperature': 38,
'chancePrecipitation': '56%',
'cloudConditions': 'partly-cloudy',
};
return apiResponse;
}
final fetchWeatherTool = FunctionDeclaration(
'fetchWeather',
'Get the weather conditions for a specific city on a specific date.',
parameters: {
'location': Schema.object(
description: 'The name of the city and its state for which to get the weather. Only cities in the USA are supported.',
properties: {
'city': Schema.string(description: 'The city of the location.'),
'state': Schema.string(description: 'The state of the location.'),
},
),
'date': Schema.string(description: 'The date for which to get the weather. Date must be in the format: YYYY-MM-DD.'),
},
);
Future<void> initFirebase() async {
await Firebase.initializeApp(options: options);
await FirebaseAuth.instance.signInAnonymously();
}
void _scrollDown() {
WidgetsBinding.instance.addPostFrameCallback(
(_) => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 750),
curve: Curves.easeOutCirc,
),
);
}
@override
Widget build(BuildContext context) {
final textFieldDecoration = InputDecoration(
contentPadding: const EdgeInsets.all(15),
hintText: 'Enter a prompt...',
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(14)),
borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary),
),
focusedBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(14)),
borderSide: BorderSide(color: Theme.of(context).colorScheme.secondary),
),
);
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ListView.builder(
controller: _scrollController,
itemBuilder: (context, idx) {
var content = _generatedContent[idx];
return MessageWidget(
text: content.text,
image: content.image,
isFromUser: content.fromUser,
);
},
itemCount: _generatedContent.length,
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 25, horizontal: 15),
child: Row(
children: [
Expanded(
child: TextField(
autofocus: true,
focusNode: _textFieldFocus,
decoration: textFieldDecoration,
controller: _textController,
onSubmitted: _sendChatMessage,
),
),
IconButton(
tooltip: 'tokenCount Test',
onPressed: !_loading ? () async { await _testCountToken(); } : null,
icon: Icon(Icons.numbers, color: _loading ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.primary),
),
IconButton(
tooltip: 'function calling Test',
onPressed: !_loading ? () async { await _testFunctionCalling(); } : null,
icon: Icon(Icons.functions, color: _loading ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.primary),
),
IconButton(
tooltip: 'image prompt',
onPressed: !_loading ? () async { await _sendImagePrompt(_textController.text); } : null,
icon: Icon(Icons.image, color: _loading ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.primary),
),
IconButton(
tooltip: 'storage prompt',
onPressed: !_loading ? () async { await _sendStorageUriPrompt(_textController.text); } : null,
icon: Icon(Icons.folder, color: _loading ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.primary),
),
IconButton(
tooltip: 'schema prompt',
onPressed: !_loading ? () async { await _promptSchemaTest(_textController.text); } : null,
icon: Icon(Icons.schema, color: _loading ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.primary),
),
if (!_loading)
IconButton(
onPressed: () async { await _sendChatMessage(_textController.text); },
icon: Icon(Icons.send, color: Theme.of(context).colorScheme.primary),
)
else
const CircularProgressIndicator(),
],
),
),
Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 25),
child: Text('Total message count: ${_chat?.history.length ?? 0}'),
),
],
),
);
}
Future<void> _promptSchemaTest(String subject) async {
setState(() { _loading = true; });
try {
final content = [Content.text("For use in a children's card game, generate 10 animal-based characters.")];
final jsonSchema = Schema.object(
properties: {
'characters': Schema.array(
items: Schema.object(
properties: {
'name': Schema.string(),
'age': Schema.integer(),
'species': Schema.string(),
'accessory': Schema.enumString(enumValues: ['hat', 'belt', 'shoes']),
},
),
),
},
optionalProperties: ['accessory'],
);
final response = await _model.generateContent(
content,
generationConfig: GenerationConfig(
responseMimeType: 'application/json',
responseSchema: jsonSchema,
),
);
var text = response.text;
_generatedContent.add((image: null, text: text, fromUser: false));
if (text == null) {
_showError('No response from API.');
return;
} else {
setState(() { _loading = false; _scrollDown(); });
}
} catch (e) {
_showError(e.toString());
setState(() { _loading = false; });
} finally {
_textController.clear();
setState(() { _loading = false; });
_textFieldFocus.requestFocus();
}
}
Future<void> _sendStorageUriPrompt(String message) async {
setState(() { _loading = true; });
try {
final content = [
Content.multi([
TextPart(message),
FileData('image/jpeg', 'gs://vertex-ai-example-ef5a2.appspot.com/foodpic.jpg'),
]),
];
_generatedContent.add((image: null, text: message, fromUser: true));
var response = await _model.generateContent(content);
var text = response.text;
_generatedContent.add((image: null, text: text, fromUser: false));
if (text == null) {
_showError('No response from API.');
return;
} else {
setState(() { _loading = false; _scrollDown(); });
}
} catch (e) {
_showError(e.toString());
setState(() { _loading = false; });
} finally {
_textController.clear();
setState(() { _loading = false; });
_textFieldFocus.requestFocus();
}
}
Future<void> _sendImagePrompt(String message) async {
setState(() { _loading = true; });
try {
ByteData catBytes = await rootBundle.load('assets/images/cat.jpg');
ByteData sconeBytes = await rootBundle.load('assets/images/scones.jpg');
final content = [
Content.multi([
TextPart(message),
InlineDataPart('image/jpeg', catBytes.buffer.asUint8List()),
InlineDataPart('image/jpeg', sconeBytes.buffer.asUint8List()),
]),
];
_generatedContent.add(
(image: Image.asset('assets/images/cat.jpg'), text: message, fromUser: true),
);
_generatedContent.add(
(image: Image.asset('assets/images/scones.jpg'), text: null, fromUser: true),
);
var response = await _model.generateContent(content);
var text = response.text;
_generatedContent.add((image: null, text: text, fromUser: false));
if (text == null) {
_showError('No response from API.');
return;
} else {
setState(() { _loading = false; _scrollDown(); });
}
} catch (e) {
_showError(e.toString());
setState(() { _loading = false; });
} finally {
_textController.clear();
setState(() { _loading = false; });
_textFieldFocus.requestFocus();
}
}
Future<void> _sendChatMessage(String message) async {
setState(() { _loading = true; });
try {
_generatedContent.add((image: null, text: message, fromUser: true));
var response = await _chat?.sendMessage(Content.text(message));
var text = response?.text;
_generatedContent.add((image: null, text: text, fromUser: false));
if (text == null) {
_showError('No response from API.');
return;
} else {
setState(() { _loading = false; _scrollDown(); });
}
} catch (e) {
_showError(e.toString());
setState(() { _loading = false; });
} finally {
_textController.clear();
setState(() { _loading = false; });
_textFieldFocus.requestFocus();
}
}
Future<void> _testFunctionCalling() async {
setState(() { _loading = true; });
final functionCallChat = _functionCallModel.startChat();
const prompt = 'What is the weather like in Boston on 10/02 this year?';
// Send the message to the generative model.
var response = await functionCallChat.sendMessage(Content.text(prompt));
final functionCalls = response.functionCalls.toList();
if (functionCalls.isNotEmpty) {
final functionCall = functionCalls.first;
if (functionCall.name == 'fetchWeather') {
Map<String, dynamic> location = functionCall.args['location']! as Map<String, dynamic>;
var date = functionCall.args['date']! as String;
var city = location['city'] as String;
var state = location['state'] as String;
final functionResult = await fetchWeather(Location(city, state), date);
response = await functionCallChat.sendMessage(Content.functionResponse(functionCall.name, functionResult));
} else {
throw UnimplementedError('Function not declared to the model: ${functionCall.name}');
}
}
if (response.text case final text?) {
_generatedContent.add((image: null, text: text, fromUser: false));
setState(() { _loading = false; });
}
}
Future<void> _testCountToken() async {
setState(() { _loading = true; });
const prompt = 'tell a short story';
var content = Content.text(prompt);
var tokenResponse = await _model.countTokens([content]);
final tokenResult = 'Count token: ${tokenResponse.totalTokens}, billable characters: ${tokenResponse.totalBillableCharacters}';
_generatedContent.add((image: null, text: tokenResult, fromUser: false));
var contentResponse = await _model.generateContent([content]);
final contentMetaData = 'result metadata, promptTokenCount:${contentResponse.usageMetadata!.promptTokenCount}, candidatesTokenCount:${contentResponse.usageMetadata!.candidatesTokenCount}, totalTokenCount:${contentResponse.usageMetadata!.totalTokenCount}';
_generatedContent.add((image: null, text: contentMetaData, fromUser: false));
setState(() { _loading = false; });
}
void _showError(String message) {
showDialog<void>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Something went wrong'),
content: SingleChildScrollView(child: SelectableText(message)),
actions: [
TextButton(
onPressed: () { Navigator.of(context).pop(); },
child: const Text('OK'),
),
],
);
},
);
}
}
class MessageWidget extends StatelessWidget {
final Image? image;
final String? text;
final bool isFromUser;
const MessageWidget({
super.key,
this.image,
this.text,
required this.isFromUser,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: isFromUser ? MainAxisAlignment.end : MainAxisAlignment.start,
children: [
Flexible(
child: Container(
constraints: const BoxConstraints(maxWidth: 600),
decoration: BoxDecoration(
color: isFromUser ? Theme.of(context).colorScheme.primaryContainer : Theme.of(context).colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(18),
),
padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
margin: const EdgeInsets.only(bottom: 8),
child: Column(
children: [
if (text case final text?) MarkdownBody(data: text),
if (image case final image?) image,
],
),
),
),
],
);
}
}
final class Location {
final String city;
final String state;
Location(this.city, this.state);
}
关键点解释
-
初始化Firebase:
- 使用
Firebase.initializeApp()
初始化Firebase。 - 使用
FirebaseAuth.instance.signInAnonymously()
进行匿名登录。
- 使用
-
创建生成模型实例:
- 使用
FirebaseVertexAI.instanceFor(auth: FirebaseAuth.instance)
获取Vertex AI实例。 - 使用
generativeModel
方法创建生成模型实例,并指定模型名称和工具。
- 使用
-
发送消息:
- 使用
startChat()
方法启动聊天会话。 - 使用
sendMessage()
方法发送消息并接收响应。
- 使用
-
处理函数调用:
- 定义一个函数声明
fetchWeatherTool
,并在模型中注册。 - 当模型返回函数调用时,调用实际的API并返回结果。
- 定义一个函数声明
-
UI交互:
- 提供多个按钮用于测试不同的功能,如发送文本、图片、存储URI等。
- 显示聊天记录和加载状态。
通过以上步骤,您可以在Flutter应用中集成并使用Vertex AI服务,实现强大的自然语言处理和图像生成功能。
更多关于Flutter集成Vertex AI服务插件firebase_vertexai的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter集成Vertex AI服务插件firebase_vertexai的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中集成Vertex AI服务通常涉及与Google Cloud Platform (GCP)的交互,尤其是通过Firebase进行身份验证和授权。然而,值得注意的是,Firebase本身并不直接提供名为firebase_vertexai
的插件。不过,你可以使用Firebase进行身份验证,然后使用Google Cloud的客户端库来与Vertex AI服务进行交互。
以下是一个基本的步骤指南和代码示例,展示如何在Flutter中集成Firebase身份验证并使用Google Cloud的Dart客户端库来调用Vertex AI服务。
步骤 1: 设置Firebase身份验证
-
在Firebase控制台中创建项目。
-
在Firebase控制台中启用Google Sign-In。
-
将Firebase SDK添加到Flutter项目中。
flutter add firebase_auth
-
**配置
android/app/build.gradle
和ios/Runner/Info.plist
**以包含Firebase的配置。
步骤 2: 使用Firebase进行身份验证
在你的Flutter项目中,使用Firebase Auth进行Google Sign-In。
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<UserCredential> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the OAuth 2.0 credential
final GoogleSignInAuthentication? googleAuth = await googleUser?.authentication;
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
// Once signed in, return the UserCredential
return await _auth.signInWithCredential(credential);
}
}
步骤 3: 使用Google Cloud客户端库与Vertex AI交互
由于Flutter主要运行在Dart环境中,而Google Cloud的官方客户端库大多是用其他语言(如Python、Java、Node.js)编写的,你需要找到或编写一个Dart/Flutter兼容的库来与Vertex AI服务交互。目前,没有官方的Dart客户端库,但你可以使用HTTP请求库(如dio
或http
)来手动调用Vertex AI的REST API。
以下是一个使用dio
库发送HTTP请求的示例:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class VertexAIService {
final Dio _dio = Dio();
final String _baseUrl = 'https://vertex.googleapis.com/v1/projects/YOUR_PROJECT_ID/locations/YOUR_LOCATION/endpoints/YOUR_ENDPOINT';
Future<dynamic> predict(Map<String, dynamic> requestBody) async {
try {
Response response = await _dio.post(_baseUrl + ':predict',
data: requestBody,
options: Options(
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
},
),
);
return response.data;
} catch (e) {
print(e);
return null;
}
}
}
注意:
- 你需要将
YOUR_PROJECT_ID
、YOUR_LOCATION
和YOUR_ENDPOINT
替换为你的Vertex AI项目的实际值。 YOUR_ACCESS_TOKEN
是通过Firebase身份验证获得的Google OAuth 2.0访问令牌。
步骤 4: 将身份验证和服务集成在一起
在你的Flutter应用中,将身份验证服务和Vertex AI服务集成在一起,以便在用户登录后发送请求。
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Vertex AI Integration'),
),
body: VertexAIIntegration(),
),
);
}
}
class VertexAIIntegration extends StatefulWidget {
@override
_VertexAIIntegrationState createState() => _VertexAIIntegrationState();
}
class _VertexAIIntegrationState extends State<VertexAIIntegration> {
AuthService _authService = AuthService();
VertexAIService _vertexAIService = VertexAIService();
User? _user;
@override
void initState() {
super.initState();
_signInWithGoogle();
}
Future<void> _signInWithGoogle() async {
UserCredential result = await _authService.signInWithGoogle();
if (result.user != null) {
_user = result.user;
// 获取访问令牌(这里你需要一个函数来获取实际的访问令牌)
String accessToken = await getAccessTokenFromFirebaseUser(_user!);
// 使用访问令牌进行Vertex AI预测
_vertexAIService.predict({
// 你的预测请求体
}).then((response) {
print(response);
});
}
}
// 这是一个占位函数,你需要实现它以从Firebase用户获取实际的OAuth 2.0访问令牌
Future<String> getAccessTokenFromFirebaseUser(User user) async {
// 实现获取访问令牌的逻辑
return 'YOUR_ACCESS_TOKEN'; // 这是一个占位符
}
@override
Widget build(BuildContext context) {
return Center(
child: Text('等待登录...'),
);
}
}
注意:上面的getAccessTokenFromFirebaseUser
函数是一个占位符,你需要实现它以从Firebase用户获取实际的OAuth 2.0访问令牌。这通常涉及到使用Firebase Auth的FirebaseUser
对象获取ID令牌,然后使用Google的OAuth 2.0令牌交换端点将其转换为访问令牌。这是一个复杂的过程,超出了这个简单示例的范围。
此外,由于Vertex AI的REST API和身份验证流程可能会随着Google Cloud Platform的更新而发生变化,因此请务必查阅最新的Google Cloud文档以确保你的实现与当前的最佳实践保持一致。