## Problem All 4 marketing tools (contentvariant, subjectlines, platformadapt, factcheck) were calling prepare_chat_style_prompt() which expects request.prompt field. This caused "object has no field 'prompt'" errors in Claude Desktop. ## Root Cause The prepare_prompt() methods were: 1. Building prompt_text string 2. Creating a copy of request 3. Setting request_copy.prompt = prompt_text 4. Calling prepare_chat_style_prompt(request_copy) But ToolRequest (and subclasses) don't have a 'prompt' field, causing AttributeError when prepare_chat_style_prompt tries to access it. ## Solution Changed all prepare_prompt() methods to return the prompt string directly instead of calling prepare_chat_style_prompt(). This is the correct pattern for SimpleTool implementations. ## Files Changed - tools/contentvariant.py: Removed copy() and prepare_chat_style_prompt() call - tools/subjectlines.py: Removed copy() and prepare_chat_style_prompt() call - tools/platformadapt.py: Removed copy() and prepare_chat_style_prompt() call - tools/factcheck.py: Removed copy() and prepare_chat_style_prompt() call ## Testing - Server startup: ✅ All 7 tools load successfully - Tool instantiation: ✅ All tools initialize without errors ## Impact This fixes the schema errors preventing users from using the new Phase 2 tools in Claude Desktop. All tools should now work correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
184 lines
7.2 KiB
Python
184 lines
7.2 KiB
Python
"""Content Variant Generator Tool
|
|
|
|
Generates multiple variations of marketing content for A/B testing.
|
|
Supports testing different hooks, tones, lengths, and psychological angles.
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
from pydantic import Field
|
|
|
|
from config import TEMPERATURE_HIGHLY_CREATIVE
|
|
from systemprompts import CONTENTVARIANT_PROMPT
|
|
from tools.models import ToolModelCategory
|
|
from tools.shared.base_models import ToolRequest
|
|
from tools.simple.base import SimpleTool
|
|
|
|
|
|
class ContentVariantRequest(ToolRequest):
|
|
"""Request model for Content Variant Generator"""
|
|
|
|
content: str = Field(
|
|
...,
|
|
description="Base content or topic to create variations from. Can be a draft post, subject line, or content concept.",
|
|
)
|
|
variation_count: int = Field(
|
|
default=10,
|
|
ge=5,
|
|
le=25,
|
|
description="Number of variations to generate (5-25). Default is 10.",
|
|
)
|
|
variation_types: Optional[list[str]] = Field(
|
|
default=None,
|
|
description="Types of variations to explore: 'hook', 'tone', 'length', 'structure', 'cta', 'angle'. Leave empty for mixed approach.",
|
|
)
|
|
platform: Optional[str] = Field(
|
|
default=None,
|
|
description="Target platform for character limits and formatting: 'twitter', 'bluesky', 'linkedin', 'instagram', 'facebook', 'email_subject', 'blog'",
|
|
)
|
|
constraints: Optional[str] = Field(
|
|
default=None,
|
|
description="Additional constraints: character limits, style requirements, brand voice guidelines, prohibited words/phrases",
|
|
)
|
|
testing_angles: Optional[list[str]] = Field(
|
|
default=None,
|
|
description="Specific psychological angles to test: 'curiosity', 'contrarian', 'knowledge_gap', 'urgency', 'insider', 'problem_solution', 'social_proof', 'transformation'",
|
|
)
|
|
|
|
|
|
class ContentVariantTool(SimpleTool):
|
|
"""Generate multiple content variations for A/B testing"""
|
|
|
|
def get_name(self) -> str:
|
|
return "contentvariant"
|
|
|
|
def get_description(self) -> str:
|
|
return (
|
|
"Generate 5-25 variations of marketing content for A/B testing. "
|
|
"Tests different hooks, tones, lengths, and psychological angles. "
|
|
"Ideal for subject lines, social posts, email copy, and ads. "
|
|
"Each variation includes testing rationale and predicted audience response."
|
|
)
|
|
|
|
def get_system_prompt(self) -> str:
|
|
return CONTENTVARIANT_PROMPT
|
|
|
|
def get_default_temperature(self) -> float:
|
|
return TEMPERATURE_HIGHLY_CREATIVE
|
|
|
|
def get_model_category(self) -> ToolModelCategory:
|
|
return ToolModelCategory.FAST_RESPONSE
|
|
|
|
def get_request_model(self):
|
|
return ContentVariantRequest
|
|
|
|
def get_tool_fields(self) -> dict:
|
|
"""
|
|
Tool-specific field definitions for ContentVariant.
|
|
|
|
Note: This method isn't used since we override get_input_schema(),
|
|
but it's required by the SimpleTool abstract base class.
|
|
"""
|
|
return {
|
|
"content": {
|
|
"type": "string",
|
|
"description": "Base content or topic to create variations from",
|
|
}
|
|
}
|
|
|
|
async def prepare_prompt(self, request: ContentVariantRequest) -> str:
|
|
"""Prepare the content variant prompt"""
|
|
prompt_parts = [f"Generate {request.variation_count} variations of this content:"]
|
|
prompt_parts.append(f"\n**Base Content:**\n{request.content}")
|
|
|
|
if request.platform:
|
|
prompt_parts.append(f"\n**Target Platform:** {request.platform}")
|
|
|
|
if request.variation_types:
|
|
prompt_parts.append(f"\n**Variation Types:** {', '.join(request.variation_types)}")
|
|
|
|
if request.testing_angles:
|
|
prompt_parts.append(f"\n**Testing Angles:** {', '.join(request.testing_angles)}")
|
|
|
|
if request.constraints:
|
|
prompt_parts.append(f"\n**Constraints:** {request.constraints}")
|
|
|
|
prompt_parts.append(
|
|
"\nGenerate variations with clear labels, character counts (if platform specified), "
|
|
"testing angles, and predicted audience responses."
|
|
)
|
|
|
|
# Return the complete prompt
|
|
return "\n".join(prompt_parts)
|
|
|
|
def get_input_schema(self) -> dict:
|
|
"""Return the JSON schema for this tool's input"""
|
|
return {
|
|
"type": "object",
|
|
"properties": {
|
|
"content": {
|
|
"type": "string",
|
|
"description": "Base content or topic to create variations from",
|
|
},
|
|
"variation_count": {
|
|
"type": "integer",
|
|
"description": "Number of variations to generate (5-25, default 10)",
|
|
"minimum": 5,
|
|
"maximum": 25,
|
|
"default": 10,
|
|
},
|
|
"variation_types": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "Types: 'hook', 'tone', 'length', 'structure', 'cta', 'angle'",
|
|
},
|
|
"platform": {
|
|
"type": "string",
|
|
"description": "Platform: 'twitter', 'bluesky', 'linkedin', 'instagram', 'facebook', 'email_subject', 'blog'",
|
|
},
|
|
"constraints": {
|
|
"type": "string",
|
|
"description": "Character limits, style requirements, brand guidelines",
|
|
},
|
|
"testing_angles": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "Angles: 'curiosity', 'contrarian', 'knowledge_gap', 'urgency', 'insider', 'problem_solution', 'social_proof', 'transformation'",
|
|
},
|
|
"files": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "Optional brand guidelines or style reference files",
|
|
},
|
|
"images": {
|
|
"type": "array",
|
|
"items": {"type": "string"},
|
|
"description": "Optional visual assets for context",
|
|
},
|
|
"continuation_id": {
|
|
"type": "string",
|
|
"description": "Thread ID to continue previous conversation",
|
|
},
|
|
"model": {
|
|
"type": "string",
|
|
"description": "AI model to use (leave empty for default fast model)",
|
|
},
|
|
"temperature": {
|
|
"type": "number",
|
|
"description": "Creativity level 0.0-1.0 (default 0.8 for high variation)",
|
|
"minimum": 0.0,
|
|
"maximum": 1.0,
|
|
},
|
|
"thinking_mode": {
|
|
"type": "string",
|
|
"description": "Thinking depth: minimal, low, medium, high, max",
|
|
"enum": ["minimal", "low", "medium", "high", "max"],
|
|
},
|
|
"use_websearch": {
|
|
"type": "boolean",
|
|
"description": "Enable web search for current platform best practices",
|
|
"default": False,
|
|
},
|
|
},
|
|
"required": ["content"],
|
|
}
|