Build Laravel 13 ticket assistant with Docker, Livewire admin, and helpdesk scraper command
This commit is contained in:
83
app/Services/AIClassifierService.php
Normal file
83
app/Services/AIClassifierService.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user