Files
TicketAssistent/app/Services/AIClassifierService.php

83 lines
2.5 KiB
PHP

<?php
namespace App\Services;
use App\DTOs\ArticleCandidateDTO;
use App\DTOs\ClassificationResultDTO;
use App\Exceptions\OllamaUnavailableException;
use Illuminate\Support\Facades\Http;
use Throwable;
class AIClassifierService
{
/** @param array<ArticleCandidateDTO> $candidates */
public function rank(string $ticketMessage, array $candidates): ClassificationResultDTO
{
if ($candidates === []) {
return new ClassificationResultDTO(null, 0.0, 'No article candidates available');
}
$articlesBlock = collect($candidates)
->map(fn (ArticleCandidateDTO $item, int $idx) => sprintf(
"%d. article_id=%d\nTitle: %s\nContent: %s",
$idx + 1,
$item->articleId,
$item->title,
$item->content
))
->implode("\n\n");
$prompt = <<<PROMPT
You are a support assistant.
User question:
"{$ticketMessage}"
Articles:
{$articlesBlock}
Task:
- Select the best matching article
- Return:
- article_id
- confidence (0-1)
- short explanation
Respond in JSON ONLY.
PROMPT;
$baseUrl = rtrim((string) config('services.ollama.base_url'), '/');
try {
$response = Http::timeout((int) config('services.ollama.timeout', 30))
->post($baseUrl.'/api/generate', [
'model' => config('services.ollama.chat_model', 'llama3'),
'prompt' => $prompt,
'stream' => false,
])
->throw()
->json();
} catch (Throwable $e) {
throw new OllamaUnavailableException('Ollama generate endpoint is unavailable', 0, $e);
}
$text = (string) ($response['response'] ?? '{}');
$decoded = json_decode($text, true);
if (!is_array($decoded)) {
return new ClassificationResultDTO(
articleId: $candidates[0]->articleId,
confidence: 0.35,
explanation: 'LLM returned invalid JSON; defaulted to top semantic match.',
rawResponse: ['raw' => $text]
);
}
return new ClassificationResultDTO(
articleId: isset($decoded['article_id']) ? (int) $decoded['article_id'] : $candidates[0]->articleId,
confidence: isset($decoded['confidence']) ? (float) $decoded['confidence'] : 0.35,
explanation: (string) ($decoded['explanation'] ?? 'No explanation provided.'),
rawResponse: $decoded
);
}
}