こんにちは、ゲームソリューション部のsoraです。
今回は、FlutterでOpenAI APIを使ってみたことについて書いていきます。
実装した画面
リクエストメッセージを入力して、OpenAIのAPIを実行して文章を生成するシンプルなアプリです。
前提
OpenAIにてAPIキーを作成して、利用可能なクレジットを準備しておいてください。
クレジットを準備しなくてもAPIキーは作成できますが、クレジットが0の状態で使用してもエラーになります。
(私はこれに気づくのに結構時間がかかりました。)
また、外部パッケージとして以下のchat_gpt_sdk
もありますが、今回は使用せずにシンプルにhttpパッケージを使用して、APIを実行します。
http | Dart package
参考:chat_gpt_sdk | Dart package
状態管理にはRiverpod(flutter_riverpod)を使用しています。
flutter_riverpod | Flutter package
コードの解説
コードは以下です。
今回はテストのため、ファイル分けせずに全てmain.dart
に書いています。
main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
// 表示する文字列を管理するProvider
class Response extends Notifier<String> {
@override
String build(){
return '';
}
void clear() {
state = '';
}
void modify(String message) {
state = message;
}
}
final responseProvider = NotifierProvider<Response, String>(() {
return Response();
});
// OpenAI API実行
Future<void> apiRequest(String message, WidgetRef ref) async {
String responseMessage;
final providerNotifier = ref.watch(responseProvider.notifier);
// 取得したAPIキーを入れる
const apiKey = '{OpenAIのAPIキー}';
const domain = 'api.openai.com';
const path = 'v1/chat/completions';
// モデルの指定
const model = 'gpt-3.5-turbo-0125';
// APIリクエスト
http.Response response = await http.post(
Uri.https(domain, path),
headers: <String, String>{
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer $apiKey',
},
body: jsonEncode(<String, dynamic>{
"model": model,
"messages": [
{
'role': 'system',
'content': 'あなたはAIではありません。人間として自然な会話をしてください。',
},
{
'role': 'user',
'content': message,
},
],
}),
);
if (response.statusCode == 200) {
String responseData = utf8.decode(response.bodyBytes).toString();
final responseJsonData = jsonDecode(responseData);
responseMessage = (responseJsonData['choices'] as List).first['message']['content'] as String;
providerNotifier.modify(responseMessage);
} else {
throw Exception('Failed to load sentence');
}
}
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
home: MyHomePage(),
);
}
}
class MyHomePage extends ConsumerWidget {
MyHomePage({super.key});
final _messageController = TextEditingController();
@override
Widget build(BuildContext context, WidgetRef ref) {
final providerValue = ref.watch(responseProvider);
final providerNotifier = ref.watch(responseProvider.notifier);
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('ChatGPT test'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.only(
left: 25,
right: 25,
),
child: TextField(
controller: _messageController,
maxLines: 1,
decoration: const InputDecoration(
hintText: 'メッセージを入力',
hintStyle: TextStyle(color: Colors.black54),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: const Text('AIチャット実行'),
onPressed: (){
var msg = _messageController.text.trim();
if(msg.isEmpty){
_messageController.clear();
return;
}
providerNotifier.clear();
apiRequest(msg, ref);
},
),
]
),
const SizedBox(height: 30),
Container(
padding: const EdgeInsets.only(
left: 25,
right: 25,
),
child: Text(
'$providerValue',
style: const TextStyle(
fontSize: 18,
),
),
),
],
),
),
);
}
}
OpenAI API実行
OpenAIのAPIキーやモデルを指定して、POSTメソッドでリクエストを投げます。
リクエストボディのメッセージの部分は、'role': 'assistant'
で'content'
にレスポンスメッセージを入れてリクエストすることで、過去のやり取りを理解してレスポンスを得ることができます。
ステータスコードが200だった場合は、受け取ったレスポンスの中から必要な部分を抜き出して、状態を更新するようにしています。
ちなみに、APIキーについて、テストのためそのまま記述するコードになっていますが、本来であれば暗号化したりサーバ側で扱うようにしてください。
よくenvied
やdotenv
を使用している例がありますが、それらは難読化であって暗号化ではなく安全性は高くないため、実際に公開するアプリでは注意が必要です。
// OpenAI API実行
Future<void> apiRequest(String message, WidgetRef ref) async {
String responseMessage;
final providerNotifier = ref.watch(responseProvider.notifier);
// 取得したAPIキーを入れる
const apiKey = '{OpenAIのAPIキー}';
const domain = 'api.openai.com';
const path = 'v1/chat/completions';
// モデルの指定
const model = 'gpt-3.5-turbo-0125';
// APIリクエスト
http.Response response = await http.post(
Uri.https(domain, path),
headers: <String, String>{
'Content-Type': 'application/json; charset=utf-8',
'Authorization': 'Bearer $apiKey',
},
body: jsonEncode(<String, dynamic>{
"model": model,
"messages": [
{
'role': 'system',
'content': 'あなたはAIではありません。人間として自然な会話をしてください。',
},
{
'role': 'user',
'content': message,
},
],
}),
);
if (response.statusCode == 200) {
String responseData = utf8.decode(response.bodyBytes).toString();
final responseJsonData = jsonDecode(responseData);
responseMessage = (responseJsonData['choices'] as List).first['message']['content'] as String;
providerNotifier.modify(responseMessage);
} else {
throw Exception('Failed to load sentence');
}
}
最後に
今回は、FlutterでOpenAI APIを使ってみたことを記事にしました。
どなたかの参考になると幸いです。