omniai 3.6.0 → 3.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ae7a0b04197a95f107c66d4d7108435f44f82d0e3e4b3a956cb755d3ac352c6a
4
- data.tar.gz: 9d8a85ede589c08431d49ada35d2a13ed57338f7eafbc7231134e1866e083bc3
3
+ metadata.gz: 7464f0580c8f8392155996aa7b4675491bec1fe62b48ff9b12e53264952a50e5
4
+ data.tar.gz: 3ef5570bd7eac6ed72a9826c90313565dc0fd00b6ec52745fe02a04ce2855264
5
5
  SHA512:
6
- metadata.gz: 05372f0de7a16a3da31702eebfa49b2cd1bbc890ee1777820b575e81c72d59d532941ed96d52ac572b58fd34ab9f897fcda2d85bd91d21be00b115de39a1dac2
7
- data.tar.gz: f2b518b58405e2a4995b282280693f4d985cd1b9ce129abc04a55e679e62b5b9a495aa9ed980449874af43f7d8cd5108c717bed5b5f4337a3d65f3ef1ecd9c31
6
+ metadata.gz: e5ab80ba78ab316ca74e46dffb0bdea26ed13b83f4ec4ff7c5e985580c5460d926366d71bf547ac905273968db44cf1949eb57c56b0560077f1b805f52a4c93d
7
+ data.tar.gz: bd453f4fd6355066833db225b771b1078fd8d916138213a1f0d4d18f37316cae7b60f47a215c46d7a76fb096eeece2e6d887a8dc9a4819c8c54dd65f3ece731f
data/README.md CHANGED
@@ -398,6 +398,19 @@ require 'omniai/openai'
398
398
  client = OmniAI::OpenAI::Client.new
399
399
  ```
400
400
 
401
+ OpenAI-compatible gateways can be configured by passing a custom host. This keeps
402
+ normal OpenAI usage unchanged while allowing private gateways, local model
403
+ servers, or governed endpoints such as Tuning Engines:
404
+
405
+ ```ruby
406
+ require 'omniai/openai'
407
+
408
+ client = OmniAI::OpenAI::Client.new(
409
+ api_key: ENV.fetch('OPENAI_API_KEY'),
410
+ host: ENV.fetch('OPENAI_HOST', 'https://api.openai.com')
411
+ )
412
+ ```
413
+
401
414
  #### Usage with LocalAI
402
415
 
403
416
  LocalAI support is offered through [OmniAI::OpenAI](https://github.com/ksylvest/omniai-openai):
@@ -16,11 +16,18 @@ module OmniAI
16
16
  # @return [Message]
17
17
  attr_accessor :message
18
18
 
19
+ # @!attribute [rw] finish_reason
20
+ # @return [FinishReason, nil] the normalized reason generation stopped (carrying both `#reason` and the
21
+ # verbatim provider `#value`), or `nil` when absent.
22
+ attr_accessor :finish_reason
23
+
19
24
  # @param message [Message]
20
25
  # @param index [Integer]
21
- def initialize(message:, index: DEFAULT_INDEX)
26
+ # @param finish_reason [FinishReason, nil]
27
+ def initialize(message:, index: DEFAULT_INDEX, finish_reason: nil)
22
28
  @message = message
23
29
  @index = index
30
+ @finish_reason = finish_reason
24
31
  end
25
32
 
26
33
  # @return [String]
@@ -39,7 +46,7 @@ module OmniAI
39
46
  index = data["index"] || DEFAULT_INDEX
40
47
  message = Message.deserialize(data["message"] || data["delta"], context:)
41
48
 
42
- new(message:, index:)
49
+ new(message:, index:, finish_reason: FinishReason.deserialize(data["finish_reason"]))
43
50
  end
44
51
 
45
52
  # @param context [OmniAI::Context] optional
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OmniAI
4
+ class Chat
5
+ # A normalized, provider-agnostic reason a generation stopped, paired with the verbatim provider value.
6
+ #
7
+ # `#reason` is one of the canonical symbols ({REASONS}) for branching/alerting; `#value` is the raw provider
8
+ # token (e.g. "RECITATION", "end_turn", "stop"), preserved verbatim — including when the reason normalizes to
9
+ # `:other`. Each provider maps its own vocabulary onto a reason in its deserializer; the verbatim value is never
10
+ # discarded, so consumers keep provider granularity without digging the provider-specific response `data`.
11
+ class FinishReason
12
+ # A natural stopping point was reached.
13
+ STOP = :stop
14
+
15
+ # The token budget (requested max tokens or the model's context window) was reached.
16
+ LENGTH = :length
17
+
18
+ # The model is requesting a tool / function call.
19
+ TOOL_CALL = :tool_call
20
+
21
+ # The provider deliberately suppressed or blocked output (safety, policy, recitation, unsupported language).
22
+ FILTER = :filter
23
+
24
+ # A value was present but does not map to a known category (forward-compatible fallback).
25
+ OTHER = :other
26
+
27
+ # The canonical normalized reasons.
28
+ REASONS = %i[stop length tool_call filter other].freeze
29
+
30
+ # The Chat Completions `finish_reason` vocabulary (originated by OpenAI; also emitted by Mistral and
31
+ # OpenAI-compatible gateways). Applied by the base `Choice.deserialize` path, which models that schema.
32
+ CHAT_COMPLETIONS = {
33
+ "stop" => STOP,
34
+ "length" => LENGTH,
35
+ "tool_calls" => TOOL_CALL,
36
+ "function_call" => TOOL_CALL,
37
+ "content_filter" => FILTER,
38
+ }.freeze
39
+
40
+ # @!attribute [r] reason
41
+ # @return [Symbol] one of {REASONS}
42
+ attr_reader :reason
43
+
44
+ # @!attribute [r] value
45
+ # @return [String] the verbatim provider token
46
+ attr_reader :value
47
+
48
+ # Normalizes a raw provider value through a mapping table into a FinishReason.
49
+ #
50
+ # - `nil` in → `nil` out (absence is not the same as unrecognized).
51
+ # - otherwise → a FinishReason whose `reason` is the table's mapping (or `:other` when unmapped) and whose
52
+ # `value` is the raw token, preserved verbatim — always, including on `:other`.
53
+ #
54
+ # @param value [String, nil] the raw provider value
55
+ # @param table [Hash{String => Symbol}] the provider's mapping table (defaults to the Chat Completions vocabulary)
56
+ #
57
+ # @return [FinishReason, nil]
58
+ def self.deserialize(value, table: CHAT_COMPLETIONS)
59
+ return if value.nil?
60
+
61
+ new(reason: table.fetch(value, OTHER), value:)
62
+ end
63
+
64
+ # @param reason [Symbol] one of {REASONS}
65
+ # @param value [String] the verbatim provider token
66
+ def initialize(reason:, value:)
67
+ @reason = reason
68
+ @value = value
69
+ end
70
+
71
+ # @return [String]
72
+ def inspect
73
+ "#<#{self.class.name} reason=#{reason.inspect} value=#{value.inspect}>"
74
+ end
75
+
76
+ # @return [Boolean]
77
+ def stop?
78
+ reason == STOP
79
+ end
80
+
81
+ # @return [Boolean]
82
+ def length?
83
+ reason == LENGTH
84
+ end
85
+
86
+ # @return [Boolean]
87
+ def tool_call?
88
+ reason == TOOL_CALL
89
+ end
90
+
91
+ # @return [Boolean]
92
+ def filter?
93
+ reason == FILTER
94
+ end
95
+
96
+ # @return [Boolean]
97
+ def other?
98
+ reason == OTHER
99
+ end
100
+ end
101
+ end
102
+ end
@@ -145,7 +145,7 @@ module OmniAI
145
145
  return if @content.nil?
146
146
  return @content if @content.is_a?(String)
147
147
 
148
- parts = arrayify(@content).filter { |content| content.is_a?(Text) }
148
+ parts = arrayify(@content).grep(Text)
149
149
  parts.map(&:text).join("\n") unless parts.empty?
150
150
  end
151
151
 
@@ -158,7 +158,7 @@ module OmniAI
158
158
  def thinking
159
159
  return if @content.nil?
160
160
 
161
- parts = arrayify(@content).filter { |content| content.is_a?(Thinking) }
161
+ parts = arrayify(@content).grep(Thinking)
162
162
  parts.map(&:thinking).join("\n") unless parts.empty?
163
163
  end
164
164
 
@@ -23,12 +23,29 @@ module OmniAI
23
23
  # @param data [Hash]
24
24
  # @param choices [Array<Choice>]
25
25
  # @param usage [Usage, nil]
26
- def initialize(data:, choices: [], usage: nil)
26
+ # @param finish_reason [FinishReason, nil] an optional response-level finish reason (used by providers that
27
+ # expose it at the response level, e.g. OpenAI's Responses API); when omitted, `#finish_reason` falls back to
28
+ # the first choice's finish reason.
29
+ def initialize(data:, choices: [], usage: nil, finish_reason: nil)
27
30
  @data = data
28
31
  @choices = choices
29
32
  @usage = usage
33
+ @finish_reason = finish_reason
30
34
  end
31
35
 
36
+ # The normalized {FinishReason} for the final turn (carrying both `#reason` and the verbatim provider `#value`),
37
+ # or `nil` when absent. Some providers (e.g. OpenAI's Responses API) expose this at the response level; most
38
+ # expose it per-choice. Prefers an explicit response-level value, then falls back to the first choice. Reflects
39
+ # this response only (the final turn) — it is not aggregated across the parent chain, unlike {#total_usage}.
40
+ #
41
+ # @return [FinishReason, nil]
42
+ def finish_reason
43
+ @finish_reason || @choices.first&.finish_reason
44
+ end
45
+
46
+ # @!attribute [w] finish_reason
47
+ attr_writer :finish_reason
48
+
32
49
  # @return [String]
33
50
  def inspect
34
51
  "#<#{self.class.name} choices=#{@choices.inspect} usage=#{@usage.inspect}>"
data/lib/omniai/client.rb CHANGED
@@ -10,7 +10,7 @@ module OmniAI
10
10
  # super
11
11
  # end
12
12
  #
13
- # # @return [HTTP::Client]
13
+ # # @return [HTTP::Client, HTTP::Session]
14
14
  # def connection
15
15
  # @connection ||= super.auth("Bearer: #{@api_key}")
16
16
  # end
@@ -175,7 +175,7 @@ module OmniAI
175
175
  "#{api_key[..2]}***" if api_key
176
176
  end
177
177
 
178
- # @return [HTTP::Client]
178
+ # @return [HTTP::Client, HTTP::Session] an `HTTP::Client` on http 5, an `HTTP::Session` on http 6
179
179
  def connection
180
180
  http = HTTP.persistent(@host)
181
181
  http = http.use(instrumentation: { instrumenter: Instrumentation.new(logger: @logger) }) if @logger
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OmniAI
4
- VERSION = "3.6.0"
4
+ VERSION = "3.7.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniai
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.6.0
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Sylvestre
@@ -41,16 +41,22 @@ dependencies:
41
41
  name: http
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '5'
47
+ - - "<"
48
+ - !ruby/object:Gem::Version
49
+ version: '7'
47
50
  type: :runtime
48
51
  prerelease: false
49
52
  version_requirements: !ruby/object:Gem::Requirement
50
53
  requirements:
51
- - - "~>"
54
+ - - ">="
52
55
  - !ruby/object:Gem::Version
53
56
  version: '5'
57
+ - - "<"
58
+ - !ruby/object:Gem::Version
59
+ version: '7'
54
60
  - !ruby/object:Gem::Dependency
55
61
  name: logger
56
62
  requirement: !ruby/object:Gem::Requirement
@@ -99,6 +105,7 @@ files:
99
105
  - lib/omniai/chat/content.rb
100
106
  - lib/omniai/chat/delta.rb
101
107
  - lib/omniai/chat/file.rb
108
+ - lib/omniai/chat/finish_reason.rb
102
109
  - lib/omniai/chat/function.rb
103
110
  - lib/omniai/chat/media.rb
104
111
  - lib/omniai/chat/message.rb