Spaces:
Sleeping
Sleeping
| """ | |
| 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"] |