llm.rb 4.19.0 → 4.20.1
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 +41 -0
- data/README.md +1 -1
- data/lib/llm/active_record/acts_as_agent.rb +2 -2
- data/lib/llm/active_record/acts_as_llm.rb +2 -2
- data/lib/llm/context/deserializer.rb +51 -1
- data/lib/llm/context/serializer.rb +52 -0
- data/lib/llm/context.rb +4 -28
- data/lib/llm/message.rb +34 -0
- data/lib/llm/sequel/plugin.rb +3 -3
- data/lib/llm/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 58f2ff0f8147443face2f3d7c48b249b8aec30de4345fa286f87c622853cb516
|
|
4
|
+
data.tar.gz: 9dba9a0609fff95e141ee5a819ff454a9dbd5ecb9c987a1a3e3b73822431d6d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 172de04003136f5b599f5b2c274d9354ca576512bc35e9af85c5672f32bd3ad5f85a8a0b7e60e29c60b8fa7e6bd8d39ed5d23692c60a4b6de0f2c941d542fd41
|
|
7
|
+
data.tar.gz: 9a3ef1da238e38ab51af3a20f235201bca736a41753c9848a589467676a979829dca6c7e5708ee12be344e9e0686ba4652504514b72769ff5d511c5d752dd9f2
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
Changes since `v4.20.1`.
|
|
6
|
+
|
|
7
|
+
## v4.20.1
|
|
8
|
+
|
|
9
|
+
Changes since `v4.20.0`.
|
|
10
|
+
|
|
11
|
+
This patch release fixes ORM option resolution in the Sequel and
|
|
12
|
+
ActiveRecord wrappers. Symbol-based `provider:` and `context:` hooks now
|
|
13
|
+
resolve correctly, and internal default option constants are referenced
|
|
14
|
+
explicitly instead of relying on nested constant lookup.
|
|
15
|
+
|
|
16
|
+
### Fix
|
|
17
|
+
|
|
18
|
+
* **Fix symbol-based ORM option hooks for provider and context hashes** <br>
|
|
19
|
+
Make `provider:` and `context:` resolve symbol hooks through the model in
|
|
20
|
+
the Sequel plugin and ActiveRecord wrappers instead of falling back to an
|
|
21
|
+
empty hash.
|
|
22
|
+
|
|
23
|
+
* **Fix ORM wrapper constant lookup for option defaults** <br>
|
|
24
|
+
Qualify internal `EMPTY_HASH` / `DEFAULTS` references in the Sequel plugin
|
|
25
|
+
and ActiveRecord wrappers so option resolution does not depend on nested
|
|
26
|
+
constant lookup quirks.
|
|
27
|
+
|
|
28
|
+
## v4.20.0
|
|
29
|
+
|
|
5
30
|
Changes since `v4.19.0`.
|
|
6
31
|
|
|
32
|
+
This release adds better support for tagged prompt content. `LLM::Context`
|
|
33
|
+
can now serialize and restore `image_url`, `local_file`, and `remote_file`
|
|
34
|
+
content cleanly, and `LLM::Message` now exposes helpers for inspecting
|
|
35
|
+
tagged image and file attachments.
|
|
36
|
+
|
|
37
|
+
### Change
|
|
38
|
+
|
|
39
|
+
* **Round-trip tagged prompt objects through `LLM::Context`** <br>
|
|
40
|
+
Teach `LLM::Context` serialization and restore to preserve
|
|
41
|
+
`image_url`, `local_file`, and `remote_file` content across
|
|
42
|
+
`to_json` / `restore`.
|
|
43
|
+
|
|
44
|
+
* **Add attachment helpers to `LLM::Message`** <br>
|
|
45
|
+
Add `image_url?`, `image_urls`, `file?`, and `files` so callers can
|
|
46
|
+
inspect messages for tagged image and file content more directly.
|
|
47
|
+
|
|
7
48
|
## v4.19.0
|
|
8
49
|
|
|
9
50
|
Changes since `v4.18.0`.
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p align="center">
|
|
5
5
|
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1"><img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc"></a>
|
|
6
6
|
<a href="https://opensource.org/license/0bsd"><img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License"></a>
|
|
7
|
-
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.
|
|
7
|
+
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.20.1-green.svg?" alt="Version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
## About
|
|
@@ -150,8 +150,8 @@ module LLM::ActiveRecord
|
|
|
150
150
|
# @return [Hash]
|
|
151
151
|
def resolve_options(option)
|
|
152
152
|
case option
|
|
153
|
-
when Proc, Hash then resolve_option(option)
|
|
154
|
-
else EMPTY_HASH.dup
|
|
153
|
+
when Proc, Symbol, Hash then resolve_option(option)
|
|
154
|
+
else ActsAsAgent::EMPTY_HASH.dup
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -270,8 +270,8 @@ module LLM::ActiveRecord
|
|
|
270
270
|
# @return [Hash]
|
|
271
271
|
def resolve_options(option)
|
|
272
272
|
case option
|
|
273
|
-
when Proc, Hash then resolve_option(option)
|
|
274
|
-
else EMPTY_HASH.dup
|
|
273
|
+
when Proc, Symbol, Hash then resolve_option(option)
|
|
274
|
+
else ActsAsLLM::EMPTY_HASH.dup
|
|
275
275
|
end
|
|
276
276
|
end
|
|
277
277
|
|
|
@@ -4,6 +4,32 @@ class LLM::Context
|
|
|
4
4
|
##
|
|
5
5
|
# @api private
|
|
6
6
|
module Deserializer
|
|
7
|
+
##
|
|
8
|
+
# Restore a saved context state
|
|
9
|
+
# @param [String, nil] path
|
|
10
|
+
# The path to a JSON file
|
|
11
|
+
# @param [String, nil] string
|
|
12
|
+
# A raw JSON string
|
|
13
|
+
# @param [Hash, nil] data
|
|
14
|
+
# A parsed context payload
|
|
15
|
+
# @raise [SystemCallError]
|
|
16
|
+
# Might raise a number of SystemCallError subclasses
|
|
17
|
+
# @return [LLM::Context]
|
|
18
|
+
def deserialize(path: nil, string: nil, data: nil)
|
|
19
|
+
ctx = if data
|
|
20
|
+
data
|
|
21
|
+
elsif path.nil? and string.nil?
|
|
22
|
+
raise ArgumentError, "a path, string, or data payload is required"
|
|
23
|
+
elsif path
|
|
24
|
+
LLM.json.load(::File.binread(path))
|
|
25
|
+
else
|
|
26
|
+
LLM.json.load(string)
|
|
27
|
+
end
|
|
28
|
+
@messages.concat [*ctx["messages"]].map { deserialize_message(_1) }
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
alias_method :restore, :deserialize
|
|
32
|
+
|
|
7
33
|
##
|
|
8
34
|
# @param [Hash] payload
|
|
9
35
|
# @return [LLM::Message]
|
|
@@ -14,12 +40,36 @@ class LLM::Context
|
|
|
14
40
|
usage = payload["usage"]
|
|
15
41
|
reasoning_content = payload["reasoning_content"]
|
|
16
42
|
extra = {tool_calls:, original_tool_calls:, tools: @params[:tools], usage:, reasoning_content:}.compact
|
|
17
|
-
content = returns.nil? ? payload["content"] : returns
|
|
43
|
+
content = returns.nil? ? deserialize_content(payload["content"]) : returns
|
|
18
44
|
LLM::Message.new(payload["role"], content, extra)
|
|
19
45
|
end
|
|
20
46
|
|
|
21
47
|
private
|
|
22
48
|
|
|
49
|
+
def deserialize_content(content)
|
|
50
|
+
case content
|
|
51
|
+
when Array
|
|
52
|
+
content.map { deserialize_content(_1) }
|
|
53
|
+
when Hash
|
|
54
|
+
deserialize_object(content)
|
|
55
|
+
else
|
|
56
|
+
content
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def deserialize_object(object)
|
|
61
|
+
case object["__llm_kind__"]
|
|
62
|
+
when "image_url"
|
|
63
|
+
LLM::Object.from(value: object["value"], kind: :image_url)
|
|
64
|
+
when "local_file"
|
|
65
|
+
LLM::Object.from(value: LLM.File(object["path"]), kind: :local_file)
|
|
66
|
+
when "remote_file"
|
|
67
|
+
LLM::Object.from(value: LLM::Object.from(object["value"] || {}), kind: :remote_file)
|
|
68
|
+
else
|
|
69
|
+
object
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
23
73
|
def deserialize_tool_calls(items)
|
|
24
74
|
items ||= []
|
|
25
75
|
items.empty? ? nil : items
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Context
|
|
4
|
+
##
|
|
5
|
+
# @api private
|
|
6
|
+
module Serializer
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def serialize_message(message)
|
|
10
|
+
h = message.to_h
|
|
11
|
+
h[:content] = serialize_content(h[:content])
|
|
12
|
+
h
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def serialize_content(content)
|
|
16
|
+
case content
|
|
17
|
+
when Array
|
|
18
|
+
content.map { serialize_content(_1) }
|
|
19
|
+
when LLM::Object
|
|
20
|
+
serialize_object(content)
|
|
21
|
+
else
|
|
22
|
+
content
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def serialize_object(object)
|
|
27
|
+
case object.kind
|
|
28
|
+
when :image_url
|
|
29
|
+
{__llm_kind__: "image_url", value: object.value}
|
|
30
|
+
when :local_file
|
|
31
|
+
{__llm_kind__: "local_file", path: object.value.path}
|
|
32
|
+
when :remote_file
|
|
33
|
+
{__llm_kind__: "remote_file", value: serialize_remote_file(object.value)}
|
|
34
|
+
else
|
|
35
|
+
object.to_h
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def serialize_remote_file(file)
|
|
40
|
+
{
|
|
41
|
+
"file?" => file.respond_to?(:file?) ? file.file? : true,
|
|
42
|
+
"id" => (file.id if file.respond_to?(:id)),
|
|
43
|
+
"filename" => (file.filename if file.respond_to?(:filename)),
|
|
44
|
+
"mime_type" => (file.mime_type if file.respond_to?(:mime_type)),
|
|
45
|
+
"uri" => (file.uri if file.respond_to?(:uri)),
|
|
46
|
+
"file_type" => (file.file_type if file.respond_to?(:file_type)),
|
|
47
|
+
"name" => (file.name if file.respond_to?(:name)),
|
|
48
|
+
"display_name" => (file.display_name if file.respond_to?(:display_name))
|
|
49
|
+
}.compact
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/llm/context.rb
CHANGED
|
@@ -34,7 +34,9 @@ module LLM
|
|
|
34
34
|
# ctx.talk(prompt)
|
|
35
35
|
# ctx.messages.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
36
36
|
class Context
|
|
37
|
+
require_relative "context/serializer"
|
|
37
38
|
require_relative "context/deserializer"
|
|
39
|
+
include Serializer
|
|
38
40
|
include Deserializer
|
|
39
41
|
|
|
40
42
|
##
|
|
@@ -279,7 +281,7 @@ module LLM
|
|
|
279
281
|
##
|
|
280
282
|
# @return [Hash]
|
|
281
283
|
def to_h
|
|
282
|
-
{schema_version: 1, model:, messages:}
|
|
284
|
+
{schema_version: 1, model:, messages: @messages.map { serialize_message(_1) }}
|
|
283
285
|
end
|
|
284
286
|
|
|
285
287
|
##
|
|
@@ -299,36 +301,10 @@ module LLM
|
|
|
299
301
|
# Might raise a number of SystemCallError subclasses
|
|
300
302
|
# @return [void]
|
|
301
303
|
def serialize(path:)
|
|
302
|
-
::File.binwrite path, LLM.json.dump(
|
|
304
|
+
::File.binwrite path, LLM.json.dump(to_h)
|
|
303
305
|
end
|
|
304
306
|
alias_method :save, :serialize
|
|
305
307
|
|
|
306
|
-
##
|
|
307
|
-
# Restore a saved context state
|
|
308
|
-
# @param [String, nil] path
|
|
309
|
-
# The path to a JSON file
|
|
310
|
-
# @param [String, nil] string
|
|
311
|
-
# A raw JSON string
|
|
312
|
-
# @param [Hash, nil] data
|
|
313
|
-
# A parsed context payload
|
|
314
|
-
# @raise [SystemCallError]
|
|
315
|
-
# Might raise a number of SystemCallError subclasses
|
|
316
|
-
# @return [LLM::Context]
|
|
317
|
-
def deserialize(path: nil, string: nil, data: nil)
|
|
318
|
-
ctx = if data
|
|
319
|
-
data
|
|
320
|
-
elsif path.nil? and string.nil?
|
|
321
|
-
raise ArgumentError, "a path, string, or data payload is required"
|
|
322
|
-
elsif path
|
|
323
|
-
LLM.json.load(::File.binread(path))
|
|
324
|
-
else
|
|
325
|
-
LLM.json.load(string)
|
|
326
|
-
end
|
|
327
|
-
@messages.concat [*ctx["messages"]].map { deserialize_message(_1) }
|
|
328
|
-
self
|
|
329
|
-
end
|
|
330
|
-
alias_method :restore, :deserialize
|
|
331
|
-
|
|
332
308
|
##
|
|
333
309
|
# @return [LLM::Cost]
|
|
334
310
|
# Returns an _approximate_ cost for a given context
|
data/lib/llm/message.rb
CHANGED
|
@@ -74,6 +74,36 @@ module LLM
|
|
|
74
74
|
extra.reasoning_content
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
##
|
|
78
|
+
# Returns true when a message contains an image URL
|
|
79
|
+
# @return [Boolean]
|
|
80
|
+
def image_url?
|
|
81
|
+
image_urls.any?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# Returns image URL content items from the message
|
|
86
|
+
# @return [Array<LLM::Object>]
|
|
87
|
+
def image_urls
|
|
88
|
+
content_items.select { LLM::Object === _1 && _1.kind == :image_url }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Returns true when a message contains a local or remote file
|
|
93
|
+
# @return [Boolean]
|
|
94
|
+
def file?
|
|
95
|
+
files.any?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# Returns local and remote file content items from the message
|
|
100
|
+
# @return [Array<LLM::Object>]
|
|
101
|
+
def files
|
|
102
|
+
content_items.select do
|
|
103
|
+
LLM::Object === _1 && [:local_file, :remote_file].include?(_1.kind)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
77
107
|
##
|
|
78
108
|
# @return [Array<LLM::Function>]
|
|
79
109
|
def functions
|
|
@@ -178,5 +208,9 @@ module LLM
|
|
|
178
208
|
tools = extra.tools || response&.__tools__ || []
|
|
179
209
|
tools.map { _1.respond_to?(:function) ? _1.function : _1 }
|
|
180
210
|
end
|
|
211
|
+
|
|
212
|
+
def content_items
|
|
213
|
+
Array(content)
|
|
214
|
+
end
|
|
181
215
|
end
|
|
182
216
|
end
|
data/lib/llm/sequel/plugin.rb
CHANGED
|
@@ -79,7 +79,7 @@ module LLM::Sequel
|
|
|
79
79
|
##
|
|
80
80
|
# @return [Hash]
|
|
81
81
|
def llm_plugin_options
|
|
82
|
-
@llm_plugin_options || DEFAULTS
|
|
82
|
+
@llm_plugin_options || Plugin::DEFAULTS
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
@@ -287,8 +287,8 @@ module LLM::Sequel
|
|
|
287
287
|
# @return [Hash]
|
|
288
288
|
def resolve_options(option)
|
|
289
289
|
case option
|
|
290
|
-
when Proc, Hash then resolve_option(option)
|
|
291
|
-
else EMPTY_HASH.dup
|
|
290
|
+
when Proc, Symbol, Hash then resolve_option(option)
|
|
291
|
+
else Plugin::EMPTY_HASH.dup
|
|
292
292
|
end
|
|
293
293
|
end
|
|
294
294
|
|
data/lib/llm/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.20.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antar Azri
|
|
@@ -231,6 +231,7 @@ files:
|
|
|
231
231
|
- lib/llm/buffer.rb
|
|
232
232
|
- lib/llm/context.rb
|
|
233
233
|
- lib/llm/context/deserializer.rb
|
|
234
|
+
- lib/llm/context/serializer.rb
|
|
234
235
|
- lib/llm/contract.rb
|
|
235
236
|
- lib/llm/contract/completion.rb
|
|
236
237
|
- lib/llm/cost.rb
|