slushsense / src /agent.py
csiboto's picture
Update src/agent.py
6095bba verified
"""
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"]