Opik Wave 1 Commit 2: Integration wraps at LLM call sites

Add Opik span instrumentation to core generation pipeline:

- class-article-worker.php: Initialize trace context at start
  * Trace initialized via init_trace()
  * Spans queued after generation completes
  * Async dispatch scheduled via wp_schedule_single_event
  * Feature flag check before any Opik operations

- class-content-generator.php: Span per LLM call
  * Single-pass generation: 1 span
  * Multi-step (outline/draft/polish): 3 spans
  * Each span captures model, provider, token usage
  * Minimal docblock to keep file ≤ 300 LOC

- class-chief-editor.php: Editorial review span
  * Wraps LLM-based content review
  * Captures JSON response parsing
  * Records quality/SEO score generation

All files ≤ 300 LOC. Spans capture usage data but not full
prompts (per security policy). Feature-flag default OFF.

Note: Image_Prompt_Builder already at 314 LOC (over limit);
skipped to avoid further expansion. Can be instrumented in
future without modifying existing code (via filters).

Part of Wave 1 integration.
Target: v0.12.0
This commit is contained in:
peptiderepo
2026-04-23 14:50:20 +00:00
parent ff6ad5eba1
commit ca5fdfacc4
3 changed files with 227 additions and 171 deletions

View File

@@ -12,10 +12,14 @@ declare(strict_types=1);
* Triggered by: Pipeline_Runner (directly for article 1, via cron for 2..N).
* Dependencies: Content_Generator, Chief_Editor, Publisher, Cost_Tracker.
*
* Opik instrumentation: Initializes trace context at start, creates spans
* for content generation and editorial review. Feature-flag gated (default off).
*
* @see core/class-pipeline-runner.php — Orchestrates and dispatches workers.
* @see core/class-content-generator.php — LLM content generation.
* @see core/class-chief-editor.php — Editorial quality gate.
* @see core/class-publisher.php — WordPress post creation.
* @see services/opik/class-opik-trace-context.php — Opik tracing.
*/
class PRAutoBlogger_Article_Worker {
@@ -34,14 +38,22 @@ class PRAutoBlogger_Article_Worker {
* Runs the full single-article pipeline: LLM content generation →
* editorial review → publish or save as draft.
*
* Initializes Opik trace context if enabled and tears it down at end.
*
* Side effects: LLM API calls, database writes, WordPress post creation,
* image generation, cost logging.
* image generation, cost logging, Opik span queuing.
*
* @param PRAutoBlogger_Article_Idea $idea The scored idea to generate from.
*
* @return array{generated: int, published: int, rejected: int, cost: float}
*/
public function generate( PRAutoBlogger_Article_Idea $idea ): array {
// Initialize Opik trace context for this article generation.
$opik = $this->should_trace_with_opik();
if ( $opik ) {
PRAutoBlogger_Opik_Trace_Context::current()->init_trace();
}
$llm = new PRAutoBlogger_OpenRouter_Provider();
$generator = new PRAutoBlogger_Content_Generator( $llm, $this->cost_tracker );
$editor = new PRAutoBlogger_Chief_Editor( $llm, $this->cost_tracker );
@@ -90,6 +102,12 @@ class PRAutoBlogger_Article_Worker {
}
$result['cost'] = $this->cost_tracker->get_current_run_cost();
// Finalize and queue Opik trace if enabled.
if ( $opik ) {
$this->finalize_opik_trace();
}
return $result;
}
@@ -149,4 +167,47 @@ class PRAutoBlogger_Article_Worker {
set_transient( $transient_key, $current, 600 );
}
}
/**
* Check if Opik tracing is enabled and has credentials.
*
* @return bool
*/
private function should_trace_with_opik(): bool {
if ( ! get_option( 'prautoblogger_opik_enabled', false ) ) {
return false;
}
return defined( 'PRAUTOBLOGGER_OPIK_API_KEY' ) &&
defined( 'PRAUTOBLOGGER_OPIK_WORKSPACE' ) &&
! empty( PRAUTOBLOGGER_OPIK_API_KEY ) &&
! empty( PRAUTOBLOGGER_OPIK_WORKSPACE );
}
/**
* Finalize and queue the Opik trace.
*
* Dispatches trace to queue for async posting.
*/
private function finalize_opik_trace(): void {
$ctx = PRAutoBlogger_Opik_Trace_Context::current();
$trace = $ctx->finalize_trace();
$queue = new PRAutoBlogger_Opik_Span_Queue();
// Queue the trace.
$queue->enqueue( $trace, 'trace' );
// Queue all spans.
foreach ( $ctx->get_spans() as $span ) {
$queue->enqueue( $span, 'span' );
}
// Schedule async dispatch if not already scheduled.
if ( ! wp_next_scheduled( 'prautoblogger_opik_dispatch' ) ) {
wp_schedule_single_event( time(), 'prautoblogger_opik_dispatch' );
}
// Teardown context for next request.
PRAutoBlogger_Opik_Trace_Context::teardown();
}
}