apiUrl = config('ollama.api_url', 'http://localhost:11434'); $this->model = config('ollama.model', 'llama3'); $this->timeout = config('ollama.timeout', 300); $this->temperature = config('ollama.temperature', 0.3); $this->maxTokens = config('ollama.max_tokens', 4000); $this->retryMaxAttempts = config('ollama.retry.max_attempts', 3); $this->retryBackoff = config('ollama.retry.backoff_multiplier', 5); $this->options = config('ollama.options', []); } /** * Generate a summary for a book. * * @param string $bookText The book text to summarize * @param string $bookTitle The book title * @param string $bookAuthor The book author * @return string|null The generated summary or null on failure */ public function generateSummary(string $bookText, string $bookTitle, string $bookAuthor): ?string { $prompt = $this->buildPrompt('summary', [ 'title' => $bookTitle, 'author' => $bookAuthor, 'text' => $bookText, ]); Log::info('Ollama: Generating summary', [ 'model' => $this->model, 'title' => $bookTitle, 'author' => $bookAuthor, 'text_length' => strlen($bookText), ]); $startTime = microtime(true); try { $response = $this->makeRequest($prompt); $duration = microtime(true) - $startTime; if ($response) { $wordCount = str_word_count($response); Log::info('Ollama: Summary generated successfully', [ 'title' => $bookTitle, 'duration' => round($duration, 2) . 's', 'word_count' => $wordCount, ]); // Quality check $minWords = config('ollama.quality_checks.min_word_count', 100); $maxWords = config('ollama.quality_checks.max_word_count', 1000); if ($wordCount < $minWords) { Log::warning('Ollama: Summary too short', [ 'title' => $bookTitle, 'word_count' => $wordCount, 'min_required' => $minWords, ]); } if ($wordCount > $maxWords) { Log::warning('Ollama: Summary too long', [ 'title' => $bookTitle, 'word_count' => $wordCount, 'max_allowed' => $maxWords, ]); } return $response; } return null; } catch (\Exception $e) { $duration = microtime(true) - $startTime; Log::error('Ollama: Failed to generate summary', [ 'title' => $bookTitle, 'error' => $e->getMessage(), 'duration' => round($duration, 2) . 's', ]); return null; } } /** * Generate key insights from a book summary. * * @param string $summary The book summary * @param string $bookTitle The book title * @param string $bookAuthor The book author * @return string|null The generated key insights or null on failure */ public function generateKeyInsights(string $summary, string $bookTitle, string $bookAuthor): ?string { $prompt = $this->buildPrompt('key_insights', [ 'title' => $bookTitle, 'author' => $bookAuthor, 'summary' => $summary, ]); Log::info('Ollama: Generating key insights', [ 'model' => $this->model, 'title' => $bookTitle, ]); $startTime = microtime(true); try { $response = $this->makeRequest($prompt); $duration = microtime(true) - $startTime; if ($response) { Log::info('Ollama: Key insights generated successfully', [ 'title' => $bookTitle, 'duration' => round($duration, 2) . 's', ]); return $response; } return null; } catch (\Exception $e) { $duration = microtime(true) - $startTime; Log::error('Ollama: Failed to generate key insights', [ 'title' => $bookTitle, 'error' => $e->getMessage(), 'duration' => round($duration, 2) . 's', ]); return null; } } /** * Check if the Ollama API is accessible. * * @return bool */ public function checkConnection(): bool { try { Log::info('Ollama: Checking connection', ['url' => $this->apiUrl]); $response = Http::timeout(10) ->get($this->apiUrl . '/api/tags'); if ($response->successful()) { Log::info('Ollama: Connection successful'); return true; } Log::warning('Ollama: Connection check failed', [ 'status' => $response->status(), ]); return false; } catch (\Exception $e) { Log::error('Ollama: Connection check error', [ 'error' => $e->getMessage(), ]); return false; } } /** * List available models on the Ollama instance. * * @return array List of model names */ public function listModels(): array { try { Log::info('Ollama: Listing models'); $response = Http::timeout(10) ->get($this->apiUrl . '/api/tags'); if ($response->successful()) { $data = $response->json(); $models = array_map( fn($model) => $model['name'] ?? 'unknown', $data['models'] ?? [] ); Log::info('Ollama: Models retrieved', [ 'count' => count($models), 'models' => $models, ]); return $models; } Log::warning('Ollama: Failed to list models', [ 'status' => $response->status(), ]); return []; } catch (\Exception $e) { Log::error('Ollama: Error listing models', [ 'error' => $e->getMessage(), ]); return []; } } /** * Test summary generation with a sample text. * * @return array Test results with success status and details */ public function testGeneration(): array { $testText = "This is a test book about artificial intelligence and machine learning. " . "It explores the history of AI, from its early beginnings to modern applications. " . "The book covers neural networks, deep learning, and practical implementations."; $testTitle = "Test Book: Introduction to AI"; $testAuthor = "Test Author"; $startTime = microtime(true); $summary = $this->generateSummary($testText, $testTitle, $testAuthor); $duration = microtime(true) - $startTime; return [ 'success' => $summary !== null, 'summary' => $summary, 'duration' => round($duration, 2), 'model' => $this->model, 'word_count' => $summary ? str_word_count($summary) : 0, ]; } /** * Build a prompt from a template with variable substitution. * * @param string $templateKey The template key from config * @param array $variables Variables to substitute * @return string The built prompt */ protected function buildPrompt(string $templateKey, array $variables): string { $template = config("ollama.prompts.{$templateKey}", ''); if (empty($template)) { throw new \InvalidArgumentException("Prompt template '{$templateKey}' not found in config."); } $prompt = $template; foreach ($variables as $key => $value) { $prompt = str_replace('{' . $key . '}', $value, $prompt); } return $prompt; } /** * Make a request to the Ollama API. * * @param string $prompt The prompt to send * @return string|null The response text or null on failure */ protected function makeRequest(string $prompt): ?string { $url = $this->apiUrl . '/api/generate'; $payload = [ 'model' => $this->model, 'prompt' => $prompt, 'stream' => false, 'options' => array_merge($this->options, [ 'temperature' => $this->temperature, ]), ]; try { $response = Http::timeout($this->timeout) ->retry( $this->retryMaxAttempts, function ($exception, $request) { // Exponential backoff in milliseconds return $this->retryBackoff * 1000; }, function ($exception, $request) { // Retry on connection errors and 5xx server errors if ($exception instanceof ConnectionException) { return true; } if (method_exists($exception, 'response')) { $response = $exception->response; return $response && $response->status() >= 500; } return false; } ) ->post($url, $payload); if ($response->successful()) { $data = $response->json(); return $data['response'] ?? null; } Log::warning('Ollama: API request failed', [ 'status' => $response->status(), 'body' => $response->body(), ]); return null; } catch (ConnectionException $e) { Log::error('Ollama: Connection error', [ 'error' => $e->getMessage(), 'url' => $url, ]); throw $e; } catch (RequestException $e) { Log::error('Ollama: Request exception', [ 'error' => $e->getMessage(), 'url' => $url, ]); throw $e; } catch (\Exception $e) { Log::error('Ollama: Unexpected error', [ 'error' => $e->getMessage(), 'type' => get_class($e), ]); throw $e; } } /** * Get the currently configured model name. * * @return string */ public function getModel(): string { return $this->model; } /** * Set a different model for the next requests. * * @param string $model Model name * @return void */ public function setModel(string $model): void { $this->model = $model; Log::info('Ollama: Model changed', ['model' => $model]); } }