Closes #119 - Extract PRAutoBlogger_Image_Scene_Parser from image prompt builder (331→300 lines) - Add frozen eval dataset (20 peptide topics at eval/dataset.json) - Add LLM-as-judge scorer (gemini-flash-1.5 via OpenRouter, 3 axes) - Add eval runner orchestrator with dry-run and Opik push support - Add WP-CLI command: wp prautoblogger opik:eval [--limit=<n>] [--dry-run] - Add image pipeline Opik instrumentation (image_prompt_rewrite span) - Add content generator eval mode (suppresses publish + image gen) - Version: v0.13.0
92 lines
2.7 KiB
PHP
92 lines
2.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Parses LLM output into scene/caption pairs and synthesises fallback prompts
|
|
* from article titles when the LLM call fails.
|
|
*
|
|
* Who calls it: PRAutoBlogger_Image_Prompt_Builder (static calls).
|
|
* Dependencies: None.
|
|
*/
|
|
class PRAutoBlogger_Image_Scene_Parser {
|
|
|
|
/**
|
|
* Parse the LLM response into separate scene and caption parts.
|
|
*
|
|
* Expected format from the LLM:
|
|
* Scene description text here.
|
|
*
|
|
* "Caption punchline here."
|
|
*
|
|
* Falls back gracefully: if no blank-line separator is found, the entire
|
|
* response becomes the scene with an empty caption.
|
|
*
|
|
* @param string $raw Raw LLM response text.
|
|
* @return array{scene: string, caption: string}
|
|
*/
|
|
public static function parse_scene_and_caption( string $raw ): array {
|
|
// Split on blank line (the format the system prompt requests).
|
|
$parts = preg_split( '/\n\s*\n/', $raw, 2 );
|
|
|
|
if ( count( $parts ) >= 2 ) {
|
|
$scene = trim( $parts[0] );
|
|
$caption = trim( $parts[1] );
|
|
// Strip surrounding quotes from caption — the LLM wraps it in quotes.
|
|
$caption = trim( $caption, '"\'' );
|
|
return array(
|
|
'scene' => $scene,
|
|
'caption' => $caption,
|
|
);
|
|
}
|
|
|
|
// No blank-line separator found — treat entire response as scene.
|
|
return array(
|
|
'scene' => trim( $raw ),
|
|
'caption' => '',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Extract the first paragraph from HTML content.
|
|
*
|
|
* Removes all tags and captures the text up to the first double-newline
|
|
* or 200 characters, whichever comes first.
|
|
*
|
|
* @param string $html HTML content.
|
|
* @return string Plain text of the first paragraph.
|
|
*/
|
|
public static function extract_first_paragraph( string $html ): string {
|
|
$text = wp_strip_all_tags( $html );
|
|
$paras = preg_split( '/\n\n+/', $text );
|
|
$para = isset( $paras[0] ) ? trim( $paras[0] ) : '';
|
|
|
|
if ( strlen( $para ) > 200 ) {
|
|
$para = substr( $para, 0, 200 ) . '...';
|
|
}
|
|
|
|
return $para;
|
|
}
|
|
|
|
/**
|
|
* Rule-based fallback when LLM rewriting is unavailable.
|
|
*
|
|
* Kept deliberately simple — this only runs if OpenRouter is down.
|
|
*
|
|
* @param string $title Main heading / topic.
|
|
* @param string $supporting_text Additional context.
|
|
* @return array{scene: string, caption: string} Scene for image gen, caption for HTML.
|
|
*/
|
|
public static function synthesize_visual_concepts_fallback( string $title, string $supporting_text ): array {
|
|
// Fallback produces a simple comic concept when the LLM is unavailable.
|
|
$scene = sprintf(
|
|
'A cartoon scientist in a lab coat looking bewildered while examining something related to: %s.',
|
|
$title
|
|
);
|
|
|
|
return array(
|
|
'scene' => trim( $scene ),
|
|
'caption' => 'Science is full of surprises.',
|
|
);
|
|
}
|
|
}
|