llm.rb 2.1.0 → 3.0.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -0
  3. data/lib/llm/bot.rb +4 -4
  4. data/lib/llm/buffer.rb +0 -9
  5. data/lib/llm/contract/completion.rb +57 -0
  6. data/lib/llm/contract.rb +48 -0
  7. data/lib/llm/error.rb +22 -14
  8. data/lib/llm/eventhandler.rb +6 -4
  9. data/lib/llm/eventstream/parser.rb +18 -13
  10. data/lib/llm/function.rb +1 -1
  11. data/lib/llm/json_adapter.rb +109 -0
  12. data/lib/llm/message.rb +7 -28
  13. data/lib/llm/multipart/enumerator_io.rb +86 -0
  14. data/lib/llm/multipart.rb +32 -51
  15. data/lib/llm/object/builder.rb +6 -6
  16. data/lib/llm/object/kernel.rb +2 -2
  17. data/lib/llm/object.rb +23 -8
  18. data/lib/llm/provider.rb +11 -3
  19. data/lib/llm/providers/anthropic/error_handler.rb +1 -1
  20. data/lib/llm/providers/anthropic/files.rb +4 -5
  21. data/lib/llm/providers/anthropic/models.rb +1 -2
  22. data/lib/llm/providers/anthropic/{format/completion_format.rb → request_adapter/completion.rb} +19 -19
  23. data/lib/llm/providers/anthropic/{format.rb → request_adapter.rb} +7 -7
  24. data/lib/llm/providers/anthropic/response_adapter/completion.rb +66 -0
  25. data/lib/llm/providers/anthropic/{response → response_adapter}/enumerable.rb +1 -1
  26. data/lib/llm/providers/anthropic/{response → response_adapter}/file.rb +1 -1
  27. data/lib/llm/providers/anthropic/{response → response_adapter}/web_search.rb +3 -3
  28. data/lib/llm/providers/anthropic/response_adapter.rb +36 -0
  29. data/lib/llm/providers/anthropic/stream_parser.rb +6 -6
  30. data/lib/llm/providers/anthropic.rb +8 -11
  31. data/lib/llm/providers/deepseek/{format/completion_format.rb → request_adapter/completion.rb} +15 -15
  32. data/lib/llm/providers/deepseek/{format.rb → request_adapter.rb} +7 -7
  33. data/lib/llm/providers/deepseek.rb +2 -2
  34. data/lib/llm/providers/gemini/audio.rb +2 -2
  35. data/lib/llm/providers/gemini/error_handler.rb +3 -3
  36. data/lib/llm/providers/gemini/files.rb +4 -7
  37. data/lib/llm/providers/gemini/images.rb +9 -14
  38. data/lib/llm/providers/gemini/models.rb +1 -2
  39. data/lib/llm/providers/gemini/{format/completion_format.rb → request_adapter/completion.rb} +14 -14
  40. data/lib/llm/providers/gemini/{format.rb → request_adapter.rb} +8 -8
  41. data/lib/llm/providers/gemini/response_adapter/completion.rb +67 -0
  42. data/lib/llm/providers/gemini/{response → response_adapter}/embedding.rb +1 -1
  43. data/lib/llm/providers/gemini/{response → response_adapter}/file.rb +1 -1
  44. data/lib/llm/providers/gemini/{response → response_adapter}/files.rb +1 -1
  45. data/lib/llm/providers/gemini/{response → response_adapter}/image.rb +3 -3
  46. data/lib/llm/providers/gemini/{response → response_adapter}/models.rb +1 -1
  47. data/lib/llm/providers/gemini/{response → response_adapter}/web_search.rb +3 -3
  48. data/lib/llm/providers/gemini/response_adapter.rb +42 -0
  49. data/lib/llm/providers/gemini/stream_parser.rb +37 -32
  50. data/lib/llm/providers/gemini.rb +10 -14
  51. data/lib/llm/providers/ollama/error_handler.rb +1 -1
  52. data/lib/llm/providers/ollama/{format/completion_format.rb → request_adapter/completion.rb} +19 -19
  53. data/lib/llm/providers/ollama/{format.rb → request_adapter.rb} +7 -7
  54. data/lib/llm/providers/ollama/response_adapter/completion.rb +61 -0
  55. data/lib/llm/providers/ollama/{response → response_adapter}/embedding.rb +1 -1
  56. data/lib/llm/providers/ollama/response_adapter.rb +32 -0
  57. data/lib/llm/providers/ollama/stream_parser.rb +2 -2
  58. data/lib/llm/providers/ollama.rb +8 -10
  59. data/lib/llm/providers/openai/audio.rb +1 -1
  60. data/lib/llm/providers/openai/error_handler.rb +12 -2
  61. data/lib/llm/providers/openai/files.rb +3 -6
  62. data/lib/llm/providers/openai/images.rb +4 -5
  63. data/lib/llm/providers/openai/models.rb +1 -3
  64. data/lib/llm/providers/openai/moderations.rb +3 -5
  65. data/lib/llm/providers/openai/{format/completion_format.rb → request_adapter/completion.rb} +22 -22
  66. data/lib/llm/providers/openai/{format/moderation_format.rb → request_adapter/moderation.rb} +5 -5
  67. data/lib/llm/providers/openai/{format/respond_format.rb → request_adapter/respond.rb} +16 -16
  68. data/lib/llm/providers/openai/{format.rb → request_adapter.rb} +12 -12
  69. data/lib/llm/providers/openai/{response → response_adapter}/audio.rb +1 -1
  70. data/lib/llm/providers/openai/response_adapter/completion.rb +62 -0
  71. data/lib/llm/providers/openai/{response → response_adapter}/embedding.rb +1 -1
  72. data/lib/llm/providers/openai/{response → response_adapter}/enumerable.rb +1 -1
  73. data/lib/llm/providers/openai/{response → response_adapter}/file.rb +1 -1
  74. data/lib/llm/providers/openai/{response → response_adapter}/image.rb +1 -1
  75. data/lib/llm/providers/openai/{response → response_adapter}/moderations.rb +1 -1
  76. data/lib/llm/providers/openai/{response → response_adapter}/responds.rb +6 -10
  77. data/lib/llm/providers/openai/{response → response_adapter}/web_search.rb +3 -3
  78. data/lib/llm/providers/openai/response_adapter.rb +47 -0
  79. data/lib/llm/providers/openai/responses/stream_parser.rb +22 -22
  80. data/lib/llm/providers/openai/responses.rb +6 -8
  81. data/lib/llm/providers/openai/stream_parser.rb +6 -5
  82. data/lib/llm/providers/openai/vector_stores.rb +8 -9
  83. data/lib/llm/providers/openai.rb +12 -14
  84. data/lib/llm/response.rb +2 -5
  85. data/lib/llm/usage.rb +10 -0
  86. data/lib/llm/version.rb +1 -1
  87. data/lib/llm.rb +33 -1
  88. metadata +44 -35
  89. data/lib/llm/providers/anthropic/response/completion.rb +0 -39
  90. data/lib/llm/providers/gemini/response/completion.rb +0 -35
  91. data/lib/llm/providers/ollama/response/completion.rb +0 -28
  92. data/lib/llm/providers/openai/response/completion.rb +0 -40
@@ -13,7 +13,6 @@ class LLM::OpenAI
13
13
  # chunks = llm.vector_stores.search(vector: store, query: "What is Ruby?")
14
14
  # chunks.each { |chunk| puts chunk }
15
15
  class VectorStores
16
- require_relative "response/enumerable"
17
16
  PollError = Class.new(LLM::Error)
18
17
 
19
18
  INTERVAL = 0.01
@@ -34,7 +33,7 @@ class LLM::OpenAI
34
33
  query = URI.encode_www_form(params)
35
34
  req = Net::HTTP::Get.new("/v1/vector_stores?#{query}", headers)
36
35
  res = execute(request: req)
37
- LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
36
+ ResponseAdapter.adapt(res, type: :enumerable)
38
37
  end
39
38
 
40
39
  ##
@@ -47,7 +46,7 @@ class LLM::OpenAI
47
46
  # @see https://platform.openai.com/docs/api-reference/vector_stores/create OpenAI docs
48
47
  def create(name:, file_ids: nil, **params)
49
48
  req = Net::HTTP::Post.new("/v1/vector_stores", headers)
50
- req.body = JSON.dump(params.merge({name:, file_ids:}).compact)
49
+ req.body = LLM.json.dump(params.merge({name:, file_ids:}).compact)
51
50
  res = execute(request: req)
52
51
  LLM::Response.new(res)
53
52
  end
@@ -85,7 +84,7 @@ class LLM::OpenAI
85
84
  def modify(vector:, name: nil, **params)
86
85
  vector_id = vector.respond_to?(:id) ? vector.id : vector
87
86
  req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}", headers)
88
- req.body = JSON.dump(params.merge({name:}).compact)
87
+ req.body = LLM.json.dump(params.merge({name:}).compact)
89
88
  res = execute(request: req)
90
89
  LLM::Response.new(res)
91
90
  end
@@ -114,9 +113,9 @@ class LLM::OpenAI
114
113
  def search(vector:, query:, **params)
115
114
  vector_id = vector.respond_to?(:id) ? vector.id : vector
116
115
  req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/search", headers)
117
- req.body = JSON.dump(params.merge({query:}).compact)
116
+ req.body = LLM.json.dump(params.merge({query:}).compact)
118
117
  res = execute(request: req)
119
- LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
118
+ ResponseAdapter.adapt(res, type: :enumerable)
120
119
  end
121
120
 
122
121
  ##
@@ -131,7 +130,7 @@ class LLM::OpenAI
131
130
  query = URI.encode_www_form(params)
132
131
  req = Net::HTTP::Get.new("/v1/vector_stores/#{vector_id}/files?#{query}", headers)
133
132
  res = execute(request: req)
134
- LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
133
+ ResponseAdapter.adapt(res, type: :enumerable)
135
134
  end
136
135
 
137
136
  ##
@@ -147,7 +146,7 @@ class LLM::OpenAI
147
146
  vector_id = vector.respond_to?(:id) ? vector.id : vector
148
147
  file_id = file.respond_to?(:id) ? file.id : file
149
148
  req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/files", headers)
150
- req.body = JSON.dump(params.merge({file_id:, attributes:}).compact)
149
+ req.body = LLM.json.dump(params.merge({file_id:, attributes:}).compact)
151
150
  res = execute(request: req)
152
151
  LLM::Response.new(res)
153
152
  end
@@ -176,7 +175,7 @@ class LLM::OpenAI
176
175
  vector_id = vector.respond_to?(:id) ? vector.id : vector
177
176
  file_id = file.respond_to?(:id) ? file.id : file
178
177
  req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/files/#{file_id}", headers)
179
- req.body = JSON.dump(params.merge({attributes:}).compact)
178
+ req.body = LLM.json.dump(params.merge({attributes:}).compact)
180
179
  res = execute(request: req)
181
180
  LLM::Response.new(res)
182
181
  end
@@ -14,11 +14,9 @@ module LLM
14
14
  # bot.chat ["Tell me about this photo", File.open("/images/capybara.jpg", "rb")]
15
15
  # bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
16
16
  class OpenAI < Provider
17
- require_relative "openai/response/embedding"
18
- require_relative "openai/response/completion"
19
- require_relative "openai/response/web_search"
20
17
  require_relative "openai/error_handler"
21
- require_relative "openai/format"
18
+ require_relative "openai/request_adapter"
19
+ require_relative "openai/response_adapter"
22
20
  require_relative "openai/stream_parser"
23
21
  require_relative "openai/models"
24
22
  require_relative "openai/responses"
@@ -28,7 +26,7 @@ module LLM
28
26
  require_relative "openai/moderations"
29
27
  require_relative "openai/vector_stores"
30
28
 
31
- include Format
29
+ include RequestAdapter
32
30
 
33
31
  HOST = "api.openai.com"
34
32
 
@@ -48,9 +46,9 @@ module LLM
48
46
  # @return (see LLM::Provider#embed)
49
47
  def embed(input, model: "text-embedding-3-small", **params)
50
48
  req = Net::HTTP::Post.new("/v1/embeddings", headers)
51
- req.body = JSON.dump({input:, model:}.merge!(params))
49
+ req.body = LLM.json.dump({input:, model:}.merge!(params))
52
50
  res = execute(request: req)
53
- LLM::Response.new(res).extend(LLM::OpenAI::Response::Embedding)
51
+ ResponseAdapter.adapt(res, type: :embedding)
54
52
  end
55
53
 
56
54
  ##
@@ -66,17 +64,16 @@ module LLM
66
64
  def complete(prompt, params = {})
67
65
  params = {role: :user, model: default_model}.merge!(params)
68
66
  tools = resolve_tools(params.delete(:tools))
69
- params = [params, format_schema(params), format_tools(tools)].inject({}, &:merge!).compact
67
+ params = [params, adapt_schema(params), adapt_tools(tools)].inject({}, &:merge!).compact
70
68
  role, stream = params.delete(:role), params.delete(:stream)
71
69
  params[:stream] = true if stream.respond_to?(:<<) || stream == true
72
70
  params[:stream_options] = {include_usage: true}.merge!(params[:stream_options] || {}) if params[:stream]
73
71
  req = Net::HTTP::Post.new(completions_path, headers)
74
72
  messages = [*(params.delete(:messages) || []), Message.new(role, prompt)]
75
- body = JSON.dump({messages: format(messages, :complete).flatten}.merge!(params))
73
+ body = LLM.json.dump({messages: adapt(messages, mode: :complete).flatten}.merge!(params))
76
74
  set_body_stream(req, StringIO.new(body))
77
75
  res = execute(request: req, stream:)
78
- LLM::Response.new(res)
79
- .extend(LLM::OpenAI::Response::Completion)
76
+ ResponseAdapter.adapt(res, type: :completion)
80
77
  .extend(Module.new { define_method(:__tools__) { tools } })
81
78
  end
82
79
 
@@ -177,9 +174,10 @@ module LLM
177
174
  # @param query [String] The search query.
178
175
  # @return [LLM::Response] The response from the LLM provider.
179
176
  def web_search(query:)
180
- responses
181
- .create(query, store: false, tools: [server_tools[:web_search]])
182
- .extend(LLM::OpenAI::Response::WebSearch)
177
+ ResponseAdapter.adapt(
178
+ responses.create(query, store: false, tools: [server_tools[:web_search]]),
179
+ type: :web_search
180
+ )
183
181
  end
184
182
 
185
183
  private
data/lib/llm/response.rb CHANGED
@@ -25,12 +25,9 @@ module LLM
25
25
 
26
26
  ##
27
27
  # Returns the response body
28
- # @return [Hash, String]
28
+ # @return [LLM::Object, String]
29
29
  def body
30
- @body ||= case @res["content-type"]
31
- when %r|\Aapplication/json\s*| then LLM::Object.from_hash(JSON.parse(@res.body))
32
- else @res.body
33
- end
30
+ @res.body
34
31
  end
35
32
 
36
33
  ##
data/lib/llm/usage.rb ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The {LLM::Usage LLM::Usage} class represents token usage for
5
+ # a given conversation or completion. As a conversation grows,
6
+ # so does the number of tokens used. This class helps track
7
+ # the number of input, output, and total tokens. It can also help
8
+ # track usage of the context window (which may vary by model).
9
+ class LLM::Usage < Struct.new(:input_tokens, :output_tokens, :total_tokens, keyword_init: true)
10
+ end
data/lib/llm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LLM
4
- VERSION = "2.1.0"
4
+ VERSION = "3.0.0"
5
5
  end
data/lib/llm.rb CHANGED
@@ -2,12 +2,15 @@
2
2
 
3
3
  module LLM
4
4
  require "stringio"
5
+ require_relative "llm/json_adapter"
6
+ require_relative "llm/error"
7
+ require_relative "llm/contract"
8
+ require_relative "llm/usage"
5
9
  require_relative "llm/builder"
6
10
  require_relative "llm/schema"
7
11
  require_relative "llm/object"
8
12
  require_relative "llm/version"
9
13
  require_relative "llm/utils"
10
- require_relative "llm/error"
11
14
  require_relative "llm/message"
12
15
  require_relative "llm/response"
13
16
  require_relative "llm/mime"
@@ -28,6 +31,35 @@ module LLM
28
31
 
29
32
  module_function
30
33
 
34
+ ##
35
+ # Returns the JSON adapter used by the library
36
+ # @return [Class]
37
+ # Returns a class that responds to +dump+ and +load+
38
+ def json
39
+ @json ||= JSONAdapter::JSON
40
+ end
41
+
42
+ ##
43
+ # Sets the JSON adapter used by the library
44
+ # @param [Class, String, Symbol] adapter
45
+ # A JSON adapter class or its name
46
+ # @return [void]
47
+ def json=(adapter)
48
+ @json = case adapter.to_s
49
+ when "JSON", "json" then JSONAdapter::JSON
50
+ when "Oj", "oj" then JSONAdapter::Oj
51
+ when "Yajl", "yajl" then JSONAdapter::Yajl
52
+ else
53
+ is_class = Class === adapter
54
+ is_subclass = is_class && adapter.ancestors.include?(LLM::JSONAdapter)
55
+ if is_subclass
56
+ adapter
57
+ else
58
+ raise TypeError, "Adapter must be a subclass of LLM::JSONAdapter"
59
+ end
60
+ end
61
+ end
62
+
31
63
  ##
32
64
  # @param (see LLM::Provider#initialize)
33
65
  # @return (see LLM::Anthropic#initialize)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Antar Azri
@@ -182,6 +182,8 @@ files:
182
182
  - lib/llm/buffer.rb
183
183
  - lib/llm/builder.rb
184
184
  - lib/llm/client.rb
185
+ - lib/llm/contract.rb
186
+ - lib/llm/contract/completion.rb
185
187
  - lib/llm/error.rb
186
188
  - lib/llm/eventhandler.rb
187
189
  - lib/llm/eventstream.rb
@@ -189,9 +191,11 @@ files:
189
191
  - lib/llm/eventstream/parser.rb
190
192
  - lib/llm/file.rb
191
193
  - lib/llm/function.rb
194
+ - lib/llm/json_adapter.rb
192
195
  - lib/llm/message.rb
193
196
  - lib/llm/mime.rb
194
197
  - lib/llm/multipart.rb
198
+ - lib/llm/multipart/enumerator_io.rb
195
199
  - lib/llm/object.rb
196
200
  - lib/llm/object/builder.rb
197
201
  - lib/llm/object/kernel.rb
@@ -199,62 +203,66 @@ files:
199
203
  - lib/llm/providers/anthropic.rb
200
204
  - lib/llm/providers/anthropic/error_handler.rb
201
205
  - lib/llm/providers/anthropic/files.rb
202
- - lib/llm/providers/anthropic/format.rb
203
- - lib/llm/providers/anthropic/format/completion_format.rb
204
206
  - lib/llm/providers/anthropic/models.rb
205
- - lib/llm/providers/anthropic/response/completion.rb
206
- - lib/llm/providers/anthropic/response/enumerable.rb
207
- - lib/llm/providers/anthropic/response/file.rb
208
- - lib/llm/providers/anthropic/response/web_search.rb
207
+ - lib/llm/providers/anthropic/request_adapter.rb
208
+ - lib/llm/providers/anthropic/request_adapter/completion.rb
209
+ - lib/llm/providers/anthropic/response_adapter.rb
210
+ - lib/llm/providers/anthropic/response_adapter/completion.rb
211
+ - lib/llm/providers/anthropic/response_adapter/enumerable.rb
212
+ - lib/llm/providers/anthropic/response_adapter/file.rb
213
+ - lib/llm/providers/anthropic/response_adapter/web_search.rb
209
214
  - lib/llm/providers/anthropic/stream_parser.rb
210
215
  - lib/llm/providers/deepseek.rb
211
- - lib/llm/providers/deepseek/format.rb
212
- - lib/llm/providers/deepseek/format/completion_format.rb
216
+ - lib/llm/providers/deepseek/request_adapter.rb
217
+ - lib/llm/providers/deepseek/request_adapter/completion.rb
213
218
  - lib/llm/providers/gemini.rb
214
219
  - lib/llm/providers/gemini/audio.rb
215
220
  - lib/llm/providers/gemini/error_handler.rb
216
221
  - lib/llm/providers/gemini/files.rb
217
- - lib/llm/providers/gemini/format.rb
218
- - lib/llm/providers/gemini/format/completion_format.rb
219
222
  - lib/llm/providers/gemini/images.rb
220
223
  - lib/llm/providers/gemini/models.rb
221
- - lib/llm/providers/gemini/response/completion.rb
222
- - lib/llm/providers/gemini/response/embedding.rb
223
- - lib/llm/providers/gemini/response/file.rb
224
- - lib/llm/providers/gemini/response/files.rb
225
- - lib/llm/providers/gemini/response/image.rb
226
- - lib/llm/providers/gemini/response/models.rb
227
- - lib/llm/providers/gemini/response/web_search.rb
224
+ - lib/llm/providers/gemini/request_adapter.rb
225
+ - lib/llm/providers/gemini/request_adapter/completion.rb
226
+ - lib/llm/providers/gemini/response_adapter.rb
227
+ - lib/llm/providers/gemini/response_adapter/completion.rb
228
+ - lib/llm/providers/gemini/response_adapter/embedding.rb
229
+ - lib/llm/providers/gemini/response_adapter/file.rb
230
+ - lib/llm/providers/gemini/response_adapter/files.rb
231
+ - lib/llm/providers/gemini/response_adapter/image.rb
232
+ - lib/llm/providers/gemini/response_adapter/models.rb
233
+ - lib/llm/providers/gemini/response_adapter/web_search.rb
228
234
  - lib/llm/providers/gemini/stream_parser.rb
229
235
  - lib/llm/providers/llamacpp.rb
230
236
  - lib/llm/providers/ollama.rb
231
237
  - lib/llm/providers/ollama/error_handler.rb
232
- - lib/llm/providers/ollama/format.rb
233
- - lib/llm/providers/ollama/format/completion_format.rb
234
238
  - lib/llm/providers/ollama/models.rb
235
- - lib/llm/providers/ollama/response/completion.rb
236
- - lib/llm/providers/ollama/response/embedding.rb
239
+ - lib/llm/providers/ollama/request_adapter.rb
240
+ - lib/llm/providers/ollama/request_adapter/completion.rb
241
+ - lib/llm/providers/ollama/response_adapter.rb
242
+ - lib/llm/providers/ollama/response_adapter/completion.rb
243
+ - lib/llm/providers/ollama/response_adapter/embedding.rb
237
244
  - lib/llm/providers/ollama/stream_parser.rb
238
245
  - lib/llm/providers/openai.rb
239
246
  - lib/llm/providers/openai/audio.rb
240
247
  - lib/llm/providers/openai/error_handler.rb
241
248
  - lib/llm/providers/openai/files.rb
242
- - lib/llm/providers/openai/format.rb
243
- - lib/llm/providers/openai/format/completion_format.rb
244
- - lib/llm/providers/openai/format/moderation_format.rb
245
- - lib/llm/providers/openai/format/respond_format.rb
246
249
  - lib/llm/providers/openai/images.rb
247
250
  - lib/llm/providers/openai/models.rb
248
251
  - lib/llm/providers/openai/moderations.rb
249
- - lib/llm/providers/openai/response/audio.rb
250
- - lib/llm/providers/openai/response/completion.rb
251
- - lib/llm/providers/openai/response/embedding.rb
252
- - lib/llm/providers/openai/response/enumerable.rb
253
- - lib/llm/providers/openai/response/file.rb
254
- - lib/llm/providers/openai/response/image.rb
255
- - lib/llm/providers/openai/response/moderations.rb
256
- - lib/llm/providers/openai/response/responds.rb
257
- - lib/llm/providers/openai/response/web_search.rb
252
+ - lib/llm/providers/openai/request_adapter.rb
253
+ - lib/llm/providers/openai/request_adapter/completion.rb
254
+ - lib/llm/providers/openai/request_adapter/moderation.rb
255
+ - lib/llm/providers/openai/request_adapter/respond.rb
256
+ - lib/llm/providers/openai/response_adapter.rb
257
+ - lib/llm/providers/openai/response_adapter/audio.rb
258
+ - lib/llm/providers/openai/response_adapter/completion.rb
259
+ - lib/llm/providers/openai/response_adapter/embedding.rb
260
+ - lib/llm/providers/openai/response_adapter/enumerable.rb
261
+ - lib/llm/providers/openai/response_adapter/file.rb
262
+ - lib/llm/providers/openai/response_adapter/image.rb
263
+ - lib/llm/providers/openai/response_adapter/moderations.rb
264
+ - lib/llm/providers/openai/response_adapter/responds.rb
265
+ - lib/llm/providers/openai/response_adapter/web_search.rb
258
266
  - lib/llm/providers/openai/responses.rb
259
267
  - lib/llm/providers/openai/responses/stream_parser.rb
260
268
  - lib/llm/providers/openai/stream_parser.rb
@@ -276,6 +284,7 @@ files:
276
284
  - lib/llm/server_tool.rb
277
285
  - lib/llm/tool.rb
278
286
  - lib/llm/tool/param.rb
287
+ - lib/llm/usage.rb
279
288
  - lib/llm/utils.rb
280
289
  - lib/llm/version.rb
281
290
  - llm.gemspec
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LLM::Anthropic::Response
4
- module Completion
5
- def choices = format_choices
6
- def role = body.role
7
- def model = body.model
8
- def prompt_tokens = body.usage["input_tokens"] || 0
9
- def completion_tokens = body.usage["output_tokens"] || 0
10
- def total_tokens = prompt_tokens + completion_tokens
11
-
12
- private
13
-
14
- def format_choices
15
- texts.map.with_index do |choice, index|
16
- extra = {
17
- index:, response: self,
18
- tool_calls: format_tool_calls(tools), original_tool_calls: tools
19
- }
20
- LLM::Message.new(role, choice["text"], extra)
21
- end
22
- end
23
-
24
- def format_tool_calls(tools)
25
- (tools || []).filter_map do |tool|
26
- tool = {
27
- id: tool.id,
28
- name: tool.name,
29
- arguments: tool.input
30
- }
31
- LLM::Object.new(tool)
32
- end
33
- end
34
-
35
- def parts = body.content
36
- def texts = @texts ||= LLM::Object.from_hash(parts.select { _1["type"] == "text" })
37
- def tools = @tools ||= LLM::Object.from_hash(parts.select { _1["type"] == "tool_use" })
38
- end
39
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LLM::Gemini::Response
4
- module Completion
5
- def model = body.modelVersion
6
- def prompt_tokens = body.usageMetadata.promptTokenCount
7
- def completion_tokens = body.usageMetadata.candidatesTokenCount
8
- def total_tokens = body.usageMetadata.totalTokenCount
9
- def choices = format_choices
10
-
11
- private
12
-
13
- def format_choices
14
- candidates.map.with_index do |choice, index|
15
- choice = LLM::Object.from_hash(choice)
16
- content = choice.content || LLM::Object.new
17
- role = content.role || "model"
18
- parts = content.parts || [{"text" => choice.finishReason}]
19
- text = parts.filter_map { _1["text"] }.join
20
- tools = parts.filter_map { _1["functionCall"] }
21
- extra = {index:, response: self, tool_calls: format_tool_calls(tools), original_tool_calls: tools}
22
- LLM::Message.new(role, text, extra)
23
- end
24
- end
25
-
26
- def format_tool_calls(tools)
27
- (tools || []).map do |tool|
28
- function = {name: tool.name, arguments: tool.args}
29
- LLM::Object.new(function)
30
- end
31
- end
32
-
33
- def candidates = body.candidates || []
34
- end
35
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LLM::Ollama::Response
4
- module Completion
5
- def model = body.model
6
- def prompt_tokens = body.prompt_eval_count || 0
7
- def completion_tokens = body.eval_count || 0
8
- def total_tokens = prompt_tokens + completion_tokens
9
- def message = body.message
10
- def choices = [format_choices]
11
-
12
- private
13
-
14
- def format_choices
15
- role, content, calls = message.to_h.values_at("role", "content", "tool_calls")
16
- extra = {response: self, tool_calls: format_tool_calls(calls)}
17
- LLM::Message.new(role, content, extra)
18
- end
19
-
20
- def format_tool_calls(tools)
21
- return [] unless tools
22
- tools.filter_map do |tool|
23
- next unless tool["function"]
24
- LLM::Object.new(tool["function"])
25
- end
26
- end
27
- end
28
- end
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module LLM::OpenAI::Response
4
- module Completion
5
- def choices
6
- body.choices.map.with_index do |choice, index|
7
- choice = LLM::Object.from_hash(choice)
8
- message = choice.message
9
- extra = {
10
- index:, response: self,
11
- logprobs: choice.logprobs,
12
- tool_calls: format_tool_calls(message.tool_calls),
13
- original_tool_calls: message.tool_calls
14
- }
15
- LLM::Message.new(message.role, message.content, extra)
16
- end
17
- end
18
- alias_method :messages, :choices
19
-
20
- def model = body.model
21
- def prompt_tokens = usage["prompt_tokens"]
22
- def completion_tokens = usage["completion_tokens"]
23
- def total_tokens = usage["total_tokens"]
24
- def usage = body.usage || {}
25
-
26
- private
27
-
28
- def format_tool_calls(tools)
29
- (tools || []).filter_map do |tool|
30
- next unless tool.function
31
- tool = {
32
- id: tool.id,
33
- name: tool.function.name,
34
- arguments: JSON.parse(tool.function.arguments)
35
- }
36
- LLM::Object.new(tool)
37
- end
38
- end
39
- end
40
- end