zen-marketing/tools/subjectlines.py
Ben f0bd374926 Fix critical prompt field error in all marketing tools
## 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>
2025-11-07 14:12:19 -04:00

203 lines
8 KiB
Python

"""Subject Lines Generator Tool
Specialized tool for email subject line generation with psychological angle testing.
Generates 15-25 subject lines grouped by psychological mechanism.
"""
from typing import Optional
from pydantic import Field
from config import TEMPERATURE_HIGHLY_CREATIVE
from systemprompts import SUBJECTLINES_PROMPT
from tools.models import ToolModelCategory
from tools.shared.base_models import ToolRequest
from tools.simple.base import SimpleTool
class SubjectLinesRequest(ToolRequest):
"""Request model for Subject Lines Generator"""
email_topic: str = Field(
...,
description="The main topic or content of the email. Can be a brief description or the full email content.",
)
target_audience: str = Field(
...,
description="Who will receive this email. Be specific about their role, interests, or pain points (e.g., 'HVAC technicians', 'B2B SaaS founders', 'technical content marketers').",
)
angles_to_test: Optional[list[str]] = Field(
default=None,
description="Psychological angles to explore: 'curiosity', 'contrarian', 'knowledge_gap', 'urgency', 'insider', 'problem_solution', 'social_proof', 'transformation', 'fomo', 'specificity'. Leave empty for comprehensive testing.",
)
subject_count: int = Field(
default=15,
ge=10,
le=25,
description="Number of subject lines to generate (10-25). Default is 15.",
)
include_emoji: bool = Field(
default=True,
description="Include emoji suggestions for each subject line. Default is True.",
)
preview_text: bool = Field(
default=False,
description="Also generate preview text (first line shown after subject in inbox) to complement each subject line.",
)
constraints: Optional[str] = Field(
default=None,
description="Additional constraints: brand voice, prohibited words, industry terminology requirements, tone preferences.",
)
class SubjectLinesTool(SimpleTool):
"""Generate email subject lines with psychological angle testing"""
def get_name(self) -> str:
return "subjectlines"
def get_description(self) -> str:
return (
"Generate 15-25 email subject lines testing different psychological angles. "
"Groups lines by mechanism (curiosity, contrarian, urgency, etc.) with A/B testing rationale. "
"Includes character counts, emoji suggestions, and preview text options. "
"Optimized for email marketing and newsletter campaigns."
)
def get_system_prompt(self) -> str:
return SUBJECTLINES_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 SubjectLinesRequest
def get_tool_fields(self) -> dict:
"""Tool-specific field definitions for SubjectLines"""
return {
"email_topic": {
"type": "string",
"description": "The main topic or content of the email",
},
"target_audience": {
"type": "string",
"description": "Who will receive this email",
},
}
async def prepare_prompt(self, request: SubjectLinesRequest) -> str:
"""Prepare the subject lines prompt"""
prompt_parts = [
f"Generate {request.subject_count} email subject lines for this topic:"
]
prompt_parts.append(f"\n**Email Topic:**\n{request.email_topic}")
prompt_parts.append(f"\n**Target Audience:** {request.target_audience}")
if request.angles_to_test:
prompt_parts.append(
f"\n**Focus on These Psychological Angles:** {', '.join(request.angles_to_test)}"
)
else:
prompt_parts.append(
"\n**Test Comprehensive Range of Angles:** Include curiosity, contrarian, urgency, knowledge gap, and other effective psychological mechanisms"
)
if request.constraints:
prompt_parts.append(f"\n**Brand/Style Constraints:** {request.constraints}")
prompt_parts.append(
f"\n**Emoji Suggestions:** {'Include' if request.include_emoji else 'Skip'}"
)
if request.preview_text:
prompt_parts.append(
"\n**Preview Text:** Also generate complementary preview text (40-100 chars) for each subject line"
)
prompt_parts.append(
"\n\nGroup by psychological angle, include character counts, A/B testing rationale, and make each line genuinely different."
)
# 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": {
"email_topic": {
"type": "string",
"description": "The main topic or content of the email",
},
"target_audience": {
"type": "string",
"description": "Who will receive this email (be specific about role, interests, or pain points)",
},
"angles_to_test": {
"type": "array",
"items": {"type": "string"},
"description": "Angles: 'curiosity', 'contrarian', 'knowledge_gap', 'urgency', 'insider', 'problem_solution', 'social_proof', 'transformation', 'fomo', 'specificity'",
},
"subject_count": {
"type": "integer",
"description": "Number of subject lines (10-25, default 15)",
"minimum": 10,
"maximum": 25,
"default": 15,
},
"include_emoji": {
"type": "boolean",
"description": "Include emoji suggestions (default true)",
"default": True,
},
"preview_text": {
"type": "boolean",
"description": "Generate preview text for each subject line",
"default": False,
},
"constraints": {
"type": "string",
"description": "Brand voice, prohibited words, tone preferences",
},
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Optional brand guidelines or previous email examples",
},
"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 email marketing best practices",
"default": False,
},
},
"required": ["email_topic", "target_audience"],
}