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 +4 -4
- data/CHANGELOG.md +139 -0
- data/Gemfile.lock +19 -16
- data/README.md +2 -2
- data/lib/raix/chat_completion.rb +369 -193
- data/lib/raix/function_dispatch.rb +11 -39
- data/lib/raix/function_tool_adapter.rb +21 -41
- data/lib/raix/mcp.rb +6 -34
- data/lib/raix/multimodal_content_adapter.rb +41 -25
- data/lib/raix/prompt_declarations.rb +1 -1
- data/lib/raix/transcript_adapter.rb +4 -2
- data/lib/raix/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8be858d495426d05d1a9eb816d24068c049ca86ed3f80aeb1af72fdb7860956b
|
|
4
|
+
data.tar.gz: 2449a9c1b03b60bbcc9df1fbff4b19b88cc35a9c6b6af55197b72d909021089d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 (
|
|
4
|
+
raix (3.0.0)
|
|
5
5
|
activesupport (>= 6.0)
|
|
6
6
|
faraday-retry (~> 2.0)
|
|
7
7
|
ostruct
|
|
8
|
-
ruby_llm (~>
|
|
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.
|
|
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.
|
|
45
|
-
faraday-net_http (>= 2.0, < 3.
|
|
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.
|
|
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
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
158
|
-
|
|
159
|
+
json (< 3)
|
|
160
|
+
marcel (>= 1.0, < 3)
|
|
161
|
+
schematist (~> 1.1)
|
|
159
162
|
zeitwerk (~> 2)
|
|
160
|
-
|
|
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 (
|
|
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.
|
|
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
|
|
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
|
|
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
|