feat: implement AI styling foundation and billing scope rework
This commit is contained in:
100
app/Services/AiEditing/Safety/AiSafetyPolicyService.php
Normal file
100
app/Services/AiEditing/Safety/AiSafetyPolicyService.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\AiEditing\Safety;
|
||||
|
||||
use App\Services\AiEditing\AiEditingRuntimeConfig;
|
||||
use App\Services\AiEditing\AiProviderResult;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AiSafetyPolicyService
|
||||
{
|
||||
public function __construct(private readonly AiEditingRuntimeConfig $runtimeConfig) {}
|
||||
|
||||
public function evaluatePrompt(?string $prompt, ?string $negativePrompt): AiSafetyDecision
|
||||
{
|
||||
$blockedTerms = array_filter(array_map(
|
||||
static fn (mixed $term): string => Str::lower(trim((string) $term)),
|
||||
$this->runtimeConfig->blockedTerms()
|
||||
));
|
||||
|
||||
if ($blockedTerms === []) {
|
||||
return AiSafetyDecision::passed();
|
||||
}
|
||||
|
||||
$fullPrompt = Str::lower(trim(sprintf('%s %s', (string) $prompt, (string) $negativePrompt)));
|
||||
if ($fullPrompt === '') {
|
||||
return AiSafetyDecision::passed();
|
||||
}
|
||||
|
||||
$matched = [];
|
||||
foreach ($blockedTerms as $term) {
|
||||
if ($term !== '' && str_contains($fullPrompt, $term)) {
|
||||
$matched[] = 'prompt_blocked_term';
|
||||
}
|
||||
}
|
||||
|
||||
if ($matched === []) {
|
||||
return AiSafetyDecision::passed();
|
||||
}
|
||||
|
||||
return AiSafetyDecision::blocked(
|
||||
reasonCodes: $matched,
|
||||
failureCode: 'prompt_policy_blocked',
|
||||
failureMessage: 'The provided prompt violates the AI editing safety policy.'
|
||||
);
|
||||
}
|
||||
|
||||
public function evaluateProviderOutput(AiProviderResult $result): AiSafetyDecision
|
||||
{
|
||||
if ($result->status === 'blocked') {
|
||||
return AiSafetyDecision::blocked(
|
||||
reasonCodes: $result->safetyReasons !== [] ? $result->safetyReasons : ['provider_blocked'],
|
||||
failureCode: $result->failureCode ?: 'output_policy_blocked',
|
||||
failureMessage: $result->failureMessage ?: 'The generated output was blocked by safety policy.'
|
||||
);
|
||||
}
|
||||
|
||||
if ($result->safetyState === 'blocked') {
|
||||
return AiSafetyDecision::blocked(
|
||||
reasonCodes: $result->safetyReasons !== [] ? $result->safetyReasons : ['provider_nsfw_content'],
|
||||
failureCode: 'output_policy_blocked',
|
||||
failureMessage: 'The generated output was blocked by safety policy.'
|
||||
);
|
||||
}
|
||||
|
||||
$payloadItems = (array) Arr::get($result->responsePayload, 'data', []);
|
||||
foreach ($payloadItems as $item) {
|
||||
if (! is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->toBool(Arr::get($item, 'NSFWContent')) || $this->toBool(Arr::get($item, 'nsfwContent'))) {
|
||||
return AiSafetyDecision::blocked(
|
||||
reasonCodes: ['provider_nsfw_content'],
|
||||
failureCode: 'output_policy_blocked',
|
||||
failureMessage: 'The generated output was blocked by safety policy.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return AiSafetyDecision::passed();
|
||||
}
|
||||
|
||||
private function toBool(mixed $value): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_numeric($value)) {
|
||||
return (int) $value === 1;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return in_array(Str::lower(trim($value)), ['1', 'true', 'yes'], true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user