[ Edit, Add ] edited and updated docs of chat model directory files code, added more docs for missing members, minor non-breaking changes for the internal functionality and adaptation for message content items..

This commit is contained in:
Anas Fikhi
2023-11-22 01:52:28 +01:00
parent d044908b14
commit 37f613abaf
13 changed files with 233 additions and 82 deletions

View File

@ -51,7 +51,7 @@ void main() async {
final message = chat.choices.first.message;
// Wether the message has a tool call.
if (message.hasToolCalls) {
if (message.haveToolCalls) {
final call = message.toolCalls!.first;
// Wether the tool call is the one we sent.

View File

@ -1,6 +1,6 @@
import 'package:meta/meta.dart';
/// {@template openai_audio}
/// {@template openai_audio_model}
/// This class represents the audio model of the OpenAI API, which is used and get returned while using the [OpenAIAudio] methods.
/// {@endtemplate}
@immutable
@ -12,8 +12,10 @@ final class OpenAIAudioModel {
@override
int get hashCode => text.hashCode;
/// {@macro openai_audio}
const OpenAIAudioModel({required this.text});
/// {@macro openai_audio_model}
const OpenAIAudioModel({
required this.text,
});
/// This is used to convert a [Map<String, dynamic>] object to a [OpenAIAudioModel] object.
factory OpenAIAudioModel.fromMap(Map<String, dynamic> json) {

View File

@ -8,15 +8,15 @@ export 'sub_models/usage.dart';
export 'sub_models/choices/choices.dart';
export 'stream/chat.dart';
/// {@template openai_chat_completion}
/// {@template openai_chat_completion_model}
/// This class represents the chat completion response model of the OpenAI API, which is used and get returned while using the [OpenAIChat] methods.
/// {@endtemplate}
@immutable
final class OpenAIChatCompletionModel {
/// The [id] of the chat completion.
/// The [id]entifier of the chat completion.
final String id;
/// The date and time when the chat completion is [created].
/// The date and time when the chat completion was [created].
final DateTime created;
/// The [choices] of the chat completion.
@ -31,12 +31,19 @@ final class OpenAIChatCompletionModel {
/// Weither the chat completion have at least one choice in [choices].
bool get haveChoices => choices.isNotEmpty;
/// Weither the chat completion have system fingerprint.
bool get haveSystemFingerprint => systemFingerprint != null;
@override
int get hashCode {
return id.hashCode ^ created.hashCode ^ choices.hashCode ^ usage.hashCode;
return id.hashCode ^
created.hashCode ^
choices.hashCode ^
usage.hashCode ^
systemFingerprint.hashCode;
}
/// {@macro openai_chat_completion}
/// {@macro openai_chat_completion_model}
const OpenAIChatCompletionModel({
required this.id,
required this.created,
@ -51,7 +58,7 @@ final class OpenAIChatCompletionModel {
id: json['id'],
created: DateTime.fromMillisecondsSinceEpoch(json['created'] * 1000),
choices: (json['choices'] as List)
.map((e) => OpenAIChatCompletionChoiceModel.fromMap(e))
.map((choice) => OpenAIChatCompletionChoiceModel.fromMap(choice))
.toList(),
usage: OpenAIChatCompletionUsageModel.fromMap(json['usage']),
systemFingerprint: json['system_fingerprint'],

View File

@ -0,0 +1,46 @@
import '../chat.dart';
/// This is a mixin class that contains helper function(s) to adapt old text-based content to the new implementation of the content that accepts a list of content types like images.
mixin class OpenAIMessageDynamicContentFromFieldAdapter {
/// This is a helper function to adapt old text-based content to the new implementation of the content that accepts a list of content types like images..
static List<OpenAIChatCompletionChoiceMessageContentItemModel>
dynamicContentFromField(
fieldData,
) {
if (fieldData is String) {
return _singleItemListFrom(fieldData);
} else if (fieldData is List) {
return _listOfContentItemsFrom(fieldData);
} else {
throw Exception(
'Invalid content type, nor text or list, please report this issue.',
);
}
}
static List<OpenAIChatCompletionChoiceMessageContentItemModel>
_singleItemListFrom(String directTextContent) {
return [
OpenAIChatCompletionChoiceMessageContentItemModel.text(
directTextContent,
),
];
}
static List<OpenAIChatCompletionChoiceMessageContentItemModel>
_listOfContentItemsFrom(List listOfContentsItems) {
return (listOfContentsItems).map(
(item) {
if (item is! Map) {
throw Exception('Invalid content item, please report this issue.');
} else {
final asMap = item as Map<String, dynamic>;
return OpenAIChatCompletionChoiceMessageContentItemModel.fromMap(
asMap,
);
}
},
).toList();
}
}

View File

@ -24,9 +24,15 @@ final class OpenAIStreamChatCompletionModel {
/// Wether the chat completion have at least one choice in [choices].
bool get haveChoices => choices.isNotEmpty;
/// Wether the chat completion have a [systemFingerprint] or not.
bool get haveSystemFingerprint => systemFingerprint != null;
@override
int get hashCode {
return id.hashCode ^ created.hashCode ^ choices.hashCode;
return id.hashCode ^
created.hashCode ^
choices.hashCode ^
systemFingerprint.hashCode;
}
/// {@macro openai_stream_chat_completion}
@ -52,6 +58,8 @@ final class OpenAIStreamChatCompletionModel {
);
}
//! This don't need toMap()?
@override
String toString() {
return 'OpenAIStreamChatCompletionModel(id: $id, created: $created, choices: $choices, systemFingerprint: $systemFingerprint)';

View File

@ -14,6 +14,9 @@ final class OpenAIStreamChatCompletionChoiceModel {
/// The [finishReason] of the choice.
final String? finishReason;
/// Weither the choice have a finish reason or not.
bool get hasFinishReason => finishReason != null;
@override
int get hashCode {
return index.hashCode ^ delta.hashCode ^ finishReason.hashCode;

View File

@ -1,5 +1,6 @@
import 'package:dart_openai/dart_openai.dart';
import '../../../../etc/message_adapter.dart';
/// {@template openai_stream_chat_completion_choice_delta_model}
/// This contains the [role] and [content] of the choice of the chat response.
@ -11,13 +12,17 @@ final class OpenAIStreamChatCompletionChoiceDeltaModel {
/// The [content] of the message.
final List<OpenAIChatCompletionChoiceMessageContentItemModel>? content;
//
/// The [toolCalls] of the message.
final List<OpenAIResponseToolCall>? toolCalls;
// /// The function that the model is requesting to call.
// final StreamFunctionCallResponse? functionCall;
/// Weither the message have a role or not.
bool get haveToolCalls => toolCalls != null;
bool get hasToolCalls => toolCalls != null;
/// Weither the message have a role or not.
bool get haveRole => role != null;
/// Weither the message have a content or not.
bool get haveContent => content != null;
@override
int get hashCode {
@ -41,7 +46,8 @@ final class OpenAIStreamChatCompletionChoiceDeltaModel {
.firstWhere((role) => role.name == json['role'])
: null,
content: json['content'] != null
? dynamicContentFromField(json['content'])
? OpenAIMessageDynamicContentFromFieldAdapter.dynamicContentFromField(
json['content'])
: null,
toolCalls: json['tool_calls'] != null
? (json['tool_calls'] as List)
@ -83,31 +89,4 @@ final class OpenAIStreamChatCompletionChoiceDeltaModel {
other.content == content &&
other.toolCalls == toolCalls;
}
static List<OpenAIChatCompletionChoiceMessageContentItemModel>
dynamicContentFromField(
dynamic fieldData,
) {
if (fieldData is String) {
return [
OpenAIChatCompletionChoiceMessageContentItemModel.text(fieldData),
];
} else if (fieldData is List) {
return (fieldData).map(
(item) {
if (item is! Map) {
throw Exception('Invalid content item');
} else {
final asMap = item as Map<String, dynamic>;
return OpenAIChatCompletionChoiceMessageContentItemModel.fromMap(
asMap,
);
}
},
).toList();
} else {
throw Exception('Invalid content');
}
}
}

View File

@ -5,6 +5,8 @@ import 'sub_models/message.dart';
/// {@endtemplate}
final class OpenAIChatCompletionChoiceModel {
/// The [index] of the choice.
//! This is dynamic because the API sometimes returns a [String] and sometimes an [int].
final index;
/// The [message] of the choice.
@ -13,6 +15,9 @@ final class OpenAIChatCompletionChoiceModel {
/// The [finishReason] of the choice.
final String? finishReason;
/// Weither the choice have a finish reason.
bool get haveFinishReason => finishReason != null;
@override
int get hashCode {
return index.hashCode ^ message.hashCode ^ finishReason.hashCode;
@ -28,6 +33,7 @@ final class OpenAIChatCompletionChoiceModel {
/// This is used to convert a [Map<String, dynamic>] object to a [OpenAIChatCompletionChoiceModel] object.
factory OpenAIChatCompletionChoiceModel.fromMap(Map<String, dynamic> json) {
return OpenAIChatCompletionChoiceModel(
//! Here we use the [int.tryParse] method to convert the [String] to an [int] if it's possible, otherwise we use the [String] value.
index: json['index'] is int
? json['index']
: int.tryParse(json['index'].toString()) ?? json['index'],

View File

@ -1,5 +1,6 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import '../../../../image/enum.dart';
import '../../../etc/message_adapter.dart';
import 'sub_models/content.dart';
import 'sub_models/tool_call.dart';
export 'sub_models/content.dart';
@ -12,15 +13,17 @@ final class OpenAIChatCompletionChoiceMessageModel {
/// The [role] of the message.
final OpenAIChatMessageRole role;
//! TODO: add the possibility to include images in the message content, see docs and do a non-breaking change.
/// The [content] of the message.
final List<OpenAIChatCompletionChoiceMessageContentItemModel>? content;
/// The function that the model is requesting to call.
final List<OpenAIResponseToolCall>? toolCalls;
bool get hasToolCalls => toolCalls != null;
/// Weither the message have tool calls.
bool get haveToolCalls => toolCalls != null;
/// Weither the message have content.
bool get haveContent => content != null && content!.isNotEmpty;
@override
int get hashCode {
@ -42,7 +45,9 @@ final class OpenAIChatCompletionChoiceMessageModel {
role: OpenAIChatMessageRole.values
.firstWhere((role) => role.name == json['role']),
content: json['content'] != null
? dynamicContentFromField(json['content'])
? OpenAIMessageDynamicContentFromFieldAdapter.dynamicContentFromField(
json['content'],
)
: null,
toolCalls: json['tool_calls'] != null
? (json['tool_calls'] as List)
@ -86,8 +91,9 @@ final class OpenAIChatCompletionChoiceMessageModel {
other.toolCalls == toolCalls;
}
//! TODO: make method for all other kind of resending messages.
/// Converts a response function message to a request function message, so that it can be used in the next request.
///
/// You should pass the response function message's [toolCallId] to this method, since it is required when requesting it.
RequestFunctionMessage asRequestFunctionMessage({
required String toolCallId,
}) {
@ -97,39 +103,17 @@ final class OpenAIChatCompletionChoiceMessageModel {
toolCallId: toolCallId,
);
}
static List<OpenAIChatCompletionChoiceMessageContentItemModel>
dynamicContentFromField(
fieldData,
) {
if (fieldData is String) {
return [
OpenAIChatCompletionChoiceMessageContentItemModel.text(fieldData),
];
} else if (fieldData is List) {
return (fieldData).map(
(item) {
if (item is! Map) {
throw Exception('Invalid content item');
} else {
final asMap = item as Map<String, dynamic>;
return OpenAIChatCompletionChoiceMessageContentItemModel.fromMap(
asMap,
);
}
},
).toList();
} else {
throw Exception('Invalid content');
}
}
}
/// {@template openai_chat_completion_function_choice_message_model}
/// This represents the message of the [RequestFunctionMessage] model of the OpenAI API, which is used while using the [OpenAIChat] methods, precisely to send a response function message as a request function message for next requests.
/// {@endtemplate}
base class RequestFunctionMessage
extends OpenAIChatCompletionChoiceMessageModel {
/// The [toolCallId] of the message.
final String toolCallId;
/// {@macro openai_chat_completion_function_choice_message_model}
RequestFunctionMessage({
required super.role,
required super.content,
@ -144,4 +128,6 @@ base class RequestFunctionMessage
"tool_call_id": toolCallId,
};
}
//! Does this needs fromMap method?
}

View File

@ -1,39 +1,57 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
/// {@template openai_chat_completion_choice_message_content_item_model}
/// This represents the content item of the [OpenAIChatCompletionChoiceMessageModel] model of the OpenAI API, which is used in the [OpenAIChat] methods.
/// {@endtemplate}
class OpenAIChatCompletionChoiceMessageContentItemModel {
/// The type of the content item.
final String type;
/// The text content of the item.
final String? text;
/// The image url content of the item.
final String? imageUrl;
OpenAIChatCompletionChoiceMessageContentItemModel({
@override
int get hashCode => type.hashCode ^ text.hashCode ^ imageUrl.hashCode;
/// {@macro openai_chat_completion_choice_message_content_item_model}
OpenAIChatCompletionChoiceMessageContentItemModel._({
required this.type,
this.text,
this.imageUrl,
});
/// This is used to convert a [Map<String, dynamic>] object to a [OpenAIChatCompletionChoiceMessageContentItemModel] object.
factory OpenAIChatCompletionChoiceMessageContentItemModel.fromMap(
Map<String, dynamic> asMap,
) {
return OpenAIChatCompletionChoiceMessageContentItemModel(
return OpenAIChatCompletionChoiceMessageContentItemModel._(
type: asMap['type'],
text: asMap['text'],
imageUrl: asMap['image_url'],
);
}
/// Represents a text content item factory, which is used to create a text [OpenAIChatCompletionChoiceMessageContentItemModel].
factory OpenAIChatCompletionChoiceMessageContentItemModel.text(String text) {
return OpenAIChatCompletionChoiceMessageContentItemModel(
return OpenAIChatCompletionChoiceMessageContentItemModel._(
type: 'text',
text: text,
);
}
/// Represents a image content item factory, which is used to create a image [OpenAIChatCompletionChoiceMessageContentItemModel].
factory OpenAIChatCompletionChoiceMessageContentItemModel.imageUrl(
String imageUrl,
) {
return OpenAIChatCompletionChoiceMessageContentItemModel(
return OpenAIChatCompletionChoiceMessageContentItemModel._(
type: 'image',
imageUrl: imageUrl,
);
}
/// This method used to convert the [OpenAIChatCompletionChoiceMessageContentItemModel] to a [Map<String, dynamic>] object.
Map<String, dynamic> toMap() {
return {
"type": type,
@ -41,4 +59,21 @@ class OpenAIChatCompletionChoiceMessageContentItemModel {
if (imageUrl != null) "image_url": imageUrl,
};
}
@override
bool operator ==(
covariant OpenAIChatCompletionChoiceMessageContentItemModel other,
) {
if (identical(this, other)) return true;
return other.type == type &&
other.text == text &&
other.imageUrl == imageUrl;
}
//! TODO: make a dynamic toString method for different types of content items.
@override
String toString() {
return 'OpenAIChatCompletionChoiceMessageContentItemModel(type: $type, text: $text, imageUrl: $imageUrl)';
}
}

View File

@ -1,13 +1,32 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
/// {@template openai_chat_completion_response_function_model}
/// This represents the response function of the [OpenAIChatCompletionChoiceMessageModel] model of the OpenAI API, which is used in the [OpenAIChat] methods.
/// {@endtemplate}
class OpenAIResponseFunction {
/// The name of the function.
final String? name;
/// The arguments of the function.
final arguments;
//! Not sure if the arguments will always be a Map<String, dynamic>, if you do confirm it from OpenAI docs please open an issue.
/// Weither the function have a name or not.
bool get hasName => name != null;
/// Weither the function have arguments or not.
bool get hasArguments => arguments != null;
@override
int get hashCode => name.hashCode ^ arguments.hashCode;
/// {@macro openai_chat_completion_response_function_model}
OpenAIResponseFunction({
required this.name,
required this.arguments,
});
/// This method used to convert a [Map<String, dynamic>] object to a [OpenAIResponseFunction] object.
factory OpenAIResponseFunction.fromMap(Map<String, dynamic> map) {
return OpenAIResponseFunction(
name: map['name'],
@ -15,10 +34,22 @@ class OpenAIResponseFunction {
);
}
/// This method used to convert the [OpenAIResponseFunction] to a [Map<String, dynamic>] object.
Map<String, dynamic> toMap() {
return {
"name": name,
"arguments": arguments,
};
}
@override
String toString() =>
'OpenAIResponseFunction(name: $name, arguments: $arguments)';
@override
bool operator ==(covariant OpenAIResponseFunction other) {
if (identical(this, other)) return true;
return other.name == name && other.arguments == arguments;
}
}

View File

@ -2,17 +2,36 @@
import 'sub_models/response_function_call.dart';
/// {@template openai_chat_completion_response_tool_call_model}
/// This represents the tool call of the [OpenAIChatCompletionChoiceMessageModel] model of the OpenAI API, which is used and get returned while using the [OpenAIChat] methods.
/// {@endtemplate}
class OpenAIResponseToolCall {
/// The id of the tool call.
final String? id;
/// The type of the tool call.
final String? type;
/// The function of the tool call.
final OpenAIResponseFunction function;
/// Weither the tool call have an id.
bool get haveId => id != null;
/// Weither the tool call have a type.
bool get haveType => type != null;
@override
int get hashCode => id.hashCode ^ type.hashCode ^ function.hashCode;
/// {@macro openai_chat_completion_response_tool_call_model}
OpenAIResponseToolCall({
required this.id,
required this.type,
required this.function,
});
/// This is used to convert a [Map<String, dynamic>] object to a [OpenAIResponseToolCall] object.
factory OpenAIResponseToolCall.fromMap(Map<String, dynamic> map) {
return OpenAIResponseToolCall(
id: map['id'],
@ -21,6 +40,7 @@ class OpenAIResponseToolCall {
);
}
/// This method used to convert the [OpenAIResponseToolCall] to a [Map<String, dynamic>] object.
Map<String, dynamic> toMap() {
return {
"id": id,
@ -33,11 +53,27 @@ class OpenAIResponseToolCall {
String toString() {
return "OpenAIResponseToolCall(id: $id,type: $type,function: $function)";
}
@override
bool operator ==(covariant OpenAIResponseToolCall other) {
if (identical(this, other)) return true;
return other.id == id && other.type == type && other.function == function;
}
}
/// {@template openai_chat_completion_response_stream_tool_call_model}
/// This represents the stream tool call of the [OpenAIChatCompletionChoiceMessageModel] model of the OpenAI API, which is used and get returned while using the [OpenAIChat] methods.
/// {@endtemplate}
class OpenAIStreamResponseToolCall extends OpenAIResponseToolCall {
/// The index of the tool call.
//! please fill an issue if it happen that the index is not an int in some cases.
final int index;
@override
int get hashCode => super.hashCode ^ index.hashCode;
/// {@macro openai_chat_completion_response_stream_tool_call_model}
OpenAIStreamResponseToolCall({
required super.id,
required super.type,
@ -45,6 +81,7 @@ class OpenAIStreamResponseToolCall extends OpenAIResponseToolCall {
required this.index,
});
/// This is used to convert a [Map<String, dynamic>] object to a [OpenAIStreamResponseToolCall] object.
factory OpenAIStreamResponseToolCall.fromMap(Map<String, dynamic> map) {
return OpenAIStreamResponseToolCall(
id: map['id'],
@ -54,6 +91,7 @@ class OpenAIStreamResponseToolCall extends OpenAIResponseToolCall {
);
}
/// This method used to convert the [OpenAIStreamResponseToolCall] to a [Map<String, dynamic>] object.
Map<String, dynamic> toMap() {
return {
"id": id,
@ -62,4 +100,14 @@ class OpenAIStreamResponseToolCall extends OpenAIResponseToolCall {
"index": index,
};
}
@override
bool operator ==(covariant OpenAIStreamResponseToolCall other) {
if (identical(this, other)) return true;
return other.index == index;
}
@override
String toString() => 'OpenAIStreamResponseToolCall(index: $index})';
}

View File

@ -1,6 +1,6 @@
export 'choices/sub_models/message.dart';
/// {@template openai_chat_completion_usage}
/// {@template openai_chat_completion_usage_model}
/// This class represents the chat completion usage model of the OpenAI API, which is used and get returned while using the [OpenAIChat] methods.
/// {@endtemplate}
final class OpenAIChatCompletionUsageModel {
@ -21,7 +21,7 @@ final class OpenAIChatCompletionUsageModel {
totalTokens.hashCode;
}
/// {@macro openai_chat_completion_usage}
/// {@macro openai_chat_completion_usage_model}
const OpenAIChatCompletionUsageModel({
required this.promptTokens,
required this.completionTokens,