llm.rb 0.1.0 → 0.2.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 +85 -24
- data/lib/llm/conversation.rb +62 -10
- data/lib/llm/core_ext/ostruct.rb +0 -0
- data/lib/llm/error.rb +0 -0
- data/lib/llm/file.rb +0 -0
- data/lib/llm/http_client.rb +0 -0
- data/lib/llm/message.rb +1 -1
- data/lib/llm/message_queue.rb +18 -11
- data/lib/llm/model.rb +7 -0
- data/lib/llm/provider.rb +144 -98
- data/lib/llm/providers/anthropic/error_handler.rb +1 -1
- data/lib/llm/providers/anthropic/format.rb +7 -1
- data/lib/llm/providers/anthropic/response_parser.rb +0 -0
- data/lib/llm/providers/anthropic.rb +31 -15
- data/lib/llm/providers/gemini/error_handler.rb +0 -0
- data/lib/llm/providers/gemini/format.rb +7 -1
- data/lib/llm/providers/gemini/response_parser.rb +0 -0
- data/lib/llm/providers/gemini.rb +25 -14
- data/lib/llm/providers/ollama/error_handler.rb +0 -0
- data/lib/llm/providers/ollama/format.rb +7 -1
- data/lib/llm/providers/ollama/response_parser.rb +13 -0
- data/lib/llm/providers/ollama.rb +32 -8
- data/lib/llm/providers/openai/error_handler.rb +0 -0
- data/lib/llm/providers/openai/format.rb +7 -1
- data/lib/llm/providers/openai/response_parser.rb +5 -3
- data/lib/llm/providers/openai.rb +22 -12
- data/lib/llm/providers/voyageai/error_handler.rb +32 -0
- data/lib/llm/providers/voyageai/response_parser.rb +13 -0
- data/lib/llm/providers/voyageai.rb +44 -0
- data/lib/llm/response/completion.rb +0 -0
- data/lib/llm/response/embedding.rb +0 -0
- data/lib/llm/response.rb +0 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +19 -9
- data/llm.gemspec +6 -1
- data/share/llm/models/anthropic.yml +35 -0
- data/share/llm/models/gemini.yml +35 -0
- data/share/llm/models/ollama.yml +155 -0
- data/share/llm/models/openai.yml +46 -0
- data/spec/anthropic/completion_spec.rb +11 -27
- data/spec/anthropic/embedding_spec.rb +25 -0
- data/spec/gemini/completion_spec.rb +34 -29
- data/spec/gemini/embedding_spec.rb +4 -12
- data/spec/llm/conversation_spec.rb +93 -1
- data/spec/ollama/completion_spec.rb +7 -16
- data/spec/ollama/embedding_spec.rb +14 -5
- data/spec/openai/completion_spec.rb +40 -43
- data/spec/openai/embedding_spec.rb +4 -12
- data/spec/readme_spec.rb +9 -12
- data/spec/setup.rb +7 -16
- metadata +81 -4
- data/lib/llm/lazy_conversation.rb +0 -39
- data/spec/llm/lazy_conversation_spec.rb +0 -110
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 732a483717a5ec8e443077fb71294b1e301c3a8867b225c1fc2a58bd02fe3130
|
4
|
+
data.tar.gz: a1c2591a07c413cebfdffa99d133855bb177cc4a6607860333dbc9991da8d33e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f5983f97b3c1e25f4147ec81f6d91df5073ea03dc4031690979f68b2053bf73bae07f7c57c3f7c9813dfd5b43eb1bd7364d5f5929234013d6b19bb49f9271ec
|
7
|
+
data.tar.gz: 286f560ce2d9e048e481796d27fd6c9a658b92cec0267f9527fad49bf5349ece0d05b943c086cce3c12bbd82f53dbf086ed91c516d39b815bd6106edca21914a
|
data/README.md
CHANGED
@@ -31,8 +31,8 @@ llm = LLM.ollama(nil)
|
|
31
31
|
|
32
32
|
The
|
33
33
|
[LLM::Provider#chat](https://0x1eef.github.io/x/llm/LLM/Provider.html#chat-instance_method)
|
34
|
-
method returns a
|
35
|
-
[LLM::
|
34
|
+
method returns a lazy-variant of a
|
35
|
+
[LLM::Conversation](https://0x1eef.github.io/x/llm/LLM/Conversation.html)
|
36
36
|
object, and it allows for a "lazy" conversation where messages are batched and
|
37
37
|
sent to the provider only when necessary. The non-lazy counterpart is available via the
|
38
38
|
[LLM::Provider#chat!](https://0x1eef.github.io/x/llm/LLM/Provider.html#chat!-instance_method)
|
@@ -51,26 +51,26 @@ belonging to a lazy conversation:
|
|
51
51
|
require "llm"
|
52
52
|
|
53
53
|
llm = LLM.openai(ENV["KEY"])
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
convo = llm.chat File.read("./share/llm/prompts/system.txt"), :system
|
55
|
+
convo.chat "Tell me the answer to 5 + 15"
|
56
|
+
convo.chat "Tell me the answer to (5 + 15) * 2"
|
57
|
+
convo.chat "Tell me the answer to ((5 + 15) * 2) / 10"
|
58
|
+
convo.messages.each { print "[#{_1.role}] ", _1.content, "\n" }
|
59
59
|
|
60
60
|
##
|
61
|
-
# [system] You are
|
62
|
-
#
|
61
|
+
# [system] You are my math assistant.
|
62
|
+
# I will provide you with (simple) equations.
|
63
|
+
# You will provide answers in the format "The answer to <equation> is <answer>".
|
63
64
|
# I will provide you a set of messages. Reply to all of them.
|
64
65
|
# A message is considered unanswered if there is no corresponding assistant response.
|
65
66
|
#
|
66
|
-
# [user]
|
67
|
-
# [user]
|
68
|
-
# [user]
|
67
|
+
# [user] Tell me the answer to 5 + 15
|
68
|
+
# [user] Tell me the answer to (5 + 15) * 2
|
69
|
+
# [user] Tell me the answer to ((5 + 15) * 2) / 10
|
69
70
|
#
|
70
|
-
# [assistant] The
|
71
|
-
#
|
72
|
-
#
|
73
|
-
# Because it ran out of juice! 🍊😂
|
71
|
+
# [assistant] The answer to 5 + 15 is 20.
|
72
|
+
# The answer to (5 + 15) * 2 is 40.
|
73
|
+
# The answer to ((5 + 15) * 2) / 10 is 4.
|
74
74
|
```
|
75
75
|
|
76
76
|
#### Prompts
|
@@ -99,28 +99,87 @@ provider accepts:
|
|
99
99
|
|
100
100
|
The
|
101
101
|
[`LLM::Provider#embed`](https://0x1eef.github.io/x/llm/LLM/Provider.html#embed-instance_method)
|
102
|
-
method generates a vector representation of
|
103
|
-
Embeddings capture the semantic meaning of text
|
104
|
-
|
105
|
-
|
106
|
-
of text
|
102
|
+
method generates a vector representation of one or more chunks
|
103
|
+
of text. Embeddings capture the semantic meaning of text –
|
104
|
+
a common use-case for them is to store chunks of text in a
|
105
|
+
vector database, and then to query the database for *semantically
|
106
|
+
similar* text. These chunks of similar text can then support the
|
107
|
+
generation of a prompt that is used to query a large language model,
|
108
|
+
which will go on to generate a response.
|
109
|
+
|
110
|
+
For example, a user query might find similar text that adds important
|
111
|
+
context to the prompt that informs the large language model in how to respond.
|
112
|
+
The chunks of text may also carry metadata that can be used to further filter
|
113
|
+
and contextualize the search results. This technique is popularly known as
|
114
|
+
retrieval-augmented generation (RAG). Embeddings can also be used for
|
115
|
+
other purposes as well – RAG is just one of the most popular use-cases.
|
116
|
+
|
117
|
+
Let's take a look at an example that generates a couple of vectors
|
118
|
+
for two chunks of text:
|
107
119
|
|
108
120
|
```ruby
|
109
121
|
#!/usr/bin/env ruby
|
110
122
|
require "llm"
|
111
123
|
|
112
124
|
llm = LLM.openai(ENV["KEY"])
|
113
|
-
res = llm.embed("
|
125
|
+
res = llm.embed(["programming is fun", "ruby is a programming language"])
|
114
126
|
print res.class, "\n"
|
115
127
|
print res.embeddings.size, "\n"
|
116
128
|
print res.embeddings[0].size, "\n"
|
117
129
|
|
118
130
|
##
|
119
131
|
# LLM::Response::Embedding
|
120
|
-
#
|
132
|
+
# 2
|
121
133
|
# 1536
|
122
134
|
```
|
123
135
|
|
136
|
+
### LLM
|
137
|
+
|
138
|
+
#### Timeouts
|
139
|
+
|
140
|
+
When running the ollama provider locally it might take a while for
|
141
|
+
the language model to reply – depending on hardware and the
|
142
|
+
size of the model. The following example demonstrates how to wait
|
143
|
+
a longer period of time for a response through the use of the
|
144
|
+
`timeout` configuration option with the `qwq` model. The following
|
145
|
+
example waits up to 15 minutes for a response:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
#!/usr/bin/env ruby
|
149
|
+
require "llm"
|
150
|
+
|
151
|
+
llm = LLM.ollama(nil, timeout: 60*15)
|
152
|
+
llm.chat "What is the meaning of life ?", model: "qwq"
|
153
|
+
llm.last_message.tap { print "[assistant] ", _1.content, "\n" }
|
154
|
+
```
|
155
|
+
|
156
|
+
#### Models
|
157
|
+
|
158
|
+
Generally each Large Language Model provides multiple models to choose
|
159
|
+
from, and each model has its own set of capabilities and limitations.
|
160
|
+
The following example demonstrates how to query the list of models
|
161
|
+
through the
|
162
|
+
[LLM::Provider#models](http://0x1eef.github.io/x/llm/LLM/Provider.html#models-instance_method)
|
163
|
+
method – the example happens to use the ollama provider but
|
164
|
+
this can be done for any provider:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
#!/usr/bin/env ruby
|
168
|
+
require "llm"
|
169
|
+
|
170
|
+
##
|
171
|
+
# List models
|
172
|
+
llm = LLM.ollama(nil)
|
173
|
+
llm.models.each { print "#{_2.name}: #{_2.description}", "\n" }
|
174
|
+
|
175
|
+
##
|
176
|
+
# Select a model
|
177
|
+
llm.chat "Hello, world!", model: llm.models["qwq"]
|
178
|
+
|
179
|
+
##
|
180
|
+
# This also works
|
181
|
+
llm.chat "Hello, world!", model: "qwq"
|
182
|
+
```
|
124
183
|
## Providers
|
125
184
|
|
126
185
|
- [x] [Anthropic](https://www.anthropic.com/)
|
@@ -139,7 +198,9 @@ A complete API reference is available at [0x1eef.github.io/x/llm](https://0x1eef
|
|
139
198
|
|
140
199
|
## Install
|
141
200
|
|
142
|
-
|
201
|
+
llm.rb can be installed via rubygems.org:
|
202
|
+
|
203
|
+
gem install llm.rb
|
143
204
|
|
144
205
|
## License
|
145
206
|
|
data/lib/llm/conversation.rb
CHANGED
@@ -3,14 +3,14 @@
|
|
3
3
|
module LLM
|
4
4
|
##
|
5
5
|
# {LLM::Conversation LLM::Conversation} provides a conversation
|
6
|
-
# object that maintains a thread of messages that
|
7
|
-
#
|
8
|
-
#
|
6
|
+
# object that maintains a thread of messages that acts as context
|
7
|
+
# throughout the conversation.
|
9
8
|
# @example
|
10
|
-
# llm = LLM.openai(
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
9
|
+
# llm = LLM.openai(ENV["KEY"])
|
10
|
+
# convo = llm.chat("You are my climate expert", :system)
|
11
|
+
# convo.chat("What's the climate like in Rio de Janerio?", :user)
|
12
|
+
# convo.chat("What's the climate like in Algiers?", :user)
|
13
|
+
# convo.chat("What's the climate like in Tokyo?", :user)
|
14
14
|
# p bot.messages.map { [_1.role, _1.content] }
|
15
15
|
class Conversation
|
16
16
|
##
|
@@ -20,8 +20,12 @@ module LLM
|
|
20
20
|
##
|
21
21
|
# @param [LLM::Provider] provider
|
22
22
|
# A provider
|
23
|
-
|
23
|
+
# @param [Hash] params
|
24
|
+
# The parameters to maintain throughout the conversation
|
25
|
+
def initialize(provider, params = {})
|
24
26
|
@provider = provider
|
27
|
+
@params = params
|
28
|
+
@lazy = false
|
25
29
|
@messages = []
|
26
30
|
end
|
27
31
|
|
@@ -30,9 +34,57 @@ module LLM
|
|
30
34
|
# @return [LLM::Conversation]
|
31
35
|
def chat(prompt, role = :user, **params)
|
32
36
|
tap do
|
33
|
-
|
34
|
-
|
37
|
+
if lazy?
|
38
|
+
@messages << [LLM::Message.new(role, prompt), @params.merge(params)]
|
39
|
+
else
|
40
|
+
completion = complete(prompt, role, params)
|
41
|
+
@messages.concat [Message.new(role, prompt), completion.choices[0]]
|
42
|
+
end
|
35
43
|
end
|
36
44
|
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# @note
|
48
|
+
# The `read_response` and `recent_message` methods are aliases of
|
49
|
+
# the `last_message` method, and you can choose the name that best
|
50
|
+
# fits your context or code style.
|
51
|
+
# @param [#to_s] role
|
52
|
+
# The role of the last message.
|
53
|
+
# Defaults to the LLM's assistant role (eg "assistant" or "model")
|
54
|
+
# @return [LLM::Message]
|
55
|
+
# The last message for the given role
|
56
|
+
def last_message(role: @provider.assistant_role)
|
57
|
+
messages.reverse_each.find { _1.role == role.to_s }
|
58
|
+
end
|
59
|
+
alias_method :recent_message, :last_message
|
60
|
+
alias_method :read_response, :last_message
|
61
|
+
|
62
|
+
##
|
63
|
+
# Enables lazy mode for the conversation.
|
64
|
+
# @return [LLM::Conversation]
|
65
|
+
def lazy
|
66
|
+
tap do
|
67
|
+
next if lazy?
|
68
|
+
@lazy = true
|
69
|
+
@messages = LLM::MessageQueue.new(@provider)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
##
|
74
|
+
# @return [Boolean]
|
75
|
+
# Returns true if the conversation is lazy
|
76
|
+
def lazy?
|
77
|
+
@lazy
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def complete(prompt, role, params)
|
83
|
+
@provider.complete(
|
84
|
+
prompt,
|
85
|
+
role,
|
86
|
+
**@params.merge(params.merge(messages:))
|
87
|
+
)
|
88
|
+
end
|
37
89
|
end
|
38
90
|
end
|
data/lib/llm/core_ext/ostruct.rb
CHANGED
File without changes
|
data/lib/llm/error.rb
CHANGED
File without changes
|
data/lib/llm/file.rb
CHANGED
File without changes
|
data/lib/llm/http_client.rb
CHANGED
File without changes
|
data/lib/llm/message.rb
CHANGED
data/lib/llm/message_queue.rb
CHANGED
@@ -13,7 +13,8 @@ module LLM
|
|
13
13
|
# @return [LLM::MessageQueue]
|
14
14
|
def initialize(provider)
|
15
15
|
@provider = provider
|
16
|
-
@
|
16
|
+
@pending = []
|
17
|
+
@completed = []
|
17
18
|
end
|
18
19
|
|
19
20
|
##
|
@@ -22,26 +23,32 @@ module LLM
|
|
22
23
|
# @raise (see LLM::Provider#complete)
|
23
24
|
# @return [void]
|
24
25
|
def each
|
25
|
-
|
26
|
-
@
|
26
|
+
complete! unless @pending.empty?
|
27
|
+
@completed.each { yield(_1) }
|
27
28
|
end
|
28
29
|
|
29
30
|
##
|
30
|
-
# @param
|
31
|
-
# A message
|
31
|
+
# @param [[LLM::Message, Hash]] item
|
32
|
+
# A message and its parameters
|
32
33
|
# @return [void]
|
33
|
-
def <<(
|
34
|
-
@
|
34
|
+
def <<(item)
|
35
|
+
@pending << item
|
36
|
+
self
|
35
37
|
end
|
36
38
|
alias_method :push, :<<
|
37
39
|
|
38
40
|
private
|
39
41
|
|
40
42
|
def complete!
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
message, params = @pending[-1]
|
44
|
+
messages = @pending[0..-2].map { _1[0] }
|
45
|
+
completion = @provider.complete(
|
46
|
+
message.content,
|
47
|
+
message.role,
|
48
|
+
**params.merge(messages:)
|
49
|
+
)
|
50
|
+
@completed.concat([*messages, message, completion.choices[0]])
|
51
|
+
@pending.clear
|
45
52
|
end
|
46
53
|
end
|
47
54
|
end
|
data/lib/llm/model.rb
ADDED
data/lib/llm/provider.rb
CHANGED
@@ -1,114 +1,160 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
##
|
4
|
+
# The Provider class represents an abstract class for
|
5
|
+
# LLM (Language Model) providers.
|
6
|
+
#
|
7
|
+
# @note
|
8
|
+
# This class is not meant to be instantiated directly.
|
9
|
+
# Instead, use one of the subclasses that implement
|
10
|
+
# the methods defined here.
|
11
|
+
#
|
12
|
+
# @abstract
|
13
|
+
# @see LLM::Provider::OpenAI
|
14
|
+
# @see LLM::Provider::Anthropic
|
15
|
+
# @see LLM::Provider::Gemini
|
16
|
+
# @see LLM::Provider::Ollama
|
17
|
+
class LLM::Provider
|
18
|
+
require_relative "http_client"
|
19
|
+
include LLM::HTTPClient
|
20
|
+
|
5
21
|
##
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@http = Net::HTTP.new(host, port).tap do |http|
|
20
|
-
http.use_ssl = ssl
|
21
|
-
end
|
22
|
+
# @param [String] secret
|
23
|
+
# The secret key for authentication
|
24
|
+
# @param [String] host
|
25
|
+
# The host address of the LLM provider
|
26
|
+
# @param [Integer] port
|
27
|
+
# The port number
|
28
|
+
# @param [Integer] timeout
|
29
|
+
# The number of seconds to wait for a response
|
30
|
+
def initialize(secret, host:, port: 443, timeout: 60, ssl: true)
|
31
|
+
@secret = secret
|
32
|
+
@http = Net::HTTP.new(host, port).tap do |http|
|
33
|
+
http.use_ssl = ssl
|
34
|
+
http.read_timeout = timeout
|
22
35
|
end
|
36
|
+
end
|
23
37
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
38
|
+
##
|
39
|
+
# Returns an inspection of the provider object
|
40
|
+
# @return [String]
|
41
|
+
# @note The secret key is redacted in inspect for security reasons
|
42
|
+
def inspect
|
43
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} @secret=[REDACTED] @http=#{@http.inspect}>"
|
44
|
+
end
|
31
45
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
46
|
+
##
|
47
|
+
# @param [String, Array<String>] input
|
48
|
+
# The input to embed
|
49
|
+
# @raise [NotImplementedError]
|
50
|
+
# When the method is not implemented by a subclass
|
51
|
+
# @return [LLM::Response::Embedding]
|
52
|
+
def embed(input, **params)
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
41
55
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
##
|
57
|
+
# Completes a given prompt using the LLM
|
58
|
+
# @example
|
59
|
+
# llm = LLM.openai(ENV["KEY"])
|
60
|
+
# context = [
|
61
|
+
# {role: "system", content: "Answer all of my questions"},
|
62
|
+
# {role: "system", content: "Your name is Pablo, you are 25 years old and you are my amigo"},
|
63
|
+
# ]
|
64
|
+
# res = llm.complete "What is your name and what age are you?", :user, messages: context
|
65
|
+
# print "[#{res.choices[0].role}]", res.choices[0].content, "\n"
|
66
|
+
# @param [String] prompt
|
67
|
+
# The input prompt to be completed
|
68
|
+
# @param [Symbol] role
|
69
|
+
# The role of the prompt (e.g. :user, :system)
|
70
|
+
# @param [Array<Hash, LLM::Message>] messages
|
71
|
+
# The messages to include in the completion
|
72
|
+
# @raise [NotImplementedError]
|
73
|
+
# When the method is not implemented by a subclass
|
74
|
+
# @return [LLM::Response::Completion]
|
75
|
+
def complete(prompt, role = :user, **params)
|
76
|
+
raise NotImplementedError
|
77
|
+
end
|
54
78
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
79
|
+
##
|
80
|
+
# Starts a new lazy conversation
|
81
|
+
# @note
|
82
|
+
# This method creates a lazy variant of a
|
83
|
+
# {LLM::Conversation LLM::Conversation} object.
|
84
|
+
# @param prompt (see LLM::Provider#complete)
|
85
|
+
# @param role (see LLM::Provider#complete)
|
86
|
+
# @raise (see LLM::Provider#complete)
|
87
|
+
# @return [LLM::LazyConversation]
|
88
|
+
def chat(prompt, role = :user, **params)
|
89
|
+
LLM::Conversation.new(self, params).lazy.chat(prompt, role)
|
90
|
+
end
|
64
91
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
92
|
+
##
|
93
|
+
# Starts a new conversation
|
94
|
+
# @note
|
95
|
+
# This method creates a non-lazy variant of a
|
96
|
+
# {LLM::Conversation LLM::Conversation} object.
|
97
|
+
# @param prompt (see LLM::Provider#complete)
|
98
|
+
# @param role (see LLM::Provider#complete)
|
99
|
+
# @raise (see LLM::Provider#complete)
|
100
|
+
# @return [LLM::Conversation]
|
101
|
+
def chat!(prompt, role = :user, **params)
|
102
|
+
LLM::Conversation.new(self, params).chat(prompt, role)
|
103
|
+
end
|
74
104
|
|
75
|
-
|
105
|
+
##
|
106
|
+
# @return [String]
|
107
|
+
# Returns the role of the assistant in the conversation.
|
108
|
+
# Usually "assistant" or "model"
|
109
|
+
def assistant_role
|
110
|
+
raise NotImplementedError
|
111
|
+
end
|
76
112
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def auth(req)
|
84
|
-
raise NotImplementedError
|
85
|
-
end
|
113
|
+
##
|
114
|
+
# @return [Hash<String, LLM::Model>]
|
115
|
+
# Returns a hash of available models
|
116
|
+
def models
|
117
|
+
raise NotImplementedError
|
118
|
+
end
|
86
119
|
|
87
|
-
|
88
|
-
# @return [Module]
|
89
|
-
# Returns the module responsible for parsing a successful LLM response
|
90
|
-
# @raise [NotImplementedError]
|
91
|
-
# (see LLM::Provider#complete)
|
92
|
-
def response_parser
|
93
|
-
raise NotImplementedError
|
94
|
-
end
|
120
|
+
private
|
95
121
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
122
|
+
##
|
123
|
+
# The headers to include with a request
|
124
|
+
# @raise [NotImplementedError]
|
125
|
+
# (see LLM::Provider#complete)
|
126
|
+
def headers
|
127
|
+
raise NotImplementedError
|
128
|
+
end
|
104
129
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
130
|
+
##
|
131
|
+
# @return [Module]
|
132
|
+
# Returns the module responsible for parsing a successful LLM response
|
133
|
+
# @raise [NotImplementedError]
|
134
|
+
# (see LLM::Provider#complete)
|
135
|
+
def response_parser
|
136
|
+
raise NotImplementedError
|
137
|
+
end
|
138
|
+
|
139
|
+
##
|
140
|
+
# @return [Class]
|
141
|
+
# Returns the class responsible for handling an unsuccessful LLM response
|
142
|
+
# @raise [NotImplementedError]
|
143
|
+
# (see LLM::Provider#complete)
|
144
|
+
def error_handler
|
145
|
+
raise NotImplementedError
|
146
|
+
end
|
147
|
+
|
148
|
+
##
|
149
|
+
# @param [String] provider
|
150
|
+
# The name of the provider
|
151
|
+
# @return [Hash<String, Hash>]
|
152
|
+
def load_models!(provider)
|
153
|
+
require "yaml" unless defined?(YAML)
|
154
|
+
rootdir = File.realpath File.join(__dir__, "..", "..")
|
155
|
+
sharedir = File.join(rootdir, "share", "llm")
|
156
|
+
provider = provider.gsub(/[^a-z0-9]/i, "")
|
157
|
+
yaml = File.join(sharedir, "models", "#{provider}.yml")
|
158
|
+
YAML.safe_load_file(yaml).transform_values { LLM::Model.new(_1) }
|
113
159
|
end
|
114
160
|
end
|
@@ -20,7 +20,7 @@ class LLM::Anthropic
|
|
20
20
|
# Raises a subclass of {LLM::Error LLM::Error}
|
21
21
|
def raise_error!
|
22
22
|
case res
|
23
|
-
when Net::
|
23
|
+
when Net::HTTPUnauthorized
|
24
24
|
raise LLM::Error::Unauthorized.new { _1.response = res }, "Authentication error"
|
25
25
|
when Net::HTTPTooManyRequests
|
26
26
|
raise LLM::Error::RateLimit.new { _1.response = res }, "Too many requests"
|
@@ -7,7 +7,13 @@ class LLM::Anthropic
|
|
7
7
|
# The messages to format
|
8
8
|
# @return [Array<Hash>]
|
9
9
|
def format(messages)
|
10
|
-
messages.map
|
10
|
+
messages.map do
|
11
|
+
if Hash === _1
|
12
|
+
{role: _1[:role], content: format_content(_1[:content])}
|
13
|
+
else
|
14
|
+
{role: _1.role, content: format_content(_1.content)}
|
15
|
+
end
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
private
|
File without changes
|