Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support multi-modal moderations in openai_dart #576

Merged
merged 1 commit into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 additions & 4 deletions packages/openai_dart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ print(res.deleted);

### Moderations

Given an input text, outputs if the model classifies it as violating OpenAI's content policy.
The moderations endpoint is a tool you can use to check whether text or images are potentially harmful. Once harmful content is identified, developers can take corrective action like filtering content or intervening with user accounts creating offending content. The moderation endpoint is free to use.

Related guide: [Moderations](https://platform.openai.com/docs/guides/moderation)

Expand All @@ -707,23 +707,48 @@ Related guide: [Moderations](https://platform.openai.com/docs/guides/moderation)
```dart
final res = await client.createModeration(
request: CreateModerationRequest(
model: ModerationModel.modelId('text-moderation-latest'),
model: ModerationModel.model(ModerationModels.omniModerationLatest),
input: ModerationInput.string('I want to kill them.'),
),
);
print(res.results.first.categories.violence);
// true
print(res.results.first.categoryScores.violence);
// 0.9925811290740967
// 0.9533573696456176
```

`ModerationModel` is a sealed class that offers two ways to specify the model:
- `ModerationModel.modelId('model-id')`: the model ID as string.
- `ModerationModel.model(ModerationModels.textModerationLatest)`: a value from `ModerationModels` enum which lists all of the available models.
- `ModerationModel.model(ModerationModels.omniModerationLatest)`: a value from `ModerationModels` enum which lists all the available models.

`ModerationInput` is a sealed class that offers four ways to specify the embedding input:
- `ModerationInput.string('input')`: the input as string.
- `ModerationInput.listString(['input'])`: batch of string inputs.
- `ModerationInput.listModerationInputObject([ModerationInputObject.imageUrl(...), ModerationInputObject.text(...)])`: batch of multi-modal inputs.

**Create multi-modal moderation:**

```dart
final res = await client.createModeration(
request: CreateModerationRequest(
model: ModerationModel.model(ModerationModels.omniModerationLatest),
input: ModerationInput.listModerationInputObject(
[
ModerationInputObject.imageUrl(
imageUrl: ModerationInputObjectImageUrlImageUrl(
url: 'https://upload.wikimedia.org/wikipedia/commons/9/92/95apple.jpeg',
),
),
ModerationInputObject.text(
text: 'I want to kill the apple.',
),
],
),
),
);
print(res.results.first.categories.violence);
// true
```

### Assistants (beta)

Expand Down
1 change: 1 addition & 0 deletions packages/openai_dart/example/openai_dart_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Future<void> _models(final OpenAIClient client) async {
Future<void> _moderations(final OpenAIClient client) async {
final res = await client.createModeration(
request: const CreateModerationRequest(
model: ModerationModel.model(ModerationModels.omniModerationLatest),
input: ModerationInput.string('I want to kill them.'),
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ class CreateModerationRequest with _$CreateModerationRequest {

/// Factory constructor for CreateModerationRequest
const factory CreateModerationRequest({
/// Two content moderations models are available: `text-moderation-stable` and `text-moderation-latest`.
///
/// The default is `text-moderation-latest` which will be automatically upgraded over time. This ensures you are always using our most accurate model. If you use `text-moderation-stable`, we will provide advanced notice before updating the model. Accuracy of `text-moderation-stable` may be slightly lower than for `text-moderation-latest`.
/// The content moderation model you would like to use. Learn more in
/// [the moderation guide](https://platform.openai.com/docs/guides/moderation), and learn about
/// available models [here](https://platform.openai.com//docs/models/moderation).
@_ModerationModelConverter()
@JsonKey(includeIfNull: false)
@Default(
ModerationModelString('text-moderation-latest'),
ModerationModelString('omni-moderation-latest'),
)
ModerationModel? model,

/// The input text to classify
/// Input (or inputs) to classify. Can be a single string, an array of strings, or
/// an array of multi-modal input objects similar to other models.
@_ModerationInputConverter() required ModerationInput input,
}) = _CreateModerationRequest;

Expand Down Expand Up @@ -56,6 +57,10 @@ class CreateModerationRequest with _$CreateModerationRequest {

/// Available moderation models. Mind that the list may not be exhaustive nor up-to-date.
enum ModerationModels {
@JsonValue('omni-moderation-latest')
omniModerationLatest,
@JsonValue('omni-moderation-2024-09-26')
omniModeration20240926,
@JsonValue('text-moderation-latest')
textModerationLatest,
@JsonValue('text-moderation-stable')
Expand All @@ -66,9 +71,9 @@ enum ModerationModels {
// CLASS: ModerationModel
// ==========================================

/// Two content moderations models are available: `text-moderation-stable` and `text-moderation-latest`.
///
/// The default is `text-moderation-latest` which will be automatically upgraded over time. This ensures you are always using our most accurate model. If you use `text-moderation-stable`, we will provide advanced notice before updating the model. Accuracy of `text-moderation-stable` may be slightly lower than for `text-moderation-latest`.
/// The content moderation model you would like to use. Learn more in
/// [the moderation guide](https://platform.openai.com/docs/guides/moderation), and learn about
/// available models [here](https://platform.openai.com//docs/models/moderation).
@freezed
sealed class ModerationModel with _$ModerationModel {
const ModerationModel._();
Expand Down Expand Up @@ -108,7 +113,7 @@ class _ModerationModelConverter
if (data is String) {
return ModerationModelString(data);
}
return ModerationModelString('text-moderation-latest');
return ModerationModelString('omni-moderation-latest');
}

@override
Expand All @@ -126,17 +131,23 @@ class _ModerationModelConverter
// CLASS: ModerationInput
// ==========================================

/// The input text to classify
/// Input (or inputs) to classify. Can be a single string, an array of strings, or
/// an array of multi-modal input objects similar to other models.
@freezed
sealed class ModerationInput with _$ModerationInput {
const ModerationInput._();

/// A list of string inputs.
/// An array of multi-modal inputs to the moderation model.
const factory ModerationInput.listModerationInputObject(
List<ModerationInputObject> value,
) = ModerationInputListModerationInputObject;

/// An array of strings to classify for moderation.
const factory ModerationInput.listString(
List<String> value,
) = ModerationInputListString;

/// A string input.
/// A string of text to classify for moderation.
const factory ModerationInput.string(
String value,
) = ModerationInputString;
Expand All @@ -153,6 +164,11 @@ class _ModerationInputConverter

@override
ModerationInput fromJson(Object? data) {
if (data is List && data.every((item) => item is Map)) {
return ModerationInputListModerationInputObject(data
.map((i) => ModerationInputObject.fromJson(i as Map<String, dynamic>))
.toList(growable: false));
}
if (data is List && data.every((item) => item is String)) {
return ModerationInputListString(data.cast());
}
Expand All @@ -167,6 +183,7 @@ class _ModerationInputConverter
@override
Object? toJson(ModerationInput data) {
return switch (data) {
ModerationInputListModerationInputObject(value: final v) => v,
ModerationInputListString(value: final v) => v,
ModerationInputString(value: final v) => v,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class Moderation with _$Moderation {
/// A list of the categories along with their scores as predicted by model.
@JsonKey(name: 'category_scores')
required ModerationCategoriesScores categoryScores,

/// A list of the categories along with the input type(s) that the score applies to.
@JsonKey(name: 'category_applied_input_types')
required ModerationCategoriesAppliedInputTypes categoryAppliedInputTypes,
}) = _Moderation;

/// Object construction from a JSON representation
Expand All @@ -34,7 +38,8 @@ class Moderation with _$Moderation {
static const List<String> propertyNames = [
'flagged',
'categories',
'category_scores'
'category_scores',
'category_applied_input_types'
];

/// Perform validations on the schema property values
Expand All @@ -48,6 +53,7 @@ class Moderation with _$Moderation {
'flagged': flagged,
'categories': categories,
'category_scores': categoryScores,
'category_applied_input_types': categoryAppliedInputTypes,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class ModerationCategories with _$ModerationCategories {
@JsonKey(name: 'harassment/threatening')
required bool harassmentThreatening,

/// Content that includes instructions or advice that facilitate the planning or execution of wrongdoing, or that gives advice or instruction on how to commit illicit acts. For example, "how to shoplift" would fit this category.
required bool illicit,

/// Content that includes instructions or advice that facilitate the planning or execution of wrongdoing that also includes violence, or that gives advice or instruction on the procurement of any weapon.
@JsonKey(name: 'illicit/violent') required bool illicitViolent,

/// Content that promotes, encourages, or depicts acts of self-harm, such as suicide, cutting, and eating disorders.
@JsonKey(name: 'self-harm') required bool selfHarm,

Expand Down Expand Up @@ -60,6 +66,8 @@ class ModerationCategories with _$ModerationCategories {
'hate/threatening',
'harassment',
'harassment/threatening',
'illicit',
'illicit/violent',
'self-harm',
'self-harm/intent',
'self-harm/instructions',
Expand All @@ -81,6 +89,8 @@ class ModerationCategories with _$ModerationCategories {
'hate/threatening': hateThreatening,
'harassment': harassment,
'harassment/threatening': harassmentThreatening,
'illicit': illicit,
'illicit/violent': illicitViolent,
'self-harm': selfHarm,
'self-harm/intent': selfHarmIntent,
'self-harm/instructions': selfHarmInstructions,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: invalid_annotation_target
part of open_a_i_schema;

// ==========================================
// CLASS: ModerationCategoriesAppliedInputTypes
// ==========================================

/// A list of the categories along with the input type(s) that the score applies to.
@freezed
class ModerationCategoriesAppliedInputTypes
with _$ModerationCategoriesAppliedInputTypes {
const ModerationCategoriesAppliedInputTypes._();

/// Factory constructor for ModerationCategoriesAppliedInputTypes
const factory ModerationCategoriesAppliedInputTypes({
/// The applied input type(s) for the category 'hate'.
required List<String> hate,

/// The applied input type(s) for the category 'hate/threatening'.
@JsonKey(name: 'hate/threatening') required List<String> hateThreatening,

/// The applied input type(s) for the category 'harassment'.
required List<String> harassment,

/// The applied input type(s) for the category 'harassment/threatening'.
@JsonKey(name: 'harassment/threatening')
required List<String> harassmentThreatening,

/// The applied input type(s) for the category 'illicit'.
required List<String> illicit,

/// The applied input type(s) for the category 'illicit/violent'.
@JsonKey(name: 'illicit/violent') required List<String> illicitViolent,

/// The applied input type(s) for the category 'self-harm'.
@JsonKey(name: 'self-harm') required List<String> selfHarm,

/// The applied input type(s) for the category 'self-harm/intent'.
@JsonKey(name: 'self-harm/intent') required List<String> selfHarmIntent,

/// The applied input type(s) for the category 'self-harm/instructions'.
@JsonKey(name: 'self-harm/instructions')
required List<String> selfHarmInstructions,

/// The applied input type(s) for the category 'sexual'.
required List<String> sexual,

/// The applied input type(s) for the category 'sexual/minors'.
@JsonKey(name: 'sexual/minors') required List<String> sexualMinors,

/// The applied input type(s) for the category 'violence'.
required List<String> violence,

/// The applied input type(s) for the category 'violence/graphic'.
@JsonKey(name: 'violence/graphic') required List<String> violenceGraphic,
}) = _ModerationCategoriesAppliedInputTypes;

/// Object construction from a JSON representation
factory ModerationCategoriesAppliedInputTypes.fromJson(
Map<String, dynamic> json) =>
_$ModerationCategoriesAppliedInputTypesFromJson(json);

/// List of all property names of schema
static const List<String> propertyNames = [
'hate',
'hate/threatening',
'harassment',
'harassment/threatening',
'illicit',
'illicit/violent',
'self-harm',
'self-harm/intent',
'self-harm/instructions',
'sexual',
'sexual/minors',
'violence',
'violence/graphic'
];

/// Perform validations on the schema property values
String? validateSchema() {
return null;
}

/// Map representation of object (not serialized)
Map<String, dynamic> toMap() {
return {
'hate': hate,
'hate/threatening': hateThreatening,
'harassment': harassment,
'harassment/threatening': harassmentThreatening,
'illicit': illicit,
'illicit/violent': illicitViolent,
'self-harm': selfHarm,
'self-harm/intent': selfHarmIntent,
'self-harm/instructions': selfHarmInstructions,
'sexual': sexual,
'sexual/minors': sexualMinors,
'violence': violence,
'violence/graphic': violenceGraphic,
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class ModerationCategoriesScores with _$ModerationCategoriesScores {
@JsonKey(name: 'harassment/threatening')
required double harassmentThreatening,

/// The score for the category 'illicit'.
required double illicit,

/// The score for the category 'illicit/violent'.
@JsonKey(name: 'illicit/violent') required double illicitViolent,

/// The score for the category 'self-harm'.
@JsonKey(name: 'self-harm') required double selfHarm,

Expand Down Expand Up @@ -61,6 +67,8 @@ class ModerationCategoriesScores with _$ModerationCategoriesScores {
'hate/threatening',
'harassment',
'harassment/threatening',
'illicit',
'illicit/violent',
'self-harm',
'self-harm/intent',
'self-harm/instructions',
Expand All @@ -82,6 +90,8 @@ class ModerationCategoriesScores with _$ModerationCategoriesScores {
'hate/threatening': hateThreatening,
'harassment': harassment,
'harassment/threatening': harassmentThreatening,
'illicit': illicit,
'illicit/violent': illicitViolent,
'self-harm': selfHarm,
'self-harm/intent': selfHarmIntent,
'self-harm/instructions': selfHarmInstructions,
Expand Down
Loading
Loading