Files
fotospiel-app/app/Services/AiEditing/Safety/AiSafetyPolicyService.php
Codex Agent 36bed12ff9
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
feat: implement AI styling foundation and billing scope rework
2026-02-06 20:01:58 +01:00

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;
}
}