116 lines
4.5 KiB
Python
116 lines
4.5 KiB
Python
"""Core sentiment analysis agent using Claude Agent SDK."""
|
||
|
||
from __future__ import annotations
|
||
|
||
from claude_agent_sdk import (
|
||
AssistantMessage,
|
||
ClaudeAgentOptions,
|
||
ClaudeSDKClient,
|
||
ResultMessage,
|
||
TextBlock,
|
||
)
|
||
|
||
from sentiment_agent.config import SafetyConfig
|
||
from sentiment_agent.tools import create_social_tools_server
|
||
|
||
SYSTEM_PROMPT = """\
|
||
You are a sentiment analysis agent. Your job is to gather data from multiple \
|
||
platforms and produce a structured, evidence-based sentiment report.
|
||
|
||
## Rules — you MUST follow these
|
||
|
||
1. **Budget awareness.** You have a limited API call budget. Call \
|
||
`get_api_budget_status` before starting and after every few tool calls. \
|
||
Stop gathering data when you have <5 calls remaining and begin your analysis.
|
||
|
||
2. **Credibility first.** Every tool result includes credibility scores and \
|
||
bot/disinfo flags. You MUST:
|
||
- NEVER quote or cite posts marked `likely_inauthentic` (score < 0.3).
|
||
- Flag posts marked `suspicious` (score 0.3–0.5) with a warning when citing them.
|
||
- Give more weight to `likely_authentic` posts (score ≥ 0.7).
|
||
- If coordination warnings appear (copy-paste campaigns, burst posting), \
|
||
call them out prominently in your report.
|
||
|
||
3. **Platform diversity.** Gather from at least 2 different platforms before \
|
||
analyzing. Do not over-index on a single source.
|
||
|
||
4. **No fabrication.** Only report on data you actually retrieved. If a tool \
|
||
call fails or returns no results, say so — do not invent data.
|
||
|
||
5. **Structured output.** Your final report MUST include these sections:
|
||
- **Data Quality Summary**: platforms queried, posts analyzed vs excluded, \
|
||
coordination warnings
|
||
- **Overall Sentiment**: score (-1.0 to +1.0) and label \
|
||
(very negative / negative / mixed / neutral / positive / very positive)
|
||
- **Platform Breakdown**: sentiment per platform with sample size
|
||
- **Key Themes**: top 3-5 themes with sentiment direction
|
||
- **Credibility Concerns**: any bot networks, disinfo patterns, or \
|
||
coordinated campaigns detected
|
||
- **Notable Quotes**: 3-5 representative quotes (authentic sources only, \
|
||
with credibility score noted)
|
||
- **Confidence Assessment**: how confident you are in the analysis given \
|
||
data quality and volume
|
||
|
||
6. **Scope discipline.** Stay focused on the requested topic. Do not expand \
|
||
scope, follow tangents, or analyze adjacent topics unless explicitly asked.
|
||
|
||
7. **No side effects.** Do not write files, run commands, or take any action \
|
||
beyond reading data and producing your report.
|
||
"""
|
||
|
||
|
||
async def run_sentiment_analysis(
|
||
topic: str,
|
||
sources: list[str] | None = None,
|
||
config: SafetyConfig | None = None,
|
||
) -> str:
|
||
"""Run the sentiment analysis agent on a given topic.
|
||
|
||
Args:
|
||
topic: The topic or subject to analyze sentiment for.
|
||
sources: Optional list of URLs or data sources to analyze.
|
||
config: Safety configuration. Defaults to SafetyConfig.from_env().
|
||
|
||
Returns:
|
||
The agent's sentiment analysis report.
|
||
"""
|
||
config = config or SafetyConfig.from_env()
|
||
|
||
source_instructions = ""
|
||
if sources:
|
||
source_list = "\n".join(f"- {s}" for s in sources)
|
||
source_instructions = f"\n\nAlso analyze these specific sources:\n{source_list}"
|
||
|
||
prompt = (
|
||
f"Perform a sentiment analysis on the following topic: {topic}\n\n"
|
||
"Start by calling `get_api_budget_status` to check your budget, then "
|
||
"gather data from multiple platforms (Reddit, Hacker News, Bluesky if "
|
||
"configured, and web search). Pay close attention to credibility scores "
|
||
"and coordination warnings in the results."
|
||
f"{source_instructions}"
|
||
)
|
||
|
||
social_server = create_social_tools_server(config)
|
||
|
||
options = ClaudeAgentOptions(
|
||
# Only allow read-only tools — no Write/Bash to prevent side effects
|
||
allowed_tools=["WebSearch", "WebFetch", "Read"],
|
||
max_turns=config.max_turns,
|
||
max_budget_usd=config.max_budget_usd,
|
||
mcp_servers={"social": social_server},
|
||
system_prompt=SYSTEM_PROMPT,
|
||
)
|
||
|
||
result_text = ""
|
||
async with ClaudeSDKClient(options=options) as client:
|
||
await client.query(prompt)
|
||
async for message in client.receive_response():
|
||
if isinstance(message, AssistantMessage):
|
||
for block in message.content:
|
||
if isinstance(block, TextBlock):
|
||
print(block.text, end="", flush=True)
|
||
if isinstance(message, ResultMessage):
|
||
result_text = message.result
|
||
|
||
return result_text
|