Skip to content

Commit f0d24ca

Browse files
committed
Fix regressions and document resolved issues
1 parent 93d3e09 commit f0d24ca

6 files changed

Lines changed: 306 additions & 53 deletions

File tree

‎README.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,14 @@ File speechFile = await OpenAI.instance.audio.createSpeech(
240240
outputFileName: "output_speech.mp3",
241241
);
242242
243+
// Create speech as bytes (useful on web or when you do not want a temp file)
244+
Uint8List speechBytes = await OpenAI.instance.audio.createSpeechBytes(
245+
model: "tts-1",
246+
input: "Text to convert to speech",
247+
voice: OpenAIAudioVoice.fable,
248+
responseFormat: OpenAIAudioSpeechResponseFormat.mp3,
249+
);
250+
243251
244252
// Transcribe audio
245253
OpenAITranscriptionGeneralModel transcription = await OpenAI.instance.audio.createTranscription(

‎RESOLVED_ISSUES.md‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Resolved Issues
2+
3+
## #223 - a bug when api service returns
4+
5+
Problem:
6+
- The networking client assumed the `error` field in a failed response was always a `Map<String, dynamic>`.
7+
- When an API-compatible backend returned a payload like `{ "error": "Not Found", ... }`, the SDK threw a Dart type error instead of a `RequestFailedException`.
8+
9+
Changes:
10+
- Added `requestFailedExceptionFromMap(...)` in [lib/src/core/networking/client.dart](./lib/src/core/networking/client.dart) to normalize error payload parsing.
11+
- Updated request error handling in `get`, `postAndGetResponse`, `post`, `imageEditForm`, `imageVariationForm`, `fileUpload`, and `delete` to use the shared helper instead of directly casting `error` to a map.
12+
- Added a regression test covering string-based error payloads in [test/regressions_test.dart](./test/regressions_test.dart).
13+
14+
## #190 - why createStream silently ignored the potenital json decode error ?
15+
16+
Problem:
17+
- The stream client swallowed JSON decode failures while accumulating response data.
18+
- Non-JSON error bodies in streamed failures could be ignored instead of being surfaced as a proper exception.
19+
20+
Changes:
21+
- Updated `postStream(...)` in [lib/src/core/networking/client.dart](./lib/src/core/networking/client.dart) to stop swallowing terminal error responses.
22+
- Added `requestFailedExceptionFromRawBody(...)` to convert non-JSON raw response bodies into a `RequestFailedException`.
23+
- Added logic to emit a stream error when the server returns an error status with a non-empty raw body and no valid stream events were emitted.
24+
- Added regression tests for raw non-JSON stream failures in [test/regressions_test.dart](./test/regressions_test.dart).
25+
26+
## #172 - OpenAIChatCompletionChoiceMessageContentItemModel.imageUrl Not working properly
27+
28+
Problem:
29+
- The chat content model stored `imageUrl` as a map, but `toMap()` wrapped it again.
30+
- That produced a nested `image_url` payload shape that did not match the API contract.
31+
32+
Changes:
33+
- Fixed `OpenAIChatCompletionChoiceMessageContentItemModel.toMap()` in [lib/src/core/models/chat/sub_models/choices/sub_models/sub_models/content.dart](./lib/src/core/models/chat/sub_models/choices/sub_models/sub_models/content.dart) so it writes the existing `imageUrl` object directly.
34+
- Added a regression test verifying the correct serialized `image_url` shape in [test/regressions_test.dart](./test/regressions_test.dart).
35+
36+
## #185 - Audio | Create Speech | return file bytes (Uint8List) for work on web and improve speed
37+
38+
Problem:
39+
- The issue requested a byte-returning API for speech generation instead of always writing a file.
40+
41+
Changes:
42+
- Confirmed the SDK already exposes `createSpeechBytes(...)` in [lib/src/instance/audio/audio.dart](./lib/src/instance/audio/audio.dart).
43+
- Documented `createSpeechBytes(...)` in [README.md](./README.md) so the in-memory byte API is visible in the public usage docs.

‎example/pubspec.lock‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ packages:
151151
path: ".."
152152
relative: true
153153
source: path
154-
version: "6.0.0"
154+
version: "6.1.1"
155155
dart_style:
156156
dependency: transitive
157157
description:

‎lib/src/core/models/chat/sub_models/choices/sub_models/sub_models/content.dart‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class OpenAIChatCompletionChoiceMessageContentItemModel {
7171
return {
7272
"type": type,
7373
if (text != null) "text": text,
74-
if (imageUrl != null) "image_url": { "url": imageUrl },
74+
if (imageUrl != null) "image_url": imageUrl,
7575
if (imageBase64 != null)
7676
"image_url": {"url": "data:image/jpeg;base64,${imageBase64}"},
7777
};

‎lib/src/core/networking/client.dart‎

Lines changed: 97 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,10 @@ abstract class OpenAINetworkingClient {
121121
OpenAILogger.decodedSuccessfully();
122122

123123
if (doesErrorExists(decodedBody)) {
124-
final Map<String, dynamic> error =
125-
decodedBody[OpenAIStrings.errorFieldKey];
126-
final message = error[OpenAIStrings.messageFieldKey];
127-
final statusCode = response.statusCode;
128-
129-
final exception = RequestFailedException(message, statusCode);
124+
final exception = requestFailedExceptionFromMap(
125+
decodedBody,
126+
response.statusCode,
127+
);
130128
OpenAILogger.errorOcurred(exception);
131129

132130
throw exception;
@@ -279,14 +277,10 @@ abstract class OpenAINetworkingClient {
279277

280278
if (doesErrorExists(decodedBody)) {
281279
OpenAILogger.errorFoundInRequest();
282-
283-
final error = decodedBody[OpenAIStrings.errorFieldKey];
284-
285-
final message = error[OpenAIStrings.messageFieldKey];
286-
287-
final statusCode = response.statusCode;
288-
289-
final exception = RequestFailedException(message, statusCode);
280+
final exception = requestFailedExceptionFromMap(
281+
decodedBody,
282+
response.statusCode,
283+
);
290284
OpenAILogger.errorOcurred(exception);
291285

292286
throw exception;
@@ -342,12 +336,10 @@ abstract class OpenAINetworkingClient {
342336
OpenAILogger.decodedSuccessfully();
343337

344338
if (doesErrorExists(decodedBody)) {
345-
final Map<String, dynamic> error =
346-
decodedBody[OpenAIStrings.errorFieldKey];
347-
final message = error[OpenAIStrings.messageFieldKey];
348-
final statusCode = response.statusCode;
349-
350-
final exception = RequestFailedException(message, statusCode);
339+
final exception = requestFailedExceptionFromMap(
340+
decodedBody,
341+
response.statusCode,
342+
);
351343
OpenAILogger.errorOcurred(exception);
352344

353345
throw exception;
@@ -387,6 +379,7 @@ abstract class OpenAINetworkingClient {
387379

388380
try {
389381
String respondData = "";
382+
var emittedData = false;
390383
await for (final value
391384
in stream.where((event) => event.isNotEmpty)) {
392385
final data = value;
@@ -405,6 +398,7 @@ abstract class OpenAINetworkingClient {
405398
break;
406399
}
407400
final decoded = jsonDecode(data) as Map<String, dynamic>;
401+
emittedData = true;
408402
yield onSuccess(decoded);
409403
continue;
410404
}
@@ -415,19 +409,28 @@ abstract class OpenAINetworkingClient {
415409
} catch (error) {/** ignore, data has not been received */}
416410

417411
if (doesErrorExists(decodedData)) {
418-
final error = decodedData[OpenAIStrings.errorFieldKey]
419-
as Map<String, dynamic>;
420-
var message = error[OpenAIStrings.messageFieldKey] as String;
421-
message = message.isEmpty ? jsonEncode(error) : message;
422-
final statusCode = respond.statusCode;
423-
final exception = RequestFailedException(message, statusCode);
412+
final exception = requestFailedExceptionFromMap(
413+
decodedData,
414+
respond.statusCode,
415+
);
424416

425417
yield* Stream<T>.error(
426418
exception,
427419
); // Error cases sent from openai
420+
return;
428421
}
429422
}
430423
} // end of await for
424+
425+
if (!emittedData &&
426+
respond.statusCode >= HttpStatus.badRequest &&
427+
respondData.trim().isNotEmpty) {
428+
final exception = requestFailedExceptionFromRawBody(
429+
respondData,
430+
respond.statusCode,
431+
);
432+
yield* Stream<T>.error(exception);
433+
}
431434
} catch (error, stackTrace) {
432435
yield* Stream<T>.error(
433436
error,
@@ -498,13 +501,10 @@ abstract class OpenAINetworkingClient {
498501
OpenAILogger.decodedSuccessfully();
499502

500503
if (doesErrorExists(decodedBody)) {
501-
final Map<String, dynamic> error =
502-
decodedBody[OpenAIStrings.errorFieldKey];
503-
504-
final message = error[OpenAIStrings.messageFieldKey];
505-
final statusCode = response.statusCode;
506-
507-
final exception = RequestFailedException(message, statusCode);
504+
final exception = requestFailedExceptionFromMap(
505+
decodedBody,
506+
response.statusCode,
507+
);
508508
OpenAILogger.errorOcurred(exception);
509509

510510
throw exception;
@@ -551,12 +551,10 @@ abstract class OpenAINetworkingClient {
551551
OpenAILogger.decodedSuccessfully();
552552

553553
if (doesErrorExists(decodedBody)) {
554-
final Map<String, dynamic> error =
555-
decodedBody[OpenAIStrings.errorFieldKey];
556-
final message = error[OpenAIStrings.messageFieldKey];
557-
final statusCode = response.statusCode;
558-
559-
final exception = RequestFailedException(message, statusCode);
554+
final exception = requestFailedExceptionFromMap(
555+
decodedBody,
556+
response.statusCode,
557+
);
560558
OpenAILogger.errorOcurred(exception);
561559

562560
throw exception;
@@ -610,12 +608,10 @@ abstract class OpenAINetworkingClient {
610608

611609
OpenAILogger.decodedSuccessfully();
612610
if (doesErrorExists(resultBody)) {
613-
final Map<String, dynamic> error =
614-
resultBody[OpenAIStrings.errorFieldKey];
615-
final message = error[OpenAIStrings.messageFieldKey];
616-
final statusCode = response.statusCode;
617-
618-
final exception = RequestFailedException(message, statusCode);
611+
final exception = requestFailedExceptionFromMap(
612+
resultBody,
613+
response.statusCode,
614+
);
619615
OpenAILogger.errorOcurred(exception);
620616

621617
throw exception;
@@ -653,12 +649,10 @@ abstract class OpenAINetworkingClient {
653649
OpenAILogger.decodedSuccessfully();
654650

655651
if (doesErrorExists(decodedBody)) {
656-
final Map<String, dynamic> error =
657-
decodedBody[OpenAIStrings.errorFieldKey];
658-
final String message = error[OpenAIStrings.messageFieldKey];
659-
final statusCode = response.statusCode;
660-
661-
final exception = RequestFailedException(message, statusCode);
652+
final exception = requestFailedExceptionFromMap(
653+
decodedBody,
654+
response.statusCode,
655+
);
662656
OpenAILogger.errorOcurred(exception);
663657

664658
throw exception;
@@ -691,6 +685,58 @@ abstract class OpenAINetworkingClient {
691685
return decodedResponseBody[OpenAIStrings.errorFieldKey] != null;
692686
}
693687

688+
static RequestFailedException requestFailedExceptionFromMap(
689+
Map<String, dynamic> decodedBody,
690+
int statusCode,
691+
) {
692+
final error = decodedBody[OpenAIStrings.errorFieldKey];
693+
final fallbackMessage = jsonEncode(decodedBody);
694+
695+
if (error is Map<String, dynamic>) {
696+
final message = error[OpenAIStrings.messageFieldKey]?.toString();
697+
698+
return RequestFailedException(
699+
message == null || message.isEmpty ? jsonEncode(error) : message,
700+
statusCode,
701+
);
702+
}
703+
704+
if (error is String && error.isNotEmpty) {
705+
final path = decodedBody['path']?.toString();
706+
final responseStatus = decodedBody['status'] ?? statusCode;
707+
708+
return RequestFailedException(
709+
path == null ? error : '$error ($responseStatus @ $path)',
710+
statusCode,
711+
);
712+
}
713+
714+
return RequestFailedException(fallbackMessage, statusCode);
715+
}
716+
717+
static RequestFailedException requestFailedExceptionFromRawBody(
718+
String responseBody,
719+
int statusCode,
720+
) {
721+
final trimmedBody = responseBody.trim();
722+
723+
if (trimmedBody.isEmpty) {
724+
return RequestFailedException(
725+
'Request failed with status code $statusCode',
726+
statusCode,
727+
);
728+
}
729+
730+
try {
731+
final decodedBody = decodeToMap(trimmedBody);
732+
return doesErrorExists(decodedBody)
733+
? requestFailedExceptionFromMap(decodedBody, statusCode)
734+
: RequestFailedException(jsonEncode(decodedBody), statusCode);
735+
} on FormatException {
736+
return RequestFailedException(trimmedBody, statusCode);
737+
}
738+
}
739+
694740
static http.Client _streamingHttpClient() {
695741
return createClient();
696742
}

0 commit comments

Comments
 (0)