rails_ai_promptable 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da4690a4e5b41e857aa8d1224909759dd184bedb35dffdda90cd56e9c867d5d8
4
- data.tar.gz: 77dd63a1d712ab28b4ce63335eb8fdfb3a173147c82553e7610bb57c1a33d771
3
+ metadata.gz: ff190c064892809b494cf10ccba89de83af0a15b2427268b187f3f6e5fcac52e
4
+ data.tar.gz: 12c92260228ab4a8dc81b625e7952273a10b940e0dd52e2c392b6cb332b9ec6a
5
5
  SHA512:
6
- metadata.gz: 49141bb3410cdfcef3ee8e7115a799b19ae37a34103237c22b26d1c724a8cd6e7552212d3c14de1af26afa8035b04c313411e41957557b29189579daaab4bf4a
7
- data.tar.gz: 74804f61ec336d0e7ae1c763ddd7addd6cc2b93a9610c1fd46ca5565d71c12f4d43f3b5a04a617375f5c5d04111ab3e73bc5db69a3651b9179b313762722ed9c
6
+ metadata.gz: 331cc36fe43dac4d8a8f949e2465f24e6b39a9030de82cc3662e28080e6ab686929f47e3d83c5e3cdee63c69200872bd179ac0746446ab50ced359946185aea1
7
+ data.tar.gz: 85110e55a94e6ce2e74da9a31c46fcf88ac5679d9b81a4052f77113f7caac63c3766a67ac70b6214dea9dac3700c185766fed9333963191555d79e2c9ce7c7fb
data/.rubocop.yml CHANGED
@@ -1,8 +1,33 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 3.1
3
+ NewCops: enable
4
+ SuggestExtensions: false
3
5
 
4
6
  Style/StringLiterals:
5
7
  EnforcedStyle: double_quotes
6
8
 
7
9
  Style/StringLiteralsInInterpolation:
8
10
  EnforcedStyle: double_quotes
11
+
12
+ Style/Documentation:
13
+ Enabled: false
14
+
15
+ Metrics/BlockLength:
16
+ Exclude:
17
+ - "spec/**/*_spec.rb"
18
+ - "*.gemspec"
19
+
20
+ Metrics/MethodLength:
21
+ Max: 30
22
+
23
+ Metrics/AbcSize:
24
+ Max: 30
25
+
26
+ Metrics/CyclomaticComplexity:
27
+ Max: 10
28
+
29
+ Layout/LineLength:
30
+ Max: 150
31
+
32
+ Lint/UnusedMethodArgument:
33
+ AllowUnusedKeywordArguments: true
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2025-11-06
4
+
5
+ ### Fixed
6
+ - Fixed GitHub Actions badge URL in README to display correctly
7
+ - Fixed gem version badge display on RubyGems page
8
+
9
+ ## [0.1.1] - 2025-11-06
10
+
11
+ ### Fixed
12
+ - Fixed render_template to handle both %{key} and %<key>s string interpolation formats
13
+ - Fixed gemspec validation error by excluding gem files from repository
14
+
15
+ ### Changed
16
+ - Applied RuboCop auto-corrections and style improvements
17
+ - Updated RuboCop configuration with NewCops enabled
18
+ - Improved code formatting and consistency across all files
19
+ - Updated ENV variable access to use ENV.fetch for better nil handling
20
+
3
21
  ## [0.1.0] - 2025-11-05
4
22
 
5
23
  - Initial release
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  A powerful and flexible gem to integrate AI capabilities into your Rails applications. Support for multiple AI providers including OpenAI, Anthropic (Claude), Google Gemini, Cohere, Azure OpenAI, Mistral AI, and OpenRouter.
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/rails_ai_promptable.svg)](https://badge.fury.io/rb/rails_ai_promptable)
6
- [![Tests](https://github.com/shoaibmalik786/rails_ai_promptable/workflows/tests/badge.svg)](https://github.com/shoaibmalik786/rails_ai_promptable/actions)
6
+ [![Ruby](https://github.com/shoaibmalik786/rails_ai_promptable/actions/workflows/main.yml/badge.svg)](https://github.com/shoaibmalik786/rails_ai_promptable/actions)
7
7
 
8
8
  ## Features
9
9
 
@@ -1,48 +1,46 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators'
3
+ require "rails/generators"
4
4
 
5
5
  module RailsAiPromptable
6
6
  module Generators
7
7
  class InstallGenerator < Rails::Generators::Base
8
- source_root File.expand_path('templates', __dir__)
8
+ source_root File.expand_path("templates", __dir__)
9
9
 
10
- desc 'Creates a RailsAIPromptable initializer file'
10
+ desc "Creates a RailsAIPromptable initializer file"
11
11
 
12
12
  class_option :provider,
13
13
  type: :string,
14
- default: 'openai',
15
- desc: 'AI provider to use (openai, anthropic, gemini, cohere, azure_openai, mistral, openrouter)'
14
+ default: "openai",
15
+ desc: "AI provider to use (openai, anthropic, gemini, cohere, azure_openai, mistral, openrouter)"
16
16
 
17
17
  def copy_initializer_file
18
- template 'rails_ai_promptable.rb.tt', 'config/initializers/rails_ai_promptable.rb'
18
+ template "rails_ai_promptable.rb.tt", "config/initializers/rails_ai_promptable.rb"
19
19
  end
20
20
 
21
21
  def show_readme
22
- readme 'POST_INSTALL' if behavior == :invoke
22
+ readme "POST_INSTALL" if behavior == :invoke
23
23
  end
24
24
 
25
25
  private
26
26
 
27
27
  def provider_name
28
- options['provider'].to_s
28
+ options["provider"].to_s
29
29
  end
30
30
 
31
31
  def provider_config
32
32
  case provider_name
33
- when 'openai'
34
- openai_config
35
- when 'anthropic', 'claude'
33
+ when "anthropic", "claude"
36
34
  anthropic_config
37
- when 'gemini', 'google'
35
+ when "gemini", "google"
38
36
  gemini_config
39
- when 'cohere'
37
+ when "cohere"
40
38
  cohere_config
41
- when 'azure_openai', 'azure'
39
+ when "azure_openai", "azure"
42
40
  azure_config
43
- when 'mistral'
41
+ when "mistral"
44
42
  mistral_config
45
- when 'openrouter'
43
+ when "openrouter"
46
44
  openrouter_config
47
45
  else
48
46
  openai_config
@@ -51,7 +49,7 @@ module RailsAiPromptable
51
49
 
52
50
  def openai_config
53
51
  {
54
- provider: ':openai',
52
+ provider: ":openai",
55
53
  api_key: "ENV['OPENAI_API_KEY']",
56
54
  default_model: "'gpt-4o-mini'",
57
55
  additional_config: " # config.openai_base_url = 'https://api.openai.com/v1' # Optional: custom endpoint"
@@ -60,7 +58,7 @@ module RailsAiPromptable
60
58
 
61
59
  def anthropic_config
62
60
  {
63
- provider: ':anthropic',
61
+ provider: ":anthropic",
64
62
  api_key: "ENV['ANTHROPIC_API_KEY']",
65
63
  default_model: "'claude-3-5-sonnet-20241022'",
66
64
  additional_config: " # config.anthropic_base_url = 'https://api.anthropic.com/v1' # Optional: custom endpoint"
@@ -69,7 +67,7 @@ module RailsAiPromptable
69
67
 
70
68
  def gemini_config
71
69
  {
72
- provider: ':gemini',
70
+ provider: ":gemini",
73
71
  api_key: "ENV['GEMINI_API_KEY']",
74
72
  default_model: "'gemini-pro'",
75
73
  additional_config: " # config.gemini_base_url = 'https://generativelanguage.googleapis.com/v1beta' # Optional: custom endpoint"
@@ -78,7 +76,7 @@ module RailsAiPromptable
78
76
 
79
77
  def cohere_config
80
78
  {
81
- provider: ':cohere',
79
+ provider: ":cohere",
82
80
  api_key: "ENV['COHERE_API_KEY']",
83
81
  default_model: "'command'",
84
82
  additional_config: " # config.cohere_base_url = 'https://api.cohere.ai/v1' # Optional: custom endpoint"
@@ -87,7 +85,7 @@ module RailsAiPromptable
87
85
 
88
86
  def azure_config
89
87
  {
90
- provider: ':azure_openai',
88
+ provider: ":azure_openai",
91
89
  api_key: "ENV['AZURE_OPENAI_API_KEY']",
92
90
  default_model: "'gpt-4'",
93
91
  additional_config: <<~CONFIG.chomp
@@ -100,7 +98,7 @@ module RailsAiPromptable
100
98
 
101
99
  def mistral_config
102
100
  {
103
- provider: ':mistral',
101
+ provider: ":mistral",
104
102
  api_key: "ENV['MISTRAL_API_KEY']",
105
103
  default_model: "'mistral-small-latest'",
106
104
  additional_config: " # config.mistral_base_url = 'https://api.mistral.ai/v1' # Optional: custom endpoint"
@@ -109,7 +107,7 @@ module RailsAiPromptable
109
107
 
110
108
  def openrouter_config
111
109
  {
112
- provider: ':openrouter',
110
+ provider: ":openrouter",
113
111
  api_key: "ENV['OPENROUTER_API_KEY']",
114
112
  default_model: "'openai/gpt-3.5-turbo'",
115
113
  additional_config: <<~CONFIG.chomp
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  begin
4
- require 'active_job'
4
+ require "active_job"
5
5
  rescue LoadError
6
6
  # ActiveJob not available, skip loading BackgroundJob
7
7
  end
@@ -19,9 +19,7 @@ if defined?(ActiveJob)
19
19
  result = record.ai_generate(context: context, **(kwargs || {}))
20
20
 
21
21
  # Call the callback method if defined on the record
22
- if record.respond_to?(:ai_generation_completed)
23
- record.ai_generation_completed(result)
24
- end
22
+ record.ai_generation_completed(result) if record.respond_to?(:ai_generation_completed)
25
23
 
26
24
  # Store result in ai_generated_content attribute if it exists
27
25
  if record.respond_to?(:ai_generated_content=)
@@ -20,60 +20,60 @@ module RailsAIPromptable
20
20
 
21
21
  def initialize
22
22
  @provider = :openai
23
- @api_key = ENV['OPENAI_API_KEY']
24
- @default_model = 'gpt-4o-mini'
23
+ @api_key = ENV.fetch("OPENAI_API_KEY", nil)
24
+ @default_model = "gpt-4o-mini"
25
25
  @timeout = 30
26
26
  @logger = Logger.new($stdout)
27
27
 
28
28
  # OpenAI settings
29
- @openai_base_url = 'https://api.openai.com/v1'
29
+ @openai_base_url = "https://api.openai.com/v1"
30
30
 
31
31
  # Anthropic settings
32
- @anthropic_api_key = ENV['ANTHROPIC_API_KEY']
33
- @anthropic_base_url = 'https://api.anthropic.com/v1'
32
+ @anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
33
+ @anthropic_base_url = "https://api.anthropic.com/v1"
34
34
 
35
35
  # Gemini settings
36
- @gemini_api_key = ENV['GEMINI_API_KEY']
37
- @gemini_base_url = 'https://generativelanguage.googleapis.com/v1beta'
36
+ @gemini_api_key = ENV.fetch("GEMINI_API_KEY", nil)
37
+ @gemini_base_url = "https://generativelanguage.googleapis.com/v1beta"
38
38
 
39
39
  # Cohere settings
40
- @cohere_api_key = ENV['COHERE_API_KEY']
41
- @cohere_base_url = 'https://api.cohere.ai/v1'
40
+ @cohere_api_key = ENV.fetch("COHERE_API_KEY", nil)
41
+ @cohere_base_url = "https://api.cohere.ai/v1"
42
42
 
43
43
  # Azure OpenAI settings
44
- @azure_api_key = ENV['AZURE_OPENAI_API_KEY']
45
- @azure_base_url = ENV['AZURE_OPENAI_BASE_URL']
46
- @azure_api_version = '2024-02-15-preview'
47
- @azure_deployment_name = ENV['AZURE_OPENAI_DEPLOYMENT_NAME']
44
+ @azure_api_key = ENV.fetch("AZURE_OPENAI_API_KEY", nil)
45
+ @azure_base_url = ENV.fetch("AZURE_OPENAI_BASE_URL", nil)
46
+ @azure_api_version = "2024-02-15-preview"
47
+ @azure_deployment_name = ENV.fetch("AZURE_OPENAI_DEPLOYMENT_NAME", nil)
48
48
 
49
49
  # Mistral AI settings
50
- @mistral_api_key = ENV['MISTRAL_API_KEY']
51
- @mistral_base_url = 'https://api.mistral.ai/v1'
50
+ @mistral_api_key = ENV.fetch("MISTRAL_API_KEY", nil)
51
+ @mistral_base_url = "https://api.mistral.ai/v1"
52
52
 
53
53
  # OpenRouter settings
54
- @openrouter_api_key = ENV['OPENROUTER_API_KEY']
55
- @openrouter_base_url = 'https://openrouter.ai/api/v1'
56
- @openrouter_app_name = ENV['OPENROUTER_APP_NAME']
57
- @openrouter_site_url = ENV['OPENROUTER_SITE_URL']
54
+ @openrouter_api_key = ENV.fetch("OPENROUTER_API_KEY", nil)
55
+ @openrouter_base_url = "https://openrouter.ai/api/v1"
56
+ @openrouter_app_name = ENV.fetch("OPENROUTER_APP_NAME", nil)
57
+ @openrouter_site_url = ENV.fetch("OPENROUTER_SITE_URL", nil)
58
58
  end
59
59
 
60
60
  # Helper method to get the appropriate default model for the current provider
61
61
  def model_for_provider
62
62
  case provider.to_sym
63
63
  when :openai
64
- 'gpt-4o-mini'
64
+ "gpt-4o-mini"
65
65
  when :anthropic
66
- 'claude-3-5-sonnet-20241022'
66
+ "claude-3-5-sonnet-20241022"
67
67
  when :gemini
68
- 'gemini-pro'
68
+ "gemini-pro"
69
69
  when :cohere
70
- 'command'
70
+ "command"
71
71
  when :azure_openai
72
- azure_deployment_name || 'gpt-4'
72
+ azure_deployment_name || "gpt-4"
73
73
  when :mistral
74
- 'mistral-small-latest'
74
+ "mistral-small-latest"
75
75
  when :openrouter
76
- 'openai/gpt-3.5-turbo'
76
+ "openai/gpt-3.5-turbo"
77
77
  else
78
78
  default_model
79
79
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
- require 'logger'
2
+
3
+ require "logger"
3
4
 
4
5
  module RailsAIPromptable
5
6
  class Logger
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_support/concern'
3
+ require "active_support/concern"
4
4
 
5
5
  module RailsAIPromptable
6
6
  module Promptable
@@ -13,6 +13,7 @@ module RailsAIPromptable
13
13
  class_methods do
14
14
  def prompt_template(template = nil)
15
15
  return ai_prompt_template if template.nil?
16
+
16
17
  self.ai_prompt_template = template
17
18
  end
18
19
 
@@ -21,7 +22,8 @@ module RailsAIPromptable
21
22
  template = RailsAIPromptable::TemplateRegistry.get(name)
22
23
 
23
24
  if template.nil?
24
- raise ArgumentError, "Template '#{name}' not found. Available templates: #{RailsAIPromptable::TemplateRegistry.list.join(', ')}"
25
+ raise ArgumentError,
26
+ "Template '#{name}' not found. Available templates: #{RailsAIPromptable::TemplateRegistry.list.join(", ")}"
25
27
  end
26
28
 
27
29
  self.ai_prompt_template = template
@@ -29,12 +31,12 @@ module RailsAIPromptable
29
31
  end
30
32
 
31
33
  def ai_generate(context: {}, model: nil, temperature: nil, format: :text)
32
- template = self.class.ai_prompt_template || ''
34
+ template = self.class.ai_prompt_template || ""
33
35
  prompt = render_template(template, context)
34
36
 
35
37
  RailsAIPromptable.configuration.logger.info("[rails_ai_promptable] prompt: ")
36
38
 
37
- response = RailsAIPromptable.client.generate(
39
+ RailsAIPromptable.client.generate(
38
40
  prompt: prompt,
39
41
  model: model || RailsAIPromptable.configuration.default_model,
40
42
  temperature: temperature || 0.7,
@@ -42,11 +44,10 @@ module RailsAIPromptable
42
44
  )
43
45
 
44
46
  # basic parsing
45
- response
46
47
  end
47
48
 
48
49
  def ai_generate_later(context: {}, **kwargs)
49
- RailsAIPromptable.configuration.logger.info('[rails_ai_promptable] enqueuing ai_generate_later')
50
+ RailsAIPromptable.configuration.logger.info("[rails_ai_promptable] enqueuing ai_generate_later")
50
51
  # Use ActiveJob to enqueue. We'll provide a default job class in later steps.
51
52
  RailsAIPromptable::BackgroundJob.perform_later(self.class.name, id, context, kwargs)
52
53
  end
@@ -56,9 +57,12 @@ module RailsAIPromptable
56
57
  def render_template(template, context)
57
58
  template % context.transform_keys(&:to_sym)
58
59
  rescue KeyError
59
- # fallback: simple interpolation using gsub
60
+ # fallback: simple interpolation using gsub for both %{key} and %<key>s formats
60
61
  result = template.dup
61
- context.each { |k, v| result.gsub!("%{#{k}}", v.to_s) }
62
+ context.each do |k, v|
63
+ result.gsub!("%{#{k}}", v.to_s)
64
+ result.gsub!("%<#{k}>s", v.to_s)
65
+ end
62
66
  result
63
67
  end
64
68
  end
@@ -1,35 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
8
8
  class AnthropicProvider < BaseProvider
9
- API_VERSION = '2023-06-01'
9
+ API_VERSION = "2023-06-01"
10
10
 
11
11
  def initialize(configuration)
12
12
  super
13
13
  @api_key = configuration.anthropic_api_key || configuration.api_key
14
- @base_url = configuration.anthropic_base_url || 'https://api.anthropic.com/v1'
14
+ @base_url = configuration.anthropic_base_url || "https://api.anthropic.com/v1"
15
15
  @timeout = configuration.timeout
16
16
  end
17
17
 
18
18
  def generate(prompt:, model:, temperature:, format:)
19
19
  uri = URI.parse("#{@base_url}/messages")
20
20
  http = Net::HTTP.new(uri.host, uri.port)
21
- http.use_ssl = uri.scheme == 'https'
21
+ http.use_ssl = uri.scheme == "https"
22
22
  http.read_timeout = @timeout
23
23
 
24
24
  request = Net::HTTP::Post.new(uri.request_uri, {
25
- 'Content-Type' => 'application/json',
26
- 'x-api-key' => @api_key,
27
- 'anthropic-version' => API_VERSION
28
- })
25
+ "Content-Type" => "application/json",
26
+ "x-api-key" => @api_key,
27
+ "anthropic-version" => API_VERSION
28
+ })
29
29
 
30
30
  body = {
31
31
  model: model,
32
- messages: [{ role: 'user', content: prompt }],
32
+ messages: [{ role: "user", content: prompt }],
33
33
  temperature: temperature,
34
34
  max_tokens: 4096
35
35
  }
@@ -40,13 +40,13 @@ module RailsAIPromptable
40
40
  parsed = JSON.parse(response.body)
41
41
 
42
42
  if response.code.to_i >= 400
43
- error_message = parsed.dig('error', 'message') || 'Unknown error'
43
+ error_message = parsed.dig("error", "message") || "Unknown error"
44
44
  raise "Anthropic API error: #{error_message}"
45
45
  end
46
46
 
47
47
  # Extract content from Anthropic response
48
- parsed.dig('content', 0, 'text')
49
- rescue => e
48
+ parsed.dig("content", 0, "text")
49
+ rescue StandardError => e
50
50
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] anthropic error: #{e.message}")
51
51
  nil
52
52
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
@@ -10,7 +10,7 @@ module RailsAIPromptable
10
10
  super
11
11
  @api_key = configuration.azure_api_key || configuration.api_key
12
12
  @base_url = configuration.azure_base_url
13
- @api_version = configuration.azure_api_version || '2024-02-15-preview'
13
+ @api_version = configuration.azure_api_version || "2024-02-15-preview"
14
14
  @timeout = configuration.timeout
15
15
  @deployment_name = configuration.azure_deployment_name
16
16
 
@@ -22,16 +22,16 @@ module RailsAIPromptable
22
22
  deployment = @deployment_name || model
23
23
  uri = URI.parse("#{@base_url}/openai/deployments/#{deployment}/chat/completions?api-version=#{@api_version}")
24
24
  http = Net::HTTP.new(uri.host, uri.port)
25
- http.use_ssl = uri.scheme == 'https'
25
+ http.use_ssl = uri.scheme == "https"
26
26
  http.read_timeout = @timeout
27
27
 
28
28
  request = Net::HTTP::Post.new(uri.request_uri, {
29
- 'Content-Type' => 'application/json',
30
- 'api-key' => @api_key
31
- })
29
+ "Content-Type" => "application/json",
30
+ "api-key" => @api_key
31
+ })
32
32
 
33
33
  body = {
34
- messages: [{ role: 'user', content: prompt }],
34
+ messages: [{ role: "user", content: prompt }],
35
35
  temperature: temperature,
36
36
  max_tokens: 2048
37
37
  }
@@ -42,13 +42,13 @@ module RailsAIPromptable
42
42
  parsed = JSON.parse(response.body)
43
43
 
44
44
  if response.code.to_i >= 400
45
- error_message = parsed.dig('error', 'message') || 'Unknown error'
45
+ error_message = parsed.dig("error", "message") || "Unknown error"
46
46
  raise "Azure OpenAI API error: #{error_message}"
47
47
  end
48
48
 
49
49
  # Extract content (same structure as OpenAI)
50
- parsed.dig('choices', 0, 'message', 'content')
51
- rescue => e
50
+ parsed.dig("choices", 0, "message", "content")
51
+ rescue StandardError => e
52
52
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] azure_openai error: #{e.message}")
53
53
  nil
54
54
  end
@@ -56,9 +56,9 @@ module RailsAIPromptable
56
56
  private
57
57
 
58
58
  def validate_azure_configuration!
59
- unless @base_url
60
- raise ArgumentError, 'Azure OpenAI requires azure_base_url to be set (e.g., https://your-resource.openai.azure.com)'
61
- end
59
+ return if @base_url
60
+
61
+ raise ArgumentError, "Azure OpenAI requires azure_base_url to be set (e.g., https://your-resource.openai.azure.com)"
62
62
  end
63
63
  end
64
64
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
@@ -9,20 +9,20 @@ module RailsAIPromptable
9
9
  def initialize(configuration)
10
10
  super
11
11
  @api_key = configuration.cohere_api_key || configuration.api_key
12
- @base_url = configuration.cohere_base_url || 'https://api.cohere.ai/v1'
12
+ @base_url = configuration.cohere_base_url || "https://api.cohere.ai/v1"
13
13
  @timeout = configuration.timeout
14
14
  end
15
15
 
16
16
  def generate(prompt:, model:, temperature:, format:)
17
17
  uri = URI.parse("#{@base_url}/generate")
18
18
  http = Net::HTTP.new(uri.host, uri.port)
19
- http.use_ssl = uri.scheme == 'https'
19
+ http.use_ssl = uri.scheme == "https"
20
20
  http.read_timeout = @timeout
21
21
 
22
22
  request = Net::HTTP::Post.new(uri.request_uri, {
23
- 'Content-Type' => 'application/json',
24
- 'Authorization' => "Bearer #{@api_key}"
25
- })
23
+ "Content-Type" => "application/json",
24
+ "Authorization" => "Bearer #{@api_key}"
25
+ })
26
26
 
27
27
  body = {
28
28
  model: model,
@@ -37,13 +37,13 @@ module RailsAIPromptable
37
37
  parsed = JSON.parse(response.body)
38
38
 
39
39
  if response.code.to_i >= 400
40
- error_message = parsed['message'] || 'Unknown error'
40
+ error_message = parsed["message"] || "Unknown error"
41
41
  raise "Cohere API error: #{error_message}"
42
42
  end
43
43
 
44
44
  # Extract content from Cohere response
45
- parsed.dig('generations', 0, 'text')
46
- rescue => e
45
+ parsed.dig("generations", 0, "text")
46
+ rescue StandardError => e
47
47
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] cohere error: #{e.message}")
48
48
  nil
49
49
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
@@ -9,7 +9,7 @@ module RailsAIPromptable
9
9
  def initialize(configuration)
10
10
  super
11
11
  @api_key = configuration.gemini_api_key || configuration.api_key
12
- @base_url = configuration.gemini_base_url || 'https://generativelanguage.googleapis.com/v1beta'
12
+ @base_url = configuration.gemini_base_url || "https://generativelanguage.googleapis.com/v1beta"
13
13
  @timeout = configuration.timeout
14
14
  end
15
15
 
@@ -17,12 +17,12 @@ module RailsAIPromptable
17
17
  # Gemini uses the API key as a query parameter
18
18
  uri = URI.parse("#{@base_url}/models/#{model}:generateContent?key=#{@api_key}")
19
19
  http = Net::HTTP.new(uri.host, uri.port)
20
- http.use_ssl = uri.scheme == 'https'
20
+ http.use_ssl = uri.scheme == "https"
21
21
  http.read_timeout = @timeout
22
22
 
23
23
  request = Net::HTTP::Post.new(uri.request_uri, {
24
- 'Content-Type' => 'application/json'
25
- })
24
+ "Content-Type" => "application/json"
25
+ })
26
26
 
27
27
  body = {
28
28
  contents: [{
@@ -40,13 +40,13 @@ module RailsAIPromptable
40
40
  parsed = JSON.parse(response.body)
41
41
 
42
42
  if response.code.to_i >= 400
43
- error_message = parsed.dig('error', 'message') || 'Unknown error'
43
+ error_message = parsed.dig("error", "message") || "Unknown error"
44
44
  raise "Gemini API error: #{error_message}"
45
45
  end
46
46
 
47
47
  # Extract content from Gemini response
48
- parsed.dig('candidates', 0, 'content', 'parts', 0, 'text')
49
- rescue => e
48
+ parsed.dig("candidates", 0, "content", "parts", 0, "text")
49
+ rescue StandardError => e
50
50
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] gemini error: #{e.message}")
51
51
  nil
52
52
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
@@ -9,24 +9,24 @@ module RailsAIPromptable
9
9
  def initialize(configuration)
10
10
  super
11
11
  @api_key = configuration.mistral_api_key || configuration.api_key
12
- @base_url = configuration.mistral_base_url || 'https://api.mistral.ai/v1'
12
+ @base_url = configuration.mistral_base_url || "https://api.mistral.ai/v1"
13
13
  @timeout = configuration.timeout
14
14
  end
15
15
 
16
16
  def generate(prompt:, model:, temperature:, format:)
17
17
  uri = URI.parse("#{@base_url}/chat/completions")
18
18
  http = Net::HTTP.new(uri.host, uri.port)
19
- http.use_ssl = uri.scheme == 'https'
19
+ http.use_ssl = uri.scheme == "https"
20
20
  http.read_timeout = @timeout
21
21
 
22
22
  request = Net::HTTP::Post.new(uri.request_uri, {
23
- 'Content-Type' => 'application/json',
24
- 'Authorization' => "Bearer #{@api_key}"
25
- })
23
+ "Content-Type" => "application/json",
24
+ "Authorization" => "Bearer #{@api_key}"
25
+ })
26
26
 
27
27
  body = {
28
28
  model: model,
29
- messages: [{ role: 'user', content: prompt }],
29
+ messages: [{ role: "user", content: prompt }],
30
30
  temperature: temperature,
31
31
  max_tokens: 2048
32
32
  }
@@ -37,13 +37,13 @@ module RailsAIPromptable
37
37
  parsed = JSON.parse(response.body)
38
38
 
39
39
  if response.code.to_i >= 400
40
- error_message = parsed.dig('message') || 'Unknown error'
40
+ error_message = parsed["message"] || "Unknown error"
41
41
  raise "Mistral API error: #{error_message}"
42
42
  end
43
43
 
44
44
  # Extract content (OpenAI-compatible format)
45
- parsed.dig('choices', 0, 'message', 'content')
46
- rescue => e
45
+ parsed.dig("choices", 0, "message", "content")
46
+ rescue StandardError => e
47
47
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] mistral error: #{e.message}")
48
48
  nil
49
49
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
@@ -16,15 +16,15 @@ module RailsAIPromptable
16
16
  def generate(prompt:, model:, temperature:, format:)
17
17
  uri = URI.parse("#{@base_url}/chat/completions")
18
18
  http = Net::HTTP.new(uri.host, uri.port)
19
- http.use_ssl = uri.scheme == 'https'
20
- request = Net::HTTP::Post.new(uri.request_uri, initheader = {
21
- 'Content-Type' => 'application/json',
22
- 'Authorization' => "Bearer #{@api_key}"
23
- })
19
+ http.use_ssl = uri.scheme == "https"
20
+ request = Net::HTTP::Post.new(uri.request_uri, {
21
+ "Content-Type" => "application/json",
22
+ "Authorization" => "Bearer #{@api_key}"
23
+ })
24
24
 
25
25
  body = {
26
26
  model: model,
27
- messages: [{ role: 'user', content: prompt }],
27
+ messages: [{ role: "user", content: prompt }],
28
28
  temperature: temperature
29
29
  }
30
30
 
@@ -34,8 +34,8 @@ module RailsAIPromptable
34
34
  parsed = JSON.parse(response.body)
35
35
 
36
36
  # naive extraction
37
- parsed.dig('choices', 0, 'message', 'content')
38
- rescue => e
37
+ parsed.dig("choices", 0, "message", "content")
38
+ rescue StandardError => e
39
39
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] openai error: #{e.message}")
40
40
  nil
41
41
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'json'
3
+ require "net/http"
4
+ require "json"
5
5
 
6
6
  module RailsAIPromptable
7
7
  module Providers
@@ -9,7 +9,7 @@ module RailsAIPromptable
9
9
  def initialize(configuration)
10
10
  super
11
11
  @api_key = configuration.openrouter_api_key || configuration.api_key
12
- @base_url = configuration.openrouter_base_url || 'https://openrouter.ai/api/v1'
12
+ @base_url = configuration.openrouter_base_url || "https://openrouter.ai/api/v1"
13
13
  @timeout = configuration.timeout
14
14
  @app_name = configuration.openrouter_app_name
15
15
  @site_url = configuration.openrouter_site_url
@@ -18,23 +18,23 @@ module RailsAIPromptable
18
18
  def generate(prompt:, model:, temperature:, format:)
19
19
  uri = URI.parse("#{@base_url}/chat/completions")
20
20
  http = Net::HTTP.new(uri.host, uri.port)
21
- http.use_ssl = uri.scheme == 'https'
21
+ http.use_ssl = uri.scheme == "https"
22
22
  http.read_timeout = @timeout
23
23
 
24
24
  headers = {
25
- 'Content-Type' => 'application/json',
26
- 'Authorization' => "Bearer #{@api_key}"
25
+ "Content-Type" => "application/json",
26
+ "Authorization" => "Bearer #{@api_key}"
27
27
  }
28
28
 
29
29
  # OpenRouter requires these headers for tracking/attribution
30
- headers['HTTP-Referer'] = @site_url if @site_url
31
- headers['X-Title'] = @app_name if @app_name
30
+ headers["HTTP-Referer"] = @site_url if @site_url
31
+ headers["X-Title"] = @app_name if @app_name
32
32
 
33
33
  request = Net::HTTP::Post.new(uri.request_uri, headers)
34
34
 
35
35
  body = {
36
36
  model: model,
37
- messages: [{ role: 'user', content: prompt }],
37
+ messages: [{ role: "user", content: prompt }],
38
38
  temperature: temperature,
39
39
  max_tokens: 2048
40
40
  }
@@ -45,13 +45,13 @@ module RailsAIPromptable
45
45
  parsed = JSON.parse(response.body)
46
46
 
47
47
  if response.code.to_i >= 400
48
- error_message = parsed.dig('error', 'message') || 'Unknown error'
48
+ error_message = parsed.dig("error", "message") || "Unknown error"
49
49
  raise "OpenRouter API error: #{error_message}"
50
50
  end
51
51
 
52
52
  # Extract content (OpenAI-compatible format)
53
- parsed.dig('choices', 0, 'message', 'content')
54
- rescue => e
53
+ parsed.dig("choices", 0, "message", "content")
54
+ rescue StandardError => e
55
55
  RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] openrouter error: #{e.message}")
56
56
  nil
57
57
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  module RailsAIPromptable
4
4
  module Providers
5
- autoload :BaseProvider, 'rails_ai_promptable/providers/base_provider'
6
- autoload :OpenAIProvider, 'rails_ai_promptable/providers/openai_provider'
7
- autoload :AnthropicProvider, 'rails_ai_promptable/providers/anthropic_provider'
8
- autoload :GeminiProvider, 'rails_ai_promptable/providers/gemini_provider'
9
- autoload :CohereProvider, 'rails_ai_promptable/providers/cohere_provider'
10
- autoload :AzureOpenAIProvider, 'rails_ai_promptable/providers/azure_openai_provider'
11
- autoload :MistralProvider, 'rails_ai_promptable/providers/mistral_provider'
12
- autoload :OpenRouterProvider, 'rails_ai_promptable/providers/openrouter_provider'
5
+ autoload :BaseProvider, "rails_ai_promptable/providers/base_provider"
6
+ autoload :OpenAIProvider, "rails_ai_promptable/providers/openai_provider"
7
+ autoload :AnthropicProvider, "rails_ai_promptable/providers/anthropic_provider"
8
+ autoload :GeminiProvider, "rails_ai_promptable/providers/gemini_provider"
9
+ autoload :CohereProvider, "rails_ai_promptable/providers/cohere_provider"
10
+ autoload :AzureOpenAIProvider, "rails_ai_promptable/providers/azure_openai_provider"
11
+ autoload :MistralProvider, "rails_ai_promptable/providers/mistral_provider"
12
+ autoload :OpenRouterProvider, "rails_ai_promptable/providers/openrouter_provider"
13
13
 
14
14
  def self.for(provider_sym, configuration)
15
15
  case provider_sym.to_sym
@@ -28,13 +28,14 @@ module RailsAIPromptable
28
28
  when :openrouter
29
29
  OpenRouterProvider.new(configuration)
30
30
  else
31
- raise ArgumentError, "Unknown provider: #{provider_sym}. Supported providers: :openai, :anthropic, :gemini, :cohere, :azure_openai, :mistral, :openrouter"
31
+ raise ArgumentError,
32
+ "Unknown provider: #{provider_sym}. Supported providers: :openai, :anthropic, :gemini, :cohere, :azure_openai, :mistral, :openrouter"
32
33
  end
33
34
  end
34
35
 
35
36
  # Helper method to list all available providers
36
37
  def self.available_providers
37
- [:openai, :anthropic, :gemini, :cohere, :azure_openai, :mistral, :openrouter]
38
+ %i[openai anthropic gemini cohere azure_openai mistral openrouter]
38
39
  end
39
40
  end
40
41
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'yaml'
3
+ require "yaml"
4
4
 
5
5
  module RailsAIPromptable
6
6
  class TemplateRegistry
@@ -43,14 +43,14 @@ module RailsAIPromptable
43
43
  def load_from_directory(directory_path)
44
44
  return unless Dir.exist?(directory_path)
45
45
 
46
- Dir.glob(File.join(directory_path, '*')).each do |file_path|
46
+ Dir.glob(File.join(directory_path, "*")).each do |file_path|
47
47
  next unless File.file?(file_path)
48
48
 
49
- name = File.basename(file_path, '.*')
49
+ name = File.basename(file_path, ".*")
50
50
 
51
- if file_path.end_with?('.yml', '.yaml')
51
+ if file_path.end_with?(".yml", ".yaml")
52
52
  content = YAML.load_file(file_path)
53
- template = content.is_a?(Hash) ? content['template'] : content.to_s
53
+ template = content.is_a?(Hash) ? content["template"] : content.to_s
54
54
  else
55
55
  template = File.read(file_path)
56
56
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsAIPromptable
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.2"
5
5
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails_ai_promptable/version'
4
- require 'rails_ai_promptable/configuration'
5
- require 'rails_ai_promptable/template_registry'
6
- require 'rails_ai_promptable/promptable'
7
- require 'rails_ai_promptable/providers'
8
- require 'rails_ai_promptable/logger'
9
- require 'rails_ai_promptable/background_job'
3
+ require "rails_ai_promptable/version"
4
+ require "rails_ai_promptable/configuration"
5
+ require "rails_ai_promptable/template_registry"
6
+ require "rails_ai_promptable/promptable"
7
+ require "rails_ai_promptable/providers"
8
+ require "rails_ai_promptable/logger"
9
+ require "rails_ai_promptable/background_job"
10
10
 
11
11
  module RailsAIPromptable
12
12
  class << self
@@ -25,4 +25,4 @@ module RailsAIPromptable
25
25
  @client = nil
26
26
  end
27
27
  end
28
- end
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_ai_promptable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shoaib Malik
@@ -11,33 +11,33 @@ cert_chain: []
11
11
  date: 2025-11-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rails
14
+ name: httparty
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.0'
19
+ version: '0.20'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '7.0'
26
+ version: '0.20'
27
27
  - !ruby/object:Gem::Dependency
28
- name: httparty
28
+ name: rails
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0.20'
33
+ version: '7.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0.20'
40
+ version: '7.0'
41
41
  description: rails_ai_promptable makes it easy to integrate AI-driven features into
42
42
  your Rails application. It allows you to define promptable methods, chain context,
43
43
  and connect with AI APIs like OpenAI, Anthropic, or local LLMs with minimal setup.