writer_llm = $writer_llm; $this->cost_tracker = $cost_tracker; } /** * Ask the writer LLM to revise the current draft given the editor's critique. * * Dispatches a targeted revision call via the injected writer_llm provider. * Cost is reserved before dispatch via the existing OpenRouter provider * seam (Cost_Governor::open_chat_reservation() is called inside * send_chat_completion) and logged via the shared cost tracker. * Returns the prior draft unchanged on empty LLM response (never silent * pass of an empty revision). * * Side effects: one OpenRouter API call, one generation_log row, two * run_stages transitions (start→done for role='writer'). * * @param int $round 1-based round number (for logging and stage snapshot). * @param string $run_id Pipeline run UUID. * @param string $item_key Article-scoped stage item key. * @param string $current Current draft HTML to revise. * @param PRAutoBlogger_Article_Idea $idea Article idea (for topic/title context). * @param string $editor_notes Chief editor critique to address. * @return string Revised HTML content, or $current if the LLM returned empty. */ public function call( int $round, string $run_id, string $item_key, string $current, PRAutoBlogger_Article_Idea $idea, string $editor_notes ): string { PRAutoBlogger_Run_Stage_State::start( $run_id, 'editorial', 'writer', $item_key ); PRAutoBlogger_Logger::instance()->info( sprintf( 'Editorial revision: round %d — dispatching writer revision.', $round ), 'editorial-loop' ); $model = (string) get_option( 'prautoblogger_writing_model', PRAUTOBLOGGER_DEFAULT_WRITING_MODEL ); $messages = array( array( 'role' => 'system', 'content' => PRAutoBlogger_Content_Prompts::build_revision_system( $idea ), ), array( 'role' => 'user', 'content' => PRAutoBlogger_Content_Prompts::build_revision_user( $current, $editor_notes ), ), ); $response = $this->writer_llm->send_chat_completion( $messages, $model, array( 'temperature' => 0.5, 'max_tokens' => 5000, 'stage' => 'editorial', 'prompt_key' => 'content.revision', ) ); $this->cost_tracker->log_api_call( null, 'editorial', $this->writer_llm->get_provider_name(), (string) ( $response['model'] ?? $model ), (int) ( $response['prompt_tokens'] ?? 0 ), (int) ( $response['completion_tokens'] ?? 0 ), 'success', '', null, 'content.revision' ); $revised = trim( (string) ( $response['content'] ?? '' ) ); if ( '' === $revised ) { PRAutoBlogger_Logger::instance()->warning( sprintf( 'Editorial revision: round %d writer returned empty content; retaining prior draft.', $round ), 'editorial-loop' ); $revised = $current; } PRAutoBlogger_Run_Stage_State::done( $run_id, 'editorial', 'writer', $item_key, (string) wp_json_encode( array( 'round' => $round ) ) ); return $revised; } }