# WP Agentic Writer: Brave Search Integration Implementation Plan
**Date:** January 29, 2026
**Status:** Planning Phase
**Integration Type:** Seamless addition to existing plugin architecture
---
## Executive Summary
This document outlines the complete implementation plan for integrating **Brave Search API** into WP Agentic Writer as an alternative web search provider alongside the existing OpenRouter `:online` models. The integration follows the plugin's existing architecture patterns and provides users with flexible, cost-effective web research capabilities.
**Key Design Principle:** Brave Search API is positioned as an **alternative provider** to OpenRouter's online models, giving users choice based on cost, performance, and feature requirements.
---
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Settings Integration](#settings-integration)
3. [Database Schema](#database-schema)
4. [Provider Architecture](#provider-architecture)
5. [REST API Endpoints](#rest-api-endpoints)
6. [Frontend Integration](#frontend-integration)
7. [Agent Integration](#agent-integration)
8. [Cost Tracking Integration](#cost-tracking-integration)
9. [Implementation Phases](#implementation-phases)
10. [File Structure](#file-structure)
11. [Testing Strategy](#testing-strategy)
---
## Architecture Overview
### Current Plugin Structure
```
wp-agentic-writer/
├── includes/
│ ├── class-openrouter-provider.php ← Existing AI provider
│ ├── class-gutenberg-sidebar.php ← Main REST API handler
│ ├── class-settings.php ← Settings management
│ ├── class-cost-tracker.php ← Cost tracking system
│ └── class-markdown-parser.php ← Content parsing
├── assets/
│ └── js/
│ └── sidebar.js ← Frontend React app
└── views/
└── settings/
└── tab-models.php ← Model settings UI
```
### Integration Points
```
┌─────────────────────────────────────────────────────────────────┐
│ SETTINGS PANEL │
│ │
│ API Configuration │
│ ├── OpenRouter API Key [sk-or-v1-...] │
│ └── Brave Search API Key [BSA...] ← NEW │
│ │
│ Web Search Provider │
│ ├── ○ OpenRouter :online models (perplexity, etc) │
│ └── ○ Brave Search API (independent index) ← NEW │
│ │
│ [Only show if Brave API key is set] │
└─────────────────────────────────────────────────────────────────┘
```
---
## Settings Integration
### 1. Settings Schema Extension
**File:** `includes/class-settings.php`
Add Brave Search settings to existing settings array:
```php
// In sanitize_settings() method - line ~320
$sanitized['openrouter_api_key'] = trim( $input['openrouter_api_key'] ?? '' );
// ADD NEW:
$sanitized['brave_api_key'] = trim( $input['brave_api_key'] ?? '' );
$sanitized['brave_api_tier'] = sanitize_text_field( $input['brave_api_tier'] ?? 'base_ai' );
$sanitized['web_search_provider'] = sanitize_text_field( $input['web_search_provider'] ?? 'openrouter' );
$sanitized['brave_search_enabled'] = isset( $input['brave_search_enabled'] ) ? 1 : 0;
$sanitized['brave_cache_enabled'] = isset( $input['brave_cache_enabled'] ) ? 1 : 0;
$sanitized['brave_cache_duration_days'] = absint( $input['brave_cache_duration_days'] ?? 30 );
$sanitized['brave_monthly_budget'] = floatval( $input['brave_monthly_budget'] ?? 50.00 );
$sanitized['brave_include_citations'] = isset( $input['brave_include_citations'] ) ? 1 : 0;
```
### 2. Settings UI - API Keys Section
**File:** `views/settings/tab-models.php`
Add Brave API key field after OpenRouter API key (around line 480):
```php
```
### 3. Settings UI - Web Search Provider Selection
Add new section after model presets:
```php
```
---
## Database Schema
### New Tables
Create three new tables for Brave Search integration. Add to plugin activation hook.
**File:** `wp-agentic-writer.php` (activation hook)
```php
function wp_agentic_writer_activate() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Existing tables...
// NEW: Brave Search tables
$sql_searches = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpaw_searches (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
post_id BIGINT NOT NULL,
search_query VARCHAR(500) NOT NULL,
search_number INT,
total_searches_for_post INT,
results_count INT,
results_json LONGTEXT,
top_result_title VARCHAR(255),
top_result_url VARCHAR(500),
cost DECIMAL(10, 4),
api_tier VARCHAR(50),
cache_enabled TINYINT DEFAULT 1,
cache_expires_at TIMESTAMP NULL,
cache_hit TINYINT DEFAULT 0,
search_category VARCHAR(100),
status VARCHAR(30) DEFAULT 'completed',
error_message TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_post (post_id),
KEY idx_query (search_query(255)),
KEY idx_cache_expires (cache_expires_at),
KEY idx_status (status)
) $charset_collate;";
$sql_citations = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpaw_citations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
post_id BIGINT NOT NULL,
citation_number INT NOT NULL,
citation_text VARCHAR(500),
context_excerpt TEXT,
search_id BIGINT,
source_url VARCHAR(500) NOT NULL,
source_title VARCHAR(255),
source_domain VARCHAR(100),
source_type VARCHAR(50),
result_position INT,
article_section VARCHAR(100),
added_by VARCHAR(50),
verified TINYINT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
KEY idx_post (post_id),
KEY idx_citation_number (post_id, citation_number),
KEY idx_source_domain (source_domain)
) $charset_collate;";
$sql_cache = "CREATE TABLE IF NOT EXISTS {$wpdb->prefix}wpaw_search_cache (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
search_query_normalized VARCHAR(500) NOT NULL,
search_category VARCHAR(100),
cache_key VARCHAR(64),
results_json LONGTEXT,
result_count INT,
cached_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP,
hit_count INT DEFAULT 0,
cost_saved DECIMAL(10, 4) DEFAULT 0,
quality_score DECIMAL(3,2),
UNIQUE KEY unique_query_category (search_query_normalized(255), search_category(100)),
KEY idx_expires (expires_at),
KEY idx_hit_count (hit_count)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql_searches );
dbDelta( $sql_citations );
dbDelta( $sql_cache );
}
```
**Also add to:** `CREATE_TABLE.sql` for manual creation
---
## Provider Architecture
### New File: `includes/class-brave-search-provider.php`
Create a new provider class following the same pattern as OpenRouter provider:
```php
api_key = $settings['brave_api_key'] ?? '';
$this->tier = $settings['brave_api_tier'] ?? 'base_ai';
}
/**
* Perform web search
*
* @param string $query Search query
* @param array $options Search options
* @return array|WP_Error Search results or error
*/
public function search( $query, $options = array() ) {
if ( empty( $this->api_key ) ) {
return new WP_Error( 'no_api_key', 'Brave Search API key not configured' );
}
// Check cache first
if ( ! empty( $options['use_cache'] ) ) {
$cached = $this->get_cached_result( $query, $options['category'] ?? null );
if ( $cached ) {
return array(
'results' => $cached['results'],
'from_cache' => true,
'cache_age_hours' => $cached['age_hours'],
'cost' => 0,
);
}
}
// Check budget
$budget_check = $this->check_budget();
if ( is_wp_error( $budget_check ) ) {
return $budget_check;
}
// Call API
$response = $this->call_api( $query, $options );
if ( is_wp_error( $response ) ) {
return $response;
}
// Calculate cost
$cost = $this->calculate_cost();
// Store search record
$search_id = $this->store_search( $query, $response, $cost, $options );
// Cache results
if ( ! empty( $options['use_cache'] ) ) {
$this->cache_results( $query, $response, $options );
}
// Track cost
$this->track_cost( $cost, $options['post_id'] ?? 0 );
return array(
'results' => $response,
'from_cache' => false,
'search_id' => $search_id,
'cost' => $cost,
'result_count' => count( $response['web']['results'] ?? array() ),
);
}
/**
* Call Brave Search API
*/
private function call_api( $query, $options = array() ) {
$endpoint = $this->api_base . '/web/search';
$params = array(
'q' => $query,
'count' => $options['count'] ?? 10,
'safesearch' => 'moderate',
'search_lang' => $options['language'] ?? 'en',
'country' => $options['country'] ?? 'US',
);
$response = wp_remote_get(
add_query_arg( $params, $endpoint ),
array(
'headers' => array(
'X-Subscription-Token' => $this->api_key,
'Accept' => 'application/json',
),
'timeout' => 30,
)
);
if ( is_wp_error( $response ) ) {
return $response;
}
$status = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 429 === $status ) {
return new WP_Error( 'rate_limited', 'Brave API rate limit exceeded' );
} elseif ( 401 === $status ) {
return new WP_Error( 'invalid_api_key', 'Invalid Brave API key' );
} elseif ( 200 !== $status ) {
return new WP_Error( 'api_error', 'Brave API error: ' . $status );
}
return $body;
}
/**
* Check cache for existing results
*/
private function get_cached_result( $query, $category = null ) {
global $wpdb;
$cache_key = sha1( strtolower( trim( $query ) ) );
$cached = $wpdb->get_row( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpaw_search_cache
WHERE cache_key = %s
AND expires_at > NOW()
ORDER BY hit_count DESC
LIMIT 1",
$cache_key
) );
if ( $cached ) {
// Update cache stats
$wpdb->update(
$wpdb->prefix . 'wpaw_search_cache',
array(
'hit_count' => $cached->hit_count + 1,
'cost_saved' => $cached->cost_saved + $this->calculate_cost(),
),
array( 'id' => $cached->id )
);
$age_seconds = time() - strtotime( $cached->cached_at );
return array(
'results' => json_decode( $cached->results_json, true ),
'age_hours' => ceil( $age_seconds / 3600 ),
);
}
return null;
}
/**
* Store search in database
*/
private function store_search( $query, $response, $cost, $options = array() ) {
global $wpdb;
$top_result = $response['web']['results'][0] ?? null;
$wpdb->insert(
$wpdb->prefix . 'wpaw_searches',
array(
'post_id' => $options['post_id'] ?? 0,
'search_query' => $query,
'search_number' => $options['search_number'] ?? 1,
'total_searches_for_post' => $options['total_searches'] ?? 1,
'results_count' => count( $response['web']['results'] ?? array() ),
'results_json' => wp_json_encode( $response ),
'top_result_title' => $top_result['title'] ?? null,
'top_result_url' => $top_result['url'] ?? null,
'cost' => $cost,
'api_tier' => $this->tier,
'search_category' => $options['category'] ?? 'general',
'status' => 'completed',
)
);
return $wpdb->insert_id;
}
/**
* Cache search results
*/
private function cache_results( $query, $response, $options = array() ) {
global $wpdb;
$settings = get_option( 'wp_agentic_writer_settings', array() );
$cache_days = absint( $settings['brave_cache_duration_days'] ?? 30 );
$cache_key = sha1( strtolower( trim( $query ) ) );
$wpdb->insert(
$wpdb->prefix . 'wpaw_search_cache',
array(
'search_query_normalized' => strtolower( trim( $query ) ),
'search_category' => $options['category'] ?? null,
'cache_key' => $cache_key,
'results_json' => wp_json_encode( $response ),
'result_count' => count( $response['web']['results'] ?? array() ),
'expires_at' => gmdate( 'Y-m-d H:i:s', strtotime( "+{$cache_days} days" ) ),
'quality_score' => 0.9,
)
);
}
/**
* Calculate cost per search
*/
private function calculate_cost() {
$tiers = array(
'free' => 0,
'base_ai' => 0.005,
'pro_ai' => 0.009,
);
return $tiers[ $this->tier ] ?? 0;
}
/**
* Check monthly budget
*/
private function check_budget() {
global $wpdb;
$settings = get_option( 'wp_agentic_writer_settings', array() );
$budget_limit = floatval( $settings['brave_monthly_budget'] ?? 50.00 );
$monthly_cost = $wpdb->get_var(
"SELECT COALESCE(SUM(cost), 0) FROM {$wpdb->prefix}wpaw_searches
WHERE MONTH(created_at) = MONTH(NOW())
AND YEAR(created_at) = YEAR(NOW())"
);
if ( $monthly_cost >= $budget_limit ) {
return new WP_Error(
'budget_exceeded',
sprintf( 'Monthly Brave Search budget of $%.2f exceeded', $budget_limit )
);
}
return true;
}
/**
* Track cost in cost tracker
*/
private function track_cost( $cost, $post_id ) {
do_action(
'wp_aw_after_api_request',
$post_id,
'brave-search',
'web_search',
0, // No input tokens
0, // No output tokens
$cost
);
}
}
```
---
## REST API Endpoints
### Add to `includes/class-gutenberg-sidebar.php`
Add new REST endpoints for Brave Search:
```php
// In register_rest_routes() method, add:
// Brave Search endpoint
register_rest_route(
'wp-agentic-writer/v1',
'/brave-search',
array(
'methods' => 'POST',
'callback' => array( $this, 'handle_brave_search' ),
'permission_callback' => array( $this, 'check_permissions' ),
'args' => array(
'query' => array(
'required' => true,
'type' => 'string',
),
'postId' => array(
'required' => true,
'type' => 'integer',
),
'category' => array(
'type' => 'string',
'default' => 'general',
),
'useCache' => array(
'type' => 'boolean',
'default' => true,
),
),
)
);
// Get searches for post
register_rest_route(
'wp-agentic-writer/v1',
'/searches/(?P\d+)',
array(
'methods' => 'GET',
'callback' => array( $this, 'handle_get_searches' ),
'permission_callback' => array( $this, 'check_permissions' ),
)
);
```
### Handler Methods
```php
/**
* Handle Brave Search request
*/
public function handle_brave_search( $request ) {
$query = $request->get_param( 'query' );
$post_id = $request->get_param( 'postId' );
$category = $request->get_param( 'category' );
$use_cache = $request->get_param( 'useCache' );
$provider = WP_Agentic_Writer_Brave_Search_Provider::get_instance();
$result = $provider->search(
$query,
array(
'post_id' => $post_id,
'category' => $category,
'use_cache' => $use_cache,
)
);
if ( is_wp_error( $result ) ) {
return new WP_Error(
$result->get_error_code(),
$result->get_error_message(),
array( 'status' => 400 )
);
}
return rest_ensure_response( $result );
}
/**
* Get all searches for a post
*/
public function handle_get_searches( $request ) {
global $wpdb;
$post_id = $request->get_param( 'post_id' );
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error( 'unauthorized', 'Not allowed', array( 'status' => 403 ) );
}
$searches = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpaw_searches
WHERE post_id = %d
ORDER BY created_at DESC",
$post_id
) );
$citations = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}wpaw_citations
WHERE post_id = %d
ORDER BY citation_number ASC",
$post_id
) );
return rest_ensure_response( array(
'searches' => $searches,
'citations' => $citations,
'total_cost' => array_sum( array_map( function( $s ) {
return floatval( $s->cost );
}, $searches ) ),
) );
}
```
---
## Frontend Integration
### Update `assets/js/sidebar.js`
Add Brave Search support to the frontend:
```javascript
// Add to state management (around line 50)
const [webSearchProvider, setWebSearchProvider] = useState('openrouter');
const [braveSearchEnabled, setBraveSearchEnabled] = useState(false);
// Load settings on mount
useEffect(() => {
const settings = wpAgenticWriter.settings || {};
setWebSearchProvider(settings.web_search_provider || 'openrouter');
setBraveSearchEnabled(settings.brave_search_enabled || false);
}, []);
// Add Brave Search function
const performBraveSearch = async (query, category = 'general') => {
try {
const response = await fetch(wpAgenticWriter.apiUrl + '/brave-search', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpAgenticWriter.nonce,
},
body: JSON.stringify({
query: query,
postId: postId,
category: category,
useCache: true,
}),
});
if (!response.ok) {
throw new Error('Brave Search failed');
}
const data = await response.json();
// Track cost
if (data.cost) {
setCost(prev => ({ ...prev, session: prev.session + data.cost }));
}
return data;
} catch (error) {
console.error('Brave Search error:', error);
return null;
}
};
// Modify article generation to use selected provider
// In sendMessage() or executePlanFromCard(), check provider:
if (webSearchProvider === 'brave' && braveSearchEnabled) {
// Use Brave Search flow
// Perform searches, then pass results to agent
} else {
// Use OpenRouter :online models
// Existing flow
}
```
---
## Agent Integration
### Research Planning
Create helper class for automatic search planning:
**New File:** `includes/class-research-planner.php`
```php
"$topic what is overview",
'category' => 'definition',
'priority' => 'critical',
);
if ( 'medium' === $depth || 'deep' === $depth ) {
// Current state
$searches[] = array(
'query' => "$topic latest news 2024",
'category' => 'news',
'priority' => 'high',
);
// Pricing/features
$searches[] = array(
'query' => "$topic pricing features comparison",
'category' => 'pricing',
'priority' => 'high',
);
}
if ( 'deep' === $depth ) {
// Use cases
$searches[] = array(
'query' => "$topic use cases examples",
'category' => 'examples',
'priority' => 'medium',
);
// Technical details
$searches[] = array(
'query' => "$topic documentation api guide",
'category' => 'technical',
'priority' => 'medium',
);
}
return $searches;
}
}
```
### Citation Management
**New File:** `includes/class-citation-manager.php`
```php
$citation_number,
'source' => $source,
);
$citation_number++;
}
// Add References section
$references = self::generate_references_section( $citations );
$content .= "\n\n" . $references;
return array(
'content' => $content,
'citations' => $citations,
);
}
/**
* Find source in search results
*/
private static function find_source_by_marker( $marker, $search_results ) {
// Match marker to search result URL/title
foreach ( $search_results as $search ) {
if ( empty( $search['results']['web']['results'] ) ) {
continue;
}
foreach ( $search['results']['web']['results'] as $result ) {
$domain = parse_url( $result['url'], PHP_URL_HOST );
if ( stripos( $domain, $marker ) !== false ||
stripos( $result['title'], $marker ) !== false ) {
return array(
'url' => $result['url'],
'title' => $result['title'],
'domain' => $domain,
'snippet' => $result['description'] ?? '',
);
}
}
}
return null;
}
/**
* Store citation in database
*/
private static function store_citation( $post_id, $citation_number, $source ) {
global $wpdb;
$wpdb->insert(
$wpdb->prefix . 'wpaw_citations',
array(
'post_id' => $post_id,
'citation_number' => $citation_number,
'source_url' => $source['url'],
'source_title' => $source['title'],
'source_domain' => $source['domain'],
'added_by' => 'agent_automatic',
)
);
}
/**
* Generate References section HTML
*/
private static function generate_references_section( $citations ) {
if ( empty( $citations ) ) {
return '';
}
$html = "## References\n\n";
foreach ( $citations as $citation ) {
$source = $citation['source'];
$html .= sprintf(
"%d. [%s](%s) - %s\n",
$citation['number'],
$source['title'],
$source['url'],
$source['domain']
);
}
return $html;
}
}
```
---
## Cost Tracking Integration
Brave Search costs are automatically tracked via the existing cost tracking system using the `wp_aw_after_api_request` action hook (already implemented in the provider class).
### Display in Sidebar
Update cost display to show Brave Search costs separately:
```javascript
// In sidebar.js cost display section
AI Models: ${cost.session.toFixed(4)}
{braveSearchCost > 0 && (
Web Search: ${braveSearchCost.toFixed(4)}
)}
Total: ${(cost.session + braveSearchCost).toFixed(4)}
```
---
## Implementation Phases
### Phase 1: Foundation (Week 1)
**Goal:** Basic Brave Search integration
- [ ] Create database tables (activation hook)
- [ ] Create `class-brave-search-provider.php`
- [ ] Add settings fields to `class-settings.php`
- [ ] Add UI to `tab-models.php`
- [ ] Test API connectivity
**Deliverable:** Can perform Brave searches via settings panel test button
### Phase 2: REST API (Week 2)
**Goal:** Frontend can call Brave Search
- [ ] Add REST endpoints to `class-gutenberg-sidebar.php`
- [ ] Add frontend functions to `sidebar.js`
- [ ] Test search from frontend
- [ ] Implement caching logic
- [ ] Test cache hit/miss scenarios
**Deliverable:** Frontend can perform searches and see cached results
### Phase 3: Agent Integration (Week 3)
**Goal:** Agent uses Brave Search during article generation
- [ ] Create `class-research-planner.php`
- [ ] Create `class-citation-manager.php`
- [ ] Integrate into article generation flow
- [ ] Add provider selection logic
- [ ] Test end-to-end: topic → searches → article with citations
**Deliverable:** Can generate articles with Brave Search and citations
### Phase 4: Analytics & Polish (Week 4)
**Goal:** Admin dashboard and optimization
- [ ] Add search analytics tab to settings
- [ ] Display cost breakdown
- [ ] Show cache performance
- [ ] Add budget alerts
- [ ] Implement cache cleanup cron
- [ ] Add error handling and logging
**Deliverable:** Complete admin experience with analytics
### Phase 5: Testing & Documentation (Week 5)
**Goal:** Production-ready
- [ ] Test with free tier limits
- [ ] Test with paid tiers
- [ ] Test budget enforcement
- [ ] Test cache expiry
- [ ] Write user documentation
- [ ] Create migration guide
**Deliverable:** Production-ready feature with docs
---
## File Structure
### New Files to Create
```
includes/
├── class-brave-search-provider.php ← Main provider class
├── class-research-planner.php ← Auto search planning
└── class-citation-manager.php ← Citation extraction
views/settings/
└── tab-brave-analytics.php ← Analytics dashboard (optional)
```
### Files to Modify
```
includes/
├── class-settings.php ← Add Brave settings
├── class-gutenberg-sidebar.php ← Add REST endpoints
└── wp-agentic-writer.php ← Add table creation
views/settings/
└── tab-models.php ← Add Brave UI
assets/js/
└── sidebar.js ← Add Brave search functions
CREATE_TABLE.sql ← Add table schemas
```
---
## Testing Strategy
### Unit Tests
1. **Provider Tests**
- API authentication
- Search query formatting
- Response parsing
- Error handling
2. **Cache Tests**
- Cache hit/miss
- Expiry logic
- Cost savings calculation
3. **Budget Tests**
- Monthly limit enforcement
- Alert triggering
- Cost tracking accuracy
### Integration Tests
1. **Settings Flow**
- Save Brave API key
- Switch providers
- Enable/disable features
2. **Search Flow**
- Perform search
- Cache result
- Reuse cached result
- Track cost
3. **Article Generation Flow**
- Plan searches
- Execute searches
- Generate article with citations
- Add References section
### User Acceptance Tests
1. **Free Tier User**
- Set up with free API key
- Generate article (2-3 searches)
- Verify cost tracking
- Test monthly limit
2. **Paid Tier User**
- Set up with paid API key
- Generate multiple articles
- Verify cache reuse
- Check cost savings
3. **OpenRouter User**
- Keep using OpenRouter :online
- Verify no breaking changes
- Test switching between providers
---
## Migration & Compatibility
### Backward Compatibility
- **No breaking changes:** Existing OpenRouter functionality remains unchanged
- **Opt-in feature:** Brave Search only activates when API key is set
- **Default behavior:** If no Brave API key, falls back to OpenRouter :online
- **Settings migration:** No migration needed - new settings are additive
### Rollout Strategy
1. **Beta Phase:** Release to select users for testing
2. **Documentation:** Create setup guide and comparison chart
3. **Announcement:** Blog post explaining benefits
4. **Support:** Monitor for issues and provide quick fixes
---
## Cost Comparison Example
### Scenario: Generate 10 articles with web research
**OpenRouter :online (Perplexity Sonar Pro)**
- Model: `perplexity/sonar-pro`
- Cost: ~$15 per 1M tokens
- Average: 50K tokens per article with research
- Total: 500K tokens = **$7.50**
**Brave Search API + Standard Model**
- Brave: 3 searches per article × 10 articles = 30 searches
- Brave cost: 30 × $0.005 = **$0.15**
- AI model: `google/gemini-2.0-flash-exp` (free or $0.075/1M)
- AI tokens: 30K tokens per article (no search overhead)
- AI cost: 300K tokens = **$0.02**
- Total: **$0.17** (97% cheaper!)
**With Caching (60% hit rate)**
- Brave: 12 fresh + 18 cached = 12 × $0.005 = **$0.06**
- AI cost: **$0.02**
- Total: **$0.08** (99% cheaper!)
---
## Next Steps
1. **Review this plan** with stakeholders
2. **Set up Brave Search API account** (free tier for testing)
3. **Begin Phase 1 implementation** (database + provider class)
4. **Create test environment** with sample searches
5. **Iterate based on feedback**
---
## Questions & Decisions Needed
1. **Should we support both providers simultaneously?**
- Current plan: User chooses one provider per article
- Alternative: Use both (Brave for facts, OpenRouter for reasoning)
2. **Citation format preference?**
- Current plan: Numbered [1], [2]... with References section
- Alternative: Inline links, footnotes, or custom format
3. **Cache invalidation strategy?**
- Current plan: 30-day automatic expiry
- Alternative: Manual invalidation, topic-based expiry
4. **Budget alert method?**
- Current plan: Email to admin
- Alternative: Dashboard notification, Slack webhook
---
**Document Status:** Ready for Implementation
**Last Updated:** January 29, 2026
**Version:** 1.0