932 lines
29 KiB
Markdown
932 lines
29 KiB
Markdown
# WP Agentic Writer: Model Recommender Implementation Brief
|
||
|
||
## Executive Summary
|
||
|
||
The **Model Recommender** is an interactive guided experience in the plugin settings that asks users 4–5 questions, then **automatically generates and applies** a personalized OpenRouter model configuration matching their budget and use case.
|
||
|
||
**Why it exists:** Users arrive with widely varying budgets (totally free → premium agencies). The 3 presets (Budget/Balanced/Premium) cover 80% of use cases, but the Recommender handles the remaining 20%: ultra-low-budget users, niche workflows, or custom combinations.
|
||
|
||
**Where it lives:** Settings page → "AI Model Configuration" card → Button: "Open Model Recommender"
|
||
|
||
**Output:** A filled-in model configuration that users can save or customize.
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [User Flow](#user-flow)
|
||
2. [System Prompt (Agent Logic)](#system-prompt-agent-logic)
|
||
3. [Question Schema](#question-schema)
|
||
4. [Response JSON Format](#response-json-format)
|
||
5. [Backend Implementation](#backend-implementation)
|
||
6. [Frontend UI Component](#frontend-ui-component)
|
||
7. [Conversation Examples](#conversation-examples)
|
||
8. [Edge Cases & Fallbacks](#edge-cases--fallbacks)
|
||
9. [Development Checklist](#development-checklist)
|
||
|
||
---
|
||
|
||
## User Flow
|
||
|
||
```
|
||
User clicks: [Open Model Recommender] button on settings page
|
||
↓
|
||
Modal opens with welcoming intro text
|
||
↓
|
||
Question 1: "What's your main goal?"
|
||
├─ Dev blog, agency client content, hobby writing, etc.
|
||
↓
|
||
Question 2: "What's your budget per article?"
|
||
├─ <$0.01, $0.01–0.05, $0.05–0.20, >$0.20
|
||
↓
|
||
Question 3: "How many articles per month?"
|
||
├─ 1–3, 4–8, 9–15, 15+
|
||
↓
|
||
Question 4: "Do you need AI-generated images?"
|
||
├─ Yes, No, Optional (depends on image quality)
|
||
↓
|
||
(Optional) Question 5: "Any specific provider preference?"
|
||
├─ No preference, Google Gemini, OpenAI, Anthropic
|
||
↓
|
||
Agent processes answers → generates recommended config JSON
|
||
↓
|
||
Modal displays: "Recommended config: [Name]"
|
||
├─ Chat: [Model]
|
||
├─ Writing: [Model]
|
||
├─ etc.
|
||
├─ Estimated cost: $X/article
|
||
↓
|
||
User: [Apply Config] or [Customize Manually] or [Cancel]
|
||
↓
|
||
Config saved to plugin options / UI refreshed
|
||
```
|
||
|
||
---
|
||
|
||
## System Prompt (Agent Logic)
|
||
|
||
This prompt runs **server-side** to convert user answers into a model configuration.
|
||
|
||
```
|
||
System Prompt for Model Recommender Agent
|
||
═══════════════════════════════════════════════════════════════
|
||
|
||
You are a Model Configuration Recommender for WP Agentic Writer.
|
||
|
||
Your job: Convert user answers (goal, budget, volume, preferences)
|
||
into an optimal OpenRouter model preset for 6 tasks:
|
||
- chat, clarity, planning, writing, refinement, image
|
||
|
||
You MUST:
|
||
1. Respect budget constraints strictly
|
||
2. Optimize cost:quality tradeoff for use case
|
||
3. Prefer Gemini Flash for planning/chat (best value)
|
||
4. Prefer Claude Sonnet for writing (industry standard)
|
||
5. Disable images if budget <$0.01/article
|
||
6. Return ONLY valid JSON (no extra text)
|
||
|
||
Budget tiers (for reference):
|
||
- Ultra (<$0.01/article): Gemini Flash only, no images
|
||
- Budget ($0.01–0.05): DeepSeek + Flash, FLUX.2 klein images
|
||
- Balanced ($0.05–0.20): Gemini Flash + Claude Sonnet, Riverflow images
|
||
- Premium (>$0.20): GPT-5.2/Opus + Flash, FLUX.2 max images
|
||
|
||
User answers will come as:
|
||
{
|
||
"goal": "string",
|
||
"budget_per_article": "string ($0.01–0.05, etc.)",
|
||
"articles_per_month": "number",
|
||
"images_needed": "yes/no/optional",
|
||
"provider_preference": "string or null"
|
||
}
|
||
|
||
Return a JSON object with this schema:
|
||
{
|
||
"preset_name": "string (e.g., 'Budget: Gemini Flash + DeepSeek')",
|
||
"rationale": "string (brief explanation of why this config)",
|
||
"estimated_cost_per_article": number (USD, including 5.5% fee),
|
||
"models": {
|
||
"chat": "model_slug_string",
|
||
"clarity": "model_slug_string",
|
||
"planning": "model_slug_string",
|
||
"writing": "model_slug_string",
|
||
"refinement": "model_slug_string",
|
||
"image": "model_slug_string or null"
|
||
},
|
||
"recommendations": {
|
||
"when_to_use": "string",
|
||
"workflow_expectation": "string"
|
||
}
|
||
}
|
||
|
||
Examples of model slugs (valid on OpenRouter):
|
||
- deepseek-v3
|
||
- google/gemini-3-flash-preview
|
||
- anthropic/claude-3.5-sonnet
|
||
- anthropic/claude-sonnet-4
|
||
- mistral/mistral-small
|
||
- openai/gpt-5.2
|
||
- black-forest-labs/flux.2-klein
|
||
- black-forest-labs/flux.2-max
|
||
- sourceful/riverflow-v2-max
|
||
|
||
If budget <$0.01/article:
|
||
- Recommend: Gemini Flash for ALL tasks
|
||
- Image: null (disabled)
|
||
- Estimated cost: ~$0.008–0.012/article
|
||
|
||
If budget $0.01–0.05/article:
|
||
- Chat/Planning: Gemini Flash
|
||
- Writing/Refinement: Mistral Small or DeepSeek
|
||
- Images: FLUX.2 klein or null
|
||
- Estimated cost: $0.03–0.05/article
|
||
|
||
If budget $0.05–0.20/article:
|
||
- Chat/Clarity/Planning: Gemini Flash
|
||
- Writing/Refinement: Claude Sonnet
|
||
- Images: Riverflow V2 Max or FLUX.2 Pro
|
||
- Estimated cost: $0.10–0.18/article
|
||
|
||
If budget >$0.20/article:
|
||
- Chat/Planning: Gemini Flash (cost doesn't improve)
|
||
- Clarity: Claude Sonnet 4 (nuanced feedback)
|
||
- Writing/Refinement: GPT-5.2 or Claude Opus
|
||
- Images: FLUX.2 max
|
||
- Estimated cost: $0.25–0.50+/article
|
||
|
||
Return valid JSON only. No markdown, no explanations outside the JSON.
|
||
```
|
||
|
||
---
|
||
|
||
## Question Schema
|
||
|
||
### Frontend: Questions Array (React/JS)
|
||
|
||
```javascript
|
||
const recommenderQuestions = [
|
||
{
|
||
id: "goal",
|
||
question: "What's your main goal for this plugin?",
|
||
type: "radio",
|
||
options: [
|
||
{ value: "dev_blog", label: "Dev blog / technical tutorials" },
|
||
{ value: "agency_content", label: "Agency client content" },
|
||
{ value: "hobby_writing", label: "Hobby writing / personal blog" },
|
||
{ value: "marketing", label: "Marketing / sales content" },
|
||
{ value: "research", label: "Research papers / long-form" }
|
||
],
|
||
required: true
|
||
},
|
||
{
|
||
id: "budget_per_article",
|
||
question: "What's your budget limit per article?",
|
||
type: "radio",
|
||
options: [
|
||
{ value: "<0.01", label: "Ultra-budget (< $0.01 / article)" },
|
||
{ value: "0.01-0.05", label: "Budget ($0.01–$0.05 / article)" },
|
||
{ value: "0.05-0.20", label: "Balanced ($0.05–$0.20 / article)" },
|
||
{ value: ">0.20", label: "Premium (> $0.20 / article)" },
|
||
{ value: "no_limit", label: "No budget limit" }
|
||
],
|
||
required: true
|
||
},
|
||
{
|
||
id: "articles_per_month",
|
||
question: "How many articles per month do you plan to generate?",
|
||
type: "radio",
|
||
options: [
|
||
{ value: "1-3", label: "1–3 articles/month" },
|
||
{ value: "4-8", label: "4–8 articles/month" },
|
||
{ value: "9-15", label: "9–15 articles/month" },
|
||
{ value: "15+", label: "15+ articles/month" }
|
||
],
|
||
required: true
|
||
},
|
||
{
|
||
id: "images_needed",
|
||
question: "Do you need AI-generated images?",
|
||
type: "radio",
|
||
options: [
|
||
{ value: "yes", label: "Yes, high-quality hero images" },
|
||
{ value: "optional", label: "Optional / nice-to-have" },
|
||
{ value: "no", label: "No, I'll upload my own" }
|
||
],
|
||
required: true
|
||
},
|
||
{
|
||
id: "provider_preference",
|
||
question: "(Optional) Any provider preference?",
|
||
type: "radio",
|
||
options: [
|
||
{ value: null, label: "No preference (use what's best)" },
|
||
{ value: "google", label: "Google (Gemini)" },
|
||
{ value: "openai", label: "OpenAI (GPT)" },
|
||
{ value: "anthropic", label: "Anthropic (Claude)" }
|
||
],
|
||
required: false
|
||
}
|
||
];
|
||
```
|
||
|
||
---
|
||
|
||
## Response JSON Format
|
||
|
||
### Backend: Agent Response
|
||
|
||
```json
|
||
{
|
||
"preset_name": "Balanced: Gemini Flash + Claude Sonnet + Riverflow",
|
||
"rationale": "Your budget ($0.10–0.20/article) and moderate volume (8 articles/month) fit the Balanced preset perfectly. Gemini Flash handles chat/planning efficiently; Claude Sonnet is the industry standard for long-form writing. Riverflow images provide high quality at flat $0.03/image.",
|
||
"estimated_cost_per_article": 0.1359,
|
||
"models": {
|
||
"chat": "google/gemini-3-flash-preview",
|
||
"clarity": "google/gemini-3-flash-preview",
|
||
"planning": "google/gemini-3-flash-preview",
|
||
"writing": "anthropic/claude-3.5-sonnet",
|
||
"refinement": "anthropic/claude-3.5-sonnet",
|
||
"image": "sourceful/riverflow-v2-max"
|
||
},
|
||
"recommendations": {
|
||
"when_to_use": "Regular blogging (4–10 articles/month), mixed content types, publishing to professional blog or portfolio.",
|
||
"workflow_expectation": "User does one refinement cycle; publishes with high confidence."
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Backend Implementation
|
||
|
||
### 1. PHP Endpoint (WordPress REST API)
|
||
|
||
```php
|
||
<?php
|
||
// File: includes/class-model-recommender.php
|
||
|
||
class Agentic_Writer_Model_Recommender {
|
||
|
||
/**
|
||
* Register REST endpoint
|
||
*/
|
||
public static function register_endpoints() {
|
||
register_rest_route(
|
||
'agentic-writer/v1',
|
||
'/recommend-models',
|
||
[
|
||
'methods' => 'POST',
|
||
'callback' => [ self::class, 'get_recommendation' ],
|
||
'permission_callback' => [ self::class, 'check_permissions' ],
|
||
'args' => [
|
||
'goal' => ['required' => true],
|
||
'budget_per_article' => ['required' => true],
|
||
'articles_per_month' => ['required' => true],
|
||
'images_needed' => ['required' => true],
|
||
'provider_preference' => ['required' => false],
|
||
]
|
||
]
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Get model recommendation from Claude/LLM
|
||
*/
|
||
public static function get_recommendation( $request ) {
|
||
$params = $request->get_json_params();
|
||
|
||
// Build prompt with user answers
|
||
$user_input = self::format_user_input( $params );
|
||
|
||
// Call OpenRouter API (using Claude 3.5 Sonnet for reasoning)
|
||
$recommendation = self::call_recommender_agent( $user_input );
|
||
|
||
if ( is_wp_error( $recommendation ) ) {
|
||
return new WP_REST_Response(
|
||
['error' => $recommendation->get_error_message()],
|
||
500
|
||
);
|
||
}
|
||
|
||
return new WP_REST_Response( $recommendation, 200 );
|
||
}
|
||
|
||
/**
|
||
* Format user answers into structured prompt
|
||
*/
|
||
private static function format_user_input( $params ) {
|
||
return json_encode([
|
||
'goal' => $params['goal'] ?? null,
|
||
'budget_per_article' => $params['budget_per_article'] ?? null,
|
||
'articles_per_month' => (int) $params['articles_per_month'] ?? null,
|
||
'images_needed' => $params['images_needed'] ?? 'no',
|
||
'provider_preference' => $params['provider_preference'] ?? null,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Call OpenRouter API with system prompt + user input
|
||
*/
|
||
private static function call_recommender_agent( $user_input ) {
|
||
$api_key = get_option( 'agentic_writer_openrouter_api_key' );
|
||
|
||
if ( !$api_key ) {
|
||
return new WP_Error(
|
||
'no_api_key',
|
||
'OpenRouter API key not configured.'
|
||
);
|
||
}
|
||
|
||
$system_prompt = self::get_system_prompt();
|
||
|
||
$response = wp_remote_post(
|
||
'https://openrouter.ai/api/v1/chat/completions',
|
||
[
|
||
'headers' => [
|
||
'Authorization' => 'Bearer ' . $api_key,
|
||
'Content-Type' => 'application/json',
|
||
],
|
||
'body' => json_encode([
|
||
'model' => 'anthropic/claude-3.5-sonnet',
|
||
'messages' => [
|
||
[
|
||
'role' => 'system',
|
||
'content' => $system_prompt,
|
||
],
|
||
[
|
||
'role' => 'user',
|
||
'content' => "User answers: {$user_input}\n\nBased on these answers, generate a recommended model configuration.",
|
||
]
|
||
],
|
||
'temperature' => 0.2, // Deterministic
|
||
'max_tokens' => 500,
|
||
]),
|
||
]
|
||
);
|
||
|
||
if ( is_wp_error( $response ) ) {
|
||
return $response;
|
||
}
|
||
|
||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||
|
||
if ( !isset( $body['choices'][0]['message']['content'] ) ) {
|
||
return new WP_Error(
|
||
'invalid_response',
|
||
'Invalid response from OpenRouter API.'
|
||
);
|
||
}
|
||
|
||
$content = $body['choices'][0]['message']['content'];
|
||
|
||
// Parse JSON from response
|
||
$recommendation = json_decode( $content, true );
|
||
|
||
if ( !$recommendation ) {
|
||
return new WP_Error(
|
||
'invalid_json',
|
||
'Could not parse agent recommendation.'
|
||
);
|
||
}
|
||
|
||
return $recommendation;
|
||
}
|
||
|
||
/**
|
||
* Get system prompt (embedded in class)
|
||
*/
|
||
private static function get_system_prompt() {
|
||
return <<<'PROMPT'
|
||
You are a Model Configuration Recommender for WP Agentic Writer.
|
||
|
||
Your job: Convert user answers (goal, budget, volume, preferences)
|
||
into an optimal OpenRouter model preset for 6 tasks:
|
||
- chat, clarity, planning, writing, refinement, image
|
||
|
||
You MUST:
|
||
1. Respect budget constraints strictly
|
||
2. Optimize cost:quality tradeoff for use case
|
||
3. Prefer Gemini Flash for planning/chat (best value)
|
||
4. Prefer Claude Sonnet for writing (industry standard)
|
||
5. Disable images if budget <$0.01/article
|
||
6. Return ONLY valid JSON (no extra text)
|
||
|
||
Budget tiers (for reference):
|
||
- Ultra (<$0.01/article): Gemini Flash only, no images
|
||
- Budget ($0.01–0.05): DeepSeek + Flash, FLUX.2 klein images
|
||
- Balanced ($0.05–0.20): Gemini Flash + Claude Sonnet, Riverflow images
|
||
- Premium (>$0.20): GPT-5.2/Opus + Flash, FLUX.2 max images
|
||
|
||
Valid model slugs (OpenRouter):
|
||
- deepseek-v3
|
||
- google/gemini-3-flash-preview
|
||
- anthropic/claude-3.5-sonnet
|
||
- anthropic/claude-sonnet-4
|
||
- mistral/mistral-small
|
||
- openai/gpt-5.2
|
||
- black-forest-labs/flux.2-klein
|
||
- black-forest-labs/flux.2-max
|
||
- sourceful/riverflow-v2-max
|
||
|
||
Return ONLY this JSON structure (no markdown, no extra text):
|
||
{
|
||
"preset_name": "string",
|
||
"rationale": "string",
|
||
"estimated_cost_per_article": number,
|
||
"models": {
|
||
"chat": "string",
|
||
"clarity": "string",
|
||
"planning": "string",
|
||
"writing": "string",
|
||
"refinement": "string",
|
||
"image": "string or null"
|
||
},
|
||
"recommendations": {
|
||
"when_to_use": "string",
|
||
"workflow_expectation": "string"
|
||
}
|
||
}
|
||
PROMPT;
|
||
}
|
||
|
||
/**
|
||
* Check user permissions
|
||
*/
|
||
public static function check_permissions() {
|
||
return current_user_can( 'manage_options' );
|
||
}
|
||
}
|
||
|
||
// Hook to register on init
|
||
add_action( 'rest_api_init', [ 'Agentic_Writer_Model_Recommender', 'register_endpoints' ] );
|
||
```
|
||
|
||
---
|
||
|
||
## Frontend UI Component
|
||
|
||
### React Component (Gutenberg/Admin UI)
|
||
|
||
```jsx
|
||
// File: components/ModelRecommender.jsx
|
||
|
||
import React, { useState } from 'react';
|
||
import { Button, Modal, RadioControl, Spinner } from '@wordpress/components';
|
||
import apiFetch from '@wordpress/api-fetch';
|
||
|
||
export const ModelRecommender = ({ onApplyConfig }) => {
|
||
const [isOpen, setIsOpen] = useState(false);
|
||
const [currentStep, setCurrentStep] = useState(0);
|
||
const [answers, setAnswers] = useState({
|
||
goal: null,
|
||
budget_per_article: null,
|
||
articles_per_month: null,
|
||
images_needed: 'no',
|
||
provider_preference: null,
|
||
});
|
||
const [loading, setLoading] = useState(false);
|
||
const [recommendation, setRecommendation] = useState(null);
|
||
|
||
const questions = [
|
||
{
|
||
id: 'goal',
|
||
question: 'What\'s your main goal for this plugin?',
|
||
options: [
|
||
{ value: 'dev_blog', label: 'Dev blog / technical tutorials' },
|
||
{ value: 'agency_content', label: 'Agency client content' },
|
||
{ value: 'hobby_writing', label: 'Hobby writing / personal blog' },
|
||
{ value: 'marketing', label: 'Marketing / sales content' },
|
||
{ value: 'research', label: 'Research papers / long-form' },
|
||
],
|
||
},
|
||
{
|
||
id: 'budget_per_article',
|
||
question: 'What\'s your budget limit per article?',
|
||
options: [
|
||
{ value: '<0.01', label: 'Ultra-budget (< $0.01)' },
|
||
{ value: '0.01-0.05', label: 'Budget ($0.01–$0.05)' },
|
||
{ value: '0.05-0.20', label: 'Balanced ($0.05–$0.20)' },
|
||
{ value: '>0.20', label: 'Premium (> $0.20)' },
|
||
{ value: 'no_limit', label: 'No budget limit' },
|
||
],
|
||
},
|
||
{
|
||
id: 'articles_per_month',
|
||
question: 'How many articles per month?',
|
||
options: [
|
||
{ value: '1-3', label: '1–3 articles/month' },
|
||
{ value: '4-8', label: '4–8 articles/month' },
|
||
{ value: '9-15', label: '9–15 articles/month' },
|
||
{ value: '15+', label: '15+ articles/month' },
|
||
],
|
||
},
|
||
{
|
||
id: 'images_needed',
|
||
question: 'Do you need AI-generated images?',
|
||
options: [
|
||
{ value: 'yes', label: 'Yes, high-quality hero images' },
|
||
{ value: 'optional', label: 'Optional / nice-to-have' },
|
||
{ value: 'no', label: 'No, I\'ll upload my own' },
|
||
],
|
||
},
|
||
{
|
||
id: 'provider_preference',
|
||
question: '(Optional) Any provider preference?',
|
||
options: [
|
||
{ value: null, label: 'No preference' },
|
||
{ value: 'google', label: 'Google Gemini' },
|
||
{ value: 'openai', label: 'OpenAI GPT' },
|
||
{ value: 'anthropic', label: 'Anthropic Claude' },
|
||
],
|
||
},
|
||
];
|
||
|
||
const handleAnswerChange = (answerId, value) => {
|
||
setAnswers(prev => ({ ...prev, [answerId]: value }));
|
||
};
|
||
|
||
const handleNext = async () => {
|
||
if (currentStep < questions.length - 1) {
|
||
setCurrentStep(currentStep + 1);
|
||
} else {
|
||
// Submit to backend
|
||
await submitRecommendation();
|
||
}
|
||
};
|
||
|
||
const submitRecommendation = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const rec = await apiFetch({
|
||
path: '/agentic-writer/v1/recommend-models',
|
||
method: 'POST',
|
||
data: answers,
|
||
});
|
||
setRecommendation(rec);
|
||
} catch (error) {
|
||
alert('Error generating recommendation: ' + error.message);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleApply = () => {
|
||
onApplyConfig(recommendation);
|
||
setIsOpen(false);
|
||
setCurrentStep(0);
|
||
setRecommendation(null);
|
||
};
|
||
|
||
const currentQuestion = questions[currentStep];
|
||
const isAnswered = answers[currentQuestion.id] !== null;
|
||
|
||
return (
|
||
<>
|
||
<Button
|
||
variant="primary"
|
||
onClick={() => setIsOpen(true)}
|
||
style={{ marginTop: '10px' }}
|
||
>
|
||
Open Model Recommender
|
||
</Button>
|
||
|
||
{isOpen && (
|
||
<Modal
|
||
title="AI Model Recommender"
|
||
onRequestClose={() => setIsOpen(false)}
|
||
style={{ width: '500px' }}
|
||
>
|
||
{recommendation ? (
|
||
// Results view
|
||
<div style={{ padding: '20px' }}>
|
||
<h3>Recommended Configuration</h3>
|
||
<p><strong>{recommendation.preset_name}</strong></p>
|
||
<p><em>{recommendation.rationale}</em></p>
|
||
|
||
<table style={{ width: '100%', marginTop: '20px', borderCollapse: 'collapse' }}>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Chat</strong></td>
|
||
<td>{recommendation.models.chat}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Clarity</strong></td>
|
||
<td>{recommendation.models.clarity}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Planning</strong></td>
|
||
<td>{recommendation.models.planning}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Writing</strong></td>
|
||
<td>{recommendation.models.writing}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Refinement</strong></td>
|
||
<td>{recommendation.models.refinement}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Image</strong></td>
|
||
<td>{recommendation.models.image || 'Disabled'}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<p style={{ marginTop: '20px' }}>
|
||
<strong>Estimated cost:</strong> ${recommendation.estimated_cost_per_article.toFixed(4)}/article
|
||
</p>
|
||
|
||
<div style={{ marginTop: '20px', display: 'flex', gap: '10px' }}>
|
||
<Button
|
||
variant="primary"
|
||
onClick={handleApply}
|
||
>
|
||
Apply This Configuration
|
||
</Button>
|
||
<Button
|
||
variant="secondary"
|
||
onClick={() => {
|
||
setRecommendation(null);
|
||
setCurrentStep(0);
|
||
}}
|
||
>
|
||
Go Back
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
// Questions view
|
||
<div style={{ padding: '20px' }}>
|
||
<p><strong>Question {currentStep + 1} of {questions.length}</strong></p>
|
||
<h3>{currentQuestion.question}</h3>
|
||
|
||
<RadioControl
|
||
selected={answers[currentQuestion.id]}
|
||
options={currentQuestion.options}
|
||
onChange={(value) => handleAnswerChange(currentQuestion.id, value)}
|
||
/>
|
||
|
||
<div style={{ marginTop: '20px', display: 'flex', gap: '10px' }}>
|
||
<Button
|
||
variant="primary"
|
||
onClick={handleNext}
|
||
disabled={!isAnswered || loading}
|
||
>
|
||
{loading ? <Spinner /> : (currentStep === questions.length - 1 ? 'Get Recommendation' : 'Next')}
|
||
</Button>
|
||
{currentStep > 0 && (
|
||
<Button
|
||
variant="secondary"
|
||
onClick={() => setCurrentStep(currentStep - 1)}
|
||
>
|
||
Back
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Modal>
|
||
)}
|
||
</>
|
||
);
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
## Conversation Examples
|
||
|
||
### Example 1: Ultra-Budget Dev Blogger
|
||
|
||
**User answers:**
|
||
- Goal: Dev blog
|
||
- Budget: <$0.01/article
|
||
- Volume: 2 articles/month
|
||
- Images: No
|
||
- Provider: No preference
|
||
|
||
**Agent response:**
|
||
```json
|
||
{
|
||
"preset_name": "Ultra-Budget: Gemini Flash Only",
|
||
"rationale": "Your ultra-low budget and dev blog focus make Gemini 3 Flash the perfect fit. It's optimized for coding + reasoning at nearly free cost. No images keeps cost minimal.",
|
||
"estimated_cost_per_article": 0.0082,
|
||
"models": {
|
||
"chat": "google/gemini-3-flash-preview",
|
||
"clarity": "google/gemini-3-flash-preview",
|
||
"planning": "google/gemini-3-flash-preview",
|
||
"writing": "google/gemini-3-flash-preview",
|
||
"refinement": "google/gemini-3-flash-preview",
|
||
"image": null
|
||
},
|
||
"recommendations": {
|
||
"when_to_use": "Solo dev bloggers with minimal budget; testing the plugin before investing.",
|
||
"workflow_expectation": "Output may need 1–2 editing rounds before publishing."
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Example 2: Agency with Quality Focus
|
||
|
||
**User answers:**
|
||
- Goal: Agency client content
|
||
- Budget: >$0.20/article
|
||
- Volume: 20 articles/month
|
||
- Images: Yes, high-quality
|
||
- Provider: No preference
|
||
|
||
**Agent response:**
|
||
```json
|
||
{
|
||
"preset_name": "Premium: GPT-5.2 + Gemini Flash + FLUX.2 max",
|
||
"rationale": "Your agency volume and quality-first mindset justify premium models. GPT-5.2 delivers publication-ready prose; FLUX.2 max generates hero images. Your clients will notice the difference.",
|
||
"estimated_cost_per_article": 0.3142,
|
||
"models": {
|
||
"chat": "google/gemini-3-flash-preview",
|
||
"clarity": "anthropic/claude-sonnet-4",
|
||
"planning": "google/gemini-3-flash-preview",
|
||
"writing": "openai/gpt-5.2",
|
||
"refinement": "openai/gpt-5.2",
|
||
"image": "black-forest-labs/flux.2-max"
|
||
},
|
||
"recommendations": {
|
||
"when_to_use": "Agencies publishing 15+ articles/month; thought leadership, flagship posts, client deliverables.",
|
||
"workflow_expectation": "Minimal editing required; publish with high confidence."
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### Example 3: Balanced Hobbyist
|
||
|
||
**User answers:**
|
||
- Goal: Hobby writing / personal blog
|
||
- Budget: $0.05–$0.20/article
|
||
- Volume: 4 articles/month
|
||
- Images: Optional
|
||
- Provider: Google Gemini
|
||
|
||
**Agent response:**
|
||
```json
|
||
{
|
||
"preset_name": "Balanced: Gemini Flash + Claude Sonnet + Riverflow",
|
||
"rationale": "Your modest volume and provider preference align perfectly with the Balanced preset. All-Gemini planning + Claude writing = great quality at fair cost. Riverflow images optional but affordable.",
|
||
"estimated_cost_per_article": 0.1359,
|
||
"models": {
|
||
"chat": "google/gemini-3-flash-preview",
|
||
"clarity": "google/gemini-3-flash-preview",
|
||
"planning": "google/gemini-3-flash-preview",
|
||
"writing": "anthropic/claude-3.5-sonnet",
|
||
"refinement": "anthropic/claude-3.5-sonnet",
|
||
"image": "sourceful/riverflow-v2-max"
|
||
},
|
||
"recommendations": {
|
||
"when_to_use": "Regular hobby blogging or personal portfolio; professional output without premium cost.",
|
||
"workflow_expectation": "One refinement cycle typical; polished final product."
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Edge Cases & Fallbacks
|
||
|
||
### 1. API failure (OpenRouter down)
|
||
|
||
**Fallback behavior:**
|
||
```php
|
||
// If recommender API fails, load from static presets
|
||
if ( is_wp_error( $recommendation ) ) {
|
||
$recommendation = self::get_fallback_preset_by_budget(
|
||
$params['budget_per_article']
|
||
);
|
||
}
|
||
```
|
||
|
||
### 2. Budget = "no_limit"
|
||
|
||
**Logic:**
|
||
- Treat as ">$0.20" (Premium tier).
|
||
- Recommend frontier models + best images.
|
||
|
||
### 3. Invalid JSON from agent
|
||
|
||
**Fallback:**
|
||
- Log error to error_log.
|
||
- Show message: "Recommender had trouble generating a config. Try adjusting your answers."
|
||
- Offer manual preset picker instead.
|
||
|
||
### 4. User cancels mid-flow
|
||
|
||
**Behavior:**
|
||
- Modal closes cleanly.
|
||
- No settings changed.
|
||
- State resets for next usage.
|
||
|
||
---
|
||
|
||
## Development Checklist
|
||
|
||
- [ ] **Backend setup**
|
||
- [ ] Create `includes/class-model-recommender.php`
|
||
- [ ] Register REST endpoint `/agentic-writer/v1/recommend-models`
|
||
- [ ] Implement `call_recommender_agent()` with OpenRouter API
|
||
- [ ] Add error handling + fallbacks
|
||
- [ ] Test with real OpenRouter key
|
||
|
||
- [ ] **System prompt**
|
||
- [ ] Write and refine system prompt (see above)
|
||
- [ ] Test with 3–5 example user inputs
|
||
- [ ] Verify JSON output is parseable
|
||
- [ ] Ensure cost estimates match model-presets.md
|
||
|
||
- [ ] **Frontend component**
|
||
- [ ] Create React component: `ModelRecommender.jsx`
|
||
- [ ] Implement question flow (5 questions)
|
||
- [ ] Add results display with config summary
|
||
- [ ] Wire up "Apply Configuration" button
|
||
- [ ] Test modal UX (open/close/back/next)
|
||
|
||
- [ ] **Integration**
|
||
- [ ] Add button to settings page → "AI Model Configuration" card
|
||
- [ ] Wire up `onApplyConfig` callback to save settings
|
||
- [ ] Refresh model selectors after apply
|
||
- [ ] Show success toast message
|
||
|
||
- [ ] **Testing**
|
||
- [ ] Test all 5 questions with valid OpenRouter API key
|
||
- [ ] Test budget boundary cases ($0.01, $0.05, $0.20)
|
||
- [ ] Test edge cases (ultra-budget, no-limit, specific providers)
|
||
- [ ] Test API failure + fallback behavior
|
||
- [ ] Test invalid JSON response handling
|
||
- [ ] Test user cancellation mid-flow
|
||
|
||
- [ ] **Documentation**
|
||
- [ ] Add to settings help: "Click 'Open Model Recommender' for personalized setup"
|
||
- [ ] Document system prompt in code comments
|
||
- [ ] Add troubleshooting guide: "Recommender not working? Check API key."
|
||
|
||
---
|
||
|
||
## Security Considerations
|
||
|
||
1. **API Key Protection:**
|
||
- Only server-side calls to OpenRouter (key never exposed to client)
|
||
- REST endpoint requires `manage_options` capability
|
||
|
||
2. **Rate Limiting:**
|
||
- Add WordPress nonce to prevent CSRF
|
||
- Limit calls to 1 per minute per user
|
||
|
||
3. **Input Validation:**
|
||
- Validate all user answers against whitelist
|
||
- Reject invalid budget ranges
|
||
|
||
```php
|
||
// Example: Add nonce validation
|
||
register_rest_route(
|
||
'agentic-writer/v1',
|
||
'/recommend-models',
|
||
[
|
||
'callback' => [ self::class, 'get_recommendation' ],
|
||
'permission_callback' => function () {
|
||
return current_user_can( 'manage_options' )
|
||
&& isset( $_REQUEST['_wpnonce'] )
|
||
&& wp_verify_nonce( $_REQUEST['_wpnonce'], 'agentic_recommender' );
|
||
}
|
||
]
|
||
);
|
||
```
|
||
|
||
---
|
||
|
||
## Cost Estimation for Development
|
||
|
||
| Task | Time | Notes |
|
||
|------|------|-------|
|
||
| Backend endpoint + system prompt | 3–4 hours | Includes testing with OpenRouter |
|
||
| React component + UI | 3–4 hours | Includes modal, form flow, validation |
|
||
| Integration with settings page | 1–2 hours | Wire up button, callbacks, save logic |
|
||
| Testing + refinement | 2–3 hours | Edge cases, error handling |
|
||
| **Total** | **9–13 hours** | Can be split across team |
|
||
|
||
---
|
||
|
||
## Next Steps (Priority Order)
|
||
|
||
1. **Refine system prompt** – Test with Claude to ensure it generates correct JSON
|
||
2. **Build backend endpoint** – Implement `/recommend-models` route with error handling
|
||
3. **Build React component** – Create modal UI with question flow
|
||
4. **Integration** – Wire up to settings page
|
||
5. **Testing** – Full QA with real OpenRouter API
|
||
|
||
---
|
||
|
||
**Document version:** 1.0
|
||
**Date:** January 22, 2026
|
||
**Author:** WP Agentic Writer Product Team
|
||
**Status:** Ready for Development
|