From 8b10ab21696aaae4a9c4c876c1b8ed60da644753 Mon Sep 17 00:00:00 2001 From: liuwei Date: Tue, 21 Nov 2023 15:11:53 +0800 Subject: [PATCH 1/2] Implement custom OpenAI endpoint for issue #33 --- .../lib/bloc/openai/openai_bloc.dart | 1 + lib/src/audio.dart | 4 ++-- lib/src/client/openai_client.dart | 15 ++++++++----- lib/src/edit.dart | 6 ++--- lib/src/embedding.dart | 2 +- lib/src/fine_tuned.dart | 22 +++++++++---------- lib/src/moderation.dart | 2 +- lib/src/openai.dart | 18 ++++++++------- lib/src/openai_file.dart | 10 ++++----- test/client/openai_wrapper_test.mocks.dart | 8 +++++++ test/openai_test.mocks.dart | 6 +++++ 11 files changed, 58 insertions(+), 36 deletions(-) diff --git a/example_app/openai_app/lib/bloc/openai/openai_bloc.dart b/example_app/openai_app/lib/bloc/openai/openai_bloc.dart index dd08d27..4d00acb 100644 --- a/example_app/openai_app/lib/bloc/openai/openai_bloc.dart +++ b/example_app/openai_app/lib/bloc/openai/openai_bloc.dart @@ -75,6 +75,7 @@ class OpenAIBloc extends Cubit { void initOpenAISdk() async { _openAI = OpenAI.instance.build( token: getToken(), + apiUrl: 'https://api.openai.com/v1/', // you can replace with your api url enableLog: true, baseOption: HttpSetup( receiveTimeout: const Duration(seconds: 30), diff --git a/lib/src/audio.dart b/lib/src/audio.dart index 76a7fbb..6b38df5 100644 --- a/lib/src/audio.dart +++ b/lib/src/audio.dart @@ -16,7 +16,7 @@ class Audio { final mRequest = await request.toJson(); return _client.postFormData( - kURL + kTranscription, + _client.apiUrl + kTranscription, mRequest, onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) => AudioResponse.fromJson(it), @@ -31,7 +31,7 @@ class Audio { final mRequest = await request.toJson(); return _client.postFormData( - kURL + kTranslations, + _client.apiUrl + kTranslations, mRequest, onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) => AudioResponse.fromJson(it), diff --git a/lib/src/client/openai_client.dart b/lib/src/client/openai_client.dart index 1131409..2614665 100644 --- a/lib/src/client/openai_client.dart +++ b/lib/src/client/openai_client.dart @@ -11,8 +11,9 @@ import 'package:chat_gpt_sdk/src/utils/json_decode_string.dart'; import 'package:dio/dio.dart'; class OpenAIClient extends OpenAIWrapper { - OpenAIClient({required Dio dio, bool isLogging = false}) { + OpenAIClient({required Dio dio, required String apiUrl, bool isLogging = false}) { _dio = dio; + _apiUrl = apiUrl; log = Logger.instance.builder(isLogging: isLogging); } @@ -22,6 +23,10 @@ class OpenAIClient extends OpenAIWrapper { ///[log] late Logger log; + late String _apiUrl; + + String get apiUrl => _apiUrl; + Future get( String url, { required T Function(Map) onSuccess, @@ -179,7 +184,7 @@ class OpenAIClient extends OpenAIWrapper { final cancelData = CancelData(cancelToken: CancelToken()); onCancel(cancelData); - log.log("starting request"); + log.log("starting request $url"); log.log("request body :$request"); final response = await _dio.post( @@ -220,7 +225,7 @@ class OpenAIClient extends OpenAIWrapper { final cancelData = CancelData(cancelToken: CancelToken()); onCancel(cancelData); - log.log("starting request"); + log.log("starting request $url"); log.log("request body :$request"); final response = _dio .post( @@ -239,7 +244,7 @@ class OpenAIClient extends OpenAIWrapper { required T Function(Map value) complete, required void Function(CancelData cancelData) onCancel, }) { - log.log("starting request"); + log.log("starting request $url"); log.log("request body :$request"); final controller = StreamController.broadcast(); final cancelData = CancelData(cancelToken: CancelToken()); @@ -349,7 +354,7 @@ class OpenAIClient extends OpenAIWrapper { final cancelData = CancelData(cancelToken: CancelToken()); onCancel(cancelData); - log.log("starting request"); + log.log("starting request $url"); log.log("request body :$request"); final response = await _dio.post( url, diff --git a/lib/src/edit.dart b/lib/src/edit.dart index cd1e606..320adfc 100644 --- a/lib/src/edit.dart +++ b/lib/src/edit.dart @@ -20,7 +20,7 @@ class Edit { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - kURL + kEditPrompt, + _client.apiUrl + kEditPrompt, request.toJson(), onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => EditResponse.fromJson(it), @@ -36,7 +36,7 @@ class Edit { final mRequest = await request.convert(); return _client.postFormData( - kURL + kImageEdit, + _client.apiUrl + kImageEdit, mRequest, onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) => GenImgResponse.fromJson(it), @@ -51,7 +51,7 @@ class Edit { final mRequest = await request.convert(); return _client.postFormData( - kURL + kVariation, + _client.apiUrl + kVariation, mRequest, onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) => GenImgResponse.fromJson(it), diff --git a/lib/src/embedding.dart b/lib/src/embedding.dart index 8b4dcbe..19c1c9c 100644 --- a/lib/src/embedding.dart +++ b/lib/src/embedding.dart @@ -17,7 +17,7 @@ class Embedding { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - kURL + kEmbedding, + _client.apiUrl + kEmbedding, request.toJson(), onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => EmbedResponse.fromJson(it), diff --git a/lib/src/fine_tuned.dart b/lib/src/fine_tuned.dart index 54e3d66..9f08a6f 100644 --- a/lib/src/fine_tuned.dart +++ b/lib/src/fine_tuned.dart @@ -21,7 +21,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - kURL + kFineTune, + _client.apiUrl + kFineTune, request.toJson(), onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => FineTuneModel.fromJson(it), @@ -33,7 +33,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) async { return _client.get( - kURL + kFineTune, + _client.apiUrl + kFineTune, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { final data = it['data'] as List; @@ -49,7 +49,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.get( - "$kURL$kFineTune/$fineTuneId", + "$_client.apiUrl$kFineTune/$fineTuneId", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => FineTuneModel.fromJson(it), ); @@ -61,7 +61,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - "$kURL$kFineTune/$fineTuneId/cancel", + "$_client.apiUrl$kFineTune/$fineTuneId/cancel", {}, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => FineTuneModel.fromJson(it), @@ -74,7 +74,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.delete( - "$kURL$kFineTuneModel/$model", + "$_client.apiUrl$kFineTuneModel/$model", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => FineTuneDelete.fromJson(it), ); @@ -86,7 +86,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.getStream( - "$kURL$kFineTune/$fineTuneId/events", + "$_client.apiUrl$kFineTune/$fineTuneId/events", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { final data = it['data'] as List; @@ -108,7 +108,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - kURL + kFineTuneJob, + _client.apiUrl + kFineTuneJob, request.toJson(), onSuccess: FineTuneModelJob.fromJson, onCancel: (it) => onCancel != null ? onCancel(it) : null, @@ -121,7 +121,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.get( - kURL + kFineTuneJob + "/$fineTuneId", + _client.apiUrl + kFineTuneJob + "/$fineTuneId", onSuccess: FineTuneList.fromJson, onCancel: (it) => onCancel != null ? onCancel(it) : null, ); @@ -132,7 +132,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) async { return _client.get( - kURL + kFineTune, + _client.apiUrl + kFineTune, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { final data = it['data'] as List; @@ -148,7 +148,7 @@ class FineTuned { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - "$kURL$kFineTuneJob/$fineTuneId/cancel", + "$_client.apiUrl$kFineTuneJob/$fineTuneId/cancel", {}, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: FineTuneList.fromJson, @@ -172,7 +172,7 @@ class FineTuned { } return _client.getStream( - "$kURL$kFineTuneJob/$fineTuneId/events", + "$_client.apiUrl$kFineTuneJob/$fineTuneId/events", queryParameters: query, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { diff --git a/lib/src/moderation.dart b/lib/src/moderation.dart index c3e0086..0ffec5f 100644 --- a/lib/src/moderation.dart +++ b/lib/src/moderation.dart @@ -30,7 +30,7 @@ class Moderation { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - kURL + kModeration, + _client.apiUrl + kModeration, {"input": input, "model": model.model}, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => ModerationData.fromJson(it), diff --git a/lib/src/openai.dart b/lib/src/openai.dart index 4cd6f1d..3400a9f 100644 --- a/lib/src/openai.dart +++ b/lib/src/openai.dart @@ -57,6 +57,7 @@ class OpenAI implements IOpenAI { OpenAI build({ String? token, String? orgId, + String? apiUrl, HttpSetup? baseOption, bool enableLog = false, }) { @@ -86,7 +87,8 @@ class OpenAI implements IOpenAI { } dio.interceptors.add(InterceptorWrapper()); - _client = OpenAIClient(dio: dio, isLogging: enableLog); + final _apiUrl = (apiUrl?.isNotEmpty ?? false) ? apiUrl! : kURL; + _client = OpenAIClient(dio: dio, apiUrl: _apiUrl , isLogging: enableLog); return instance; } @@ -97,7 +99,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) async { return _client.get( - "$kURL$kModelList", + "${_client.apiUrl}$kModelList", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => OpenAiModel.fromJson(it), ); @@ -109,7 +111,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) { return _client.get( - "$kURL$kEngineList", + "${_client.apiUrl}$kEngineList", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { return EngineModel.fromJson(it); @@ -129,7 +131,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) => _client.post( - "$kURL$kCompletion", + "${_client.apiUrl}$kCompletion", request.toJson(), onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { @@ -145,7 +147,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) { return _client.post( - "$kURL$kChatGptTurbo", + "${_client.apiUrl}$kChatGptTurbo", request.toJson(), onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { @@ -161,7 +163,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) async { return _client.post( - "$kURL$kGenerateImage", + "${_client.apiUrl}$kGenerateImage", request.toJson(), onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) { @@ -179,7 +181,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) { return _client.sse( - "$kURL$kChatGptTurbo", + "${_client.apiUrl}$kChatGptTurbo", request.toJson()..addAll({"stream": true}), onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) { @@ -200,7 +202,7 @@ class OpenAI implements IOpenAI { void Function(CancelData cancelData)? onCancel, }) { return _client.sse( - '$kURL$kCompletion', + '${_client.apiUrl}$kCompletion', request.toJson()..addAll({"stream": true}), onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) { diff --git a/lib/src/openai_file.dart b/lib/src/openai_file.dart index 37066f8..c0a6292 100644 --- a/lib/src/openai_file.dart +++ b/lib/src/openai_file.dart @@ -14,7 +14,7 @@ class OpenAIFile { ///Returns a list of files that belong to the user's organization. Future get({void Function(CancelData cancelData)? onCancel}) { return _client.get( - kURL + kFile, + _client.apiUrl + kFile, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => FileResponse.fromJson(it), ); @@ -32,7 +32,7 @@ class OpenAIFile { final mRequest = await request.getForm(); return _client.postFormData( - kURL + kFile, + _client.apiUrl + kFile, mRequest, onCancel: (it) => onCancel != null ? onCancel(it) : null, complete: (it) => UploadResponse.fromJson(it), @@ -45,7 +45,7 @@ class OpenAIFile { void Function(CancelData cancelData)? onCancel, }) { return _client.delete( - "$kURL$kFile/{$fileId}", + "${_client.apiUrl}$kFile/{$fileId}", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => DeleteFile.fromJson(it), ); @@ -57,7 +57,7 @@ class OpenAIFile { void Function(CancelData cancelData)? onCancel, }) { return _client.get( - "$kURL$kFile/$fileId", + "${_client.apiUrl}$kFile/$fileId", onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => UploadResponse.fromJson(it), ); @@ -69,7 +69,7 @@ class OpenAIFile { void Function(CancelData cancelData)? onCancel, }) { return _client.get( - '$kURL$kFile/$fileId/content', + '${_client.apiUrl}$kFile/$fileId/content', returnRawData: true, onCancel: (it) => onCancel != null ? onCancel(it) : null, onSuccess: (it) => it, diff --git a/test/client/openai_wrapper_test.mocks.dart b/test/client/openai_wrapper_test.mocks.dart index 5edd699..bd04e30 100644 --- a/test/client/openai_wrapper_test.mocks.dart +++ b/test/client/openai_wrapper_test.mocks.dart @@ -80,6 +80,14 @@ class MockOpenAIClient extends _i1.Mock implements _i5.OpenAIClient { ), returnValueForMissingStub: null, ); + + @override + String get apiUrl => (super.noSuchMethod( + Invocation.getter(#apiUrl), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override _i3.Future get( String? url, { diff --git a/test/openai_test.mocks.dart b/test/openai_test.mocks.dart index 00c5c61..f79192f 100644 --- a/test/openai_test.mocks.dart +++ b/test/openai_test.mocks.dart @@ -401,10 +401,12 @@ class MockOpenAI extends _i1.Mock implements _i8.OpenAI { ), returnValueForMissingStub: null, ); + @override _i8.OpenAI build({ String? token, String? orgId, + String? apiUrl, _i23.HttpSetup? baseOption, bool? enableLog = false, }) => @@ -415,6 +417,7 @@ class MockOpenAI extends _i1.Mock implements _i8.OpenAI { { #token: token, #orgId: orgId, + #apiUrl: apiUrl, #baseOption: baseOption, #enableLog: enableLog, }, @@ -427,6 +430,7 @@ class MockOpenAI extends _i1.Mock implements _i8.OpenAI { { #token: token, #orgId: orgId, + #apiUrl: apiUrl, #baseOption: baseOption, #enableLog: enableLog, }, @@ -440,12 +444,14 @@ class MockOpenAI extends _i1.Mock implements _i8.OpenAI { { #token: token, #orgId: orgId, + #apiUrl: apiUrl, #baseOption: baseOption, #enableLog: enableLog, }, ), ), ) as _i8.OpenAI); + @override _i24.Future<_i9.OpenAiModel> listModel( {void Function(_i25.CancelData)? onCancel}) => From 356206bccd28fcad0b4f1a707c67f1700bae9093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90inh=20Va=CC=86n=20Nam?= Date: Tue, 21 Nov 2023 23:59:58 +0700 Subject: [PATCH 2/2] Fix sse bad state close --- lib/src/client/openai_client.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/client/openai_client.dart b/lib/src/client/openai_client.dart index 1131409..f692441 100644 --- a/lib/src/client/openai_client.dart +++ b/lib/src/client/openai_client.dart @@ -273,6 +273,8 @@ class OpenAIClient extends OpenAIWrapper { final mData = data.substring(6); if (mData.startsWith("[DONE]")) { log.log("stream response is done"); + controller.done; + controller.close(); return; } @@ -283,8 +285,6 @@ class OpenAIClient extends OpenAIWrapper { controller ..sink ..add(complete(jsonMap[jsonMap.keys.last])); - - controller.close(); } else { log.log("stream response invalid try regenerate"); log.log("last json error :$mData");