SentimentInsights is a Ruby gem for extracting sentiment, key phrases, and named entities from survey responses or free-form textual data. It offers a plug-and-play interface to different NLP providers, including OpenAI, Claude AI, and AWS.
- Installation
- Configuration
- Usage
- Provider Options & Custom Prompts
- Full Example
- Contributing
- License
Add to your Gemfile:
gem 'sentiment_insights'
Then install:
bundle install
Or install it directly:
gem install sentiment_insights
Configure the provider and (if using OpenAI, Claude AI, or AWS) your API key:
require 'sentiment_insights'
# For OpenAI
SentimentInsights.configure do |config|
config.provider = :openai
config.openai_api_key = ENV["OPENAI_API_KEY"]
end
# For Claude AI
SentimentInsights.configure do |config|
config.provider = :claude
config.claude_api_key = ENV["CLAUDE_API_KEY"]
end
# For AWS
SentimentInsights.configure do |config|
config.provider = :aws
config.aws_region = 'us-east-1'
end
# For sentimental
SentimentInsights.configure do |config|
config.provider = :sentimental
end
Supported providers:
:openai
:claude
:aws
:sentimental
(local fallback, limited feature set)
Data entries should be hashes with at least an :answer
key. Optionally include segmentation info under :segment
.
entries = [
{ answer: "Amazon Checkout was smooth!", segment: { age_group: "18-25", gender: "Female" } },
{ answer: "Walmart Shipping was delayed.", segment: { age_group: "18-25", gender: "Female" } },
{ answer: "Target Support was decent.", segment: { age_group: "26-35", gender: "Male" } },
{ answer: "Loved the product!", segment: { age_group: "18-25", gender: "Male" } }
]
Quickly classify and summarize user responses as positive, neutral, or negative โ globally or by segment (e.g., age, region).
insight = SentimentInsights::Insights::Sentiment.new
result = insight.analyze(entries)
With options:
custom_prompt = <<~PROMPT
For each of the following customer responses, classify the sentiment as Positive, Neutral, or Negative, and assign a score between -1.0 (very negative) and 1.0 (very positive).
Reply with a numbered list like:
1. Positive (0.9)
2. Negative (-0.8)
3. Neutral (0.0)
PROMPT
insight = SentimentInsights::Insights::Sentiment.new
result = insight.analyze(
entries,
question: "How was your experience today?",
prompt: custom_prompt,
batch_size: 10
)
Option | Type | Description | Provider |
---|---|---|---|
question |
String | Contextual question for the batch | OpenAI, Claude only |
prompt |
String | Custom prompt text for LLM | OpenAI, Claude only |
batch_size |
Integer | Number of entries per completion call (default: 50) | OpenAI, Claude only |
{:global_summary=>
{:total_count=>5,
:positive_count=>3,
:neutral_count=>0,
:negative_count=>2,
:positive_percentage=>60.0,
:neutral_percentage=>0.0,
:negative_percentage=>40.0,
:net_sentiment_score=>20.0},
:segment_summary=>
{:age=>
{"25-34"=>
{:total_count=>3,
:positive_count=>3,
:neutral_count=>0,
:negative_count=>0,
:positive_percentage=>100.0,
:neutral_percentage=>0.0,
:negative_percentage=>0.0,
:net_sentiment_score=>100.0}},
:top_positive_comments=>
[{:answer=>
"I absolutely loved the experience shopping with Everlane. The website is clean,\n" +
"product descriptions are spot-on, and my jeans arrived two days early with eco-friendly packaging.",
:score=>0.9}],
:top_negative_comments=>
[{:answer=>
"The checkout flow on your site was a nightmare. The promo code from your Instagram campaign didnโt work,\n" +
"and it kept redirecting me to the homepage. Shopify integration needs a serious fix.",
:score=>-0.7}],
:responses=>
[{:answer=>
"I absolutely loved the experience shopping with Everlane. The website is clean,\n" +
"product descriptions are spot-on, and my jeans arrived two days early with eco-friendly packaging.",
:segment=>{:age=>"25-34", :region=>"West"},
:sentiment_label=>:positive,
:sentiment_score=>0.9}]}}
Extract frequently mentioned phrases and identify their associated sentiment and segment spread.
insight = SentimentInsights::Insights::KeyPhrases.new
result = insight.extract(entries)
With options:
key_phrase_prompt = <<~PROMPT.strip
Extract the most important key phrases that represent the main ideas or feedback in the sentence below.
Ignore stop words and return each key phrase in its natural form, comma-separated.
Question: %{question}
Text: %{text}
PROMPT
sentiment_prompt = <<~PROMPT
For each of the following customer responses, classify the sentiment as Positive, Neutral, or Negative, and assign a score between -1.0 (very negative) and 1.0 (very positive).
Reply with a numbered list like:
1. Positive (0.9)
2. Negative (-0.8)
3. Neutral (0.0)
PROMPT
insight = SentimentInsights::Insights::KeyPhrases.new
result = insight.extract(
entries,
question: "What are the recurring themes?",
key_phrase_prompt: key_phrase_prompt,
sentiment_prompt: sentiment_prompt
)
Option | Type | Description | Provider |
---|---|---|---|
question |
String | Context question to help guide phrase extraction | OpenAI, Claude only |
key_phrase_prompt |
String | Custom prompt for extracting key phrases | OpenAI, Claude only |
sentiment_prompt |
String | Custom prompt for classifying tone of extracted phrases | OpenAI, Claude only |
{:phrases=>
[{:phrase=>"everlane",
:mentions=>["r_1"],
:summary=>
{:total_mentions=>1,
:sentiment_distribution=>{:positive=>1, :negative=>0, :neutral=>0},
:segment_distribution=>{:age=>{"25-34"=>1}, :region=>{"West"=>1}}}}],
:responses=>
[{:id=>"r_1",
:sentence=>
"I absolutely loved the experience shopping with Everlane. The website is clean,\n" +
"product descriptions are spot-on, and my jeans arrived two days early with eco-friendly packaging.",
:sentiment=>:positive,
:segment=>{:age=>"25-34", :region=>"West"}}]}
insight = SentimentInsights::Insights::Entities.new
result = insight.extract(entries)
With options:
entity_prompt = <<~PROMPT.strip
Identify brand names, competitors, and product references in the sentence below.
Return each as a JSON object with "text" and "type" (e.g., BRAND, PRODUCT, COMPANY).
Question: %{question}
Sentence: "%{text}"
PROMPT
insight = SentimentInsights::Insights::Entities.new
result = insight.extract(
entries,
question: "Which products or brands are mentioned?",
prompt: entity_prompt
)
Option | Type | Description | Provider |
---|---|---|---|
question |
String | Context question to guide entity extraction | OpenAI, Claude only |
prompt |
String | Custom instructions for entity extraction | OpenAI, Claude only |
{:entities=>
[{:entity=>"everlane",
:type=>"ORGANIZATION",
:mentions=>["r_1"],
:summary=>
{:total_mentions=>1,
:segment_distribution=>{:age=>{"25-34"=>1}, :region=>{"West"=>1}}}},
{:entity=>"jeans",
:type=>"PRODUCT",
:mentions=>["r_1"],
:summary=>
{:total_mentions=>1,
:segment_distribution=>{:age=>{"25-34"=>1}, :region=>{"West"=>1}}}},
{:entity=>"24 hours",
:type=>"TIME",
:mentions=>["r_4"],
:summary=>
{:total_mentions=>1,
:segment_distribution=>{:age=>{"45-54"=>1}, :region=>{"Midwest"=>1}}}}],
:responses=>
[{:id=>"r_1",
:sentence=>
"I absolutely loved the experience shopping with Everlane. The website is clean,\n" +
"product descriptions are spot-on, and my jeans arrived two days early with eco-friendly packaging.",
:segment=>{:age=>"25-34", :region=>"West"}},
{:id=>"r_4",
:sentence=>
"I reached out to your Zendesk support team about a missing package, and while they responded within 24 hours,\n" +
"the response was copy-paste and didn't address my issue directly.",
:segment=>{:age=>"45-54", :region=>"Midwest"}}]}
โ ๏ธ All advanced options (question
,prompt
,key_phrase_prompt
,sentiment_prompt
,batch_size
) apply only to the:openai
and:claude
providers.
They are safely ignored for:aws
and:sentimental
.
OPENAI_API_KEY=your_openai_key_here
CLAUDE_API_KEY=your_claude_key_here
AWS_ACCESS_KEY_ID=your_aws_key
AWS_SECRET_ACCESS_KEY=your_aws_secret
AWS_REGION=us-east-1
- Minimum Ruby version: 2.7
bundle exec rspec
- Sentiment Analysis
- Key Phrase Extraction
- Entity Recognition
- Topic Modeling
- CSV/JSON Export Helpers
- Visual Dashboard Add-on
MIT License
Pull requests welcome! Please open an issue to discuss major changes first.