ruby_llm 0.1.0.pre4 → 0.1.0.pre6
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 +4 -4
- data/.github/workflows/test.yml +5 -2
- data/.overcommit.yml +1 -1
- data/README.md +56 -181
- data/bin/console +6 -0
- data/lib/ruby_llm/chat.rb +95 -0
- data/lib/ruby_llm/chunk.rb +6 -0
- data/lib/ruby_llm/configuration.rb +2 -4
- data/lib/ruby_llm/message.rb +26 -18
- data/lib/ruby_llm/model_capabilities/anthropic.rb +43 -48
- data/lib/ruby_llm/model_capabilities/openai.rb +82 -89
- data/lib/ruby_llm/model_info.rb +26 -17
- data/lib/ruby_llm/models.json +686 -0
- data/lib/ruby_llm/models.rb +52 -0
- data/lib/ruby_llm/provider.rb +99 -0
- data/lib/ruby_llm/providers/anthropic.rb +92 -243
- data/lib/ruby_llm/providers/openai.rb +130 -174
- data/lib/ruby_llm/tool.rb +71 -50
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +35 -37
- data/lib/tasks/models.rake +25 -0
- data/ruby_llm.gemspec +8 -6
- metadata +39 -15
- data/lib/ruby_llm/active_record/acts_as.rb +0 -115
- data/lib/ruby_llm/client.rb +0 -70
- data/lib/ruby_llm/conversation.rb +0 -19
- data/lib/ruby_llm/model_capabilities/base.rb +0 -35
- data/lib/ruby_llm/providers/base.rb +0 -67
metadata
CHANGED
@@ -1,14 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_llm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.pre6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carmine Paolino
|
8
|
+
autorequire:
|
8
9
|
bindir: exe
|
9
10
|
cert_chain: []
|
10
|
-
date: 2025-01
|
11
|
+
date: 2025-02-01 00:00:00.000000000 Z
|
11
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: event_stream_parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.3.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.0
|
12
33
|
- !ruby/object:Gem::Dependency
|
13
34
|
name: faraday
|
14
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -301,9 +322,10 @@ dependencies:
|
|
301
322
|
- - ">="
|
302
323
|
- !ruby/object:Gem::Version
|
303
324
|
version: '0.9'
|
304
|
-
description:
|
305
|
-
|
306
|
-
|
325
|
+
description: A delightful Ruby way to work with AI language models. Provides a unified
|
326
|
+
interface to OpenAI and Anthropic models with automatic token counting, proper streaming
|
327
|
+
support, and a focus on developer happiness. No wrapping your head around multiple
|
328
|
+
APIs - just clean Ruby code that works.
|
307
329
|
email:
|
308
330
|
- carmine@paolino.me
|
309
331
|
executables: []
|
@@ -322,21 +344,22 @@ files:
|
|
322
344
|
- bin/console
|
323
345
|
- bin/setup
|
324
346
|
- lib/ruby_llm.rb
|
325
|
-
- lib/ruby_llm/
|
326
|
-
- lib/ruby_llm/
|
347
|
+
- lib/ruby_llm/chat.rb
|
348
|
+
- lib/ruby_llm/chunk.rb
|
327
349
|
- lib/ruby_llm/configuration.rb
|
328
|
-
- lib/ruby_llm/conversation.rb
|
329
350
|
- lib/ruby_llm/message.rb
|
330
351
|
- lib/ruby_llm/model_capabilities/anthropic.rb
|
331
|
-
- lib/ruby_llm/model_capabilities/base.rb
|
332
352
|
- lib/ruby_llm/model_capabilities/openai.rb
|
333
353
|
- lib/ruby_llm/model_info.rb
|
354
|
+
- lib/ruby_llm/models.json
|
355
|
+
- lib/ruby_llm/models.rb
|
356
|
+
- lib/ruby_llm/provider.rb
|
334
357
|
- lib/ruby_llm/providers/anthropic.rb
|
335
|
-
- lib/ruby_llm/providers/base.rb
|
336
358
|
- lib/ruby_llm/providers/openai.rb
|
337
359
|
- lib/ruby_llm/railtie.rb
|
338
360
|
- lib/ruby_llm/tool.rb
|
339
361
|
- lib/ruby_llm/version.rb
|
362
|
+
- lib/tasks/models.rake
|
340
363
|
- ruby_llm.gemspec
|
341
364
|
homepage: https://github.com/crmne/ruby_llm
|
342
365
|
licenses:
|
@@ -344,9 +367,10 @@ licenses:
|
|
344
367
|
metadata:
|
345
368
|
homepage_uri: https://github.com/crmne/ruby_llm
|
346
369
|
source_code_uri: https://github.com/crmne/ruby_llm
|
347
|
-
changelog_uri: https://github.com/crmne/ruby_llm/
|
348
|
-
documentation_uri: https://
|
370
|
+
changelog_uri: https://github.com/crmne/ruby_llm/commits/main
|
371
|
+
documentation_uri: https://github.com/crmne/ruby_llm
|
349
372
|
bug_tracker_uri: https://github.com/crmne/ruby_llm/issues
|
373
|
+
post_install_message:
|
350
374
|
rdoc_options: []
|
351
375
|
require_paths:
|
352
376
|
- lib
|
@@ -361,8 +385,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
361
385
|
- !ruby/object:Gem::Version
|
362
386
|
version: '0'
|
363
387
|
requirements: []
|
364
|
-
rubygems_version: 3.
|
388
|
+
rubygems_version: 3.5.22
|
389
|
+
signing_key:
|
365
390
|
specification_version: 4
|
366
|
-
summary:
|
367
|
-
other LLM providers
|
391
|
+
summary: Clean Ruby interface to modern AI language models
|
368
392
|
test_files: []
|
@@ -1,115 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
module ActiveRecord
|
5
|
-
# Provides ActsAs functionality for LLM-related models
|
6
|
-
module ActsAs
|
7
|
-
def acts_as_llm_model(_options = {})
|
8
|
-
include ModelMethods
|
9
|
-
end
|
10
|
-
|
11
|
-
def acts_as_llm_conversation(_options = {})
|
12
|
-
include ConversationMethods
|
13
|
-
has_many :messages, -> { order(created_at: :asc) }
|
14
|
-
end
|
15
|
-
|
16
|
-
def acts_as_llm_message(_options = {})
|
17
|
-
include MessageMethods
|
18
|
-
belongs_to :conversation
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# Methods for LLM model functionality
|
23
|
-
module ModelMethods
|
24
|
-
extend ActiveSupport::Concern
|
25
|
-
|
26
|
-
included do
|
27
|
-
validates :name, presence: true
|
28
|
-
validates :provider, presence: true
|
29
|
-
end
|
30
|
-
|
31
|
-
class_methods do
|
32
|
-
def sync_models
|
33
|
-
# Logic to sync models from providers
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
# Methods for LLM conversation handling
|
39
|
-
module ConversationMethods
|
40
|
-
extend ActiveSupport::Concern
|
41
|
-
|
42
|
-
included do
|
43
|
-
before_create :set_default_model
|
44
|
-
end
|
45
|
-
|
46
|
-
def send_message(content, model: nil)
|
47
|
-
transaction do
|
48
|
-
create_user_message(content)
|
49
|
-
create_assistant_response(model)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def create_user_message(content)
|
56
|
-
messages.create!(
|
57
|
-
role: :user,
|
58
|
-
content: content
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
|
-
def create_assistant_response(model)
|
63
|
-
response = RubyLLM.client.chat(
|
64
|
-
conversation_messages,
|
65
|
-
model: model || current_model
|
66
|
-
)
|
67
|
-
|
68
|
-
messages.create!(
|
69
|
-
role: :assistant,
|
70
|
-
content: response.content,
|
71
|
-
token_count: response.token_count
|
72
|
-
)
|
73
|
-
end
|
74
|
-
|
75
|
-
def conversation_messages
|
76
|
-
messages.map(&:to_llm_format)
|
77
|
-
end
|
78
|
-
|
79
|
-
def set_default_model
|
80
|
-
self.current_model ||= RubyLLM.configuration.default_model
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
# Methods for LLM message handling
|
85
|
-
module MessageMethods
|
86
|
-
extend ActiveSupport::Concern
|
87
|
-
|
88
|
-
included do
|
89
|
-
validates :role, presence: true, inclusion: { in: RubyLLM::Message::VALID_ROLES.map(&:to_s) }
|
90
|
-
validates :content, presence: true, unless: :tool_call?
|
91
|
-
|
92
|
-
before_save :calculate_tokens
|
93
|
-
end
|
94
|
-
|
95
|
-
def to_llm_format
|
96
|
-
RubyLLM::Message.new(
|
97
|
-
role: role.to_sym,
|
98
|
-
content: content,
|
99
|
-
tool_calls: tool_calls,
|
100
|
-
tool_results: tool_results
|
101
|
-
)
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def calculate_tokens
|
107
|
-
# Logic to calculate tokens
|
108
|
-
end
|
109
|
-
|
110
|
-
def tool_call?
|
111
|
-
tool_calls.present? || tool_results.present?
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
data/lib/ruby_llm/client.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
# Client class for handling LLM provider interactions
|
5
|
-
class Client
|
6
|
-
def initialize
|
7
|
-
@providers = {}
|
8
|
-
end
|
9
|
-
|
10
|
-
def chat(messages, model: nil, temperature: 0.7, stream: false, tools: nil, &block)
|
11
|
-
# Convert any hash messages to Message objects
|
12
|
-
formatted_messages = messages.map do |msg|
|
13
|
-
msg.is_a?(Message) ? msg : Message.new(**msg)
|
14
|
-
end
|
15
|
-
|
16
|
-
provider = provider_for(model)
|
17
|
-
response_messages = provider.chat(
|
18
|
-
formatted_messages,
|
19
|
-
model: model,
|
20
|
-
temperature: temperature,
|
21
|
-
stream: stream,
|
22
|
-
tools: tools,
|
23
|
-
&block
|
24
|
-
)
|
25
|
-
|
26
|
-
# Always return an array of messages, even for single responses
|
27
|
-
response_messages.is_a?(Array) ? response_messages : [response_messages]
|
28
|
-
end
|
29
|
-
|
30
|
-
def list_models(provider = nil)
|
31
|
-
if provider
|
32
|
-
provider_for(nil, provider).list_models
|
33
|
-
else
|
34
|
-
all_providers.flat_map(&:list_models)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def all_providers
|
41
|
-
[
|
42
|
-
provider_for(nil, :openai),
|
43
|
-
provider_for(nil, :anthropic)
|
44
|
-
]
|
45
|
-
end
|
46
|
-
|
47
|
-
def provider_for(model, specific_provider = nil)
|
48
|
-
provider_name = specific_provider || detect_provider(model)
|
49
|
-
@providers[provider_name] ||= case provider_name
|
50
|
-
when :openai then Providers::OpenAI.new
|
51
|
-
when :anthropic then Providers::Anthropic.new
|
52
|
-
else
|
53
|
-
raise Error, "Unsupported provider: #{provider_name}"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def detect_provider(model)
|
58
|
-
return RubyLLM.configuration.default_provider unless model
|
59
|
-
|
60
|
-
case model
|
61
|
-
when /^gpt-/, /^text-davinci/
|
62
|
-
:openai
|
63
|
-
when /^claude/
|
64
|
-
:anthropic
|
65
|
-
else
|
66
|
-
RubyLLM.configuration.default_provider
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
# Represents a conversation with an LLM
|
5
|
-
class Conversation
|
6
|
-
attr_reader :id, :messages, :tools
|
7
|
-
|
8
|
-
def initialize(tools: [])
|
9
|
-
@id = SecureRandom.uuid
|
10
|
-
@messages = []
|
11
|
-
@tools = tools
|
12
|
-
end
|
13
|
-
|
14
|
-
def add_message(message)
|
15
|
-
@messages << message
|
16
|
-
message
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
module ModelCapabilities
|
5
|
-
class Base
|
6
|
-
def determine_context_window(_model_id)
|
7
|
-
raise NotImplementedError
|
8
|
-
end
|
9
|
-
|
10
|
-
def determine_max_tokens(_model_id)
|
11
|
-
raise NotImplementedError
|
12
|
-
end
|
13
|
-
|
14
|
-
def get_input_price(_model_id)
|
15
|
-
raise NotImplementedError
|
16
|
-
end
|
17
|
-
|
18
|
-
def get_output_price(_model_id)
|
19
|
-
raise NotImplementedError
|
20
|
-
end
|
21
|
-
|
22
|
-
def supports_vision?(_model_id)
|
23
|
-
raise NotImplementedError
|
24
|
-
end
|
25
|
-
|
26
|
-
def supports_functions?(_model_id)
|
27
|
-
raise NotImplementedError
|
28
|
-
end
|
29
|
-
|
30
|
-
def supports_json_mode?(_model_id)
|
31
|
-
raise NotImplementedError
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module RubyLLM
|
4
|
-
module Providers
|
5
|
-
# Base provider class for LLM interactions
|
6
|
-
class Base
|
7
|
-
attr_reader :connection
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@connection = build_connection
|
11
|
-
end
|
12
|
-
|
13
|
-
def chat(messages, **options, &block)
|
14
|
-
raise NotImplementedError
|
15
|
-
end
|
16
|
-
|
17
|
-
protected
|
18
|
-
|
19
|
-
def check_for_api_error(response)
|
20
|
-
return unless response.body.is_a?(Hash) && response.body['type'] == 'error'
|
21
|
-
|
22
|
-
error_msg = response.body.dig('error', 'message') || 'Unknown API error'
|
23
|
-
raise RubyLLM::Error, "API error: #{error_msg}"
|
24
|
-
end
|
25
|
-
|
26
|
-
def build_connection
|
27
|
-
Faraday.new(url: api_base) do |f|
|
28
|
-
f.options.timeout = RubyLLM.configuration.request_timeout
|
29
|
-
f.request :json
|
30
|
-
f.response :json
|
31
|
-
f.adapter Faraday.default_adapter
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def handle_error(error)
|
36
|
-
case error
|
37
|
-
when Faraday::TimeoutError
|
38
|
-
raise RubyLLM::Error, 'Request timed out'
|
39
|
-
when Faraday::ConnectionFailed
|
40
|
-
raise RubyLLM::Error, 'Connection failed'
|
41
|
-
when Faraday::ClientError
|
42
|
-
handle_api_error(error)
|
43
|
-
else
|
44
|
-
raise error
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def handle_api_error(error)
|
49
|
-
raise RubyLLM::Error, "API error: #{error.response[:status]}"
|
50
|
-
end
|
51
|
-
|
52
|
-
def parse_error_message(response)
|
53
|
-
return "HTTP #{response.status}" unless response.body
|
54
|
-
|
55
|
-
if response.body.is_a?(String)
|
56
|
-
begin
|
57
|
-
JSON.parse(response.body).dig('error', 'message')
|
58
|
-
rescue StandardError
|
59
|
-
"HTTP #{response.status}"
|
60
|
-
end
|
61
|
-
else
|
62
|
-
response.body.dig('error', 'message') || "HTTP #{response.status}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|