zen-marketing/tools/platformadapt.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

198 lines
7.8 KiB
Python

"""Platform Adaptation Tool
Adapts content from one platform to multiple other platforms while preserving
core message and optimizing for each platform's unique characteristics.
"""
from typing import Optional
from pydantic import Field
from config import TEMPERATURE_CREATIVE
from systemprompts import PLATFORMADAPT_PROMPT
from tools.models import ToolModelCategory
from tools.shared.base_models import ToolRequest
from tools.simple.base import SimpleTool
class PlatformAdaptRequest(ToolRequest):
"""Request model for Platform Adaptation"""
source_content: str = Field(
...,
description="The original content to adapt. Can be a social post, blog intro, email, or any marketing content.",
)
source_platform: Optional[str] = Field(
default=None,
description="Where this content originated: 'twitter', 'linkedin', 'blog', 'email', 'instagram', 'facebook', 'bluesky'. Optional but helps inform adaptations.",
)
target_platforms: list[str] = Field(
...,
description="Platforms to adapt to. Options: 'twitter', 'bluesky', 'linkedin', 'instagram', 'facebook', 'email_subject', 'blog_title'. Specify at least one.",
)
preserve_urls: bool = Field(
default=True,
description="Keep links intact across platforms (True) or adapt/remove them (False). Default True.",
)
brand_voice: Optional[str] = Field(
default=None,
description="Brand voice guidelines to maintain: tone, style, personality traits, terminology preferences.",
)
include_hashtags: bool = Field(
default=True,
description="Include platform-appropriate hashtags in adaptations. Default True.",
)
adaptation_notes: Optional[str] = Field(
default=None,
description="Special adaptation requirements: CTAs to include, key messages to emphasize, phrases to avoid.",
)
class PlatformAdaptTool(SimpleTool):
"""Adapt content across multiple social media platforms"""
def get_name(self) -> str:
return "platformadapt"
def get_description(self) -> str:
return (
"Adapt content from one platform to multiple others while preserving core message. "
"Automatically respects character limits, adjusts tone, optimizes formatting, and applies "
"platform-specific best practices (hashtags, CTAs, threading). "
"Ideal for multi-channel campaigns and content repurposing."
)
def get_system_prompt(self) -> str:
return PLATFORMADAPT_PROMPT
def get_default_temperature(self) -> float:
return TEMPERATURE_CREATIVE
def get_model_category(self) -> ToolModelCategory:
return ToolModelCategory.FAST_RESPONSE
def get_request_model(self):
return PlatformAdaptRequest
def get_tool_fields(self) -> dict:
"""Tool-specific field definitions for PlatformAdapt"""
return {
"source_content": {
"type": "string",
"description": "The original content to adapt",
},
"target_platforms": {
"type": "array",
"items": {"type": "string"},
"description": "Platforms to adapt content for",
},
}
async def prepare_prompt(self, request: PlatformAdaptRequest) -> str:
"""Prepare the platform adaptation prompt"""
prompt_parts = [
"Adapt the following content across multiple platforms while preserving the core message:"
]
prompt_parts.append(f"\n**Source Content:**\n{request.source_content}")
if request.source_platform:
prompt_parts.append(f"\n**Original Platform:** {request.source_platform}")
prompt_parts.append(
f"\n**Target Platforms:** {', '.join(request.target_platforms)}"
)
if request.brand_voice:
prompt_parts.append(
f"\n**Brand Voice Guidelines:** {request.brand_voice}"
)
if request.adaptation_notes:
prompt_parts.append(
f"\n**Special Requirements:** {request.adaptation_notes}"
)
prompt_parts.append(f"\n**URL Handling:** {'Preserve links' if request.preserve_urls else 'Adapt or remove links as needed'}")
prompt_parts.append(f"**Hashtags:** {'Include platform-appropriate hashtags' if request.include_hashtags else 'Skip hashtags'}")
prompt_parts.append(
"\n\nFor each platform, provide the adapted content with character counts, "
"adaptation rationale, and platform-specific optimizations applied."
)
# 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": {
"source_content": {
"type": "string",
"description": "The original content to adapt across platforms",
},
"source_platform": {
"type": "string",
"description": "Original platform: 'twitter', 'linkedin', 'blog', 'email', 'instagram', 'facebook', 'bluesky'",
},
"target_platforms": {
"type": "array",
"items": {"type": "string"},
"description": "Platforms to adapt to: 'twitter', 'bluesky', 'linkedin', 'instagram', 'facebook', 'email_subject', 'blog_title'",
},
"preserve_urls": {
"type": "boolean",
"description": "Keep links intact (true) or adapt/remove (false)",
"default": True,
},
"brand_voice": {
"type": "string",
"description": "Brand voice guidelines: tone, style, personality, terminology",
},
"include_hashtags": {
"type": "boolean",
"description": "Include platform-appropriate hashtags",
"default": True,
},
"adaptation_notes": {
"type": "string",
"description": "Special requirements: CTAs, key messages, phrases to avoid",
},
"files": {
"type": "array",
"items": {"type": "string"},
"description": "Optional brand guidelines or style references",
},
"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 creative model)",
},
"temperature": {
"type": "number",
"description": "Creativity level 0.0-1.0 (default 0.7 for creative adaptation)",
"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": ["source_content", "target_platforms"],
}