raix 2.0.2 → 2.0.4

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: 254c9a26d73389e655db17cf571ab2a47b54e6b2bc664741e0a260534d6d2537
4
- data.tar.gz: f7846475556924487dc0206ba340f2c568a23e85ef4a0b2bd0504231c175f02a
3
+ metadata.gz: ec3b7449e51440e1d669e323dea0c371a3d18d7d4083090d61599ff1dbce3bbe
4
+ data.tar.gz: 6f2029218c2099ab1172d1d458be9ccb33836078d5abc9b04865cae0d497bc07
5
5
  SHA512:
6
- metadata.gz: 8301e1d977df4f84c587da285890382856d7f1490845cec4b4ab19e955462680e55b1dc9263166250b5f2697eae8e4005939e2d12e1ab70ca2c8b6d81ea7e9d0
7
- data.tar.gz: b30eb6832ab3a658a8bfcb6b7e6bf6d4349462d81dd35f79bab514be08104eee0712219b98169e692943413ad4bc47ad8e7a8883e74d1df47da48a59f9c41c4e
6
+ metadata.gz: 76e1d77e80023dbc634e4b2f72eaa269f05d539ea97470df1355e69e73d75a6cf4bd2bd7e627ab2cf01fcb381fd85c4b153d28005f32190ff1b8ae69c3bea275
7
+ data.tar.gz: 6fedf4bae5c406b72b83a1aacbc985849eb7c461237e7cc1e12d9ca73ea05a861912f13739bc92ba8ad992bf88f5a31fa530dc9468c5bdb4763f53caf3833843
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ ## [Unreleased]
2
+
3
+ ## [2.0.4] - 2026-05-19
4
+
5
+ ### Fixed
6
+ - `ruby_llm_request` now preserves the upstream provider's `id`, `model`, and `provider` fields, plus the full `usage` payload (including `prompt_tokens_details.cached_tokens` and `completion_tokens_details.reasoning_tokens`) on the OpenAI-compatible response hash. Previously the conversion dropped everything except `choices` and basic token counts, which broke callers that needed the generation id for authoritative cost lookups (e.g. OpenRouter's `/api/v1/generation` endpoint) or that wanted to verify prompt-cache hits via `cached_tokens`.
7
+ - Added `require "active_support/core_ext/module/delegation"` so `Raix::ChatCompletion` loads cleanly without an external preload of ActiveSupport. The class uses `delegate :configuration, to: :class` but did not pull in the required core ext, so a bare `require "raix"` would raise `NoMethodError` for `delegate`.
8
+
9
+ ## [2.0.3] - 2026-04-30
10
+
11
+ ### Fixed
12
+ - `NoMethodError: undefined method 'strip' for nil` in `Raix::ChatCompletion` when an LLM (notably Gemini under certain stop conditions) returns a final assistant message with `"content": null`. Three call sites in `lib/raix/chat_completion.rb` now use `content.to_s.strip` so a nil response coerces to `""` instead of raising.
13
+
1
14
  ## [2.0.2] - 2026-03-27
2
15
 
3
16
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (2.0.1)
4
+ raix (2.0.4)
5
5
  activesupport (>= 6.0)
6
6
  faraday-retry (~> 2.0)
7
7
  ostruct
@@ -49,8 +49,8 @@ GEM
49
49
  net-http
50
50
  faraday-retry (2.4.0)
51
51
  faraday (~> 2.0)
52
- ffi (1.17.2)
53
52
  ffi (1.17.2-arm64-darwin)
53
+ ffi (1.17.2-x86_64-linux-gnu)
54
54
  formatador (1.1.0)
55
55
  guard (2.18.1)
56
56
  formatador (>= 0.2.4)
@@ -83,18 +83,16 @@ GEM
83
83
  lumberjack (1.2.10)
84
84
  marcel (1.1.0)
85
85
  method_source (1.1.0)
86
- mini_portile2 (2.8.9)
87
86
  minitest (5.27.0)
88
87
  multipart-post (2.4.1)
89
88
  nenv (0.3.0)
90
89
  net-http (0.4.1)
91
90
  uri
92
91
  netrc (0.11.0)
93
- nokogiri (1.18.8)
94
- mini_portile2 (~> 2.8.2)
95
- racc (~> 1.4)
96
92
  nokogiri (1.18.8-arm64-darwin)
97
93
  racc (~> 1.4)
94
+ nokogiri (1.18.8-x86_64-linux-gnu)
95
+ racc (~> 1.4)
98
96
  notiffany (0.1.3)
99
97
  nenv (~> 0.1)
100
98
  shellany (~> 0.0)
@@ -4,6 +4,7 @@ require "active_support/concern"
4
4
  require "active_support/core_ext/object/blank"
5
5
  require "active_support/core_ext/string/filters"
6
6
  require "active_support/core_ext/hash/indifferent_access"
7
+ require "active_support/core_ext/module/delegation"
7
8
  require "ruby_llm"
8
9
 
9
10
  module Raix
@@ -56,9 +57,7 @@ module Raix
56
57
  end
57
58
 
58
59
  # Instance level access to the class-level configuration.
59
- def configuration
60
- self.class.configuration
61
- end
60
+ delegate :configuration, to: :class
62
61
 
63
62
  # This method performs chat completion based on the provided transcript and parameters.
64
63
  #
@@ -174,7 +173,7 @@ module Raix
174
173
  # Process the final response
175
174
  content = response.dig("choices", 0, "message", "content")
176
175
  transcript << { assistant: content } if save_response
177
- return raw ? response : content.strip
176
+ return raw ? response : content.to_s.strip
178
177
  end
179
178
 
180
179
  # Dispatch tool calls
@@ -215,7 +214,7 @@ module Raix
215
214
 
216
215
  content = response.dig("choices", 0, "message", "content")
217
216
  transcript << { assistant: content } if save_response
218
- return raw ? response : content.strip
217
+ return raw ? response : content.to_s.strip
219
218
  end
220
219
  end
221
220
 
@@ -223,7 +222,7 @@ module Raix
223
222
  content = res.dig("choices", 0, "message", "content")
224
223
 
225
224
  transcript << { assistant: content } if save_response
226
- content = content.strip
225
+ content = content.to_s.strip
227
226
 
228
227
  if json
229
228
  # Make automatic JSON parsing available to non-OpenAI providers that don't support the response_format parameter
@@ -398,8 +397,34 @@ module Raix
398
397
  # Non-streaming mode - return OpenAI-compatible response format
399
398
  response_message = has_user_message ? chat.complete : chat.ask
400
399
 
401
- # Convert RubyLLM response to OpenAI format for compatibility
400
+ # Pull through the raw provider payload when available. OpenRouter's
401
+ # `id` is the only handle we have to look up authoritative billing
402
+ # cost via /api/v1/generation, and callers that watch the response
403
+ # snapshot for `model` / cached-token counts shouldn't have to break
404
+ # out of the OpenAI-compatible shape to get them.
405
+ raw_body = response_message.raw.respond_to?(:body) ? response_message.raw.body : nil
406
+ raw_body = {} unless raw_body.is_a?(Hash)
407
+
408
+ usage_payload = {
409
+ "prompt_tokens" => response_message.input_tokens,
410
+ "completion_tokens" => response_message.output_tokens,
411
+ "total_tokens" => (response_message.input_tokens || 0) + (response_message.output_tokens || 0)
412
+ }
413
+
414
+ # Merge prompt_tokens_details / completion_tokens_details (cached tokens,
415
+ # reasoning tokens) when the provider supplied them.
416
+ if (upstream_usage = raw_body["usage"]).is_a?(Hash)
417
+ upstream_usage.each do |key, value|
418
+ next if usage_payload.key?(key)
419
+
420
+ usage_payload[key] = value
421
+ end
422
+ end
423
+
402
424
  {
425
+ "id" => raw_body["id"],
426
+ "model" => raw_body["model"] || response_message.model_id,
427
+ "provider" => raw_body["provider"],
403
428
  "choices" => [
404
429
  {
405
430
  "message" => {
@@ -410,11 +435,7 @@ module Raix
410
435
  "finish_reason" => response_message.tool_call? ? "tool_calls" : "stop"
411
436
  }
412
437
  ],
413
- "usage" => {
414
- "prompt_tokens" => response_message.input_tokens,
415
- "completion_tokens" => response_message.output_tokens,
416
- "total_tokens" => (response_message.input_tokens || 0) + (response_message.output_tokens || 0)
417
- }
438
+ "usage" => usage_payload
418
439
  }
419
440
  end
420
441
  rescue StandardError => e
data/lib/raix/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Raix
4
- VERSION = "2.0.2"
4
+ VERSION = "2.0.4"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: raix
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.2
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Obie Fernandez
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-03-27 00:00:00.000000000 Z
10
+ date: 2026-05-19 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -114,7 +114,6 @@ files:
114
114
  - lib/raix/response_format.rb
115
115
  - lib/raix/transcript_adapter.rb
116
116
  - lib/raix/version.rb
117
- - raix.gemspec
118
117
  - sig/raix.rbs
119
118
  homepage: https://github.com/OlympiaAI/raix
120
119
  licenses:
data/raix.gemspec DELETED
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/raix/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "raix"
7
- spec.version = Raix::VERSION
8
- spec.authors = ["Obie Fernandez"]
9
- spec.email = ["obiefernandez@gmail.com"]
10
-
11
- spec.summary = "Ruby AI eXtensions"
12
- spec.homepage = "https://github.com/OlympiaAI/raix"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.2.2"
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/OlympiaAI/raix"
18
- spec.metadata["changelog_uri"] = "https://github.com/OlympiaAI/raix/blob/main/CHANGELOG.md"
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(__dir__) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
25
- end
26
- end
27
-
28
- # Ensure all gem files are world-readable so they work in Docker containers
29
- # where gems are installed as root but the app runs as a non-root user.
30
- spec.files.each do |f|
31
- path = File.join(__dir__, f)
32
- File.chmod(0o644, path) if File.file?(path) && !File.executable?(path)
33
- end
34
- spec.bindir = "exe"
35
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
36
- spec.require_paths = ["lib"]
37
-
38
- spec.add_dependency "activesupport", ">= 6.0"
39
- spec.add_dependency "faraday-retry", "~> 2.0"
40
- spec.add_dependency "ostruct"
41
- spec.add_dependency "ruby_llm", "~> 1.9"
42
- spec.add_dependency "zeitwerk", "~> 2.7"
43
- end