ai-content-generation
π―Skillfrom venture-formations/aiprodaily
ai-content-generation skill from venture-formations/aiprodaily
Installation
npx skills add https://github.com/venture-formations/aiprodaily --skill ai-content-generationSkill Details
AI content generation with OpenAI and Claude, callAIWithPrompt usage, prompt storage in app_settings, structured outputs, response format validation, multi-criteria scoring, rate limiting, JSON schema, and AI API best practices. Use when generating content, creating prompts, scoring articles, or working with OpenAI/Claude APIs.
Overview
# AI Content Generation - Integration Guide
Purpose
Comprehensive guide for AI content generation in the AIProDaily platform, covering OpenAI/Claude integration, prompt management, structured outputs, and content scoring systems.
When to Use
Automatically activates when:
- Calling AI APIs (OpenAI, Claude)
- Using
callAIWithPrompt() - Creating or modifying prompts
- Working with
app_settingsAI prompts - Implementing content generation
- Building scoring systems
- Handling AI responses
---
Core Pattern: callAIWithPrompt()
Standard Usage
Location: src/lib/openai.ts
```typescript
import { callAIWithPrompt } from '@/lib/openai'
// Generate article title
const result = await callAIWithPrompt(
'ai_prompt_primary_article_title', // Prompt key in app_settings
newsletterId, // Tenant context
{
// Variables for placeholder replacement
title: post.title,
description: post.description,
content: post.full_article_text
}
)
// result = { headline: "AI-Generated Title" }
```
How It Works
- Loads prompt from
app_settingstable by key + newsletter_id - Replaces placeholders like
{{title}},{{content}}with provided variables - Calls AI API (OpenAI or Claude) with complete request
- Parses response according to
response_formatschema - Returns structured JSON object
Key Features
β Database-driven: All prompts stored in database, not hardcoded
β Tenant-scoped: Each newsletter can customize prompts
β Type-safe: JSON schema enforces response structure
β Flexible: Supports both OpenAI and Claude
β Reusable: Same function for all AI operations
---
Prompt Storage Format
Database Schema
```sql
-- app_settings table
CREATE TABLE app_settings (
key TEXT PRIMARY KEY,
value JSONB NOT NULL,
description TEXT,
newsletter_id UUID NOT NULL,
ai_provider TEXT, -- 'openai' or 'claude'
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
)
```
Complete Prompt Structure
```sql
INSERT INTO app_settings (key, value, newsletter_id, ai_provider, description)
VALUES (
'ai_prompt_primary_article_title',
'{
"model": "gpt-4o",
"temperature": 0.7,
"max_output_tokens": 500,
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "article_title_response",
"strict": true,
"schema": {
"type": "object",
"properties": {
"headline": {
"type": "string",
"description": "The generated article headline"
}
},
"required": ["headline"],
"additionalProperties": false
}
}
},
"messages": [
{
"role": "system",
"content": "You are an expert headline writer for accounting professionals..."
},
{
"role": "user",
"content": "Source Title: {{title}}\n\nSource Content: {{content}}\n\nWrite a compelling headline."
}
]
}'::jsonb,
'newsletter-uuid-here',
'openai',
'Content Generation - Primary Article Title: Generates engaging headlines'
);
```
All parameters stored in database:
model- AI model to usetemperature- Creativity level (0-1)max_output_tokens- Response length limitresponse_format- JSON schema for structured outputmessages- System and user prompts with placeholders
---
Response Format Patterns
Simple String Response
```json
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "simple_response",
"strict": true,
"schema": {
"type": "object",
"properties": {
"result": { "type": "string" }
},
"required": ["result"],
"additionalProperties": false
}
}
}
}
```
Complex Structured Response
```json
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "article_body_response",
"strict": true,
"schema": {
"type": "object",
"properties": {
"headline": { "type": "string" },
"body": { "type": "string" },
"summary": { "type": "string" },
"key_points": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["headline", "body"],
"additionalProperties": false
}
}
}
}
```
Scoring Response
```json
{
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "content_score_response",
"strict": true,
"schema": {
"type": "object",
"properties": {
"score": {
"type": "number",
"minimum": 0,
"maximum": 10
},
"reasoning": { "type": "string" }
},
"required": ["score", "reasoning"],
"additionalProperties": false
}
}
}
}
```
---
Multi-Criteria Scoring System
Overview
Purpose: Evaluate RSS posts using multiple weighted criteria
Location: src/lib/rss-processor.ts
Storage: post_ratings table
Configuration
```typescript
// Criteria settings in app_settings
{
"criteria_enabled_count": 3, // 1-5 criteria
"criteria_1_name": "Interest Level",
"criteria_1_weight": 1.5,
"criteria_2_name": "Relevance",
"criteria_2_weight": 1.5,
"criteria_3_name": "Impact",
"criteria_3_weight": 1.0
}
```
Scoring Process
```typescript
// Each criterion gets separate AI call
for (let i = 1; i <= criteriaCount; i++) {
const promptKey = ai_prompt_criteria_${i}
const weight = settings[criteria_${i}_weight]
// Call AI for this criterion
const result = await callAIWithPrompt(
promptKey,
newsletterId,
{
title: post.title,
description: post.description,
content: post.content
}
)
// Store individual score
await supabaseAdmin
.from('post_ratings')
.insert({
post_id: post.id,
newsletter_id: newsletterId,
criterion_name: criteriaName,
score: result.score, // 0-10
weighted_score: result.score * weight,
reasoning: result.reasoning
})
}
// Calculate total score (sum of weighted scores)
const totalScore = ratings.reduce((sum, r) => sum + r.weighted_score, 0)
```
Example Scoring
```
Criterion 1: Interest Level (weight 1.5) β score 8 β weighted 12.0
Criterion 2: Relevance (weight 1.5) β score 7 β weighted 10.5
Criterion 3: Impact (weight 1.0) β score 6 β weighted 6.0
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Total Score: 28.5
```
---
Prompt Design Best Practices
System Message
```typescript
{
"role": "system",
"content": `You are an expert content writer for accounting professionals.
Your audience is CPAs, accountants, and financial professionals.
Write in a professional yet engaging tone.
Focus on practical, actionable information.
Keep content concise and scannable.`
}
```
User Message with Placeholders
```typescript
{
"role": "user",
"content": `Source Article:
Title: {{title}}
Description: {{description}}
Full Content: {{content}}
Task: Write a 200-300 word article summary that:
- Captures the key takeaways
- Explains why this matters to accountants
- Uses clear, professional language
- Ends with a thought-provoking statement
Output the summary as a JSON object with a "body" field.`
}
```
Temperature Guidelines
```typescript
// Creative content (headlines, summaries)
"temperature": 0.7
// Factual content (analysis, scoring)
"temperature": 0.3
// Consistent output (classifications)
"temperature": 0.1
```
---
Model Selection
OpenAI Models
```typescript
// Fast, cost-effective (most common)
"model": "gpt-4o"
// Latest, most capable
"model": "gpt-4o-2024-11-20"
// Smaller, faster for simple tasks
"model": "gpt-4o-mini"
```
Claude Models
```typescript
// Most capable
"model": "claude-3-5-sonnet-20241022"
// Fast, cost-effective
"model": "claude-3-5-haiku-20241022"
// Older, still powerful
"model": "claude-3-opus-20240229"
```
---
Error Handling
Standard Pattern
```typescript
try {
const result = await callAIWithPrompt(
promptKey,
newsletterId,
variables
)
// Validate response
if (!result || !result.headline) {
throw new Error('Invalid AI response: missing required fields')
}
return result
} catch (error: any) {
console.error('[AI] Error calling AI:', error.message)
// Check for specific errors
if (error.message.includes('rate_limit')) {
console.error('[AI] Rate limit exceeded, implement backoff')
}
if (error.message.includes('context_length')) {
console.error('[AI] Input too long, need to truncate')
}
throw error
}
```
Retry with Backoff
```typescript
async function callAIWithRetry(
promptKey: string,
newsletterId: string,
variables: Record
maxRetries = 2
) {
let retryCount = 0
while (retryCount <= maxRetries) {
try {
return await callAIWithPrompt(promptKey, newsletterId, variables)
} catch (error: any) {
retryCount++
// Don't retry on validation errors
if (error.message.includes('Invalid')) {
throw error
}
if (retryCount > maxRetries) {
throw error
}
console.log([AI] Retry ${retryCount}/${maxRetries} after error)
await new Promise(resolve => setTimeout(resolve, 2000 * retryCount))
}
}
}
```
---
Rate Limiting
OpenAI Limits
Tier 1 (free/trial):
- gpt-4o: 500 requests/day
- gpt-4o-mini: 10,000 requests/day
Tier 2 (paid):
- gpt-4o: 5,000 requests/min
- gpt-4o-mini: 30,000 requests/min
Batching Strategy
```typescript
// Process in batches to avoid rate limits
const BATCH_SIZE = 3
const BATCH_DELAY = 2000 // 2 seconds between batches
const batches = chunkArray(posts, BATCH_SIZE)
for (const batch of batches) {
// Process batch in parallel
await Promise.all(
batch.map(post => generateArticle(post))
)
// Wait before next batch
if (batches.indexOf(batch) < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, BATCH_DELAY))
}
}
console.log([AI] Processed ${posts.length} items in ${batches.length} batches)
```
---
Content Generation Workflows
Article Title Generation
```typescript
const titleResult = await callAIWithPrompt(
'ai_prompt_primary_article_title',
newsletterId,
{
title: rssPost.title,
description: rssPost.description,
content: rssPost.full_article_text
}
)
// Store generated title
await supabaseAdmin
.from('articles')
.insert({
newsletter_id: newsletterId,
campaign_id: campaignId,
rss_post_id: rssPost.id,
headline: titleResult.headline,
article_text: null // Body generated separately
})
```
Article Body Generation
```typescript
const bodyResult = await callAIWithPrompt(
'ai_prompt_primary_article_body',
newsletterId,
{
title: rssPost.title,
headline: article.headline, // Use AI-generated headline
description: rssPost.description,
content: rssPost.full_article_text
}
)
// Update with generated body
await supabaseAdmin
.from('articles')
.update({
article_text: bodyResult.body
})
.eq('id', article.id)
.eq('newsletter_id', newsletterId)
```
Fact-Checking
```typescript
const factCheckResult = await callAIWithPrompt(
'ai_prompt_fact_check',
newsletterId,
{
headline: article.headline,
body: article.article_text,
source_content: article.rss_post.full_article_text
}
)
// Store fact-check score
await supabaseAdmin
.from('articles')
.update({
fact_check_score: factCheckResult.score,
fact_check_reasoning: factCheckResult.reasoning
})
.eq('id', article.id)
.eq('newsletter_id', newsletterId)
```
---
Testing Prompts
Test in Isolation
```typescript
// Create test route: app/api/test/prompt/route.ts
export async function POST(request: NextRequest) {
const { promptKey, variables } = await request.json()
try {
const result = await callAIWithPrompt(
promptKey,
'test-newsletter-id',
variables
)
return NextResponse.json({
success: true,
result
})
} catch (error: any) {
return NextResponse.json({
error: error.message
}, { status: 500 })
}
}
export const maxDuration = 60
```
Validate Response Schema
```typescript
function validateArticleResponse(result: any): boolean {
if (!result) return false
if (typeof result.headline !== 'string') return false
if (typeof result.body !== 'string') return false
if (result.headline.length < 10) return false
if (result.body.length < 50) return false
return true
}
```
---
Best Practices
β DO:
- Store all prompts in
app_settingsdatabase - Use JSON schema for response format validation
- Include clear instructions in system message
- Use placeholders for dynamic content
- Implement retry logic for transient errors
- Batch API calls to respect rate limits
- Validate AI responses before using
- Log AI calls for debugging
- Use appropriate temperature for task
- Test prompts thoroughly before production
β DON'T:
- Hardcode prompts in code
- Skip response validation
- Ignore rate limits
- Use overly complex prompts
- Forget error handling
- Expose API keys client-side
- Use wrong model for task
- Trust AI output blindly
- Skip testing with real data
- Make unbatched parallel calls
---
Troubleshooting
AI Returns Invalid Format
Check:
- JSON schema is correct
strict: trueis set- Instructions are clear
- Model supports structured outputs
Rate Limit Errors
Solutions:
- Implement batching (3-5 requests per batch)
- Add delays between batches (2-5 seconds)
- Use retry with exponential backoff
- Upgrade API tier if needed
Content Quality Issues
Improve:
- Refine system message instructions
- Adjust temperature (lower for consistency)
- Provide better examples in prompt
- Add validation rules
- Use more capable model
Timeout Errors
Fix:
- Reduce max_output_tokens
- Simplify prompt
- Use faster model (gpt-4o-mini, claude-haiku)
- Increase API route maxDuration
---
Reference
Main Function: src/lib/openai.ts - callAIWithPrompt()
Prompt Storage: app_settings table
Response Storage: articles, post_ratings tables
Scoring Logic: src/lib/rss-processor.ts
Related Docs:
docs/AI_PROMPT_SYSTEM_GUIDE.mddocs/OPENAI_RESPONSES_API_GUIDE.mddocs/workflows/MULTI_CRITERIA_SCORING_GUIDE.md
---
Skill Status: ACTIVE β
Line Count: < 500 β
Integration: OpenAI + Claude β