diff --git a/CHANGELOG.md b/CHANGELOG.md index 4159cf3..24c5b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## [0.1.0] - 2024-11-22 +All by [@BlazingRockStorm](https://github.com/BlazingRockStorm): +- Add `positive_check` +- Add Anthropic + ## [0.0.5] - 2024-11-13 All by [@BlazingRockStorm](https://github.com/BlazingRockStorm): - Rewrite the `analyze_sentence` method to have the output in form of Hash diff --git a/Gemfile.lock b/Gemfile.lock index e41d09c..1069792 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,13 +1,17 @@ PATH remote: . specs: - sentiment-ai (0.0.5) + sentiment-ai (0.1.0) GEM remote: https://rubygems.org/ specs: addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) + anthropic (0.3.2) + event_stream_parser (>= 0.3.0, < 2.0.0) + faraday (>= 1) + faraday-multipart (>= 1) base64 (0.2.0) bigdecimal (3.1.8) concurrent-ruby (1.3.4) @@ -98,6 +102,7 @@ PLATFORMS x86_64-darwin-22 DEPENDENCIES + anthropic (~> 0.3.2) csv (~> 3.3) dotenv gemini-ai (~> 4.2) diff --git a/README.md b/README.md index ee2b573..a376acd 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ require "sentiment_ai" Then use it like this: ```ruby -sentiment = SentimentAI.new(YOUR_MODEL,YOUR_API_KEY) +sentiment = SentimentAI.new(YOUR_PROVIDER,YOUR_API_KEY) ``` For example: @@ -38,7 +38,7 @@ sentiment = SentimentAI.new(:open_ai, OPEN_AI_KEY) For the current version, the gem supports only OpenAI and Google Gemini. -After calling the model, use: +After calling the provider, use: ```ruby sentiment.analyze_sentence("I Love Ruby") # => { :sentence => "I Love Ruby", :sentiment => "positive" } @@ -71,12 +71,19 @@ sentiment = SentimentAI.new(:open_ai, OPEN_AI_KEY, :ja) sentiment.analyze_sentence("Rubyは世界一プログラミング言語") # => { :sentence => "Rubyは世界一プログラミング言語", :sentiment => "肯定的" } ``` -### Supported GenAI models +If you want to only choose positive, use: +```ruby +sentiment.positive_check("I Love Ruby") +# => { :sentence => "I Love Ruby", :positive => true } +# => { :sentence => "It was never love for me", :positive => false } +``` +### Supported GenAI providers | Language | Code | |----------|------| | OpenAI(GPT) | `:open_ai` | | Google Gemini | `:gemini_ai_pro` | +| Anthropic | `:anthropic` | ### Supported languages diff --git a/config/locales/en.yml b/config/locales/en.yml index 877d689..647372e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,3 +1,4 @@ en: prompt: - sentence: "Analyze the sentiment of the sentence given below.\n%{sentence}\nThe output should be in the format- value" \ No newline at end of file + sentence: "Analyze the sentiment of the sentence given below.\n%{sentence}\nThe output should be in the format- value" + positive_check: "Is the sentence given below positive?\n%{sentence}\nThe output should be true or false" \ No newline at end of file diff --git a/config/locales/ja.yml b/config/locales/ja.yml index e4f90f8..236f30a 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1,3 +1,4 @@ ja: prompt: - sentence: "テキストを「中立」、「否定的」、または「肯定的」に分類してください。 テキスト:%{sentence}\n結果の形式- 値" \ No newline at end of file + sentence: "テキストを「中立」、「否定的」、または「肯定的」に分類してください。 テキスト:%{sentence}\n結果の形式- 値" + positive_check: "テキストは肯定的ですか?\n%{sentence}\n「はい」はtrue、 「いいえ」は false" \ No newline at end of file diff --git a/config/locales/vi.yml b/config/locales/vi.yml index a59238e..532a9fc 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -1,3 +1,4 @@ vi: prompt: - sentence: "Phân tích sắc thái của câu dưới đây là tích cực, tiêu cực hay trung lập.\n%{sentence}\nKết quả trả về dưới dạng- Gía trị" \ No newline at end of file + sentence: "Phân tích sắc thái của câu dưới đây là tích cực, tiêu cực hay trung lập.\n%{sentence}\nKết quả trả về dưới dạng- Gía trị" + positive_check: "Câu sau đây có tích cực hay không?\n%{sentence}\nkết quả trả về là true hoặc false" \ No newline at end of file diff --git a/lib/sentiment_ai.rb b/lib/sentiment_ai.rb index 6fcc8b4..e72169f 100644 --- a/lib/sentiment_ai.rb +++ b/lib/sentiment_ai.rb @@ -3,6 +3,7 @@ require 'sentiment_ai/version' require 'sentiment_ai/core/gemini_driver' require 'sentiment_ai/core/openai_driver' +require 'sentiment_ai/core/anthropic_driver' require 'i18n' require 'csv' @@ -19,13 +20,15 @@ def self.new(*args) end class Base - def initialize(model, api_key, language = :en) + def initialize(provider, api_key, language = :en) I18n.locale = language - @generative_ai = case model + @generative_ai = case provider when :open_ai Core::OpenAIDriver.new(api_key) when :gemini_ai_pro Core::GeminiDriver.new(api_key) + when :anthropic + Core::AnthropicDriver.new(api_key) else raise ArgumentError end @@ -36,6 +39,11 @@ def analyze_sentence(sentence) { sentence: sentence, sentiment: sentiment } end + def positive_check(sentence) + sentiment_bool = @generative_ai.positive_check(sentence) + { sentence: sentence, positive: sentiment_bool == 'true' } + end + def analyze_array(array) array.map { |sentence| analyze_sentence(sentence) } end diff --git a/lib/sentiment_ai/core/anthropic_driver.rb b/lib/sentiment_ai/core/anthropic_driver.rb new file mode 100644 index 0000000..ad93de7 --- /dev/null +++ b/lib/sentiment_ai/core/anthropic_driver.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'anthropic' + +module SentimentAI + module Core + class AnthropicDriver + def initialize(api_key) + @sentiment_ai = Anthropic::Client.new(access_token: api_key) + end + + def analyze_sentence(sentence) + text_request = I18n.t('prompt.sentence', sentence: sentence) + + @sentiment_ai.messages( + parameters: { + model: 'claude-3-haiku-20240307', + messages: [ + { 'role': 'user', 'content': text_request } + ], + max_tokens: 1000 + } + ) + end + + def positive_check(sentence) + text_request = I18n.t('prompt.positive_check', sentence: sentence) + + @sentiment_ai.messages( + parameters: { + model: 'claude-3-haiku-20240307', + messages: [ + { 'role': 'user', 'content': text_request } + ], + max_tokens: 1000 + } + ) + end + end + end +end diff --git a/lib/sentiment_ai/core/gemini_driver.rb b/lib/sentiment_ai/core/gemini_driver.rb index 9a326b0..7f8aa6b 100644 --- a/lib/sentiment_ai/core/gemini_driver.rb +++ b/lib/sentiment_ai/core/gemini_driver.rb @@ -28,6 +28,16 @@ def analyze_sentence(sentence) extract_candidates(response) end + def positive_check(sentence) + text_request = I18n.t('prompt.positive_check', sentence: sentence) + + response = @sentiment_ai.stream_generate_content({ + contents: { role: 'user', parts: { text: text_request } }, + generationConfig: { temperature: 0 } + }) + extract_candidates(response) + end + private def extract_candidates(candidates) diff --git a/lib/sentiment_ai/core/openai_driver.rb b/lib/sentiment_ai/core/openai_driver.rb index c11494e..12daa50 100644 --- a/lib/sentiment_ai/core/openai_driver.rb +++ b/lib/sentiment_ai/core/openai_driver.rb @@ -23,6 +23,21 @@ def analyze_sentence(sentence) } ) end + + def positive_check(sentence) + text_request = I18n.t('prompt.positive_check', sentence: sentence) + + @sentiment_ai.chat( + parameters: { + model: 'gpt-4o', + messages: [{ role: 'user', content: text_request }], + temperature: 0.7, + stream: proc do |chunk, _bytesize| + print chunk.dig('choices', 0, 'delta', 'content') + end + } + ) + end end end end diff --git a/lib/sentiment_ai/version.rb b/lib/sentiment_ai/version.rb index aaafe65..ca55e1d 100644 --- a/lib/sentiment_ai/version.rb +++ b/lib/sentiment_ai/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SentimentAI - VERSION = '0.0.5' + VERSION = '0.1.0' end diff --git a/sentiment-ai.gemspec b/sentiment-ai.gemspec index b016248..486fa2a 100644 --- a/sentiment-ai.gemspec +++ b/sentiment-ai.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'csv', '~> 3.3' spec.add_development_dependency 'i18n', '~> 1.14' + spec.add_development_dependency 'anthropic', '~> 0.3.2' spec.add_development_dependency 'gemini-ai', '~> 4.2' spec.add_development_dependency 'ruby-openai', '~> 6.0' end diff --git a/spec/ai_models/anthropic_spec.rb b/spec/ai_models/anthropic_spec.rb new file mode 100644 index 0000000..c432870 --- /dev/null +++ b/spec/ai_models/anthropic_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'dotenv/load' + +RSpec.describe 'Using Anthropic provider' do + let(:sentiment) { SentimentAI.new(:anthropic, ENV['ANTHROPIC_KEY']) } + let(:japanese_sentiment) { SentimentAI.new(:anthropic, ENV['ANTHROPIC_KEY'], :ja) } + + describe 'new provider behaviours' do + it 'provider being called correctly' do + expect(sentiment).to be_truthy + end + end + + # describe 'analyze sentence in another language' do + # describe '#analyze_sentence' do + # it 'return the sentiment of the sentence' do + # expect(japanese_sentiment.analyze_sentence('うまい!')).to eq({ sentence: 'うまい!', sentiment: '肯定的' }) + # expect(japanese_sentiment.analyze_sentence('不愉快')).to eq({ sentence: '不愉快', sentiment: '否定的' }) + # expect(japanese_sentiment.analyze_sentence('休暇はまずまずでした。')).to eq({ sentence: '休暇はまずまずでした。', sentiment: '中立' }) + # end + # end + # end + + describe 'analyze feature' do + describe '#analyze_sentence' do + it 'return the sentiment of the sentence' do + expect(sentiment.analyze_sentence('Delicious food')).to eq({ sentence: 'Delicious food', + sentiment: 'positive' }) + expect(sentiment.analyze_sentence('Too noisy!!!')).to eq({ sentence: 'Too noisy!!!', sentiment: 'negative' }) + expect(sentiment.analyze_sentence("I really don't know how to feel about Pokemon")).to eq({ + sentence: "I really don't know how to feel about Pokemon", sentiment: 'neutral' + }) + end + end + + describe '#positive_check' do + it 'return true or false' do + expect(sentiment.positive_check('Delicious food')).to eq({ sentence: 'Delicious food', + positive: true }) + expect(sentiment.positive_check('Too noisy!!!')).to eq({ sentence: 'Too noisy!!!', positive: false }) + end + end + + describe '#analyze_array' do + let(:array) { ['Delicious food', 'Too noisy!!!', "I really don't know how to feel about Pokemon"] } + let(:result_array) do + [{ sentence: 'Delicious food', sentiment: 'positive' }, + { sentence: 'Too noisy!!!', sentiment: 'negative' }, + { sentence: "I really don't know how to feel about Pokemon", sentiment: 'neutral' }] + end + + it 'return the sentiments of all sentences in the array' do + expect(sentiment.analyze_array(array)).to eq(result_array) + end + end + end +end diff --git a/spec/ai_models/gemini_pro_spec.rb b/spec/ai_models/gemini_pro_spec.rb index 8d25353..5bacd32 100644 --- a/spec/ai_models/gemini_pro_spec.rb +++ b/spec/ai_models/gemini_pro_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' require 'dotenv/load' -RSpec.describe 'Using Gemini Pro model' do +RSpec.describe 'Using Gemini Pro provider' do let(:sentiment) { SentimentAI.new(:gemini_ai_pro, ENV['GEMINI_API']) } let(:japanese_sentiment) { SentimentAI.new(:gemini_ai_pro, ENV['GEMINI_API'], :ja) } - describe 'new model behaviours' do - it 'model being called correctly' do + describe 'new provider behaviours' do + it 'provider being called correctly' do expect(sentiment).to be_truthy end end @@ -35,6 +35,14 @@ end end + describe '#positive_check' do + it 'return true or false' do + expect(sentiment.positive_check('Delicious food')).to eq({ sentence: 'Delicious food', + positive: true }) + expect(sentiment.positive_check('Too noisy!!!')).to eq({ sentence: 'Too noisy!!!', positive: false }) + end + end + describe '#analyze_array' do let(:array) { ['Delicious food', 'Too noisy!!!', "I really don't know how to feel about Pokemon"] } let(:result_array) do diff --git a/spec/ai_models/openai_spec.rb b/spec/ai_models/openai_spec.rb index a83f554..1231719 100644 --- a/spec/ai_models/openai_spec.rb +++ b/spec/ai_models/openai_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' require 'dotenv/load' -RSpec.describe 'Using OpenAI model' do +RSpec.describe 'Using OpenAI provider' do let(:sentiment) { SentimentAI.new(:open_ai, ENV['OPENAI_KEY']) } - let(:japanese_sentiment) { SentimentAI.new(:gemini_ai_pro, ENV['OPENAI_KEY'], :ja) } + let(:japanese_sentiment) { SentimentAI.new(:open_ai, ENV['OPENAI_KEY'], :ja) } - describe 'new model behaviours' do - it 'model being called correctly' do + describe 'new provider behaviours' do + it 'provider being called correctly' do expect(sentiment).to be_truthy end end @@ -35,6 +35,14 @@ end end + describe '#positive_check' do + it 'return true or false' do + expect(sentiment.positive_check('Delicious food')).to eq({ sentence: 'Delicious food', + positive: true }) + expect(sentiment.positive_check('Too noisy!!!')).to eq({ sentence: 'Too noisy!!!', positive: false }) + end + end + describe '#analyze_array' do let(:array) { ['Delicious food', 'Too noisy!!!', "I really don't know how to feel about Pokemon"] } let(:result_array) do