101 lines
3.3 KiB
PHP
101 lines
3.3 KiB
PHP
<?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;
|
|
}
|
|
}
|