rails_ai_promptable 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +596 -0
- data/Rakefile +12 -0
- data/lib/generators/rails_ai_promptable/install_generator.rb +124 -0
- data/lib/generators/rails_ai_promptable/templates/POST_INSTALL +46 -0
- data/lib/generators/rails_ai_promptable/templates/rails_ai_promptable.rb.tt +42 -0
- data/lib/rails_ai_promptable/background_job.rb +38 -0
- data/lib/rails_ai_promptable/configuration.rb +82 -0
- data/lib/rails_ai_promptable/logger.rb +18 -0
- data/lib/rails_ai_promptable/promptable.rb +65 -0
- data/lib/rails_ai_promptable/providers/anthropic_provider.rb +55 -0
- data/lib/rails_ai_promptable/providers/azure_openai_provider.rb +65 -0
- data/lib/rails_ai_promptable/providers/base_provider.rb +15 -0
- data/lib/rails_ai_promptable/providers/cohere_provider.rb +52 -0
- data/lib/rails_ai_promptable/providers/gemini_provider.rb +55 -0
- data/lib/rails_ai_promptable/providers/mistral_provider.rb +52 -0
- data/lib/rails_ai_promptable/providers/openai_provider.rb +44 -0
- data/lib/rails_ai_promptable/providers/openrouter_provider.rb +60 -0
- data/lib/rails_ai_promptable/providers.rb +40 -0
- data/lib/rails_ai_promptable/template_registry.rb +73 -0
- data/lib/rails_ai_promptable/version.rb +5 -0
- data/lib/rails_ai_promptable.rb +28 -0
- data/sig/rails_ai_promptable.rbs +4 -0
- metadata +104 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module RailsAIPromptable
|
|
7
|
+
module Providers
|
|
8
|
+
class MistralProvider < BaseProvider
|
|
9
|
+
def initialize(configuration)
|
|
10
|
+
super
|
|
11
|
+
@api_key = configuration.mistral_api_key || configuration.api_key
|
|
12
|
+
@base_url = configuration.mistral_base_url || 'https://api.mistral.ai/v1'
|
|
13
|
+
@timeout = configuration.timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate(prompt:, model:, temperature:, format:)
|
|
17
|
+
uri = URI.parse("#{@base_url}/chat/completions")
|
|
18
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
19
|
+
http.use_ssl = uri.scheme == 'https'
|
|
20
|
+
http.read_timeout = @timeout
|
|
21
|
+
|
|
22
|
+
request = Net::HTTP::Post.new(uri.request_uri, {
|
|
23
|
+
'Content-Type' => 'application/json',
|
|
24
|
+
'Authorization' => "Bearer #{@api_key}"
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
body = {
|
|
28
|
+
model: model,
|
|
29
|
+
messages: [{ role: 'user', content: prompt }],
|
|
30
|
+
temperature: temperature,
|
|
31
|
+
max_tokens: 2048
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
request.body = body.to_json
|
|
35
|
+
|
|
36
|
+
response = http.request(request)
|
|
37
|
+
parsed = JSON.parse(response.body)
|
|
38
|
+
|
|
39
|
+
if response.code.to_i >= 400
|
|
40
|
+
error_message = parsed.dig('message') || 'Unknown error'
|
|
41
|
+
raise "Mistral API error: #{error_message}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Extract content (OpenAI-compatible format)
|
|
45
|
+
parsed.dig('choices', 0, 'message', 'content')
|
|
46
|
+
rescue => e
|
|
47
|
+
RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] mistral error: #{e.message}")
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module RailsAIPromptable
|
|
7
|
+
module Providers
|
|
8
|
+
class OpenAIProvider < BaseProvider
|
|
9
|
+
def initialize(configuration)
|
|
10
|
+
super
|
|
11
|
+
@api_key = configuration.api_key
|
|
12
|
+
@base_url = configuration.openai_base_url
|
|
13
|
+
@timeout = configuration.timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate(prompt:, model:, temperature:, format:)
|
|
17
|
+
uri = URI.parse("#{@base_url}/chat/completions")
|
|
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
|
+
})
|
|
24
|
+
|
|
25
|
+
body = {
|
|
26
|
+
model: model,
|
|
27
|
+
messages: [{ role: 'user', content: prompt }],
|
|
28
|
+
temperature: temperature
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
request.body = body.to_json
|
|
32
|
+
|
|
33
|
+
response = http.request(request)
|
|
34
|
+
parsed = JSON.parse(response.body)
|
|
35
|
+
|
|
36
|
+
# naive extraction
|
|
37
|
+
parsed.dig('choices', 0, 'message', 'content')
|
|
38
|
+
rescue => e
|
|
39
|
+
RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] openai error: #{e.message}")
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'net/http'
|
|
4
|
+
require 'json'
|
|
5
|
+
|
|
6
|
+
module RailsAIPromptable
|
|
7
|
+
module Providers
|
|
8
|
+
class OpenRouterProvider < BaseProvider
|
|
9
|
+
def initialize(configuration)
|
|
10
|
+
super
|
|
11
|
+
@api_key = configuration.openrouter_api_key || configuration.api_key
|
|
12
|
+
@base_url = configuration.openrouter_base_url || 'https://openrouter.ai/api/v1'
|
|
13
|
+
@timeout = configuration.timeout
|
|
14
|
+
@app_name = configuration.openrouter_app_name
|
|
15
|
+
@site_url = configuration.openrouter_site_url
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generate(prompt:, model:, temperature:, format:)
|
|
19
|
+
uri = URI.parse("#{@base_url}/chat/completions")
|
|
20
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
21
|
+
http.use_ssl = uri.scheme == 'https'
|
|
22
|
+
http.read_timeout = @timeout
|
|
23
|
+
|
|
24
|
+
headers = {
|
|
25
|
+
'Content-Type' => 'application/json',
|
|
26
|
+
'Authorization' => "Bearer #{@api_key}"
|
|
27
|
+
}
|
|
28
|
+
|
|
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
|
|
32
|
+
|
|
33
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
34
|
+
|
|
35
|
+
body = {
|
|
36
|
+
model: model,
|
|
37
|
+
messages: [{ role: 'user', content: prompt }],
|
|
38
|
+
temperature: temperature,
|
|
39
|
+
max_tokens: 2048
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
request.body = body.to_json
|
|
43
|
+
|
|
44
|
+
response = http.request(request)
|
|
45
|
+
parsed = JSON.parse(response.body)
|
|
46
|
+
|
|
47
|
+
if response.code.to_i >= 400
|
|
48
|
+
error_message = parsed.dig('error', 'message') || 'Unknown error'
|
|
49
|
+
raise "OpenRouter API error: #{error_message}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Extract content (OpenAI-compatible format)
|
|
53
|
+
parsed.dig('choices', 0, 'message', 'content')
|
|
54
|
+
rescue => e
|
|
55
|
+
RailsAIPromptable.configuration.logger.error("[rails_ai_promptable] openrouter error: #{e.message}")
|
|
56
|
+
nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsAIPromptable
|
|
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'
|
|
13
|
+
|
|
14
|
+
def self.for(provider_sym, configuration)
|
|
15
|
+
case provider_sym.to_sym
|
|
16
|
+
when :openai
|
|
17
|
+
OpenAIProvider.new(configuration)
|
|
18
|
+
when :anthropic, :claude
|
|
19
|
+
AnthropicProvider.new(configuration)
|
|
20
|
+
when :gemini, :google
|
|
21
|
+
GeminiProvider.new(configuration)
|
|
22
|
+
when :cohere
|
|
23
|
+
CohereProvider.new(configuration)
|
|
24
|
+
when :azure_openai, :azure
|
|
25
|
+
AzureOpenAIProvider.new(configuration)
|
|
26
|
+
when :mistral
|
|
27
|
+
MistralProvider.new(configuration)
|
|
28
|
+
when :openrouter
|
|
29
|
+
OpenRouterProvider.new(configuration)
|
|
30
|
+
else
|
|
31
|
+
raise ArgumentError, "Unknown provider: #{provider_sym}. Supported providers: :openai, :anthropic, :gemini, :cohere, :azure_openai, :mistral, :openrouter"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Helper method to list all available providers
|
|
36
|
+
def self.available_providers
|
|
37
|
+
[:openai, :anthropic, :gemini, :cohere, :azure_openai, :mistral, :openrouter]
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module RailsAIPromptable
|
|
6
|
+
class TemplateRegistry
|
|
7
|
+
class << self
|
|
8
|
+
def templates
|
|
9
|
+
@templates ||= {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Register a template with a name
|
|
13
|
+
def register(name, template)
|
|
14
|
+
templates[name.to_sym] = template
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get a template by name
|
|
18
|
+
def get(name)
|
|
19
|
+
templates[name.to_sym]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Load templates from a YAML file
|
|
23
|
+
def load_from_file(file_path)
|
|
24
|
+
return unless File.exist?(file_path)
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
loaded_templates = YAML.load_file(file_path)
|
|
28
|
+
return unless loaded_templates.is_a?(Hash)
|
|
29
|
+
|
|
30
|
+
loaded_templates.each do |name, template|
|
|
31
|
+
register(name, template)
|
|
32
|
+
end
|
|
33
|
+
rescue Psych::SyntaxError => e
|
|
34
|
+
# Log the error but don't raise - invalid YAML files are silently skipped
|
|
35
|
+
if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
36
|
+
Rails.logger.warn("[rails_ai_promptable] Failed to load templates from #{file_path}: #{e.message}")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Load templates from a directory
|
|
42
|
+
# Each file should be named <template_name>.yml or <template_name>.txt
|
|
43
|
+
def load_from_directory(directory_path)
|
|
44
|
+
return unless Dir.exist?(directory_path)
|
|
45
|
+
|
|
46
|
+
Dir.glob(File.join(directory_path, '*')).each do |file_path|
|
|
47
|
+
next unless File.file?(file_path)
|
|
48
|
+
|
|
49
|
+
name = File.basename(file_path, '.*')
|
|
50
|
+
|
|
51
|
+
if file_path.end_with?('.yml', '.yaml')
|
|
52
|
+
content = YAML.load_file(file_path)
|
|
53
|
+
template = content.is_a?(Hash) ? content['template'] : content.to_s
|
|
54
|
+
else
|
|
55
|
+
template = File.read(file_path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
register(name, template) if template
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Clear all registered templates
|
|
63
|
+
def clear!
|
|
64
|
+
@templates = {}
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# List all registered template names
|
|
68
|
+
def list
|
|
69
|
+
templates.keys
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
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'
|
|
10
|
+
|
|
11
|
+
module RailsAIPromptable
|
|
12
|
+
class << self
|
|
13
|
+
attr_accessor :configuration
|
|
14
|
+
|
|
15
|
+
def configure
|
|
16
|
+
self.configuration ||= Configuration.new
|
|
17
|
+
yield(configuration) if block_given?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def client
|
|
21
|
+
@client ||= Providers.for(configuration.provider, configuration)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reset_client!
|
|
25
|
+
@client = nil
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails_ai_promptable
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shoaib Malik
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-11-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: httparty
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.20'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.20'
|
|
41
|
+
description: rails_ai_promptable makes it easy to integrate AI-driven features into
|
|
42
|
+
your Rails application. It allows you to define promptable methods, chain context,
|
|
43
|
+
and connect with AI APIs like OpenAI, Anthropic, or local LLMs with minimal setup.
|
|
44
|
+
email:
|
|
45
|
+
- shoaib2109@gmail.com
|
|
46
|
+
executables: []
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- ".rspec"
|
|
51
|
+
- ".rubocop.yml"
|
|
52
|
+
- CHANGELOG.md
|
|
53
|
+
- CODE_OF_CONDUCT.md
|
|
54
|
+
- LICENSE.txt
|
|
55
|
+
- README.md
|
|
56
|
+
- Rakefile
|
|
57
|
+
- lib/generators/rails_ai_promptable/install_generator.rb
|
|
58
|
+
- lib/generators/rails_ai_promptable/templates/POST_INSTALL
|
|
59
|
+
- lib/generators/rails_ai_promptable/templates/rails_ai_promptable.rb.tt
|
|
60
|
+
- lib/rails_ai_promptable.rb
|
|
61
|
+
- lib/rails_ai_promptable/background_job.rb
|
|
62
|
+
- lib/rails_ai_promptable/configuration.rb
|
|
63
|
+
- lib/rails_ai_promptable/logger.rb
|
|
64
|
+
- lib/rails_ai_promptable/promptable.rb
|
|
65
|
+
- lib/rails_ai_promptable/providers.rb
|
|
66
|
+
- lib/rails_ai_promptable/providers/anthropic_provider.rb
|
|
67
|
+
- lib/rails_ai_promptable/providers/azure_openai_provider.rb
|
|
68
|
+
- lib/rails_ai_promptable/providers/base_provider.rb
|
|
69
|
+
- lib/rails_ai_promptable/providers/cohere_provider.rb
|
|
70
|
+
- lib/rails_ai_promptable/providers/gemini_provider.rb
|
|
71
|
+
- lib/rails_ai_promptable/providers/mistral_provider.rb
|
|
72
|
+
- lib/rails_ai_promptable/providers/openai_provider.rb
|
|
73
|
+
- lib/rails_ai_promptable/providers/openrouter_provider.rb
|
|
74
|
+
- lib/rails_ai_promptable/template_registry.rb
|
|
75
|
+
- lib/rails_ai_promptable/version.rb
|
|
76
|
+
- sig/rails_ai_promptable.rbs
|
|
77
|
+
homepage: https://github.com/shoaibmalik786/rails_ai_promptable
|
|
78
|
+
licenses:
|
|
79
|
+
- MIT
|
|
80
|
+
metadata:
|
|
81
|
+
allowed_push_host: https://rubygems.org
|
|
82
|
+
homepage_uri: https://github.com/shoaibmalik786/rails_ai_promptable
|
|
83
|
+
source_code_uri: https://github.com/shoaibmalik786/rails_ai_promptable
|
|
84
|
+
changelog_uri: https://github.com/shoaibmalik786/rails_ai_promptable/blob/main/CHANGELOG.md
|
|
85
|
+
post_install_message:
|
|
86
|
+
rdoc_options: []
|
|
87
|
+
require_paths:
|
|
88
|
+
- lib
|
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: 3.1.0
|
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - ">="
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: '0'
|
|
99
|
+
requirements: []
|
|
100
|
+
rubygems_version: 3.4.10
|
|
101
|
+
signing_key:
|
|
102
|
+
specification_version: 4
|
|
103
|
+
summary: Add AI promptable behavior to your Rails models and classes.
|
|
104
|
+
test_files: []
|