llm.rb 12.1.0 → 12.2.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 +81 -0
- data/README.md +26 -17
- data/data/anthropic.json +3 -2
- data/data/bedrock.json +47 -0
- data/data/deepinfra.json +2 -2
- data/data/google.json +32 -0
- data/data/mistral.json +968 -0
- data/data/xai.json +60 -1
- data/lib/llm/agent.rb +17 -1
- data/lib/llm/context.rb +2 -2
- data/lib/llm/function.rb +6 -0
- data/lib/llm/provider.rb +2 -2
- data/lib/llm/providers/anthropic.rb +2 -2
- data/lib/llm/providers/google.rb +3 -1
- data/lib/llm/providers/mistral/request_adapter/completion.rb +122 -0
- data/lib/llm/providers/mistral/request_adapter.rb +20 -0
- data/lib/llm/providers/mistral.rb +96 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +9 -0
- data/llm.gemspec +1 -1
- metadata +6 -2
data/data/xai.json
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"grok-4.3": {
|
|
120
120
|
"id": "grok-4.3",
|
|
121
121
|
"name": "Grok 4.3",
|
|
122
|
-
"description": "xAI's
|
|
122
|
+
"description": "xAI's Grok for chat, coding, agentic tools, and lower hallucination risk",
|
|
123
123
|
"family": "grok",
|
|
124
124
|
"attachment": true,
|
|
125
125
|
"reasoning": true,
|
|
@@ -232,6 +232,65 @@
|
|
|
232
232
|
"output": 0
|
|
233
233
|
}
|
|
234
234
|
},
|
|
235
|
+
"grok-4.5": {
|
|
236
|
+
"id": "grok-4.5",
|
|
237
|
+
"name": "Grok 4.5",
|
|
238
|
+
"description": "xAI's latest Grok for chat, coding, agentic tools, and lower hallucination risk",
|
|
239
|
+
"family": "grok",
|
|
240
|
+
"attachment": true,
|
|
241
|
+
"reasoning": true,
|
|
242
|
+
"reasoning_options": [
|
|
243
|
+
{
|
|
244
|
+
"type": "effort",
|
|
245
|
+
"values": [
|
|
246
|
+
"low",
|
|
247
|
+
"medium",
|
|
248
|
+
"high"
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
"tool_call": true,
|
|
253
|
+
"structured_output": true,
|
|
254
|
+
"temperature": true,
|
|
255
|
+
"release_date": "2026-07-08",
|
|
256
|
+
"last_updated": "2026-07-08",
|
|
257
|
+
"modalities": {
|
|
258
|
+
"input": [
|
|
259
|
+
"text",
|
|
260
|
+
"image",
|
|
261
|
+
"pdf"
|
|
262
|
+
],
|
|
263
|
+
"output": [
|
|
264
|
+
"text"
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"open_weights": false,
|
|
268
|
+
"limit": {
|
|
269
|
+
"context": 500000,
|
|
270
|
+
"output": 500000
|
|
271
|
+
},
|
|
272
|
+
"cost": {
|
|
273
|
+
"input": 2,
|
|
274
|
+
"output": 6,
|
|
275
|
+
"cache_read": 0.5,
|
|
276
|
+
"tiers": [
|
|
277
|
+
{
|
|
278
|
+
"input": 4,
|
|
279
|
+
"output": 12,
|
|
280
|
+
"cache_read": 1,
|
|
281
|
+
"tier": {
|
|
282
|
+
"type": "context",
|
|
283
|
+
"size": 200000
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
"context_over_200k": {
|
|
288
|
+
"input": 4,
|
|
289
|
+
"output": 12,
|
|
290
|
+
"cache_read": 1
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
},
|
|
235
294
|
"grok-4.20-0309-reasoning": {
|
|
236
295
|
"id": "grok-4.20-0309-reasoning",
|
|
237
296
|
"name": "Grok 4.20 (Reasoning)",
|
data/lib/llm/agent.rb
CHANGED
|
@@ -393,10 +393,26 @@ module LLM
|
|
|
393
393
|
# with the agent and its current state. This
|
|
394
394
|
# method requires the 'curses' gem to be installed
|
|
395
395
|
# and available to require.
|
|
396
|
+
#
|
|
397
|
+
# @note
|
|
398
|
+
# By default this method disables the tracer for
|
|
399
|
+
# the duration of the repl session, and restores
|
|
400
|
+
# it afterwards.
|
|
401
|
+
# @param [Boolean] trace
|
|
402
|
+
# When true, the tracer is kept alive during the
|
|
403
|
+
# repl session. Default is false.
|
|
396
404
|
# @return [void]
|
|
397
|
-
def repl
|
|
405
|
+
def repl(trace: false)
|
|
406
|
+
if !trace
|
|
407
|
+
previous = tracer
|
|
408
|
+
self.tracer = nil
|
|
409
|
+
end
|
|
398
410
|
require_relative "repl" unless defined?(::LLM::Repl)
|
|
399
411
|
LLM::Repl.new(self).start
|
|
412
|
+
ensure
|
|
413
|
+
if !trace
|
|
414
|
+
self.tracer = previous
|
|
415
|
+
end
|
|
400
416
|
end
|
|
401
417
|
|
|
402
418
|
##
|
data/lib/llm/context.rb
CHANGED
|
@@ -426,7 +426,7 @@ module LLM
|
|
|
426
426
|
# A tracer, or nil.
|
|
427
427
|
# @return [void]
|
|
428
428
|
def tracer=(other)
|
|
429
|
-
@llm.tracer =
|
|
429
|
+
@llm.tracer = other || LLM::Tracer::Null.new(@llm)
|
|
430
430
|
end
|
|
431
431
|
|
|
432
432
|
##
|
|
@@ -604,7 +604,7 @@ module LLM
|
|
|
604
604
|
def repair!(messages, prompt)
|
|
605
605
|
message = messages.last
|
|
606
606
|
return unless message&.tool_call?
|
|
607
|
-
returns = self.returns
|
|
607
|
+
returns = [self.returns, prompt].flatten.grep(LLM::Function::Return)
|
|
608
608
|
cancelled = []
|
|
609
609
|
[*message.extra.tool_calls].each do |tool|
|
|
610
610
|
next if returns.any? { _1.id == tool[:id] }
|
data/lib/llm/function.rb
CHANGED
|
@@ -390,6 +390,12 @@ class LLM::Function
|
|
|
390
390
|
type: "function", name: @name, description: @description,
|
|
391
391
|
parameters: (@params || {type: "object", properties: {}}).to_h.merge(additionalProperties: false), strict: false
|
|
392
392
|
}.compact
|
|
393
|
+
when "LLM::Mistral"
|
|
394
|
+
params = @params || {type: "object", properties: {}}
|
|
395
|
+
{
|
|
396
|
+
type: "function",
|
|
397
|
+
function: {name: @name, description: @description, parameters: params}
|
|
398
|
+
}.compact
|
|
393
399
|
else
|
|
394
400
|
params = @params || {type: "object", properties: {}}
|
|
395
401
|
{
|
data/lib/llm/provider.rb
CHANGED
|
@@ -26,7 +26,7 @@ class LLM::Provider
|
|
|
26
26
|
# Requires the net-http-persistent gem.
|
|
27
27
|
# @param [LLM::Transport, Class, nil] transport
|
|
28
28
|
# Optional override with any {LLM::Transport} instance or subclass.
|
|
29
|
-
def initialize(key:, host:, port: 443, timeout:
|
|
29
|
+
def initialize(key:, host:, port: 443, timeout: 180, ssl: true, base_path: "", persistent: false, transport: nil)
|
|
30
30
|
@key = key
|
|
31
31
|
@host = host
|
|
32
32
|
@port = port
|
|
@@ -303,7 +303,7 @@ class LLM::Provider
|
|
|
303
303
|
def with_tracer(tracer)
|
|
304
304
|
had_override = weakmap.key?(self)
|
|
305
305
|
previous = weakmap[self]
|
|
306
|
-
weakmap[self] = tracer
|
|
306
|
+
weakmap[self] = tracer || LLM::Tracer::Null.new(self)
|
|
307
307
|
yield
|
|
308
308
|
ensure
|
|
309
309
|
if had_override
|
|
@@ -92,10 +92,10 @@ module LLM
|
|
|
92
92
|
|
|
93
93
|
##
|
|
94
94
|
# Returns the default model for chat completions
|
|
95
|
-
# @see https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table claude-
|
|
95
|
+
# @see https://docs.anthropic.com/en/docs/about-claude/models/all-models#model-comparison-table claude-opus-4-8
|
|
96
96
|
# @return [String]
|
|
97
97
|
def default_model
|
|
98
|
-
"claude-
|
|
98
|
+
"claude-opus-4-8"
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
##
|
data/lib/llm/providers/google.rb
CHANGED
|
@@ -194,9 +194,11 @@ module LLM
|
|
|
194
194
|
end
|
|
195
195
|
|
|
196
196
|
def normalize_complete_params(params)
|
|
197
|
+
except = %i[role model messages stream]
|
|
197
198
|
params = {role: :user, model: default_model}.merge!(params)
|
|
198
199
|
tools = resolve_tools(params.delete(:tools))
|
|
199
|
-
|
|
200
|
+
config = adapt_generation_config(params.except(*except))
|
|
201
|
+
params = [params.except(:schema), config, adapt_tools(tools)].inject({}, &:merge!).compact
|
|
200
202
|
role, model, stream = [:role, :model, :stream].map { params.delete(_1) }
|
|
201
203
|
[params, stream, tools, role, model]
|
|
202
204
|
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::Mistral::RequestAdapter
|
|
4
|
+
##
|
|
5
|
+
# @private
|
|
6
|
+
class Completion
|
|
7
|
+
##
|
|
8
|
+
# @param [LLM::Message, Hash] message
|
|
9
|
+
# The message to format
|
|
10
|
+
def initialize(message)
|
|
11
|
+
@message = message
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Adapts the message for the Mistral chat completions API
|
|
16
|
+
# @return [Hash]
|
|
17
|
+
def adapt
|
|
18
|
+
catch(:abort) do
|
|
19
|
+
if Hash === message
|
|
20
|
+
{role: message[:role], content: adapt_content(message[:content])}
|
|
21
|
+
elsif message.tool_call?
|
|
22
|
+
{role: message.role, content: nil, tool_calls: message.extra[:original_tool_calls]}
|
|
23
|
+
else
|
|
24
|
+
adapt_message
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def adapt_message
|
|
32
|
+
case content
|
|
33
|
+
when Array
|
|
34
|
+
adapt_array
|
|
35
|
+
else
|
|
36
|
+
{role: message.role, content: adapt_content(content)}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def adapt_array
|
|
41
|
+
if content.empty?
|
|
42
|
+
nil
|
|
43
|
+
elsif returns.any?
|
|
44
|
+
returns.map do
|
|
45
|
+
{
|
|
46
|
+
role: "tool",
|
|
47
|
+
name: _1.name,
|
|
48
|
+
tool_call_id: _1.id,
|
|
49
|
+
content: LLM.json.dump(_1.value)
|
|
50
|
+
}
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
{role: message.role, content: content.flat_map { adapt_content(_1) }}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def adapt_content(content)
|
|
58
|
+
case content
|
|
59
|
+
when LLM::Object
|
|
60
|
+
adapt_object(content)
|
|
61
|
+
when String
|
|
62
|
+
[{type: :text, text: content.to_s}]
|
|
63
|
+
when LLM::Response
|
|
64
|
+
adapt_remote_file(content)
|
|
65
|
+
when LLM::Message
|
|
66
|
+
adapt_content(content.content)
|
|
67
|
+
when LLM::Function::Return
|
|
68
|
+
throw(:abort, {
|
|
69
|
+
role: "tool",
|
|
70
|
+
name: content.name,
|
|
71
|
+
tool_call_id: content.id,
|
|
72
|
+
content: LLM.json.dump(content.value)
|
|
73
|
+
})
|
|
74
|
+
else
|
|
75
|
+
prompt_error!(content)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def adapt_object(object)
|
|
80
|
+
case object.kind
|
|
81
|
+
when :image_url
|
|
82
|
+
[{type: :image_url, image_url: {url: object.value}}]
|
|
83
|
+
when :local_file
|
|
84
|
+
adapt_local_file(object.value)
|
|
85
|
+
when :remote_file
|
|
86
|
+
adapt_remote_file(object.value)
|
|
87
|
+
else
|
|
88
|
+
prompt_error!(object)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def adapt_local_file(file)
|
|
93
|
+
if file.image?
|
|
94
|
+
[{type: :image_url, image_url: {url: file.to_data_uri}}]
|
|
95
|
+
else
|
|
96
|
+
[{type: :file, file: {filename: file.basename, file_data: file.to_data_uri}}]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def adapt_remote_file(file)
|
|
101
|
+
if file.file?
|
|
102
|
+
[{type: :file, file: {file_id: file.id}}]
|
|
103
|
+
else
|
|
104
|
+
prompt_error!(file)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def prompt_error!(content)
|
|
109
|
+
if LLM::Object === content
|
|
110
|
+
raise LLM::PromptError, "The given LLM::Object with kind '#{content.kind}' is not " \
|
|
111
|
+
"supported by the Mistral chat completions API."
|
|
112
|
+
else
|
|
113
|
+
raise LLM::PromptError, "The given object (an instance of #{content.class}) " \
|
|
114
|
+
"is not supported by the Mistral chat completions API."
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def message = @message
|
|
119
|
+
def content = message.content
|
|
120
|
+
def returns = content.grep(LLM::Function::Return)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Mistral
|
|
4
|
+
##
|
|
5
|
+
# @private
|
|
6
|
+
module RequestAdapter
|
|
7
|
+
require_relative "request_adapter/completion"
|
|
8
|
+
include LLM::OpenAI::RequestAdapter
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# @param [Array<LLM::Message>] messages
|
|
12
|
+
# The messages to adapt
|
|
13
|
+
# @return [Array<Hash>]
|
|
14
|
+
def adapt(messages, mode: nil)
|
|
15
|
+
messages.filter_map do |message|
|
|
16
|
+
Completion.new(message).adapt
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "openai" unless defined?(LLM::OpenAI)
|
|
4
|
+
|
|
5
|
+
module LLM
|
|
6
|
+
##
|
|
7
|
+
# The Mistral class implements a provider for
|
|
8
|
+
# [Mistral](https://mistral.ai) through its
|
|
9
|
+
# OpenAI-compatible API.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# #!/usr/bin/env ruby
|
|
13
|
+
# require "llm"
|
|
14
|
+
#
|
|
15
|
+
# llm = LLM.mistral(key: ENV["KEY"])
|
|
16
|
+
# ctx = LLM::Context.new(llm)
|
|
17
|
+
# ctx.talk "Hello"
|
|
18
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
19
|
+
class Mistral < OpenAI
|
|
20
|
+
require_relative "mistral/request_adapter"
|
|
21
|
+
include Mistral::RequestAdapter
|
|
22
|
+
|
|
23
|
+
HOST = "api.mistral.ai"
|
|
24
|
+
BASE_PATH = "/v1/"
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# @param key (see LLM::Provider#initialize)
|
|
28
|
+
# @param host (see LLM::Provider#initialize)
|
|
29
|
+
# @param base_path (see LLM::Provider#initialize)
|
|
30
|
+
# @return [LLM::Mistral]
|
|
31
|
+
def initialize(host: HOST, base_path: BASE_PATH, **)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# @return [Symbol]
|
|
37
|
+
# Returns the provider's name
|
|
38
|
+
def name
|
|
39
|
+
:mistral
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# @return [NotImplementedError]
|
|
44
|
+
def images
|
|
45
|
+
raise NotImplementedError
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Provides an embedding.
|
|
50
|
+
# @param input (see LLM::Provider#embed)
|
|
51
|
+
# @param model (see LLM::Provider#embed)
|
|
52
|
+
# @param params (see LLM::Provider#embed)
|
|
53
|
+
# @raise (see LLM::Provider#request)
|
|
54
|
+
# @return (see LLM::Provider#embed)
|
|
55
|
+
def embed(input, model: "mistral-embed", **params)
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# @raise [NotImplementedError]
|
|
61
|
+
def responses
|
|
62
|
+
raise NotImplementedError
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# @return [LLM::Mistral::Audio]
|
|
67
|
+
def audio
|
|
68
|
+
raise NotImplementedError
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# @raise [NotImplementedError]
|
|
73
|
+
def files
|
|
74
|
+
raise NotImplementedError
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# @raise [NotImplementedError]
|
|
79
|
+
def moderations
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# @raise [NotImplementedError]
|
|
85
|
+
def vector_stores
|
|
86
|
+
raise NotImplementedError
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Returns the default model for chat completions
|
|
91
|
+
# @return [String]
|
|
92
|
+
def default_model
|
|
93
|
+
"mistral-large-latest"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/llm/version.rb
CHANGED
data/lib/llm.rb
CHANGED
|
@@ -180,6 +180,15 @@ module LLM
|
|
|
180
180
|
LLM::XAI.new(**)
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
+
##
|
|
184
|
+
# @param key (see LLM::Mistral#initialize)
|
|
185
|
+
# @param host (see LLM::Mistral#initialize)
|
|
186
|
+
# @return (see LLM::Mistral#initialize)
|
|
187
|
+
def mistral(**)
|
|
188
|
+
lock(:require) { require_relative "llm/providers/mistral" unless defined?(LLM::Mistral) }
|
|
189
|
+
LLM::Mistral.new(**)
|
|
190
|
+
end
|
|
191
|
+
|
|
183
192
|
##
|
|
184
193
|
# @param key (see LLM::ZAI#initialize)
|
|
185
194
|
# @param host (see LLM::ZAI#initialize)
|
data/llm.gemspec
CHANGED
|
@@ -5,7 +5,7 @@ require_relative "lib/llm/version"
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "llm.rb"
|
|
7
7
|
spec.version = LLM::VERSION
|
|
8
|
-
spec.authors = ["
|
|
8
|
+
spec.authors = ["robert", "Antar Azri", "Rodrigo Serrano"]
|
|
9
9
|
spec.email = ["robert@r.uby.dev"]
|
|
10
10
|
|
|
11
11
|
spec.summary = "Ruby's capable AI runtime"
|
metadata
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 12.
|
|
4
|
+
version: 12.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- robert
|
|
8
8
|
- Antar Azri
|
|
9
9
|
- Rodrigo Serrano
|
|
10
10
|
bindir: bin
|
|
@@ -293,6 +293,7 @@ files:
|
|
|
293
293
|
- data/deepinfra.json
|
|
294
294
|
- data/deepseek.json
|
|
295
295
|
- data/google.json
|
|
296
|
+
- data/mistral.json
|
|
296
297
|
- data/openai.json
|
|
297
298
|
- data/xai.json
|
|
298
299
|
- data/zai.json
|
|
@@ -419,6 +420,9 @@ files:
|
|
|
419
420
|
- lib/llm/providers/google/stream_parser.rb
|
|
420
421
|
- lib/llm/providers/google/utils.rb
|
|
421
422
|
- lib/llm/providers/llamacpp.rb
|
|
423
|
+
- lib/llm/providers/mistral.rb
|
|
424
|
+
- lib/llm/providers/mistral/request_adapter.rb
|
|
425
|
+
- lib/llm/providers/mistral/request_adapter/completion.rb
|
|
422
426
|
- lib/llm/providers/ollama.rb
|
|
423
427
|
- lib/llm/providers/ollama/error_handler.rb
|
|
424
428
|
- lib/llm/providers/ollama/models.rb
|