PR Vision v0.1.1: rename from peptide-geo-monitor + QA P2 fixes (#2)
Rename to PR Vision (PRV_/pr-vision); fix www-strip char-mask in competitor display (str_starts_with); make PHPCS a CI gate; remove dead in-run cache. QA: APPROVE — qa-reviews/pr-vision/2026-06-13-c633fdb9e1541386a713a6de12d2ee2ae4239640.md Agent-Session: pr-vision rename v0.1.1 merge
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Detects whether peptiderepo.com appears in LLM source citations.
|
||||
*
|
||||
* @package PrVision
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
@@ -8,13 +14,13 @@ declare(strict_types=1);
|
||||
* without duplication. Also parses raw citation arrays/URLs into a clean
|
||||
* domain list.
|
||||
*
|
||||
* Who triggers: PGM_Perplexity_Provider, PGM_OpenRouter_Provider.
|
||||
* Who triggers: PRV_Perplexity_Provider, PRV_OpenRouter_Provider.
|
||||
* Dependencies: None (pure PHP).
|
||||
*
|
||||
* @see CONTEXT.md — "cited", "our_position", "source_domains".
|
||||
* @package PeptideGeoMonitor
|
||||
* @package PrVision
|
||||
*/
|
||||
class PGM_Citation_Detector {
|
||||
class PRV_Citation_Detector {
|
||||
|
||||
/**
|
||||
* Parse raw citation data from an API response into a clean domain list.
|
||||
@@ -43,7 +49,8 @@ class PGM_Citation_Detector {
|
||||
|
||||
$host = (string) wp_parse_url( $url, PHP_URL_HOST );
|
||||
if ( '' !== $host ) {
|
||||
$domains[] = strtolower( ltrim( $host, 'www.' ) );
|
||||
$bare = str_starts_with( $host, 'www.' ) ? substr( $host, 4 ) : $host;
|
||||
$domains[] = strtolower( $bare );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,18 +58,18 @@ class PGM_Citation_Detector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether PGM_TARGET_DOMAIN appears in a domain list.
|
||||
* Detect whether PRV_TARGET_DOMAIN appears in a domain list.
|
||||
*
|
||||
* @param string[] $domains Parsed domain list from parse_domains().
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_cited( array $domains ): bool {
|
||||
return in_array( PGM_TARGET_DOMAIN, $domains, true );
|
||||
return in_array( PRV_TARGET_DOMAIN, $domains, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the 1-based position of PGM_TARGET_DOMAIN in a domain list.
|
||||
* Find the 1-based position of PRV_TARGET_DOMAIN in a domain list.
|
||||
*
|
||||
* Returns null when the target domain is not found.
|
||||
*
|
||||
@@ -71,7 +78,7 @@ class PGM_Citation_Detector {
|
||||
* @return int|null
|
||||
*/
|
||||
public function get_our_position( array $domains ): ?int {
|
||||
$pos = array_search( PGM_TARGET_DOMAIN, $domains, true );
|
||||
$pos = array_search( PRV_TARGET_DOMAIN, $domains, true );
|
||||
if ( false === $pos ) {
|
||||
return null;
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Cloudflare AI Gateway HTTP client shared by all providers.
|
||||
*
|
||||
* @package PrVision
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
@@ -13,19 +19,19 @@ declare(strict_types=1);
|
||||
* https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}/openrouter
|
||||
*
|
||||
* Required WP-config constants (documented in README.md):
|
||||
* PGM_CF_ACCOUNT_ID — Cloudflare account ID
|
||||
* PGM_CF_GATEWAY_ID — AI Gateway name (e.g. "peptiderepo-prod")
|
||||
* PGM_OPENROUTER_API_KEY — OpenRouter API key (sk-or-…)
|
||||
* PRV_CF_ACCOUNT_ID — Cloudflare account ID
|
||||
* PRV_CF_GATEWAY_ID — AI Gateway name (e.g. "peptiderepo-prod")
|
||||
* PRV_OPENROUTER_API_KEY — OpenRouter API key (sk-or-…)
|
||||
*
|
||||
* Who triggers: PGM_Perplexity_Provider, PGM_OpenRouter_Provider.
|
||||
* Who triggers: PRV_Perplexity_Provider, PRV_OpenRouter_Provider.
|
||||
* Dependencies: wp_remote_post(), add_action(), curl_setopt().
|
||||
*
|
||||
* @see class-pgm-perplexity-provider.php — Uses post_to_gateway().
|
||||
* @see class-pgm-openrouter-provider.php — Uses post_to_gateway().
|
||||
* @see class-prv-perplexity-provider.php — Uses post_to_gateway().
|
||||
* @see class-prv-openrouter-provider.php — Uses post_to_gateway().
|
||||
* @see ARCHITECTURE.md — §External API integrations.
|
||||
* @package PeptideGeoMonitor
|
||||
* @package PrVision
|
||||
*/
|
||||
class PGM_Gateway_Client {
|
||||
class PRV_Gateway_Client {
|
||||
|
||||
/**
|
||||
* Base URL for the Cloudflare AI Gateway (without trailing slash).
|
||||
@@ -55,15 +61,15 @@ class PGM_Gateway_Client {
|
||||
* @throws \RuntimeException On permanent HTTP failure or parse error.
|
||||
*/
|
||||
public function post_to_gateway( string $provider_slug, array $body, string $api_key, string $endpoint = '/chat/completions' ): array {
|
||||
$base_url = $this->build_base_url( $provider_slug );
|
||||
$url = $base_url . $endpoint;
|
||||
$base_url = $this->build_base_url( $provider_slug );
|
||||
$url = $base_url . $endpoint;
|
||||
$base_host = (string) wp_parse_url( $base_url, PHP_URL_HOST );
|
||||
|
||||
$headers = array(
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
'Content-Type' => 'application/json',
|
||||
'HTTP-Referer' => home_url(),
|
||||
'X-Title' => 'Peptide GEO Monitor',
|
||||
'X-Title' => 'PR Vision',
|
||||
);
|
||||
|
||||
// Belt-and-suspenders cURL injection — some hosts strip Authorization.
|
||||
@@ -72,11 +78,11 @@ class PGM_Gateway_Client {
|
||||
$last_error = '';
|
||||
|
||||
try {
|
||||
for ( $attempt = 1; $attempt <= PGM_MAX_RETRIES; $attempt++ ) {
|
||||
for ( $attempt = 1; $attempt <= PRV_MAX_RETRIES; $attempt++ ) {
|
||||
$response = wp_remote_post(
|
||||
$url,
|
||||
array(
|
||||
'timeout' => PGM_API_TIMEOUT_SECONDS,
|
||||
'timeout' => PRV_API_TIMEOUT_SECONDS,
|
||||
'headers' => $headers,
|
||||
'body' => wp_json_encode( $body ),
|
||||
)
|
||||
@@ -84,8 +90,8 @@ class PGM_Gateway_Client {
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$last_error = $response->get_error_message();
|
||||
if ( $attempt < PGM_MAX_RETRIES ) {
|
||||
sleep( PGM_RETRY_BASE_DELAY_SECONDS * (int) pow( 2, $attempt - 1 ) );
|
||||
if ( $attempt < PRV_MAX_RETRIES ) {
|
||||
sleep( PRV_RETRY_BASE_DELAY_SECONDS * (int) pow( 2, $attempt - 1 ) );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -95,23 +101,22 @@ class PGM_Gateway_Client {
|
||||
|
||||
if ( $status >= 500 || 429 === $status ) {
|
||||
$last_error = sprintf( 'HTTP %d', $status );
|
||||
if ( $attempt < PGM_MAX_RETRIES ) {
|
||||
if ( $attempt < PRV_MAX_RETRIES ) {
|
||||
$retry_after = wp_remote_retrieve_header( $response, 'retry-after' );
|
||||
$delay = $retry_after ? min( (int) $retry_after, 60 ) : PGM_RETRY_BASE_DELAY_SECONDS * (int) pow( 2, $attempt - 1 );
|
||||
$delay = $retry_after ? min( (int) $retry_after, 60 ) : PRV_RETRY_BASE_DELAY_SECONDS * (int) pow( 2, $attempt - 1 );
|
||||
sleep( $delay );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $status >= 400 ) {
|
||||
throw new \RuntimeException(
|
||||
sprintf( 'GEO Monitor gateway HTTP %d: %s', $status, substr( $raw_body, 0, 300 ) )
|
||||
);
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message, not HTML output
|
||||
throw new \RuntimeException( sprintf( 'PR Vision gateway HTTP %d: %s', $status, substr( $raw_body, 0, 300 ) ) );
|
||||
}
|
||||
|
||||
$data = json_decode( $raw_body, true );
|
||||
if ( ! is_array( $data ) ) {
|
||||
throw new \RuntimeException( 'GEO Monitor: invalid JSON response from gateway.' );
|
||||
throw new \RuntimeException( 'PR Vision: invalid JSON response from gateway.' );
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -120,9 +125,8 @@ class PGM_Gateway_Client {
|
||||
remove_action( 'http_api_curl', $curl_filter, 99 );
|
||||
}
|
||||
|
||||
throw new \RuntimeException(
|
||||
sprintf( 'GEO Monitor: gateway failed after %d attempts. Last: %s', PGM_MAX_RETRIES, $last_error )
|
||||
);
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message, not HTML output
|
||||
throw new \RuntimeException( sprintf( 'PR Vision: gateway failed after %d attempts. Last: %s', PRV_MAX_RETRIES, $last_error ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,13 +137,13 @@ class PGM_Gateway_Client {
|
||||
* @return string Base URL without trailing slash.
|
||||
*/
|
||||
public function build_base_url( string $provider_slug ): string {
|
||||
if ( defined( 'PGM_CF_ACCOUNT_ID' ) && defined( 'PGM_CF_GATEWAY_ID' )
|
||||
&& '' !== PGM_CF_ACCOUNT_ID && '' !== PGM_CF_GATEWAY_ID ) {
|
||||
if ( defined( 'PRV_CF_ACCOUNT_ID' ) && defined( 'PRV_CF_GATEWAY_ID' )
|
||||
&& '' !== PRV_CF_ACCOUNT_ID && '' !== PRV_CF_GATEWAY_ID ) {
|
||||
return sprintf(
|
||||
'%s/%s/%s/%s',
|
||||
self::GATEWAY_BASE,
|
||||
PGM_CF_ACCOUNT_ID,
|
||||
PGM_CF_GATEWAY_ID,
|
||||
PRV_CF_ACCOUNT_ID,
|
||||
PRV_CF_GATEWAY_ID,
|
||||
$provider_slug
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Generic OpenRouter provider for GPT-search and Gemini-search models.
|
||||
*
|
||||
* @package PrVision
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
@@ -6,7 +12,7 @@ declare(strict_types=1);
|
||||
*
|
||||
* One class handles any OpenRouter-routed model. The model identifier is set
|
||||
* at construction time; the runner instantiates one provider per model from
|
||||
* PGM_Config::get_models().
|
||||
* PRV_Config::get_models().
|
||||
*
|
||||
* GPT-search (openai/gpt-4o-search-preview) and Gemini-search
|
||||
* (google/gemini-2.0-flash-001) do not return a structured citations array
|
||||
@@ -14,18 +20,18 @@ declare(strict_types=1);
|
||||
* text or from any 'annotations' key the model may include.
|
||||
*
|
||||
* Required WP-config constant:
|
||||
* PGM_OPENROUTER_API_KEY — OpenRouter key (sk-or-…)
|
||||
* PRV_OPENROUTER_API_KEY — OpenRouter key (sk-or-…)
|
||||
*
|
||||
* Who triggers: PGM_Probe_Runner via PGM_Probe_Provider interface.
|
||||
* Dependencies: PGM_Gateway_Client, PGM_Citation_Detector, PGM_Probe_Result.
|
||||
* Who triggers: PRV_Probe_Runner via PRV_Probe_Provider interface.
|
||||
* Dependencies: PRV_Gateway_Client, PRV_Citation_Detector, PRV_Probe_Result.
|
||||
*
|
||||
* @see interface-pgm-probe-provider.php — Interface this implements.
|
||||
* @see class-pgm-gateway-client.php — Shared HTTP + retry logic.
|
||||
* @see class-pgm-citation-detector.php — Domain extraction + detection.
|
||||
* @see interface-prv-probe-provider.php — Interface this implements.
|
||||
* @see class-prv-gateway-client.php — Shared HTTP + retry logic.
|
||||
* @see class-prv-citation-detector.php — Domain extraction + detection.
|
||||
* @see ARCHITECTURE.md — §Provider implementations.
|
||||
* @package PeptideGeoMonitor
|
||||
* @package PrVision
|
||||
*/
|
||||
class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
class PRV_OpenRouter_Provider implements PRV_Probe_Provider {
|
||||
|
||||
/**
|
||||
* Estimated cost per probe call in USD (conservative cross-model default).
|
||||
@@ -33,45 +39,57 @@ class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
const ESTIMATED_COST_PER_PROBE = 0.003;
|
||||
|
||||
/**
|
||||
* @var string OpenRouter model identifier (e.g. "openai/gpt-4o-search-preview").
|
||||
* OpenRouter model identifier (e.g. "openai/gpt-4o-search-preview").
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $model;
|
||||
|
||||
/**
|
||||
* @var PGM_Gateway_Client
|
||||
* HTTP gateway client.
|
||||
*
|
||||
* @var PRV_Gateway_Client
|
||||
*/
|
||||
private PGM_Gateway_Client $gateway;
|
||||
private PRV_Gateway_Client $gateway;
|
||||
|
||||
/**
|
||||
* @var PGM_Citation_Detector
|
||||
* Citation domain detector.
|
||||
*
|
||||
* @var PRV_Citation_Detector
|
||||
*/
|
||||
private PGM_Citation_Detector $detector;
|
||||
private PRV_Citation_Detector $detector;
|
||||
|
||||
/**
|
||||
* @param string $model OpenRouter model identifier.
|
||||
* @param PGM_Gateway_Client|null $gateway Injected for testing.
|
||||
* @param PGM_Citation_Detector|null $detector Injected for testing.
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $model OpenRouter model identifier.
|
||||
* @param PRV_Gateway_Client|null $gateway Injected for testing.
|
||||
* @param PRV_Citation_Detector|null $detector Injected for testing.
|
||||
*/
|
||||
public function __construct( string $model, ?PGM_Gateway_Client $gateway = null, ?PGM_Citation_Detector $detector = null ) {
|
||||
public function __construct( string $model, ?PRV_Gateway_Client $gateway = null, ?PRV_Citation_Detector $detector = null ) {
|
||||
$this->model = $model;
|
||||
$this->gateway = $gateway ?? new PGM_Gateway_Client();
|
||||
$this->detector = $detector ?? new PGM_Citation_Detector();
|
||||
$this->gateway = $gateway ?? new PRV_Gateway_Client();
|
||||
$this->detector = $detector ?? new PRV_Citation_Detector();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @param string $query
|
||||
* @return PGM_Probe_Result
|
||||
* @param string $query The search query to probe.
|
||||
*
|
||||
* @return PRV_Probe_Result
|
||||
* @throws \RuntimeException On permanent API failure.
|
||||
*/
|
||||
public function probe( string $query ): PGM_Probe_Result {
|
||||
public function probe( string $query ): PRV_Probe_Result {
|
||||
$api_key = $this->resolve_api_key();
|
||||
|
||||
$body = array(
|
||||
'model' => $this->model,
|
||||
'messages' => array(
|
||||
array( 'role' => 'user', 'content' => $query ),
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => $query,
|
||||
),
|
||||
),
|
||||
'max_tokens' => 512,
|
||||
);
|
||||
@@ -82,7 +100,7 @@ class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the OpenRouter response into a PGM_Probe_Result.
|
||||
* Parse the OpenRouter response into a PRV_Probe_Result.
|
||||
*
|
||||
* Attempts to extract citations from:
|
||||
* 1. `annotations` array (some models include structured source references).
|
||||
@@ -90,9 +108,9 @@ class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
*
|
||||
* @param array<string, mixed> $data Decoded API response.
|
||||
*
|
||||
* @return PGM_Probe_Result
|
||||
* @return PRV_Probe_Result
|
||||
*/
|
||||
public function parse_response( array $data ): PGM_Probe_Result {
|
||||
public function parse_response( array $data ): PRV_Probe_Result {
|
||||
$content = '';
|
||||
if ( isset( $data['choices'][0]['message']['content'] ) ) {
|
||||
$content = (string) $data['choices'][0]['message']['content'];
|
||||
@@ -126,7 +144,7 @@ class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
$cost_usd = (float) $data['usage']['total_tokens'] * 0.000002;
|
||||
}
|
||||
|
||||
return new PGM_Probe_Result( $raw_excerpt, $domains, $cited, $our_position, $cost_usd );
|
||||
return new PRV_Probe_Result( $raw_excerpt, $domains, $cited, $our_position, $cost_usd );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,7 +158,7 @@ class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function is_configured(): bool {
|
||||
return defined( 'PGM_OPENROUTER_API_KEY' ) && '' !== PGM_OPENROUTER_API_KEY;
|
||||
return defined( 'PRV_OPENROUTER_API_KEY' ) && '' !== PRV_OPENROUTER_API_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,11 +168,10 @@ class PGM_OpenRouter_Provider implements PGM_Probe_Provider {
|
||||
* @throws \RuntimeException When the constant is not defined or empty.
|
||||
*/
|
||||
private function resolve_api_key(): string {
|
||||
if ( ! defined( 'PGM_OPENROUTER_API_KEY' ) || '' === PGM_OPENROUTER_API_KEY ) {
|
||||
throw new \RuntimeException(
|
||||
__( 'PGM_OPENROUTER_API_KEY constant is not defined. Add it to wp-config.php.', 'peptide-geo-monitor' )
|
||||
);
|
||||
if ( ! defined( 'PRV_OPENROUTER_API_KEY' ) || '' === PRV_OPENROUTER_API_KEY ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message, not HTML output
|
||||
throw new \RuntimeException( __( 'PRV_OPENROUTER_API_KEY constant is not defined. Add it to wp-config.php.', 'pr-vision' ) );
|
||||
}
|
||||
return PGM_OPENROUTER_API_KEY;
|
||||
return PRV_OPENROUTER_API_KEY;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Perplexity sonar provider — primary citation signal.
|
||||
*
|
||||
* @package PrVision
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
@@ -10,19 +16,19 @@ declare(strict_types=1);
|
||||
* available (real web retrieval vs. training-time knowledge).
|
||||
*
|
||||
* Required WP-config constant:
|
||||
* PGM_OPENROUTER_API_KEY — OpenRouter key (sk-or-…)
|
||||
* PRV_OPENROUTER_API_KEY — OpenRouter key (sk-or-…)
|
||||
*
|
||||
* Who triggers: PGM_Probe_Runner via the PGM_Probe_Provider interface.
|
||||
* Dependencies: PGM_Gateway_Client, PGM_Citation_Detector, PGM_Probe_Result.
|
||||
* Who triggers: PRV_Probe_Runner via the PRV_Probe_Provider interface.
|
||||
* Dependencies: PRV_Gateway_Client, PRV_Citation_Detector, PRV_Probe_Result.
|
||||
*
|
||||
* @see interface-pgm-probe-provider.php — Interface this implements.
|
||||
* @see class-pgm-gateway-client.php — Shared HTTP + retry logic.
|
||||
* @see class-pgm-citation-detector.php — Domain extraction + detection.
|
||||
* @see interface-prv-probe-provider.php — Interface this implements.
|
||||
* @see class-prv-gateway-client.php — Shared HTTP + retry logic.
|
||||
* @see class-prv-citation-detector.php — Domain extraction + detection.
|
||||
* @see ARCHITECTURE.md — §Provider implementations.
|
||||
* @see CONTEXT.md — "Perplexity sonar", "citations".
|
||||
* @package PeptideGeoMonitor
|
||||
* @package PrVision
|
||||
*/
|
||||
class PGM_Perplexity_Provider implements PGM_Probe_Provider {
|
||||
class PRV_Perplexity_Provider implements PRV_Probe_Provider {
|
||||
|
||||
/**
|
||||
* Model identifier passed to OpenRouter for Perplexity routing.
|
||||
@@ -36,22 +42,28 @@ class PGM_Perplexity_Provider implements PGM_Probe_Provider {
|
||||
const ESTIMATED_COST_PER_PROBE = 0.005;
|
||||
|
||||
/**
|
||||
* @var PGM_Gateway_Client
|
||||
* HTTP gateway client.
|
||||
*
|
||||
* @var PRV_Gateway_Client
|
||||
*/
|
||||
private PGM_Gateway_Client $gateway;
|
||||
private PRV_Gateway_Client $gateway;
|
||||
|
||||
/**
|
||||
* @var PGM_Citation_Detector
|
||||
* Citation domain detector.
|
||||
*
|
||||
* @var PRV_Citation_Detector
|
||||
*/
|
||||
private PGM_Citation_Detector $detector;
|
||||
private PRV_Citation_Detector $detector;
|
||||
|
||||
/**
|
||||
* @param PGM_Gateway_Client|null $gateway Injected for testing; auto-created otherwise.
|
||||
* @param PGM_Citation_Detector|null $detector Injected for testing; auto-created otherwise.
|
||||
* Constructor.
|
||||
*
|
||||
* @param PRV_Gateway_Client|null $gateway Injected for testing; auto-created otherwise.
|
||||
* @param PRV_Citation_Detector|null $detector Injected for testing; auto-created otherwise.
|
||||
*/
|
||||
public function __construct( ?PGM_Gateway_Client $gateway = null, ?PGM_Citation_Detector $detector = null ) {
|
||||
$this->gateway = $gateway ?? new PGM_Gateway_Client();
|
||||
$this->detector = $detector ?? new PGM_Citation_Detector();
|
||||
public function __construct( ?PRV_Gateway_Client $gateway = null, ?PRV_Citation_Detector $detector = null ) {
|
||||
$this->gateway = $gateway ?? new PRV_Gateway_Client();
|
||||
$this->detector = $detector ?? new PRV_Citation_Detector();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,18 +72,21 @@ class PGM_Perplexity_Provider implements PGM_Probe_Provider {
|
||||
* Sends the query as a user message to perplexity/sonar via OpenRouter.
|
||||
* Parses the citations[] array from the OpenRouter response envelope.
|
||||
*
|
||||
* @param string $query
|
||||
* @param string $query The search query to probe.
|
||||
*
|
||||
* @return PGM_Probe_Result
|
||||
* @return PRV_Probe_Result
|
||||
* @throws \RuntimeException On permanent API failure.
|
||||
*/
|
||||
public function probe( string $query ): PGM_Probe_Result {
|
||||
public function probe( string $query ): PRV_Probe_Result {
|
||||
$api_key = $this->resolve_api_key();
|
||||
|
||||
$body = array(
|
||||
'model' => self::MODEL,
|
||||
'messages' => array(
|
||||
array( 'role' => 'user', 'content' => $query ),
|
||||
'model' => self::MODEL,
|
||||
'messages' => array(
|
||||
array(
|
||||
'role' => 'user',
|
||||
'content' => $query,
|
||||
),
|
||||
),
|
||||
'max_tokens' => 512,
|
||||
);
|
||||
@@ -82,15 +97,15 @@ class PGM_Perplexity_Provider implements PGM_Probe_Provider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the OpenRouter/Perplexity response into a PGM_Probe_Result.
|
||||
* Parse the OpenRouter/Perplexity response into a PRV_Probe_Result.
|
||||
*
|
||||
* Side effects: None.
|
||||
*
|
||||
* @param array<string, mixed> $data Decoded API response.
|
||||
*
|
||||
* @return PGM_Probe_Result
|
||||
* @return PRV_Probe_Result
|
||||
*/
|
||||
public function parse_response( array $data ): PGM_Probe_Result {
|
||||
public function parse_response( array $data ): PRV_Probe_Result {
|
||||
// Extract text excerpt from the first choice.
|
||||
$content = '';
|
||||
if ( isset( $data['choices'][0]['message']['content'] ) ) {
|
||||
@@ -115,7 +130,7 @@ class PGM_Perplexity_Provider implements PGM_Probe_Provider {
|
||||
$cost_usd = (float) $data['usage']['total_tokens'] * 0.000001;
|
||||
}
|
||||
|
||||
return new PGM_Probe_Result( $raw_excerpt, $domains, $cited, $our_position, $cost_usd );
|
||||
return new PRV_Probe_Result( $raw_excerpt, $domains, $cited, $our_position, $cost_usd );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,21 +144,20 @@ class PGM_Perplexity_Provider implements PGM_Probe_Provider {
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function is_configured(): bool {
|
||||
return defined( 'PGM_OPENROUTER_API_KEY' ) && '' !== PGM_OPENROUTER_API_KEY;
|
||||
return defined( 'PRV_OPENROUTER_API_KEY' ) && '' !== PRV_OPENROUTER_API_KEY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the OpenRouter API key from wp-config constants.
|
||||
*
|
||||
* @return string
|
||||
* @return string The API key.
|
||||
* @throws \RuntimeException When the constant is not defined or empty.
|
||||
*/
|
||||
private function resolve_api_key(): string {
|
||||
if ( ! defined( 'PGM_OPENROUTER_API_KEY' ) || '' === PGM_OPENROUTER_API_KEY ) {
|
||||
throw new \RuntimeException(
|
||||
__( 'PGM_OPENROUTER_API_KEY constant is not defined. Add it to wp-config.php.', 'peptide-geo-monitor' )
|
||||
);
|
||||
if ( ! defined( 'PRV_OPENROUTER_API_KEY' ) || '' === PRV_OPENROUTER_API_KEY ) {
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- exception message, not HTML output
|
||||
throw new \RuntimeException( __( 'PRV_OPENROUTER_API_KEY constant is not defined. Add it to wp-config.php.', 'pr-vision' ) );
|
||||
}
|
||||
return PGM_OPENROUTER_API_KEY;
|
||||
return PRV_OPENROUTER_API_KEY;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* Contract for all LLM probe providers.
|
||||
*
|
||||
* @package PrVision
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
@@ -8,34 +14,34 @@ declare(strict_types=1);
|
||||
* OpenRouter GPT-search, OpenRouter Gemini-search, …). Swapping providers
|
||||
* requires only a new class — the runner, ledger, and storage are agnostic.
|
||||
*
|
||||
* Who triggers: PGM_Probe_Runner for each (model, query) combination.
|
||||
* Who triggers: PRV_Probe_Runner for each (model, query) combination.
|
||||
* Dependencies: None — pure interface.
|
||||
*
|
||||
* @see class-pgm-perplexity-provider.php — Perplexity sonar implementation.
|
||||
* @see class-pgm-openrouter-provider.php — OpenRouter generic implementation.
|
||||
* @see class-pgm-probe-runner.php — Dispatches to providers via this interface.
|
||||
* @see class-prv-perplexity-provider.php — Perplexity sonar implementation.
|
||||
* @see class-prv-openrouter-provider.php — OpenRouter generic implementation.
|
||||
* @see class-prv-probe-runner.php — Dispatches to providers via this interface.
|
||||
* @see CONTEXT.md — "provider", "probe".
|
||||
* @package PeptideGeoMonitor
|
||||
* @package PrVision
|
||||
*/
|
||||
interface PGM_Probe_Provider {
|
||||
interface PRV_Probe_Provider {
|
||||
|
||||
/**
|
||||
* Send a search/retrieval query and return a structured probe result.
|
||||
*
|
||||
* Implementations MUST:
|
||||
* - Extract cited source domains from the model's response.
|
||||
* - Detect whether PGM_TARGET_DOMAIN appears in those sources.
|
||||
* - Detect whether PRV_TARGET_DOMAIN appears in those sources.
|
||||
* - Record our 1-based position (null when not cited).
|
||||
* - Return the call's estimated USD cost.
|
||||
* - Throw \RuntimeException on unrecoverable failure (caller logs + skips).
|
||||
*
|
||||
* @param string $query The natural-language query to send.
|
||||
*
|
||||
* @return PGM_Probe_Result
|
||||
* @return PRV_Probe_Result
|
||||
*
|
||||
* @throws \RuntimeException When the API call fails after retries.
|
||||
*/
|
||||
public function probe( string $query ): PGM_Probe_Result;
|
||||
public function probe( string $query ): PRV_Probe_Result;
|
||||
|
||||
/**
|
||||
* Get a short human-readable name for this provider (e.g. "Perplexity sonar").
|
||||
Reference in New Issue
Block a user