llm.rb 4.19.0 → 4.20.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 +20 -0
- data/README.md +1 -1
- 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/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: d57e4d2af8568cdd6328c0a956fddb40aecbd2943a268b595dbd87ee811553a4
|
|
4
|
+
data.tar.gz: 8cf576171c3bfd7328b8316d42aecbb364a7e4d0a6bbff707cdc65cc9ddfbd01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4d9087909b30c47e5ddb9c9407b53efdbbd2a3732629579dfd53415d60e1457a56b738b9942b578434888b97ee597d78955f21a1af5235847e6daa944810e8d7
|
|
7
|
+
data.tar.gz: a890e08d0129ccfa18188efb503e8ac32e4d5424a79851bf2bfa15424b713cae4431afeb78fe7645437d8b3d0c11cc2d975d7415279a2880ca5cd557de57cf5f
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
Changes since `v4.20.0`.
|
|
6
|
+
|
|
7
|
+
## v4.20.0
|
|
8
|
+
|
|
5
9
|
Changes since `v4.19.0`.
|
|
6
10
|
|
|
11
|
+
This release adds better support for tagged prompt content. `LLM::Context`
|
|
12
|
+
can now serialize and restore `image_url`, `local_file`, and `remote_file`
|
|
13
|
+
content cleanly, and `LLM::Message` now exposes helpers for inspecting
|
|
14
|
+
tagged image and file attachments.
|
|
15
|
+
|
|
16
|
+
### Change
|
|
17
|
+
|
|
18
|
+
* **Round-trip tagged prompt objects through `LLM::Context`** <br>
|
|
19
|
+
Teach `LLM::Context` serialization and restore to preserve
|
|
20
|
+
`image_url`, `local_file`, and `remote_file` content across
|
|
21
|
+
`to_json` / `restore`.
|
|
22
|
+
|
|
23
|
+
* **Add attachment helpers to `LLM::Message`** <br>
|
|
24
|
+
Add `image_url?`, `image_urls`, `file?`, and `files` so callers can
|
|
25
|
+
inspect messages for tagged image and file content more directly.
|
|
26
|
+
|
|
7
27
|
## v4.19.0
|
|
8
28
|
|
|
9
29
|
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.0-green.svg?" alt="Version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
## About
|
|
@@ -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/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.0
|
|
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
|