PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 2.5.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v2.5.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / includes / ai / class-perplexity-client.php

class-perplexity-client.php in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 2.5.0, at includes/ai/class-perplexity-client.php

144 lines 4.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Perplexity API Client (Brand Visibility probes only).
4 *
5 * Perplexity exposes an OpenAI-compatible Chat Completions endpoint, so the
6 * request/response handling mirrors OpenAI_Client. It exists as its own client
7 * for one reason: Perplexity answers are **search-grounded** — the model
8 * retrieves live web results before answering — which makes it the closest
9 * available proxy for what a real person sees in an AI answer engine.
10 *
11 * That is also why this client is deliberately NOT registered as a general
12 * ThinkRank AI provider: it is tuned for "what would an assistant tell a user
13 * about this brand", not for metadata generation or content briefs.
14 *
15 * @package ThinkRank\AI
16 * @since 1.28.0
17 */
18
19 declare(strict_types=1);
20
21 namespace ThinkRank\AI;
22
23 // Prevent direct access
24 if (!defined('ABSPATH')) {
25 exit;
26 }
27
28 /**
29 * Minimal Perplexity chat client used by the Brand Visibility runner.
30 */
31 class Perplexity_Client {
32
33 /**
34 * Perplexity API base URL.
35 */
36 private const API_BASE_URL = 'https://api.perplexity.ai';
37
38 /**
39 * Default search-grounded model.
40 */
41 public const DEFAULT_MODEL = 'sonar';
42
43 /**
44 * API key.
45 *
46 * @var string
47 */
48 private string $api_key;
49
50 /**
51 * Model id.
52 *
53 * @var string
54 */
55 private string $model;
56
57 /**
58 * Request timeout, seconds.
59 *
60 * @var int
61 */
62 private int $timeout;
63
64 /**
65 * Constructor.
66 *
67 * @param string $api_key Perplexity API key.
68 * @param string $model Model id.
69 * @param int $timeout Timeout in seconds.
70 */
71 public function __construct(string $api_key, string $model = self::DEFAULT_MODEL, int $timeout = 45) {
72 $this->api_key = $api_key;
73 $this->model = '' !== $model ? $model : self::DEFAULT_MODEL;
74 $this->timeout = $timeout;
75 }
76
77 /**
78 * Generate a completion.
79 *
80 * Returns the raw decoded body so Manager::request_completion() can read it
81 * with the same `choices[0].message.content` path it uses for OpenAI.
82 *
83 * @param string $prompt User prompt.
84 * @param array $options max_tokens, temperature.
85 * @return array Decoded API response.
86 * @throws \Exception On transport failure or a non-2xx response.
87 */
88 public function generate_completion(string $prompt, array $options = []): array {
89 $body = [
90 'model' => $this->model,
91 'messages' => [
92 ['role' => 'user', 'content' => $prompt],
93 ],
94 'max_tokens' => (int) ($options['max_tokens'] ?? 1000),
95 'temperature' => (float) ($options['temperature'] ?? 0.4),
96 ];
97
98 $response = wp_remote_post(self::API_BASE_URL . '/chat/completions', [
99 'timeout' => $this->timeout,
100 'headers' => [
101 'Authorization' => 'Bearer ' . $this->api_key,
102 'Content-Type' => 'application/json',
103 'User-Agent' => 'ThinkRank/' . THINKRANK_VERSION,
104 ],
105 'body' => wp_json_encode($body),
106 ]);
107
108 if (is_wp_error($response)) {
109 throw new \Exception('Perplexity request failed: ' . esc_html($response->get_error_message()));
110 }
111
112 $status = wp_remote_retrieve_response_code($response);
113 $raw = wp_remote_retrieve_body($response);
114
115 if ($status >= 400) {
116 $decoded = json_decode($raw, true);
117 $message = $decoded['error']['message'] ?? ($decoded['detail'] ?? 'Unknown API error');
118 throw new \Exception(sprintf('Perplexity API error (%d): %s', (int) $status, esc_html((string) $message)));
119 }
120
121 $data = json_decode($raw, true);
122
123 if (!is_array($data)) {
124 throw new \Exception('Unexpected non-array response from Perplexity API');
125 }
126
127 return $data;
128 }
129
130 /**
131 * Lightweight credential check.
132 *
133 * @return bool True when the key answers a minimal request.
134 */
135 public function test_connection(): bool {
136 try {
137 $this->generate_completion('Reply with OK.', ['max_tokens' => 5]);
138 return true;
139 } catch (\Exception $e) {
140 return false;
141 }
142 }
143 }
144