feat(prompts): versioned prompt registry, v1 seed, pinning + stamping
Pipeline v2 Phase 1, commit 2/4. Prompt_Registry (read) + Prompt_Registry_Writer (immutable create/activate/seed) over the new prompts table; canonical v1 bodies extracted verbatim into Prompt_Defaults(+Editorial) — single source for BOTH the seed migration (Migrate_Prompt_Seed_V0180, flag-gated, self-healing) and the in-code fallback, so a missing table renders byte-identical prompts and never fatals. Call sites swapped: Content_Prompts, Analysis_Prompts, Chief_Editor, LLM_Research_Provider. Includes the image-composer seam keys image.rewriter_system + image.style_template (composer PR consumes, must not add its own prompt storage). Pinning: Cost_Tracker::set_run_id() registers the process run context, ensures the runs ledger row, and pins active versions once per run; log_api_call/log_image_generation stamp prompt_version (run pins) + agent_role (stage map) on every generation_log row. Single-pass keeps stage 'draft' and stamps content.single_pass explicitly. Under-300 refactors: Content_Generator's four duplicated span/log blocks consolidated into execute_stage() (301 -> 224 lines, behavior identical); get_avg_tokens_for_stages body moved to Cost_Reporter with a delegating wrapper kept for API compatibility. Tests: byte-identical fallback renders (single_pass/outline/polish/ editor.system), token filling, key coverage incl. stage-map integrity. Agent-Session: pipeline-v2-phase1-engineer-20260612
This commit is contained in:
73
includes/core/class-chief-editor.php
Executable file → Normal file
73
includes/core/class-chief-editor.php
Executable file → Normal file
@@ -9,7 +9,7 @@ declare(strict_types=1);
|
||||
* Opik instrumentation: spans per review LLM call.
|
||||
*
|
||||
* Triggered by: PRAutoBlogger_Article_Worker.
|
||||
* Dependencies: LLM_Provider_Interface, Cost_Tracker.
|
||||
* Dependencies: LLM_Provider_Interface, Cost_Tracker, Prompt_Registry.
|
||||
*/
|
||||
class PRAutoBlogger_Chief_Editor {
|
||||
|
||||
@@ -92,54 +92,47 @@ class PRAutoBlogger_Chief_Editor {
|
||||
|
||||
/**
|
||||
* Build system prompt for editorial review.
|
||||
*
|
||||
* v0.18.0: renders from the versioned registry ('editor.system') with a
|
||||
* byte-identical in-code fallback; the admin's extra editor
|
||||
* instructions stay a setting and are injected as a token value.
|
||||
*
|
||||
* @param string $niche Niche description from settings.
|
||||
* @return string System prompt text.
|
||||
*/
|
||||
private function build_system_prompt( string $niche ): string {
|
||||
$prompt = 'You are a senior blog editor';
|
||||
if ( '' !== $niche ) {
|
||||
$prompt .= " specializing in {$niche} content";
|
||||
}
|
||||
$prompt .= ". Review article drafts before publication.\n\n";
|
||||
$instructions = trim( (string) get_option( 'prautoblogger_editor_instructions', '' ) );
|
||||
$instructions_block = '' !== $instructions ? "\nAdditional instructions:\n" . $instructions . "\n" : '';
|
||||
|
||||
$prompt .= "Evaluate on: QUALITY, ACCURACY, SEO, COMPLETENESS, READABILITY.\n";
|
||||
$prompt .= "Respond with JSON: {\n";
|
||||
$prompt .= ' "verdict": "approved" | "revised" | "rejected",' . "\n";
|
||||
$prompt .= ' "quality_score": 0.0-1.0,' . "\n";
|
||||
$prompt .= ' "seo_score": 0.0-1.0,' . "\n";
|
||||
$prompt .= ' "issues": ["issue1", "issue2"],' . "\n";
|
||||
$prompt .= ' "notes": "Editorial notes",' . "\n";
|
||||
$prompt .= ' "revised_content": "Full revised HTML if revised, null otherwise"' . "\n";
|
||||
$prompt .= "}\n\n";
|
||||
|
||||
$prompt .= "Rules:\n";
|
||||
$prompt .= "- APPROVE if quality_score >= 0.7 and seo_score >= 0.6\n";
|
||||
$prompt .= "- REVISE if fixable issues exist — provide full revised HTML\n";
|
||||
$prompt .= "- REJECT if fundamentally flawed\n";
|
||||
$prompt .= "- Preserve formatting, links, lists when revising\n";
|
||||
|
||||
$instructions = trim( (string) get_option( 'prautoblogger_editor_instructions', '' ) );
|
||||
if ( '' !== $instructions ) {
|
||||
$prompt .= "\nAdditional instructions:\n" . $instructions . "\n";
|
||||
}
|
||||
|
||||
return $prompt;
|
||||
return PRAutoBlogger_Prompt_Registry::render(
|
||||
'editor.system',
|
||||
array(
|
||||
'niche_clause' => '' !== $niche ? " specializing in {$niche} content" : '',
|
||||
'instructions_block' => $instructions_block,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build user prompt for editorial review.
|
||||
*
|
||||
* v0.18.0: renders from the versioned registry ('editor.review').
|
||||
*
|
||||
* @param string $content The generated HTML content.
|
||||
* @param PRAutoBlogger_Article_Idea $idea The original article idea.
|
||||
* @return string User prompt text.
|
||||
*/
|
||||
private function build_review_prompt( string $content, PRAutoBlogger_Article_Idea $idea ): string {
|
||||
return sprintf(
|
||||
"Review this article draft:\n\n" .
|
||||
"BRIEF: Title: %s | Topic: %s | Type: %s\n" .
|
||||
"KEY POINTS: %s\n" .
|
||||
"TARGET KEYWORDS: %s\n\n" .
|
||||
"CONTENT:\n%s",
|
||||
$idea->get_suggested_title(),
|
||||
$idea->get_topic(),
|
||||
$idea->get_article_type(),
|
||||
implode( ', ', $idea->get_key_points() ),
|
||||
implode( ', ', $idea->get_target_keywords() ),
|
||||
$content
|
||||
return PRAutoBlogger_Prompt_Registry::render(
|
||||
'editor.review',
|
||||
array(
|
||||
'title' => $idea->get_suggested_title(),
|
||||
'topic' => $idea->get_topic(),
|
||||
'article_type' => $idea->get_article_type(),
|
||||
'key_points' => implode( ', ', $idea->get_key_points() ),
|
||||
'keywords' => implode( ', ', $idea->get_target_keywords() ),
|
||||
'content' => $content,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user