83 lines
2.5 KiB
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
|
|
);
|
|
}
|
|
} |