ruby_llm 0.1.0.pre12 → 0.1.0.pre13

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: 70c8538fcfbdaba0d5bbb502affe66acfc38c5f67318ee69fe9dc203dbc4e7f7
4
- data.tar.gz: 51a0759de14ee600876471dd034e0ff99b3763d765fd3b81eadfe4dacbd3f7e5
3
+ metadata.gz: 325d22d60cd9f0a4157670442830fe2473e8e46879ee672d0e56207f6b3b8e72
4
+ data.tar.gz: 355be22334837f40ac717974058d4cceb4d46dd13e36a808c11410a9c4624dc0
5
5
  SHA512:
6
- metadata.gz: 3b5a3362b20ffb9153f9668612a0b6fde3fdf040d7234baee08978cdcc60717fce0cf8a0950929e0ab384db0fe9cfd82456e0a3c4f6eaba566999c80f32fc941
7
- data.tar.gz: 6f12660ca79c9d00196003c15bc866b831e47bbce52fc6d6f2ad731655b8e6243388ad4637d7b3c45cb1076c489fa6a8ca9973da20230cbc16b756782917056c
6
+ metadata.gz: a7ea97dbe171c7f62df442134b613c84ec277dffcdf1e86fafeed14a95c7ba2545b950d454153606e205f13ffd5a79a9e6e8158d1f053be51a915bbfb03d4806
7
+ data.tar.gz: bb20ec1351e7597550283b7b7b2b1dff50a26ceba1b123b5fabd8636150f2fae5eb935dac51153448895b7782cdbb85575c25a671064928a8c83465489bea6b9
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
1
  require:
2
2
  - rubocop-rake
3
- - rubocop-rspec
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 3.1
data/README.md CHANGED
@@ -79,15 +79,14 @@ puts "Conversation used #{last_message.input_tokens} input tokens and #{last_mes
79
79
 
80
80
  ## Using Tools
81
81
 
82
- Give Claude some Ruby superpowers by letting it call your code. Simply create tool classes that do one thing well:
82
+ Give your AI assistants access to your Ruby code by creating tool classes that do one thing well:
83
83
 
84
84
  ```ruby
85
- class CalculatorTool < RubyLLM::Tool
85
+ class Calculator < RubyLLM::Tool
86
86
  description "Performs arithmetic calculations"
87
87
 
88
88
  param :expression,
89
89
  type: :string,
90
- required: true,
91
90
  desc: "A mathematical expression to evaluate (e.g. '2 + 2')"
92
91
 
93
92
  def execute(expression:)
@@ -95,11 +94,10 @@ class CalculatorTool < RubyLLM::Tool
95
94
  end
96
95
  end
97
96
 
98
- class DocumentSearchTool < RubyLLM::Tool
97
+ class Search < RubyLLM::Tool
99
98
  description "Searches documents by similarity"
100
99
 
101
100
  param :query,
102
- type: :string,
103
101
  desc: "The search query"
104
102
 
105
103
  param :limit,
@@ -107,12 +105,12 @@ class DocumentSearchTool < RubyLLM::Tool
107
105
  desc: "Number of results to return",
108
106
  required: false
109
107
 
110
- def initialize(document_class:)
111
- @document_class = document_class
108
+ def initialize(repo:)
109
+ @repo = repo
112
110
  end
113
111
 
114
112
  def execute(query:, limit: 5)
115
- @document_class.similarity_search(query:, k: limit)
113
+ @repo.similarity_search(query, limit:)
116
114
  end
117
115
  end
118
116
  ```
@@ -121,11 +119,15 @@ Then use them in your conversations:
121
119
 
122
120
  ```ruby
123
121
  # Simple tools just work
124
- chat = RubyLLM.chat.with_tool(CalculatorTool.new)
122
+ chat = RubyLLM.chat.with_tool Calculator
125
123
 
126
124
  # Tools with dependencies are just regular Ruby objects
127
- search = DocumentSearchTool.new(document_class: Document)
128
- chat.with_tools(search, CalculatorTool.new)
125
+ search = Search.new repo: Document
126
+ chat.with_tools search, CalculatorTool
127
+
128
+ # Need more control? Configure as needed
129
+ chat.with_model('claude-3-5-sonnet-20241022')
130
+ .with_temperature(0.9)
129
131
 
130
132
  chat.ask "What's 2+2?"
131
133
  # => "Let me calculate that for you. The result is 4."
data/lib/ruby_llm/chat.rb CHANGED
@@ -10,6 +10,7 @@ module RubyLLM
10
10
  model_id = model || RubyLLM.config.default_model
11
11
  @model = Models.find model_id
12
12
  @provider = Models.provider_for model_id
13
+ @temperature = 0.7
13
14
  @messages = []
14
15
  @tools = {}
15
16
 
@@ -26,7 +27,7 @@ module RubyLLM
26
27
  def with_tool(tool)
27
28
  raise Error, "Model #{@model.id} doesn't support function calling" unless @model.supports_functions
28
29
 
29
- tool_instance = tool.is_a?(Class) ? tool.to_tool : tool
30
+ tool_instance = tool.is_a?(Class) ? tool.new : tool
30
31
  @tools[tool_instance.name.to_sym] = tool_instance
31
32
  self
32
33
  end
@@ -36,6 +37,17 @@ module RubyLLM
36
37
  self
37
38
  end
38
39
 
40
+ def with_model(model_id)
41
+ @model = Models.find model_id
42
+ @provider = Models.provider_for model_id
43
+ self
44
+ end
45
+
46
+ def with_temperature(temperature)
47
+ @temperature = temperature
48
+ self
49
+ end
50
+
39
51
  def each(&block)
40
52
  messages.each(&block)
41
53
  end
@@ -43,19 +55,17 @@ module RubyLLM
43
55
  private
44
56
 
45
57
  def complete(&block)
46
- response = @provider.complete(messages, tools: @tools, model: @model.id, &block)
58
+ response = @provider.complete messages, tools: @tools, temperature: @temperature, model: @model.id, &block
47
59
 
60
+ add_message response
48
61
  if response.tool_call?
49
62
  handle_tool_calls response, &block
50
63
  else
51
- add_message response
52
64
  response
53
65
  end
54
66
  end
55
67
 
56
68
  def handle_tool_calls(response, &block)
57
- add_message response
58
-
59
69
  response.tool_calls.each_value do |tool_call|
60
70
  result = execute_tool tool_call
61
71
  add_tool_result tool_call.id, result if result
@@ -26,7 +26,7 @@ module RubyLLM
26
26
  !tool_call_id.nil? && !tool_call_id.empty?
27
27
  end
28
28
 
29
- def tool_result
29
+ def tool_results
30
30
  content if tool_result?
31
31
  end
32
32
 
@@ -7,8 +7,8 @@ module RubyLLM
7
7
  end
8
8
 
9
9
  module InstanceMethods
10
- def complete(messages, tools: [], model: nil, &block)
11
- payload = build_payload messages, tools, model: model, stream: block_given?
10
+ def complete(messages, tools:, temperature:, model:, &block)
11
+ payload = build_payload messages, tools: tools, temperature: temperature, model: model, stream: block_given?
12
12
 
13
13
  if block_given?
14
14
  stream_response payload, &block
@@ -26,7 +26,7 @@ module RubyLLM
26
26
  '/v1/models'
27
27
  end
28
28
 
29
- def build_payload(messages, tools, model:, temperature: 0.7, stream: false)
29
+ def build_payload(messages, tools:, temperature:, model:, stream: false)
30
30
  {
31
31
  model: model,
32
32
  messages: format_messages(messages),
@@ -34,7 +34,7 @@ module RubyLLM
34
34
  stream: stream,
35
35
  max_tokens: RubyLLM.models.find(model).max_tokens
36
36
  }.tap do |payload|
37
- payload[:tools] = tools.map { |t| function_for(t) } if tools.any?
37
+ payload[:tools] = tools.values.map { |t| function_for(t) } if tools.any?
38
38
  end
39
39
  end
40
40
 
@@ -42,26 +42,26 @@ module RubyLLM
42
42
  data = response.body
43
43
  content_blocks = data['content'] || []
44
44
 
45
- text_content = content_blocks.find { |c| c['type'] == 'text' }&.fetch('text', '')
45
+ text_blocks = content_blocks.select { |c| c['type'] == 'text' }
46
+ text_content = text_blocks.map { |c| c['text'] }.join('')
47
+
46
48
  tool_use = content_blocks.find { |c| c['type'] == 'tool_use' }
47
49
 
48
50
  if tool_use
49
51
  Message.new(
50
52
  role: :assistant,
51
53
  content: text_content,
52
- tool_calls: [
53
- {
54
- name: tool_use['name'],
55
- arguments: JSON.generate(tool_use['input'] || {})
56
- }
57
- ]
54
+ tool_calls: parse_tool_calls(tool_use),
55
+ input_tokens: data.dig('usage', 'input_tokens'),
56
+ output_tokens: data.dig('usage', 'output_tokens'),
57
+ model_id: data['model']
58
58
  )
59
59
  else
60
60
  Message.new(
61
61
  role: :assistant,
62
62
  content: text_content,
63
- input_tokens: data['usage']['input_tokens'],
64
- output_tokens: data['usage']['output_tokens'],
63
+ input_tokens: data.dig('usage', 'input_tokens'),
64
+ output_tokens: data.dig('usage', 'output_tokens'),
65
65
  model_id: data['model']
66
66
  )
67
67
  end
@@ -90,18 +90,44 @@ module RubyLLM
90
90
 
91
91
  def handle_stream(&block)
92
92
  to_json_stream do |data|
93
- block.call(
94
- Chunk.new(
95
- role: :assistant,
96
- model_id: data.dig('message', 'model'),
97
- content: data.dig('delta', 'text'),
98
- input_tokens: data.dig('message', 'usage', 'input_tokens'),
99
- output_tokens: data.dig('message', 'usage', 'output_tokens') || data.dig('usage', 'output_tokens')
93
+ if data['type'] == 'content_block_delta' && data.dig('delta', 'type') == 'input_json_delta'
94
+ block.call(
95
+ Chunk.new(
96
+ role: :assistant,
97
+ model_id: data.dig('message', 'model'),
98
+ content: data.dig('delta', 'text'),
99
+ input_tokens: data.dig('message', 'usage', 'input_tokens'),
100
+ output_tokens: data.dig('message', 'usage', 'output_tokens') || data.dig('usage', 'output_tokens'),
101
+ tool_calls: { nil => ToolCall.new(id: nil, name: nil, arguments: data.dig('delta', 'partial_json')) }
102
+ )
100
103
  )
101
- )
104
+ else
105
+ block.call(
106
+ Chunk.new(
107
+ role: :assistant,
108
+ model_id: data.dig('message', 'model'),
109
+ content: data.dig('delta', 'text'),
110
+ input_tokens: data.dig('message', 'usage', 'input_tokens'),
111
+ output_tokens: data.dig('message', 'usage', 'output_tokens') || data.dig('usage', 'output_tokens'),
112
+ tool_calls: parse_tool_calls(data['content_block'])
113
+ )
114
+ )
115
+ end
102
116
  end
103
117
  end
104
118
 
119
+ def parse_tool_calls(content_block)
120
+ return nil unless content_block && content_block['type'] == 'tool_use'
121
+
122
+ {
123
+ content_block['id'] => ToolCall.new(
124
+ id: content_block['id'],
125
+ name: content_block['name'],
126
+ arguments: content_block['input']
127
+ )
128
+ }
129
+ end
130
+
105
131
  def function_for(tool)
106
132
  {
107
133
  name: tool.name,
@@ -116,16 +142,31 @@ module RubyLLM
116
142
 
117
143
  def format_messages(messages)
118
144
  messages.map do |msg|
119
- if msg.tool_results
145
+ if msg.tool_call?
120
146
  {
121
- role: convert_role(msg.role),
147
+ role: 'assistant',
148
+ content: [
149
+ {
150
+ type: 'text',
151
+ text: msg.content
152
+ },
153
+ {
154
+ type: 'tool_use',
155
+ id: msg.tool_calls.values.first.id,
156
+ name: msg.tool_calls.values.first.name,
157
+ input: msg.tool_calls.values.first.arguments
158
+ }
159
+ ]
160
+ }
161
+ elsif msg.tool_result?
162
+ {
163
+ role: 'user',
122
164
  content: [
123
165
  {
124
166
  type: 'tool_result',
125
- tool_use_id: msg.tool_results[:tool_use_id],
126
- content: msg.tool_results[:content],
127
- is_error: msg.tool_results[:is_error]
128
- }.compact
167
+ tool_use_id: msg.tool_call_id,
168
+ content: msg.content
169
+ }
129
170
  ]
130
171
  }
131
172
  else
@@ -139,19 +180,23 @@ module RubyLLM
139
180
 
140
181
  def convert_role(role)
141
182
  case role
183
+ when :tool then 'user'
142
184
  when :user then 'user'
143
185
  else 'assistant'
144
186
  end
145
187
  end
146
188
 
147
189
  def clean_parameters(parameters)
148
- parameters.transform_values do |props|
149
- props.except(:required)
190
+ parameters.transform_values do |param|
191
+ {
192
+ type: param.type,
193
+ description: param.description
194
+ }.compact
150
195
  end
151
196
  end
152
197
 
153
198
  def required_parameters(parameters)
154
- parameters.select { |_, props| props[:required] }.keys
199
+ parameters.select { |_, param| param.required }.keys
155
200
  end
156
201
  end
157
202
  end
@@ -25,7 +25,7 @@ module RubyLLM
25
25
  '/v1/models'
26
26
  end
27
27
 
28
- def build_payload(messages, tools, model:, temperature: 0.7, stream: false)
28
+ def build_payload(messages, tools:, temperature:, model:, stream: false)
29
29
  {
30
30
  model: model,
31
31
  messages: format_messages(messages),
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '0.1.0.pre12'
4
+ VERSION = '0.1.0.pre13'
5
5
  end
data/ruby_llm.gemspec CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
15
15
  ' works.'
16
16
  spec.homepage = 'https://github.com/crmne/ruby_llm'
17
17
  spec.license = 'MIT'
18
- spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
18
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.1.0')
19
19
 
20
20
  spec.metadata['homepage_uri'] = spec.homepage
21
21
  spec.metadata['source_code_uri'] = spec.homepage
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre12
4
+ version: 0.1.0.pre13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-03 00:00:00.000000000 Z
11
+ date: 2025-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: event_stream_parser
@@ -380,7 +380,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
380
380
  requirements:
381
381
  - - ">="
382
382
  - !ruby/object:Gem::Version
383
- version: 2.7.0
383
+ version: 3.1.0
384
384
  required_rubygems_version: !ruby/object:Gem::Requirement
385
385
  requirements:
386
386
  - - ">="