@polybiouslabs/polybious
Version:
Polybius is a next-generation intelligent agent framework built for adaptability across diverse domains. It merges contextual awareness, multi-agent collaboration, and predictive reasoning to deliver dynamic, self-optimizing performance.
250 lines (249 loc) • 10.3 kB
JavaScript
import { BaseTool } from '../base-tool';
import { SentimentAnalyzer, PorterStemmer } from 'natural';
import { logger } from '../../config/logger';
export class SentimentAnalysisTool extends BaseTool {
analyzer;
constructor() {
super();
this.analyzer = new SentimentAnalyzer('English', PorterStemmer, 'afinn');
}
getConfig() {
return {
name: 'sentiment_analysis',
description: 'Analyze the sentiment and emotional tone of text content',
parameters: {
type: 'object',
properties: {
text: {
type: 'string',
description: 'The text to analyze for sentiment',
maxLength: 5000
},
includeEmotions: {
type: 'boolean',
description: 'Include detailed emotion analysis',
default: false
},
language: {
type: 'string',
description: 'Language of the text',
enum: ['en', 'es', 'fr', 'de', 'it'],
default: 'en'
}
},
required: ['text']
},
handler: 'SentimentAnalysisTool',
enabled: true,
rateLimited: {
maxCalls: 200,
timeWindow: 60
}
};
}
validate(params) {
if (!this.validateRequired(params, ['text'])) {
return false;
}
if (typeof params.text !== 'string' || params.text.trim().length === 0) {
return false;
}
return true;
}
async execute(params) {
const { text, includeEmotions = false, language = 'en' } = this.sanitizeParams(params);
try {
logger.info('Analyzing sentiment', { textLength: text.length, includeEmotions });
const sentiment = this.analyzeSentiment(text);
const polarity = this.calculatePolarity(text);
const confidence = this.calculateConfidence(sentiment, text);
const result = {
text: text.substring(0, 200) + (text.length > 200 ? '...' : ''),
sentiment: this.categorizeSentiment(sentiment),
score: sentiment,
polarity,
confidence,
wordCount: text.split(/\s+/).length,
timestamp: new Date().toISOString()
};
if (includeEmotions) {
result.emotions = this.analyzeEmotions(text);
result.toxicity = this.analyzeToxicity(text);
result.subjectivity = this.analyzeSubjectivity(text);
}
return result;
}
catch (error) {
logger.error('Sentiment analysis failed', { error, textLength: text.length });
throw new Error(`Sentiment analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
analyzeSentiment(text) {
// Tokenize and clean text
const tokens = text.toLowerCase()
.replace(/[^\w\s]/g, ' ')
.split(/\s+/)
.filter(token => token.length > 0);
if (tokens.length === 0)
return 0;
// Simple sentiment scoring using word lists
const sentimentWords = this.getSentimentWords();
let totalScore = 0;
let scoredWords = 0;
for (const token of tokens) {
if (sentimentWords.positive.includes(token)) {
totalScore += 1;
scoredWords++;
}
else if (sentimentWords.negative.includes(token)) {
totalScore -= 1;
scoredWords++;
}
}
// Normalize by number of sentiment-bearing words
return scoredWords > 0 ? totalScore / scoredWords : 0;
}
calculatePolarity(text) {
const score = this.analyzeSentiment(text);
if (score > 0.1)
return 'positive';
if (score < -0.1)
return 'negative';
return 'neutral';
}
calculateConfidence(score, text) {
const absScore = Math.abs(score);
const textLength = text.split(/\s+/).length;
// Confidence increases with stronger sentiment and longer text (up to a point)
const lengthFactor = Math.min(textLength / 50, 1);
const sentimentFactor = Math.min(absScore * 2, 1);
return Math.round((lengthFactor * sentimentFactor) * 100) / 100;
}
categorizeSentiment(score) {
if (score > 0.5)
return 'very positive';
if (score > 0.1)
return 'positive';
if (score > -0.1)
return 'neutral';
if (score > -0.5)
return 'negative';
return 'very negative';
}
analyzeEmotions(text) {
const emotionWords = this.getEmotionWords();
const tokens = text.toLowerCase().split(/\s+/);
const emotions = {
joy: 0,
anger: 0,
fear: 0,
sadness: 0,
surprise: 0,
disgust: 0,
trust: 0,
anticipation: 0
};
for (const token of tokens) {
for (const [emotion, words] of Object.entries(emotionWords)) {
if (words.includes(token)) {
emotions[emotion]++;
}
}
}
// Normalize by total words
const totalWords = tokens.length;
for (const emotion in emotions) {
emotions[emotion] = Math.round((emotions[emotion] / totalWords) * 1000) / 1000;
}
return emotions;
}
analyzeToxicity(text) {
const toxicWords = [
'hate', 'stupid', 'idiot', 'kill', 'die', 'worst', 'terrible', 'awful',
'disgusting', 'pathetic', 'loser', 'failure', 'worthless'
];
const tokens = text.toLowerCase().split(/\s+/);
const toxicCount = tokens.filter(token => toxicWords.includes(token)).length;
return Math.min(toxicCount / tokens.length, 1);
}
analyzeSubjectivity(text) {
const subjectiveWords = [
'think', 'feel', 'believe', 'opinion', 'probably', 'maybe', 'seems',
'appears', 'likely', 'possibly', 'apparently', 'supposedly'
];
const tokens = text.toLowerCase().split(/\s+/);
const subjectiveCount = tokens.filter(token => subjectiveWords.includes(token)).length;
return Math.min(subjectiveCount / tokens.length * 2, 1);
}
getSentimentWords() {
return {
positive: [
'good', 'great', 'excellent', 'amazing', 'wonderful', 'fantastic', 'awesome',
'brilliant', 'perfect', 'outstanding', 'superb', 'magnificent', 'marvelous',
'love', 'like', 'enjoy', 'happy', 'pleased', 'delighted', 'thrilled',
'excited', 'grateful', 'thankful', 'appreciate', 'success', 'win', 'achieve'
],
negative: [
'bad', 'terrible', 'awful', 'horrible', 'disgusting', 'pathetic', 'worst',
'hate', 'dislike', 'loathe', 'despise', 'angry', 'furious', 'mad', 'upset',
'sad', 'depressed', 'miserable', 'disappointed', 'frustrated', 'annoyed',
'fail', 'failure', 'lose', 'problem', 'issue', 'trouble', 'difficult'
]
};
}
getEmotionWords() {
return {
joy: ['happy', 'joy', 'cheerful', 'delighted', 'pleased', 'glad', 'content', 'elated'],
anger: ['angry', 'mad', 'furious', 'rage', 'irritated', 'annoyed', 'frustrated', 'hostile'],
fear: ['afraid', 'scared', 'terrified', 'anxious', 'worried', 'nervous', 'panic', 'dread'],
sadness: ['sad', 'depressed', 'miserable', 'gloomy', 'melancholy', 'sorrowful', 'dejected'],
surprise: ['surprised', 'amazed', 'astonished', 'shocked', 'stunned', 'bewildered'],
disgust: ['disgusted', 'revolted', 'repulsed', 'sickened', 'nauseated', 'appalled'],
trust: ['trust', 'confident', 'secure', 'reliable', 'dependable', 'faithful', 'loyal'],
anticipation: ['excited', 'eager', 'hopeful', 'optimistic', 'expectant', 'anticipating']
};
}
transform(result) {
return {
...result,
recommendation: this.generateRecommendation(result),
insights: this.generateInsights(result)
};
}
generateRecommendation(result) {
const { sentiment, confidence, polarity } = result;
if (confidence < 0.3) {
return 'The sentiment analysis has low confidence. Consider providing more context or emotional cues.';
}
if (polarity === 'negative' && result.toxicity > 0.1) {
return 'Content contains negative sentiment with some toxic elements. Consider revising for more positive engagement.';
}
if (polarity === 'positive') {
return 'Content has positive sentiment. This is likely to generate good engagement.';
}
if (polarity === 'neutral') {
return 'Content is emotionally neutral. Consider adding more emotional elements to increase engagement.';
}
return 'Review content sentiment and adjust tone as needed for your audience.';
}
generateInsights(result) {
const insights = [];
if (result.confidence > 0.8) {
insights.push('High confidence in sentiment analysis');
}
if (result.emotions) {
const dominantEmotion = Object.entries(result.emotions)
.sort(([, a], [, b]) => b - a)[0];
if (dominantEmotion[1] > 0.05) {
insights.push(`Dominant emotion detected: ${dominantEmotion[0]}`);
}
}
if (result.subjectivity > 0.3) {
insights.push('Content is highly subjective/opinion-based');
}
if (result.wordCount < 10) {
insights.push('Short text may limit sentiment analysis accuracy');
}
return insights;
}
}