feat: Wave 2 eval harness + image pipeline instrumentation (v0.13.0)
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
This commit is contained in:
91
includes/core/class-image-scene-parser.php
Normal file
91
includes/core/class-image-scene-parser.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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.',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user