baseUrl = rtrim($baseUrl ?? CredentialService::getOllamaHost(), '/'); $this->model = $model; $this->timeout = $timeout; } public function execute(string $prompt, array $options = []): AIResponse { $model = $options['model'] ?? $this->model; $startTime = microtime(true); try { $ch = curl_init($this->baseUrl . '/api/generate'); $payload = [ 'model' => $model, 'prompt' => $prompt, 'stream' => false, ]; if (isset($options['system'])) { $payload['system'] = $options['system']; } if (isset($options['temperature'])) { $payload['options']['temperature'] = $options['temperature']; } curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Content-Type: application/json'], CURLOPT_TIMEOUT => $this->timeout, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); $durationMs = (int) ((microtime(true) - $startTime) * 1000); if ($response === false || $error !== '') { return AIResponse::error("cURL Error: {$error}", $model); } if ($httpCode !== 200) { return AIResponse::error("HTTP Error: {$httpCode}", $model); } $data = json_decode($response, true); if (!isset($data['response'])) { return AIResponse::error('Invalid response format', $model); } $tokensInput = $data['prompt_eval_count'] ?? $this->estimateTokens($prompt); $tokensOutput = $data['eval_count'] ?? $this->estimateTokens($data['response']); return AIResponse::success( $data['response'], $tokensInput, $tokensOutput, $durationMs, $model, [ 'total_duration' => $data['total_duration'] ?? null, 'load_duration' => $data['load_duration'] ?? null, 'eval_duration' => $data['eval_duration'] ?? null, ] ); } catch (\Exception $e) { return AIResponse::error($e->getMessage(), $model); } } public function isAvailable(): bool { try { $ch = curl_init($this->baseUrl . '/api/tags'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return $httpCode === 200; } catch (\Exception $e) { return false; } } public function getClientName(): string { return 'ollama'; } public function getModelName(): string { return $this->model; } public function listModels(): array { try { $ch = curl_init($this->baseUrl . '/api/tags'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode !== 200) { return []; } $data = json_decode($response, true); return array_column($data['models'] ?? [], 'name'); } catch (\Exception $e) { return []; } } private function estimateTokens(string $text): int { return max(1, (int) (strlen($text) / 4)); } }