""" SlushSense Agent Module Uses Claude API to provide deep manuscript analysis. Adapted for HuggingFace Spaces (reads API key from secrets). """ import os import logging from typing import Dict, List, Optional logger = logging.getLogger(__name__) try: import anthropic ANTHROPIC_AVAILABLE = True except ImportError: ANTHROPIC_AVAILABLE = False logger.warning("anthropic package not installed. Agent features disabled.") SYSTEM_PROMPT = """You are an expert editorial assistant for SlushSense, an AI-powered manuscript evaluation platform for publishers. Your role is to provide insightful, actionable analysis of manuscripts to help editors make informed decisions. You have access to: 1. The manuscript text (or excerpt) 2. An AI-generated commercial potential score from a fine-tuned DistilBERT model Your analysis should be: - **Professional**: Write like a senior acquisitions editor - **Balanced**: Highlight both strengths and areas for improvement - **Actionable**: Give specific, useful feedback - **Honest**: Don't oversell weak manuscripts or undersell strong ones When analyzing a manuscript, consider: - Writing quality (prose style, voice, pacing) - Commercial viability (market fit, audience appeal, trends) - Genre conventions (does it meet/subvert expectations effectively?) - Comparable titles (what successful books is this similar to?) - Unique selling points (what makes this stand out?) Always maintain a constructive, respectful tone.""" def get_client() -> Optional["anthropic.Anthropic"]: """Get Anthropic client if available.""" if not ANTHROPIC_AVAILABLE: return None # HuggingFace Spaces stores secrets as environment variables api_key = os.environ.get("ANTHROPIC_API_KEY") if not api_key: logger.warning("ANTHROPIC_API_KEY not set in environment") return None return anthropic.Anthropic(api_key=api_key) def analyze_manuscript( text: str, title: str = "Untitled", author: str = "Unknown", genre: str = "", score: Optional[float] = None, confidence: Optional[float] = None, score_label: Optional[str] = None, ) -> Dict: """ Perform deep analysis of a manuscript using Claude. """ client = get_client() if not client: return { "success": False, "error": "Claude API not available. ANTHROPIC_API_KEY not configured.", "analysis": None } # Truncate text if too long max_chars = 15000 if len(text) > max_chars: excerpt = text[:10000] + "\n\n[...middle section omitted...]\n\n" + text[-5000:] else: excerpt = text # Build context about the model's score score_context = "" if score is not None: score_pct = score * 100 score_context = f""" Our AI model (fine-tuned DistilBERT) has scored this manuscript: - **Commercial Potential Score**: {score_pct:.1f}% ({score_label or 'N/A'}) - **Model Confidence**: {confidence*100:.1f}% if confidence else 'N/A' Use this as one data point in your analysis.""" user_prompt = f"""Please analyze this manuscript submission: **Title**: {title} **Author**: {author} **Genre**: {genre or 'Not specified'} {score_context} --- **MANUSCRIPT TEXT:** {excerpt} --- Please provide a comprehensive editorial analysis with the following sections: ## 1. Executive Summary A 2-3 sentence overview and overall assessment. ## 2. Genre & Market Position - Genre/subgenre identification - Market trends for this category - Target audience ## 3. Comparable Titles List 3-4 similar published books with brief explanations. ## 4. Strengths What works well? Be specific with examples. ## 5. Areas for Development What could be improved? Constructive, actionable feedback. ## 6. Commercial Assessment - **Verdict**: [STRONG RECOMMEND / RECOMMEND WITH REVISIONS / CONSIDER / PASS] - Explain your reasoning ## 7. Editor's Notes Additional observations or recommendations.""" try: response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=2000, system=SYSTEM_PROMPT, messages=[{"role": "user", "content": user_prompt}] ) return { "success": True, "error": None, "analysis": response.content[0].text, "model": response.model, "usage": { "input_tokens": response.usage.input_tokens, "output_tokens": response.usage.output_tokens } } except anthropic.APIError as e: logger.error(f"Anthropic API error: {e}") return {"success": False, "error": f"API error: {str(e)}", "analysis": None} except Exception as e: logger.error(f"Error analyzing manuscript: {e}") return {"success": False, "error": str(e), "analysis": None} def chat_about_manuscript( messages: List[Dict], manuscript_context: str, title: str = "the manuscript", ) -> Dict: """Have a conversation about a manuscript.""" client = get_client() if not client: return {"success": False, "error": "Claude API not available.", "response": None} system = f"""{SYSTEM_PROMPT} You are discussing "{title}". Context: {manuscript_context} Answer questions helpfully and specifically.""" try: response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1000, system=system, messages=messages ) return { "success": True, "error": None, "response": response.content[0].text, "usage": { "input_tokens": response.usage.input_tokens, "output_tokens": response.usage.output_tokens } } except Exception as e: logger.error(f"Error in chat: {e}") return {"success": False, "error": str(e), "response": None} def is_available() -> bool: """Check if agent features are available.""" return get_client() is not None __all__ = ["analyze_manuscript", "chat_about_manuscript", "is_available"]