host = $host ?? CredentialService::getOllamaHost(); } /** * Generates an embedding vector for the given text. * * Uses the specified model to convert text into a numerical vector representation. * The default model mxbai-embed-large produces 1024-dimensional embeddings. * * @param string $text The text to embed * @param string $model The embedding model to use (default: mxbai-embed-large) * * @return array The embedding vector as an array of floats * * @throws RuntimeException If the API request fails or returns invalid data * * @example * $service = new OllamaService(); * $embedding = $service->getEmbedding('Hello world'); * // Returns: [0.123, -0.456, 0.789, ...] (1024 dimensions) */ public function getEmbedding(string $text, string $model = 'mxbai-embed-large'): array { $url = $this->host . '/api/embeddings'; $payload = [ 'model' => $model, 'prompt' => $text, ]; $response = $this->makeRequest($url, $payload, self::DEFAULT_TIMEOUT); if (!isset($response['embedding']) || !is_array($response['embedding'])) { throw new RuntimeException('Invalid embedding response from Ollama API'); } return array_map( static fn (mixed $value): float => (float) $value, $response['embedding'] ); } /** * Generates text using the specified LLM model. * * Sends a prompt to the Ollama API and receives generated text in response. * This method does not stream responses - it waits for the complete response. * * @param string $prompt The prompt to generate text from * @param string $model The LLM model to use (default: gemma3:4b-it-qat) * @param float $temperature Sampling temperature 0.0-1.0 (default: 0.7) * * @return string The generated text response * * @throws RuntimeException If the API request fails or returns invalid data * * @example * $service = new OllamaService(); * $response = $service->generate('Explain quantum computing in simple terms.', 'gemma3:4b-it-qat', 0.7); * // Returns: "Quantum computing is a type of computing that..." */ public function generate(string $prompt, string $model = 'gemma3:4b-it-qat', float $temperature = 0.7): string { $url = $this->host . '/api/generate'; $payload = [ 'model' => $model, 'prompt' => $prompt, 'stream' => false, 'options' => [ 'temperature' => max(0.0, min(1.0, $temperature)), ], ]; $response = $this->makeRequest($url, $payload, 120); if (!isset($response['response'])) { throw new RuntimeException('Invalid generate response from Ollama API'); } return (string) $response['response']; } /** * Checks if the Ollama API is available and responding. * * Attempts to fetch the list of available models as a health check. * This is a quick operation with a short timeout. * * @return bool True if the API is available, false otherwise * * @example * $service = new OllamaService(); * if ($service->isAvailable()) { * echo "Ollama is running"; * } else { * echo "Ollama is not available"; * } */ public function isAvailable(): bool { $url = $this->host . '/api/tags'; try { $ch = curl_init($url); if ($ch === false) { return false; } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => self::HEALTH_CHECK_TIMEOUT, CURLOPT_CONNECTTIMEOUT => self::HEALTH_CHECK_TIMEOUT, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $result !== false && $httpCode === 200; } catch (\Throwable) { return false; } } /** * Makes an HTTP POST request to the Ollama API. * * Internal helper method that handles cURL initialization, request execution, * error handling, and response parsing. * * @param string $url The API endpoint URL * @param array $payload The JSON payload to send * @param int $timeout The request timeout in seconds * * @return array The decoded JSON response * * @throws RuntimeException If the request fails or returns an error */ private function makeRequest(string $url, array $payload, int $timeout): array { $ch = curl_init($url); if ($ch === false) { throw new RuntimeException('Failed to initialize cURL'); } $jsonPayload = json_encode($payload); if ($jsonPayload === false) { curl_close($ch); throw new RuntimeException('Failed to encode JSON payload'); } curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $jsonPayload, CURLOPT_TIMEOUT => $timeout, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Content-Length: ' . strlen($jsonPayload), ], ]); $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $curlError = curl_error($ch); curl_close($ch); if ($result === false) { throw new RuntimeException( sprintf('cURL request failed: %s', $curlError !== '' ? $curlError : 'Unknown error') ); } if ($httpCode !== 200) { throw new RuntimeException( sprintf('Ollama API returned HTTP %d: %s', $httpCode, $result) ); } $decoded = json_decode((string) $result, true); if (!is_array($decoded)) { throw new RuntimeException('Failed to decode JSON response from Ollama API'); } return $decoded; } }