llm.rb 0.13.0 β 0.14.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/README.md +42 -11
- data/lib/llm/buffer.rb +23 -6
- data/lib/llm/file.rb +1 -1
- data/lib/llm/message.rb +7 -0
- data/lib/llm/mime.rb +6 -2
- data/lib/llm/provider.rb +2 -1
- data/lib/llm/providers/anthropic/files.rb +155 -0
- data/lib/llm/providers/anthropic/format/completion_format.rb +12 -2
- data/lib/llm/providers/anthropic/models.rb +2 -1
- data/lib/llm/providers/anthropic/response/enumerable.rb +11 -0
- data/lib/llm/providers/anthropic/response/file.rb +23 -0
- data/lib/llm/providers/anthropic.rb +11 -1
- data/lib/llm/providers/gemini/files.rb +2 -1
- data/lib/llm/providers/gemini/models.rb +2 -1
- data/lib/llm/providers/gemini/response/files.rb +15 -0
- data/lib/llm/providers/gemini/response/models.rb +15 -0
- data/lib/llm/providers/openai/files.rb +2 -1
- data/lib/llm/providers/openai/models.rb +3 -1
- data/lib/llm/providers/openai/response/enumerable.rb +11 -0
- data/lib/llm/providers/openai/responses.rb +8 -0
- data/lib/llm/providers/openai/vector_stores.rb +5 -3
- data/lib/llm/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b5d6ed41bd22685f456bdd30f90bb462b1d8c89d63b496862c241cd2a9a11ff
|
4
|
+
data.tar.gz: feb10f6589c89741ed978a5bd9eb96c228649fd4348ff93b7fa222e3a4d6fd1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b922ffea8f415e50ab5a33e16bfe40d9dc9f8875fefccb6e1ad1afe0001ab3e3b52e2f5389d152ecf65f68913af57b6bada63eff25121a9b0d45013072f813c4
|
7
|
+
data.tar.gz: f930afdee74629503347f2115c0e9a4a734282cbade8591804988d910ac5180ef26a727d937cd496a76a46f3aa100548e61e103b7edaa6b57e35d8aa4552eb2f
|
data/README.md
CHANGED
@@ -5,12 +5,39 @@ includes OpenAI, Gemini, Anthropic, xAI (grok), DeepSeek, Ollama, and
|
|
5
5
|
LlamaCpp. The toolkit includes full support for chat, streaming, tool calling,
|
6
6
|
audio, images, files, and structured outputs (JSON Schema).
|
7
7
|
|
8
|
+
The library provides a common, uniform interface for all the providers and
|
9
|
+
features it supports, in addition to provider-specific features as well. Keep
|
10
|
+
reading to find out more.
|
11
|
+
|
12
|
+
## Quick start
|
13
|
+
|
14
|
+
#### Demo
|
15
|
+
|
16
|
+
<details>
|
17
|
+
<summary>Play</summary>
|
18
|
+
<img src="share/llm-shell/examples/demo.gif/">
|
19
|
+
</details>
|
20
|
+
|
21
|
+
#### Guides
|
22
|
+
|
23
|
+
* [An introduction to RAG](https://0x1eef.github.io/posts/an-introduction-to-rag-with-llm.rb/) –
|
24
|
+
a blog post that implements the RAG pattern
|
25
|
+
* [How to estimate the age of a person in a photo](https://0x1eef.github.io/posts/age-estimation-with-llm.rb/) –
|
26
|
+
a blog post that implements an age estimation tool
|
27
|
+
* [How to edit an image with Gemini](https://0x1eef.github.io/posts/how-to-edit-images-with-gemini/) –
|
28
|
+
a blog post that implements image editing with Gemini
|
29
|
+
|
30
|
+
#### Ecosystem
|
31
|
+
|
32
|
+
* [llm-shell](https://github.com/llmrb/llm-shell) – is a developer-oriented console for Large Language Model communication
|
33
|
+
* [llm-spell](https://github.com/llmrb/llm-spell) – is a utility that can correct spelling mistakes with a Large Language Model
|
34
|
+
|
8
35
|
## Features
|
9
36
|
|
10
37
|
#### General
|
11
38
|
- β
A single unified interface for multiple providers
|
12
39
|
- π¦ Zero dependencies outside Ruby's standard library
|
13
|
-
- π
|
40
|
+
- π Smart API design that minimizes the number of requests made
|
14
41
|
|
15
42
|
#### Chat, Agents
|
16
43
|
- π§ Stateless and stateful chat via completions and responses API
|
@@ -44,12 +71,12 @@ breaks things down by provider, so you can see exactly whatβs supported where.
|
|
44
71
|
| **Streaming** | β
| β
| β
| β
| β
| β
| β
|
|
45
72
|
| **Tool Calling** | β
| β
| β
| β
| β
| β
| β
|
|
46
73
|
| **JSON Schema / Structured Output** | β
| β | β
| β | β
| β
* | β
* |
|
47
|
-
| **Audio (TTS / Transcribe / Translate)** | β
| β | β
| β | β | β | β |
|
48
|
-
| **Image Generation & Editing** | β
| β | β
| β | β
| β | β |
|
49
|
-
| **File Uploads** | β
| β | β
| β | β | β | β |
|
50
|
-
| **Multimodal Prompts** *(text, documents, audio, images, videos, URLs, etc)* | β
| β
| β
| β
| β
| β
| β
|
|
51
74
|
| **Embeddings** | β
| β
| β
| β
| β | β
| β
|
|
75
|
+
| **Multimodal Prompts** *(text, documents, audio, images, videos, URLs, etc)* | β
| β
| β
| β
| β
| β
| β
|
|
76
|
+
| **Files API** | β
| β
| β
| β | β | β | β |
|
52
77
|
| **Models API** | β
| β
| β
| β
| β
| β
| β
|
|
78
|
+
| **Audio (TTS / Transcribe / Translate)** | β
| β | β
| β | β | β | β |
|
79
|
+
| **Image Generation & Editing** | β
| β | β
| β | β
| β | β |
|
53
80
|
| **Local Model Support** | β | β | β | β | β | β
| β
|
|
54
81
|
| **Vector Stores (RAG)** | β
| β | β | β | β | β | β |
|
55
82
|
| **Responses** | β
| β | β | β | β | β | β |
|
@@ -167,10 +194,10 @@ require "llm"
|
|
167
194
|
##
|
168
195
|
# Objects
|
169
196
|
llm = LLM.openai(key: ENV["KEY"])
|
170
|
-
schema = llm.schema.object(probability: llm.schema.
|
197
|
+
schema = llm.schema.object(probability: llm.schema.number.required)
|
171
198
|
bot = LLM::Bot.new(llm, schema:)
|
172
199
|
bot.chat "Does the earth orbit the sun?", role: :user
|
173
|
-
bot.messages.find(&:assistant?).content! # => {probability: 1}
|
200
|
+
bot.messages.find(&:assistant?).content! # => {probability: 1.0}
|
174
201
|
|
175
202
|
##
|
176
203
|
# Enums
|
@@ -259,7 +286,7 @@ require "llm"
|
|
259
286
|
llm = LLM.openai(key: ENV["KEY"])
|
260
287
|
bot = LLM::Bot.new(llm)
|
261
288
|
file = llm.files.create(file: "/books/goodread.pdf")
|
262
|
-
bot.chat
|
289
|
+
bot.chat ["Tell me about this file", file]
|
263
290
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
264
291
|
```
|
265
292
|
|
@@ -477,7 +504,7 @@ end
|
|
477
504
|
##
|
478
505
|
# Select a model
|
479
506
|
model = llm.models.all.find { |m| m.id == "gpt-3.5-turbo" }
|
480
|
-
bot = LLM::Bot.new(llm, model:)
|
507
|
+
bot = LLM::Bot.new(llm, model: model.id)
|
481
508
|
bot.chat "Hello #{model.id} :)"
|
482
509
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
483
510
|
```
|
@@ -493,8 +520,12 @@ over or doesn't cover at all. The API reference is available at
|
|
493
520
|
|
494
521
|
### Guides
|
495
522
|
|
496
|
-
* [An introduction to RAG
|
497
|
-
a blog post that implements the RAG pattern
|
523
|
+
* [An introduction to RAG](https://0x1eef.github.io/posts/an-introduction-to-rag-with-llm.rb/) –
|
524
|
+
a blog post that implements the RAG pattern
|
525
|
+
* [How to estimate the age of a person in a photo](https://0x1eef.github.io/posts/age-estimation-with-llm.rb/) –
|
526
|
+
a blog post that implements an age estimation tool
|
527
|
+
* [How to edit an image with Gemini](https://0x1eef.github.io/posts/how-to-edit-images-with-gemini/) –
|
528
|
+
a blog post that implements image editing with Gemini
|
498
529
|
* [docs/](docs/) – the docs directory contains additional guides
|
499
530
|
|
500
531
|
|
data/lib/llm/buffer.rb
CHANGED
@@ -87,6 +87,20 @@ module LLM
|
|
87
87
|
@pending.empty? and @completed.empty?
|
88
88
|
end
|
89
89
|
|
90
|
+
##
|
91
|
+
# @example
|
92
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
93
|
+
# bot = LLM::Bot.new(llm, stream: $stdout)
|
94
|
+
# bot.chat "Hello", role: :user
|
95
|
+
# bot.messages.drain
|
96
|
+
# @note
|
97
|
+
# This method is especially useful when using the streaming API.
|
98
|
+
# Drains the buffer and returns all messages as an array
|
99
|
+
# @return [Array<LLM::Message>]
|
100
|
+
def drain
|
101
|
+
to_a
|
102
|
+
end
|
103
|
+
|
90
104
|
private
|
91
105
|
|
92
106
|
def empty!
|
@@ -101,23 +115,26 @@ module LLM
|
|
101
115
|
end
|
102
116
|
|
103
117
|
def complete!(message, params)
|
104
|
-
|
105
|
-
|
118
|
+
oldparams = @pending.map { _1[1] }
|
119
|
+
pendings = @pending.map { _1[0] }
|
120
|
+
messages = [*@completed, *pendings]
|
106
121
|
role = message.role
|
107
122
|
completion = @provider.complete(
|
108
123
|
message.content,
|
109
|
-
params.merge(role:, messages:)
|
124
|
+
[*oldparams, params.merge(role:, messages:)].inject({}, &:merge!)
|
110
125
|
)
|
111
126
|
@completed.concat([*pendings, message, *completion.choices[0]])
|
112
127
|
@pending.clear
|
113
128
|
end
|
114
129
|
|
115
130
|
def respond!(message, params)
|
116
|
-
|
117
|
-
|
131
|
+
oldparams = @pending.map { _1[1] }
|
132
|
+
pendings = @pending.map { _1[0] }
|
133
|
+
messages = [*pendings]
|
118
134
|
role = message.role
|
119
135
|
params = [
|
120
|
-
|
136
|
+
*oldparams,
|
137
|
+
params.merge(input: messages),
|
121
138
|
@response ? {previous_response_id: @response.id} : {}
|
122
139
|
].inject({}, &:merge!)
|
123
140
|
@response = @provider.responses.create(message.content, params.merge(role:))
|
data/lib/llm/file.rb
CHANGED
data/lib/llm/message.rb
CHANGED
@@ -117,6 +117,13 @@ module LLM
|
|
117
117
|
[*content].grep(LLM::Function::Return).any?
|
118
118
|
end
|
119
119
|
|
120
|
+
##
|
121
|
+
# @return [LLM::Response, nil]
|
122
|
+
# Returns the response associated with the message, or nil
|
123
|
+
def response
|
124
|
+
extra[:response]
|
125
|
+
end
|
126
|
+
|
120
127
|
##
|
121
128
|
# Returns a string representation of the message
|
122
129
|
# @return [String]
|
data/lib/llm/mime.rb
CHANGED
@@ -3,12 +3,16 @@
|
|
3
3
|
##
|
4
4
|
# @private
|
5
5
|
class LLM::Mime
|
6
|
+
EXTNAME = /\A\.[a-zA-Z0-9]+\z/
|
7
|
+
private_constant :EXTNAME
|
8
|
+
|
6
9
|
##
|
7
10
|
# Lookup a mime type
|
8
11
|
# @return [String, nil]
|
9
12
|
def self.[](key)
|
10
|
-
key = key.respond_to?(:path) ?
|
11
|
-
|
13
|
+
key = key.respond_to?(:path) ? key.path : key
|
14
|
+
extname = (key =~ EXTNAME) ? key : File.extname(key)
|
15
|
+
types[extname] || "application/octet-stream"
|
12
16
|
end
|
13
17
|
|
14
18
|
##
|
data/lib/llm/provider.rb
CHANGED
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LLM::Anthropic
|
4
|
+
##
|
5
|
+
# The {LLM::Anthropic::Files LLM::Anthropic::Files} class provides a files
|
6
|
+
# object for interacting with [Anthropic's Files API](https://docs.anthropic.com/en/docs/build-with-claude/files).
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# #!/usr/bin/env ruby
|
10
|
+
# require "llm"
|
11
|
+
#
|
12
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
13
|
+
# bot = LLM::Bot.new(llm)
|
14
|
+
# file = llm.files.create file: "/books/goodread.pdf"
|
15
|
+
# bot.chat ["Tell me about this PDF", file]
|
16
|
+
# bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
17
|
+
class Files
|
18
|
+
require_relative "response/file"
|
19
|
+
##
|
20
|
+
# Returns a new Files object
|
21
|
+
# @param provider [LLM::Provider]
|
22
|
+
# @return [LLM::Anthropic::Files]
|
23
|
+
def initialize(provider)
|
24
|
+
@provider = provider
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
# List all files
|
29
|
+
# @example
|
30
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
31
|
+
# res = llm.files.all
|
32
|
+
# res.each do |file|
|
33
|
+
# print "id: ", file.id, "\n"
|
34
|
+
# end
|
35
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files Anthropic docs
|
36
|
+
# @param [Hash] params Other parameters (see Anthropic docs)
|
37
|
+
# @raise (see LLM::Provider#request)
|
38
|
+
# @return [LLM::Response]
|
39
|
+
def all(**params)
|
40
|
+
query = URI.encode_www_form(params)
|
41
|
+
req = Net::HTTP::Get.new("/v1/files?#{query}", headers)
|
42
|
+
res = execute(request: req)
|
43
|
+
LLM::Response.new(res).extend(LLM::Anthropic::Response::Enumerable)
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# Create a file
|
48
|
+
# @example
|
49
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
50
|
+
# res = llm.files.create file: "/documents/haiku.txt"
|
51
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files Anthropic docs
|
52
|
+
# @param [File, LLM::File, String] file The file
|
53
|
+
# @param [Hash] params Other parameters (see Anthropic docs)
|
54
|
+
# @raise (see LLM::Provider#request)
|
55
|
+
# @return [LLM::Response]
|
56
|
+
def create(file:, **params)
|
57
|
+
multi = LLM::Multipart.new(params.merge!(file: LLM.File(file)))
|
58
|
+
req = Net::HTTP::Post.new("/v1/files", headers)
|
59
|
+
req["content-type"] = multi.content_type
|
60
|
+
set_body_stream(req, multi.body)
|
61
|
+
res = execute(request: req)
|
62
|
+
LLM::Response.new(res).extend(LLM::Anthropic::Response::File)
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Get a file
|
67
|
+
# @example
|
68
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
69
|
+
# res = llm.files.get(file: "file-1234567890")
|
70
|
+
# print "id: ", res.id, "\n"
|
71
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files Anthropic docs
|
72
|
+
# @param [#id, #to_s] file The file ID
|
73
|
+
# @param [Hash] params Other parameters - if any (see Anthropic docs)
|
74
|
+
# @raise (see LLM::Provider#request)
|
75
|
+
# @return [LLM::Response]
|
76
|
+
def get(file:, **params)
|
77
|
+
file_id = file.respond_to?(:id) ? file.id : file
|
78
|
+
query = URI.encode_www_form(params)
|
79
|
+
req = Net::HTTP::Get.new("/v1/files/#{file_id}?#{query}", headers)
|
80
|
+
res = execute(request: req)
|
81
|
+
LLM::Response.new(res).extend(LLM::Anthropic::Response::File)
|
82
|
+
end
|
83
|
+
|
84
|
+
##
|
85
|
+
# Retrieve file metadata
|
86
|
+
# @example
|
87
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
88
|
+
# res = llm.files.get_metadata(file: "file-1234567890")
|
89
|
+
# print "id: ", res.id, "\n"
|
90
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files
|
91
|
+
# @param [#id, #to_s] file The file ID
|
92
|
+
# @param [Hash] params Other parameters - if any (see Anthropic docs)
|
93
|
+
# @raise (see LLM::Provider#request)
|
94
|
+
# @return [LLM::Response]
|
95
|
+
def get_metadata(file:, **params)
|
96
|
+
query = URI.encode_www_form(params)
|
97
|
+
file_id = file.respond_to?(:id) ? file.id : file
|
98
|
+
req = Net::HTTP::Get.new("/v1/files/#{file_id}?#{query}", headers)
|
99
|
+
res = execute(request: req)
|
100
|
+
LLM::Response.new(res).extend(LLM::Anthropic::Response::File)
|
101
|
+
end
|
102
|
+
alias_method :retrieve_metadata, :get_metadata
|
103
|
+
|
104
|
+
##
|
105
|
+
# Delete a file
|
106
|
+
# @example
|
107
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
108
|
+
# res = llm.files.delete(file: "file-1234567890")
|
109
|
+
# print res.deleted, "\n"
|
110
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files Anthropic docs
|
111
|
+
# @param [#id, #to_s] file The file ID
|
112
|
+
# @raise (see LLM::Provider#request)
|
113
|
+
# @return [LLM::Response]
|
114
|
+
def delete(file:)
|
115
|
+
file_id = file.respond_to?(:id) ? file.id : file
|
116
|
+
req = Net::HTTP::Delete.new("/v1/files/#{file_id}", headers)
|
117
|
+
res = execute(request: req)
|
118
|
+
LLM::Response.new(res)
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Download the contents of a file
|
123
|
+
# @note
|
124
|
+
# You can only download files that were created by the code
|
125
|
+
# execution tool. Files that you uploaded cannot be downloaded.
|
126
|
+
# @example
|
127
|
+
# llm = LLM.anthropic(key: ENV["KEY"])
|
128
|
+
# res = llm.files.download(file: "file-1234567890")
|
129
|
+
# File.binwrite "program.c", res.file.read
|
130
|
+
# print res.file.read, "\n"
|
131
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files Anthropic docs
|
132
|
+
# @param [#id, #to_s] file The file ID
|
133
|
+
# @param [Hash] params Other parameters (see Anthropic docs)
|
134
|
+
# @raise (see LLM::Provider#request)
|
135
|
+
# @return [LLM::Response]
|
136
|
+
def download(file:, **params)
|
137
|
+
query = URI.encode_www_form(params)
|
138
|
+
file_id = file.respond_to?(:id) ? file.id : file
|
139
|
+
req = Net::HTTP::Get.new("/v1/files/#{file_id}/content?#{query}", headers)
|
140
|
+
io = StringIO.new("".b)
|
141
|
+
res = execute(request: req) { |res| res.read_body { |chunk| io << chunk } }
|
142
|
+
LLM::Response.new(res).tap { _1.define_singleton_method(:file) { io } }
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def key
|
148
|
+
@provider.instance_variable_get(:@key)
|
149
|
+
end
|
150
|
+
|
151
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
152
|
+
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -60,6 +60,12 @@ module LLM::Anthropic::Format
|
|
60
60
|
"is not an image or PDF, and therefore not supported by the " \
|
61
61
|
"Anthropic API"
|
62
62
|
end
|
63
|
+
when LLM::Response
|
64
|
+
if content.file?
|
65
|
+
[{type: content.file_type, source: {type: :file, file_id: content.id}}]
|
66
|
+
else
|
67
|
+
prompt_error!(content)
|
68
|
+
end
|
63
69
|
when String
|
64
70
|
[{type: :text, text: content}]
|
65
71
|
when LLM::Message
|
@@ -67,11 +73,15 @@ module LLM::Anthropic::Format
|
|
67
73
|
when LLM::Function::Return
|
68
74
|
[{type: "tool_result", tool_use_id: content.id, content: [{type: :text, text: JSON.dump(content.value)}]}]
|
69
75
|
else
|
70
|
-
|
71
|
-
"is not supported by the Anthropic API"
|
76
|
+
prompt_error!(content)
|
72
77
|
end
|
73
78
|
end
|
74
79
|
|
80
|
+
def prompt_error!(content)
|
81
|
+
raise LLM::PromptError, "The given object (an instance of #{content.class}) " \
|
82
|
+
"is not supported by the Anthropic API"
|
83
|
+
end
|
84
|
+
|
75
85
|
def message = @message
|
76
86
|
def content = message.content
|
77
87
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class LLM::Anthropic
|
4
|
+
require_relative "response/enumerable"
|
4
5
|
##
|
5
6
|
# The {LLM::Anthropic::Models LLM::Anthropic::Models} class provides a model
|
6
7
|
# object for interacting with [Anthropic's models API](https://platform.anthropic.com/docs/api-reference/models/list).
|
@@ -41,7 +42,7 @@ class LLM::Anthropic
|
|
41
42
|
query = URI.encode_www_form(params)
|
42
43
|
req = Net::HTTP::Get.new("/v1/models?#{query}", headers)
|
43
44
|
res = execute(request: req)
|
44
|
-
LLM::Response.new(res)
|
45
|
+
LLM::Response.new(res).extend(LLM::Anthropic::Response::Enumerable)
|
45
46
|
end
|
46
47
|
|
47
48
|
private
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM::Anthropic::Response
|
4
|
+
module File
|
5
|
+
##
|
6
|
+
# Always return true
|
7
|
+
# @return [Boolean]
|
8
|
+
def file? = true
|
9
|
+
|
10
|
+
##
|
11
|
+
# Returns the file type referenced by a prompt
|
12
|
+
# @return [Symbol]
|
13
|
+
def file_type
|
14
|
+
if mime_type.start_with?("image/")
|
15
|
+
:image
|
16
|
+
elsif mime_type == "text/plain" || mime_type == "application/pdf"
|
17
|
+
:document
|
18
|
+
else
|
19
|
+
:container_upload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -18,6 +18,7 @@ module LLM
|
|
18
18
|
require_relative "anthropic/format"
|
19
19
|
require_relative "anthropic/error_handler"
|
20
20
|
require_relative "anthropic/stream_parser"
|
21
|
+
require_relative "anthropic/files"
|
21
22
|
require_relative "anthropic/models"
|
22
23
|
include Format
|
23
24
|
|
@@ -60,6 +61,14 @@ module LLM
|
|
60
61
|
LLM::Anthropic::Models.new(self)
|
61
62
|
end
|
62
63
|
|
64
|
+
##
|
65
|
+
# Provides an interface to Anthropic's files API
|
66
|
+
# @see https://docs.anthropic.com/en/docs/build-with-claude/files Anthropic docs
|
67
|
+
# @return [LLM::Anthropic::Files]
|
68
|
+
def files
|
69
|
+
LLM::Anthropic::Files.new(self)
|
70
|
+
end
|
71
|
+
|
63
72
|
##
|
64
73
|
# @return (see LLM::Provider#assistant_role)
|
65
74
|
def assistant_role
|
@@ -80,7 +89,8 @@ module LLM
|
|
80
89
|
(@headers || {}).merge(
|
81
90
|
"Content-Type" => "application/json",
|
82
91
|
"x-api-key" => @key,
|
83
|
-
"anthropic-version" => "2023-06-01"
|
92
|
+
"anthropic-version" => "2023-06-01",
|
93
|
+
"anthropic-beta" => "files-api-2025-04-14"
|
84
94
|
)
|
85
95
|
end
|
86
96
|
|
@@ -24,6 +24,7 @@ class LLM::Gemini
|
|
24
24
|
# bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
25
25
|
class Files
|
26
26
|
require_relative "response/file"
|
27
|
+
require_relative "response/files"
|
27
28
|
|
28
29
|
##
|
29
30
|
# Returns a new Files object
|
@@ -49,7 +50,7 @@ class LLM::Gemini
|
|
49
50
|
query = URI.encode_www_form(params.merge!(key: key))
|
50
51
|
req = Net::HTTP::Get.new("/v1beta/files?#{query}", headers)
|
51
52
|
res = execute(request: req)
|
52
|
-
LLM::Response.new(res)
|
53
|
+
LLM::Response.new(res).extend(LLM::Gemini::Response::Files)
|
53
54
|
end
|
54
55
|
|
55
56
|
##
|
@@ -17,6 +17,7 @@ class LLM::Gemini
|
|
17
17
|
# print "id: ", model.id, "\n"
|
18
18
|
# end
|
19
19
|
class Models
|
20
|
+
require_relative "response/models"
|
20
21
|
include LLM::Utils
|
21
22
|
|
22
23
|
##
|
@@ -43,7 +44,7 @@ class LLM::Gemini
|
|
43
44
|
query = URI.encode_www_form(params.merge!(key: key))
|
44
45
|
req = Net::HTTP::Get.new("/v1beta/models?#{query}", headers)
|
45
46
|
res = execute(request: req)
|
46
|
-
LLM::Response.new(res)
|
47
|
+
LLM::Response.new(res).extend(LLM::Gemini::Response::Models)
|
47
48
|
end
|
48
49
|
|
49
50
|
private
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module LLM::Gemini::Response
|
4
|
+
module Models
|
5
|
+
include ::Enumerable
|
6
|
+
def each(&)
|
7
|
+
return enum_for(:each) unless block_given?
|
8
|
+
models.each { yield(_1) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def models
|
12
|
+
body.models || []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -18,6 +18,7 @@ class LLM::OpenAI
|
|
18
18
|
# bot.chat ["Tell me about this PDF", file]
|
19
19
|
# bot.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
20
20
|
class Files
|
21
|
+
require_relative "response/enumerable"
|
21
22
|
require_relative "response/file"
|
22
23
|
|
23
24
|
##
|
@@ -44,7 +45,7 @@ class LLM::OpenAI
|
|
44
45
|
query = URI.encode_www_form(params)
|
45
46
|
req = Net::HTTP::Get.new("/v1/files?#{query}", headers)
|
46
47
|
res = execute(request: req)
|
47
|
-
LLM::Response.new(res)
|
48
|
+
LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
|
48
49
|
end
|
49
50
|
|
50
51
|
##
|
@@ -17,6 +17,8 @@ class LLM::OpenAI
|
|
17
17
|
# print "id: ", model.id, "\n"
|
18
18
|
# end
|
19
19
|
class Models
|
20
|
+
require_relative "response/enumerable"
|
21
|
+
|
20
22
|
##
|
21
23
|
# Returns a new Models object
|
22
24
|
# @param provider [LLM::Provider]
|
@@ -41,7 +43,7 @@ class LLM::OpenAI
|
|
41
43
|
query = URI.encode_www_form(params)
|
42
44
|
req = Net::HTTP::Get.new("/v1/models?#{query}", headers)
|
43
45
|
res = execute(request: req)
|
44
|
-
LLM::Response.new(res)
|
46
|
+
LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
|
45
47
|
end
|
46
48
|
|
47
49
|
private
|
@@ -80,5 +80,13 @@ class LLM::OpenAI
|
|
80
80
|
:format_tools].each do |m|
|
81
81
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
82
82
|
end
|
83
|
+
|
84
|
+
def format_schema(params)
|
85
|
+
return {} unless params && params[:schema]
|
86
|
+
schema = params.delete(:schema)
|
87
|
+
schema = schema.to_h.merge(additionalProperties: false)
|
88
|
+
name = "JSONSchema"
|
89
|
+
{text: {format: {type: "json_schema", name:, schema:}}}
|
90
|
+
end
|
83
91
|
end
|
84
92
|
end
|
@@ -5,6 +5,8 @@ class LLM::OpenAI
|
|
5
5
|
# The {LLM::OpenAI::VectorStores LLM::OpenAI::VectorStores} class provides
|
6
6
|
# an interface for [OpenAI's vector stores API](https://platform.openai.com/docs/api-reference/vector_stores/create)
|
7
7
|
class VectorStores
|
8
|
+
require_relative "response/enumerable"
|
9
|
+
|
8
10
|
##
|
9
11
|
# @param [LLM::Provider] provider
|
10
12
|
# An OpenAI provider
|
@@ -20,7 +22,7 @@ class LLM::OpenAI
|
|
20
22
|
query = URI.encode_www_form(params)
|
21
23
|
req = Net::HTTP::Get.new("/v1/vector_stores?#{query}", headers)
|
22
24
|
res = execute(request: req)
|
23
|
-
LLM::Response.new(res)
|
25
|
+
LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
|
24
26
|
end
|
25
27
|
|
26
28
|
##
|
@@ -93,7 +95,7 @@ class LLM::OpenAI
|
|
93
95
|
req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/search", headers)
|
94
96
|
req.body = JSON.dump(params.merge({query:}).compact)
|
95
97
|
res = execute(request: req)
|
96
|
-
LLM::Response.new(res)
|
98
|
+
LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
|
97
99
|
end
|
98
100
|
|
99
101
|
##
|
@@ -108,7 +110,7 @@ class LLM::OpenAI
|
|
108
110
|
query = URI.encode_www_form(params)
|
109
111
|
req = Net::HTTP::Get.new("/v1/vector_stores/#{vector_id}/files?#{query}", headers)
|
110
112
|
res = execute(request: req)
|
111
|
-
LLM::Response.new(res)
|
113
|
+
LLM::Response.new(res).extend(LLM::OpenAI::Response::Enumerable)
|
112
114
|
end
|
113
115
|
|
114
116
|
##
|
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: 0.
|
4
|
+
version: 0.14.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Antar Azri
|
@@ -186,10 +186,13 @@ files:
|
|
186
186
|
- lib/llm/provider.rb
|
187
187
|
- lib/llm/providers/anthropic.rb
|
188
188
|
- lib/llm/providers/anthropic/error_handler.rb
|
189
|
+
- lib/llm/providers/anthropic/files.rb
|
189
190
|
- lib/llm/providers/anthropic/format.rb
|
190
191
|
- lib/llm/providers/anthropic/format/completion_format.rb
|
191
192
|
- lib/llm/providers/anthropic/models.rb
|
192
193
|
- lib/llm/providers/anthropic/response/completion.rb
|
194
|
+
- lib/llm/providers/anthropic/response/enumerable.rb
|
195
|
+
- lib/llm/providers/anthropic/response/file.rb
|
193
196
|
- lib/llm/providers/anthropic/stream_parser.rb
|
194
197
|
- lib/llm/providers/deepseek.rb
|
195
198
|
- lib/llm/providers/deepseek/format.rb
|
@@ -205,7 +208,9 @@ files:
|
|
205
208
|
- lib/llm/providers/gemini/response/completion.rb
|
206
209
|
- lib/llm/providers/gemini/response/embedding.rb
|
207
210
|
- lib/llm/providers/gemini/response/file.rb
|
211
|
+
- lib/llm/providers/gemini/response/files.rb
|
208
212
|
- lib/llm/providers/gemini/response/image.rb
|
213
|
+
- lib/llm/providers/gemini/response/models.rb
|
209
214
|
- lib/llm/providers/gemini/stream_parser.rb
|
210
215
|
- lib/llm/providers/llamacpp.rb
|
211
216
|
- lib/llm/providers/ollama.rb
|
@@ -230,6 +235,7 @@ files:
|
|
230
235
|
- lib/llm/providers/openai/response/audio.rb
|
231
236
|
- lib/llm/providers/openai/response/completion.rb
|
232
237
|
- lib/llm/providers/openai/response/embedding.rb
|
238
|
+
- lib/llm/providers/openai/response/enumerable.rb
|
233
239
|
- lib/llm/providers/openai/response/file.rb
|
234
240
|
- lib/llm/providers/openai/response/image.rb
|
235
241
|
- lib/llm/providers/openai/response/moderations.rb
|