raix 2.0.6 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 238b5d66fbe10f61f2264758964451d8e3a45da5b7ea0056e50a2cb87674d608
4
- data.tar.gz: f80fc111967055c7d62d3facecb6e10190efaa6d9ad0e8df32034aae837bf2d2
3
+ metadata.gz: 8be858d495426d05d1a9eb816d24068c049ca86ed3f80aeb1af72fdb7860956b
4
+ data.tar.gz: 2449a9c1b03b60bbcc9df1fbff4b19b88cc35a9c6b6af55197b72d909021089d
5
5
  SHA512:
6
- metadata.gz: b45d5cf20fda5333e00b563af114e670d69cb22fb32b386753b05bc85cfc5e2c88a7a627f9d759697ba0c507ab93c63b0a242099bfce47f4ea8b82b34c9267a2
7
- data.tar.gz: cabedce14ff63b6b7227aa8e2457541769170a4b1a0c8a52a0eeef73a0e62c53ff567cbfdfbf15d9045dca02582dccd9daf0155878d7b59d01b55a718b4bd185
6
+ metadata.gz: 00ff419a67755427343a092356b8315684f407e92b4474544db64a05d6e1af6b70e11a24c124c84559762529f6638248eedcaf43f5f73a74e2b00d7d7cc7c874
7
+ data.tar.gz: 2181994a3a6884ea121c5fc4a235034b412f3ac2b76761b2ce9d16e8abbdd78cfadc41265c037628b31d6b3fed2f1c305343a859f9b17b36bcc2c56bd6efff93
data/CHANGELOG.md CHANGED
@@ -1,5 +1,144 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [3.0.0] - 2026-09-20
4
+
5
+ Raix now requires `ruby_llm ~> 2.0`. RubyLLM 1.x is no longer supported.
6
+
7
+ The immediate reason is CVE-2026-67991, a high-severity ReDoS in `ruby_llm` fixed
8
+ in 2.0.0.rc1: Raix's `~> 1.9` cap left every app that depends on Raix pinned to a
9
+ vulnerable version.
10
+
11
+ ### Breaking
12
+
13
+ - Requires `ruby_llm ~> 2.0`. There is no dual-path support for 1.x.
14
+ - Requests go through RubyLLM's `chat_completions` protocol explicitly. RubyLLM 2.0
15
+ defaults OpenAI models to the Responses API; Raix pins the Chat Completions
16
+ protocol so its OpenAI-shaped response envelope, transcript format, and tool-call
17
+ handling keep their documented behavior.
18
+ - `Raix::FunctionToolAdapter::ToolCallsCapReached` and `StopToolCallsRequested` are
19
+ gone, along with `ChatCompletion#increment_tool_call_count`. All three existed only
20
+ to enforce the tool budget from inside RubyLLM 1.x's internal tool loop.
21
+ - `Raix::MultimodalContentAdapter.translate` returns a `Result` struct
22
+ (`content`, `attachments`, `cache_boundary`) instead of a `RubyLLM::Content`, which
23
+ RubyLLM 2.0 removed.
24
+ - A tool declared with no parameters now reports an empty object schema
25
+ (`{"type" => "object", "properties" => {}, ...}`) rather than `nil`, following
26
+ RubyLLM 2.0's `Tool#parameters_schema`.
27
+ - `chat_completion(raw: true)` responses carry `choices[0].message.tool_calls` in
28
+ OpenAI's array-of-hashes shape (`[{ "id", "type", "function" => { "name",
29
+ "arguments" } }]`, arguments JSON-encoded). Under 2.x this field held RubyLLM's
30
+ `{ id => RubyLLM::ToolCall }` Hash.
31
+ - An `image_url` part in a multimodal message is forwarded only when it is an
32
+ http(s) URL or a base64 `data:` URI, matching OpenAI's content schema. Any other
33
+ string is skipped. RubyLLM would otherwise read it as a local filesystem path.
34
+
35
+ ### Changed
36
+
37
+ - Raix drives the tool-call loop itself again. Each request asks RubyLLM for exactly
38
+ one completion (`Chat#generate`), and Raix dispatches the returned tool calls,
39
+ appends the exchange to the transcript, and continues. RubyLLM 2.0 exposes the loop
40
+ (`generate` / `run_tools` / `step`), so the 2.0.6 workaround of halting from inside
41
+ a generated tool wrapper is no longer needed. `max_tool_calls` is still counted per
42
+ call, so a response packing several parallel calls runs the ones that fit under the
43
+ budget and refuses the rest.
44
+ - Continuations after a tool round now carry the messages that drove the request plus
45
+ the tool exchange, instead of re-reading the transcript. A caller-supplied
46
+ `messages:` argument never enters the transcript, so it used to be dropped from
47
+ every request after the first tool call.
48
+ - Anthropic-style `cache_control` parts in a message's content array are translated
49
+ into RubyLLM 2.0 cache boundaries (`Chat#with_caching` plus `Message#cache_until_here`).
50
+ - Multiple system messages accumulate instead of replacing one another, and each is
51
+ sent once. `transcript << { system: ... }` used to store the prompt twice, and a
52
+ structured (array) system prompt pushed that way raised under RubyLLM 2.0.
53
+ - Continuation rounds replay the model's own assistant turn, with its tool-call ids,
54
+ Gemini thought signatures, and OpenRouter `reasoning_details`, followed by one
55
+ result message per tool call the model made (refused and malformed calls get an
56
+ explanatory result). Reasoning models that require signed state across tool rounds
57
+ keep working, and a nested `chat_completion` inside a tool body no longer has its
58
+ internal tool exchange replayed to the outer model.
59
+ - The transcript records the model's own tool-call turn (real ids, signatures,
60
+ reasoning details) and the results, rather than a synthetic assistant/tool pair
61
+ generated by `FunctionDispatch` or `MCP`. A later `chat_completion` on the same
62
+ transcript therefore replays signed history faithfully.
63
+ - When one tool in a batch raises, the exchange is still recorded in the transcript
64
+ before the error propagates: completed results as they were, the failing call marked
65
+ with the exception class (never its message, which can carry data the model must
66
+ not see), and any later calls marked as not executed. A retry then has evidence of
67
+ the side effects that already happened. The record was previously lost if a later
68
+ tool failed.
69
+ - A `JSON::ParserError` raised inside a tool body propagates as the tool's failure,
70
+ through every continuation round. It used to be caught by the blank-JSON-response
71
+ retry and could re-issue the request, running the batch's tools a second time.
72
+ - `max_tokens` and `max_completion_tokens` reach the provider again (as RubyLLM's
73
+ output token ceiling, `max_completion_tokens` taking precedence). Both were dropped
74
+ from the request under the 2.x RubyLLM backend.
75
+ - `save_response: false` keeps a call's tool exchanges out of the transcript as well
76
+ as its final answer. A nested `chat_completion` inside a tool body that passes it
77
+ therefore leaves no trace in the outer conversation's history.
78
+ - `chat_completion` works on a copy of `params`, so a Hash the caller reuses across
79
+ calls is no longer mutated.
80
+ - A transcript message whose role RubyLLM does not accept (including the legacy
81
+ `function` role) is skipped with a warning instead of silently.
82
+ - A tool call for a function that was not offered on the request (excluded by
83
+ `available_tools`, or never declared) is answered with a refusal result the model
84
+ can recover from, instead of raising `"Unauthorized function call"` after earlier
85
+ calls in the batch already ran.
86
+ - `cache_control.ttl` on a content part is forwarded to RubyLLM's caching options.
87
+ RubyLLM marks cache boundaries per message, so a boundary inside a content array
88
+ applies at the end of that message.
89
+ - Streaming requests that call tools now run the tool loop: chunks stream to the
90
+ block on every round and `chat_completion` returns the final content. Under 2.x
91
+ a streamed request returned `nil`.
92
+ - `usage.prompt_tokens` and `total_tokens` come from the provider when it reports
93
+ them; RubyLLM 2.0's own input count excludes cached tokens. Without provider usage,
94
+ cached tokens are added into the prompt count.
95
+
96
+ ### Fixed
97
+
98
+ - The `"Maximum tool calls (N) exceeded"` message quotes the budget the caller asked
99
+ for. It previously reported the allowance left at the moment the cap was hit, which
100
+ was always 0.
101
+ - A forced `tool_choice` (`"required"` or a named function) applies to the first
102
+ round of a tool loop only. It used to be re-sent on every continuation round, so
103
+ the model kept calling tools until the `max_tool_calls` budget ran out instead of
104
+ answering.
105
+ - `available_tools:` now restricts which tools the model is offered and which it can
106
+ execute. The filtered list was computed but every declared function was still
107
+ registered with RubyLLM, so the model saw tools the caller had asked to hide, and
108
+ the dispatch guard accepted any declared function regardless of the filter.
109
+ - A tool body that calls `chat_completion` on the same instance (a sub-agent
110
+ pattern) no longer corrupts the outer tool loop. The nested call used to overwrite
111
+ the outer `max_tool_calls` budget and clear a pending `stop_tool_calls_and_respond!`
112
+ request; both are now preserved across the nested call.
113
+ - A user message whose only `image_url` parts were skipped is sent with empty
114
+ content rather than no `content` field, which providers reject.
115
+ - The forced final completion after a cap breach or `stop_tool_calls_and_respond!`
116
+ goes through the same response handling as any other, so `json: true` returns a
117
+ parsed Hash and `Thread.current[:chat_completion_response]` points at it.
118
+ - `prediction` is wrapped once. Continuation rounds used to wrap the already-wrapped
119
+ value again.
120
+ - Malformed tool arguments are reported back to the model as a tool error instead of
121
+ aborting the completion: arguments that are not valid JSON at all (RubyLLM raises
122
+ `ToolCallParseError` before Raix sees the message; the assistant turn, including
123
+ its signed reasoning, is rebuilt from the raw payload) and arguments that parse but
124
+ are not an object (`[]`, `null`). Whitespace-only arguments count as malformed.
125
+ Recovery from unparseable arguments applies to non-streaming requests; a streamed
126
+ response arrives as SSE text, which RubyLLM does not hand back in a form that can
127
+ be rebuilt, so that case still raises.
128
+ - An assistant turn with neither content nor tool calls is sent with empty content
129
+ rather than no `content` field.
130
+ - Known limitation, unchanged from 2.x: a `ChatCompletion` instance keeps its tool-loop
131
+ state on the instance, so concurrent `chat_completion` calls from multiple threads on
132
+ one instance are not supported. Nested calls from within a tool body on the same
133
+ thread are.
134
+ - `PromptDeclarations` text prompts no longer raise `NoMethodError` on classes that
135
+ do not define a class-level `system_prompt`; the declaration is optional, as
136
+ documented.
137
+ - Tool use inside a class that also includes `PromptDeclarations` works again.
138
+ Continuation rounds re-entered the public `chat_completion`, which
139
+ `PromptDeclarations` overrides with a narrower signature, so the round after a
140
+ tool call raised `ArgumentError` and the prompt finished without its answer.
141
+
3
142
  ## [2.0.6] - 2026-07-23
4
143
 
5
144
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,11 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- raix (2.0.6)
4
+ raix (3.0.0)
5
5
  activesupport (>= 6.0)
6
6
  faraday-retry (~> 2.0)
7
7
  ostruct
8
- ruby_llm (~> 1.9)
8
+ ruby_llm (~> 2.0)
9
9
  zeitwerk (~> 2.7)
10
10
 
11
11
  GEM
@@ -27,7 +27,7 @@ GEM
27
27
  public_suffix (>= 2.0.2, < 6.0)
28
28
  ast (2.4.2)
29
29
  backport (1.2.0)
30
- base64 (0.2.0)
30
+ base64 (0.3.0)
31
31
  benchmark (0.3.0)
32
32
  bigdecimal (3.1.8)
33
33
  coderay (1.1.3)
@@ -41,12 +41,14 @@ GEM
41
41
  e2mmap (0.1.0)
42
42
  erubi (1.13.0)
43
43
  event_stream_parser (1.0.0)
44
- faraday (2.9.2)
45
- faraday-net_http (>= 2.0, < 3.2)
44
+ faraday (2.14.4)
45
+ faraday-net_http (>= 2.0, < 3.5)
46
+ json
47
+ logger
46
48
  faraday-multipart (1.2.0)
47
49
  multipart-post (~> 2.0)
48
- faraday-net_http (3.1.0)
49
- net-http
50
+ faraday-net_http (3.4.4)
51
+ net-http (~> 0.5)
50
52
  faraday-retry (2.4.0)
51
53
  faraday (~> 2.0)
52
54
  ffi (1.17.2-arm64-darwin)
@@ -81,13 +83,13 @@ GEM
81
83
  rb-inotify (~> 0.9, >= 0.9.10)
82
84
  logger (1.7.0)
83
85
  lumberjack (1.2.10)
84
- marcel (1.1.0)
86
+ marcel (1.2.1)
85
87
  method_source (1.1.0)
86
88
  minitest (5.27.0)
87
89
  multipart-post (2.4.1)
88
90
  nenv (0.3.0)
89
- net-http (0.4.1)
90
- uri
91
+ net-http (0.9.1)
92
+ uri (>= 0.11.1)
91
93
  netrc (0.11.0)
92
94
  nokogiri (1.18.8-arm64-darwin)
93
95
  racc (~> 1.4)
@@ -147,17 +149,18 @@ GEM
147
149
  rubocop-ast (1.31.2)
148
150
  parser (>= 3.3.0.4)
149
151
  ruby-progressbar (1.13.0)
150
- ruby_llm (1.14.0)
152
+ ruby_llm (2.0.0)
151
153
  base64
152
154
  event_stream_parser (~> 1)
153
155
  faraday (>= 1.10.0)
154
156
  faraday-multipart (>= 1)
155
157
  faraday-net_http (>= 1)
156
158
  faraday-retry (>= 1)
157
- marcel (~> 1)
158
- ruby_llm-schema (~> 0)
159
+ json (< 3)
160
+ marcel (>= 1.0, < 3)
161
+ schematist (~> 1.1)
159
162
  zeitwerk (~> 2)
160
- ruby_llm-schema (0.2.5)
163
+ schematist (1.1.0)
161
164
  securerandom (0.4.1)
162
165
  shellany (0.0.1)
163
166
  solargraph (0.50.0)
@@ -206,7 +209,7 @@ GEM
206
209
  tzinfo (2.0.6)
207
210
  concurrent-ruby (~> 1.0)
208
211
  unicode-display_width (2.5.0)
209
- uri (0.13.0)
212
+ uri (1.1.1)
210
213
  vcr (6.2.0)
211
214
  webmock (3.18.1)
212
215
  addressable (>= 2.8.0)
@@ -216,7 +219,7 @@ GEM
216
219
  yard-sorbet (0.8.1)
217
220
  sorbet-runtime (>= 0.5)
218
221
  yard (>= 0.9)
219
- zeitwerk (2.7.3)
222
+ zeitwerk (2.8.3)
220
223
 
221
224
  PLATFORMS
222
225
  arm64-darwin-21
data/README.md CHANGED
@@ -6,7 +6,7 @@ Raix (pronounced "ray" because the x is silent) is a library that gives you ever
6
6
 
7
7
  Understanding how to use discrete AI components in otherwise normal code is key to productively leveraging Raix, and the subject of a book written by Raix's author Obie Fernandez, titled [Patterns of Application Development Using AI](https://leanpub.com/patterns-of-application-development-using-ai). You can easily support the ongoing development of this project by buying the book at Leanpub.
8
8
 
9
- Raix 2.0 is powered by [RubyLLM](https://github.com/crmne/ruby_llm), giving you unified access to OpenAI, Anthropic, Google Gemini, and dozens of other providers through OpenRouter. Note that you can use Raix to add AI capabilities to non-Rails applications as long as you include ActiveSupport as a dependency.
9
+ Raix 3.0 is powered by [RubyLLM](https://github.com/crmne/ruby_llm), giving you unified access to OpenAI, Anthropic, Google Gemini, and dozens of other providers through OpenRouter. Note that you can use Raix to add AI capabilities to non-Rails applications as long as you include ActiveSupport as a dependency.
10
10
 
11
11
  ### Chat Completions
12
12
 
@@ -855,7 +855,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
855
855
 
856
856
  ### Configuration
857
857
 
858
- Raix 2.0 uses [RubyLLM](https://github.com/crmne/ruby_llm) as its backend for LLM provider connections. Configure your API keys through RubyLLM:
858
+ Raix 3.0 uses [RubyLLM](https://github.com/crmne/ruby_llm) as its backend for LLM provider connections. Configure your API keys through RubyLLM:
859
859
 
860
860
  ```ruby
861
861
  # config/initializers/raix.rb