llm.rb 0.7.2 → 0.9.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/README.md +93 -63
- data/lib/llm/{chat → bot}/builder.rb +1 -1
- data/lib/llm/bot/conversable.rb +31 -0
- data/lib/llm/{chat → bot}/prompt/completion.rb +14 -4
- data/lib/llm/{chat → bot}/prompt/respond.rb +16 -5
- data/lib/llm/{chat.rb → bot.rb} +48 -66
- data/lib/llm/buffer.rb +2 -2
- data/lib/llm/error.rb +24 -16
- data/lib/llm/event_handler.rb +44 -0
- data/lib/llm/eventstream/event.rb +69 -0
- data/lib/llm/eventstream/parser.rb +88 -0
- data/lib/llm/eventstream.rb +8 -0
- data/lib/llm/function.rb +9 -12
- data/lib/{json → llm/json}/schema/array.rb +1 -1
- data/lib/llm/message.rb +1 -1
- data/lib/llm/model.rb +1 -1
- data/lib/llm/object/builder.rb +38 -0
- data/lib/llm/object/kernel.rb +45 -0
- data/lib/llm/object.rb +77 -0
- data/lib/llm/provider.rb +68 -26
- data/lib/llm/providers/anthropic/error_handler.rb +3 -3
- data/lib/llm/providers/anthropic/models.rb +3 -7
- data/lib/llm/providers/anthropic/response_parser/completion_parser.rb +5 -5
- data/lib/llm/providers/anthropic/response_parser.rb +1 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +66 -0
- data/lib/llm/providers/anthropic.rb +9 -4
- data/lib/llm/providers/deepseek/format/completion_format.rb +68 -0
- data/lib/llm/providers/deepseek/format.rb +28 -0
- data/lib/llm/providers/deepseek.rb +60 -0
- data/lib/llm/providers/gemini/error_handler.rb +4 -4
- data/lib/llm/providers/gemini/files.rb +13 -16
- data/lib/llm/providers/gemini/images.rb +4 -8
- data/lib/llm/providers/gemini/models.rb +3 -7
- data/lib/llm/providers/gemini/response_parser/completion_parser.rb +2 -2
- data/lib/llm/providers/gemini/stream_parser.rb +69 -0
- data/lib/llm/providers/gemini.rb +19 -11
- data/lib/llm/providers/llamacpp.rb +16 -2
- data/lib/llm/providers/ollama/error_handler.rb +3 -3
- data/lib/llm/providers/ollama/format/completion_format.rb +1 -1
- data/lib/llm/providers/ollama/models.rb +3 -7
- data/lib/llm/providers/ollama/response_parser/completion_parser.rb +2 -2
- data/lib/llm/providers/ollama/stream_parser.rb +44 -0
- data/lib/llm/providers/ollama.rb +16 -9
- data/lib/llm/providers/openai/audio.rb +5 -9
- data/lib/llm/providers/openai/error_handler.rb +3 -3
- data/lib/llm/providers/openai/files.rb +15 -18
- data/lib/llm/providers/openai/format/moderation_format.rb +35 -0
- data/lib/llm/providers/openai/format.rb +3 -3
- data/lib/llm/providers/openai/images.rb +8 -11
- data/lib/llm/providers/openai/models.rb +3 -7
- data/lib/llm/providers/openai/moderations.rb +67 -0
- data/lib/llm/providers/openai/response_parser/completion_parser.rb +5 -5
- data/lib/llm/providers/openai/response_parser/respond_parser.rb +2 -2
- data/lib/llm/providers/openai/response_parser.rb +15 -0
- data/lib/llm/providers/openai/responses.rb +14 -16
- data/lib/llm/providers/openai/stream_parser.rb +77 -0
- data/lib/llm/providers/openai.rb +22 -7
- data/lib/llm/providers/voyageai/error_handler.rb +3 -3
- data/lib/llm/providers/voyageai.rb +1 -1
- data/lib/llm/response/filelist.rb +1 -1
- data/lib/llm/response/image.rb +1 -1
- data/lib/llm/response/modellist.rb +1 -1
- data/lib/llm/response/moderationlist/moderation.rb +47 -0
- data/lib/llm/response/moderationlist.rb +51 -0
- data/lib/llm/response.rb +1 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +13 -4
- data/llm.gemspec +2 -2
- metadata +42 -28
- data/lib/llm/chat/conversable.rb +0 -53
- data/lib/llm/core_ext/ostruct.rb +0 -43
- /data/lib/{json → llm/json}/schema/boolean.rb +0 -0
- /data/lib/{json → llm/json}/schema/integer.rb +0 -0
- /data/lib/{json → llm/json}/schema/leaf.rb +0 -0
- /data/lib/{json → llm/json}/schema/null.rb +0 -0
- /data/lib/{json → llm/json}/schema/number.rb +0 -0
- /data/lib/{json → llm/json}/schema/object.rb +0 -0
- /data/lib/{json → llm/json}/schema/string.rb +0 -0
- /data/lib/{json → llm/json}/schema/version.rb +0 -0
- /data/lib/{json → llm/json}/schema.rb +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb2885c3c77d0ac7555b59fd57ccbf15ed5c72e2b385f5f4d83f8ea906b34171
|
4
|
+
data.tar.gz: 0cf0fa38bec61167de57441f11c389f13a1f86ea9dd04caf597698363fa53c71
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 280bccde2d4d730485845440986b27ce7b753cdde7f080bc3c7e2f1381a3e924fa1aebe48fe734d9d6d58c5d0d5ff7e182a5d7bba5b3e390c79c4c2738bbd8c2
|
7
|
+
data.tar.gz: dba89769f1fe6f35ac98e7ad6fce9e90775e8b61e47ee3215e8fb8df555d9787f9d289206bd3cdc60b3b1bdee4a5217e8e69b1d3aa33f29e0e193ba90f0e33f8
|
data/README.md
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
## About
|
2
2
|
|
3
3
|
llm.rb is a zero-dependency Ruby toolkit for Large Language Models that
|
4
|
-
includes OpenAI, Gemini, Anthropic, Ollama, and LlamaCpp.
|
5
|
-
and composable – with full support for chat,
|
6
|
-
images, files, and JSON Schema
|
4
|
+
includes OpenAI, Gemini, Anthropic, DeepSeek, Ollama, and LlamaCpp.
|
5
|
+
It's fast, simple and composable – with full support for chat,
|
6
|
+
streaming, tool calling, audio, images, files, and JSON Schema
|
7
|
+
generation.
|
7
8
|
|
8
9
|
## Features
|
9
10
|
|
@@ -11,12 +12,12 @@ images, files, and JSON Schema generation.
|
|
11
12
|
- ✅ A single unified interface for multiple providers
|
12
13
|
- 📦 Zero dependencies outside Ruby's standard library
|
13
14
|
- 🚀 Optimized for performance and low memory usage
|
14
|
-
- 🔌 Retrieve models dynamically for introspection and selection
|
15
15
|
|
16
16
|
#### Chat, Agents
|
17
17
|
- 🧠 Stateless and stateful chat via completions and responses API
|
18
18
|
- 🤖 Tool calling and function execution
|
19
19
|
- 🗂️ JSON Schema support for structured, validated responses
|
20
|
+
- 📡 Streaming support for real-time response updates
|
20
21
|
|
21
22
|
#### Media
|
22
23
|
- 🗣️ Text-to-speech, transcription, and translation
|
@@ -24,24 +25,25 @@ images, files, and JSON Schema generation.
|
|
24
25
|
- 📎 File uploads and prompt-aware file interaction
|
25
26
|
- 💡 Multimodal prompts (text, images, PDFs, URLs, files)
|
26
27
|
|
27
|
-
####
|
28
|
+
#### Miscellaneous
|
28
29
|
- 🧮 Text embeddings and vector support
|
30
|
+
- 🔌 Retrieve models dynamically for introspection and selection
|
29
31
|
|
30
32
|
## Demos
|
31
33
|
|
32
34
|
<details>
|
33
35
|
<summary><b>1. Tools: "system" function</b></summary>
|
34
|
-
<img src="share/llm-shell/examples/toolcalls.gif">
|
36
|
+
<img src="https://github.com/llmrb/llm/raw/main/share/llm-shell/examples/toolcalls.gif">
|
35
37
|
</details>
|
36
38
|
|
37
39
|
<details>
|
38
|
-
<summary><b>2. Files: import at
|
39
|
-
<img src="share/llm-shell/examples/files-
|
40
|
+
<summary><b>2. Files: import at runtime</b></summary>
|
41
|
+
<img src="https://github.com/llmrb/llm/raw/main/share/llm-shell/examples/files-runtime.gif">
|
40
42
|
</details>
|
41
43
|
|
42
44
|
<details>
|
43
|
-
<summary><b>3. Files: import at
|
44
|
-
<img src="share/llm-shell/examples/files-
|
45
|
+
<summary><b>3. Files: import at boot time</b></summary>
|
46
|
+
<img src="https://github.com/llmrb/llm/raw/main/share/llm-shell/examples/files-boottime.gif">
|
45
47
|
</details>
|
46
48
|
|
47
49
|
## Examples
|
@@ -59,12 +61,18 @@ using an API key (if required) and an optional set of configuration options via
|
|
59
61
|
#!/usr/bin/env ruby
|
60
62
|
require "llm"
|
61
63
|
|
64
|
+
##
|
65
|
+
# remote providers
|
62
66
|
llm = LLM.openai(key: "yourapikey")
|
63
67
|
llm = LLM.gemini(key: "yourapikey")
|
64
68
|
llm = LLM.anthropic(key: "yourapikey")
|
69
|
+
llm = LLM.deepseek(key: "yourapikey")
|
70
|
+
llm = LLM.voyageai(key: "yourapikey")
|
71
|
+
|
72
|
+
##
|
73
|
+
# local providers
|
65
74
|
llm = LLM.ollama(key: nil)
|
66
75
|
llm = LLM.llamacpp(key: nil)
|
67
|
-
llm = LLM.voyageai(key: "yourapikey")
|
68
76
|
```
|
69
77
|
|
70
78
|
### Conversations
|
@@ -73,24 +81,24 @@ llm = LLM.voyageai(key: "yourapikey")
|
|
73
81
|
|
74
82
|
> This example uses the stateless chat completions API that all
|
75
83
|
> providers support. A similar example for OpenAI's stateful
|
76
|
-
> responses API is available in the [docs/](docs/
|
84
|
+
> responses API is available in the [docs/](docs/OPENAI.md#responses)
|
77
85
|
> directory.
|
78
86
|
|
79
|
-
The following example
|
80
|
-
[LLM::
|
81
|
-
|
82
|
-
sent to the provider
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
+
The following example creates an instance of
|
88
|
+
[LLM::Bot](https://0x1eef.github.io/x/llm.rb/LLM/Bot.html)
|
89
|
+
by entering into a conversation where messages are buffered and
|
90
|
+
sent to the provider on-demand. This is the default behavior
|
91
|
+
because it can reduce the number of requests sent to a provider,
|
92
|
+
and avoids unneccessary requests until an attempt to iterate over
|
93
|
+
[LLM::Bot#messages](https://0x1eef.github.io/x/llm.rb/LLM/Bot.html#messages-instance_method)
|
94
|
+
is made:
|
87
95
|
|
88
96
|
```ruby
|
89
97
|
#!/usr/bin/env ruby
|
90
98
|
require "llm"
|
91
99
|
|
92
100
|
llm = LLM.openai(key: ENV["KEY"])
|
93
|
-
bot = LLM::
|
101
|
+
bot = LLM::Bot.new(llm)
|
94
102
|
msgs = bot.chat do |prompt|
|
95
103
|
prompt.system File.read("./share/llm/prompts/system.txt")
|
96
104
|
prompt.user "Tell me the answer to 5 + 15"
|
@@ -100,70 +108,92 @@ end
|
|
100
108
|
|
101
109
|
# At this point, we execute a single request
|
102
110
|
msgs.each { print "[#{_1.role}] ", _1.content, "\n" }
|
111
|
+
```
|
103
112
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
#### Streaming
|
114
|
+
|
115
|
+
> There Is More Than One Way To Do It (TIMTOWTDI) when you are
|
116
|
+
> using llm.rb – and this is especially true when it
|
117
|
+
> comes to streaming. See the streaming documentation in
|
118
|
+
> [docs/](docs/STREAMING.md#flexibility) for more details.
|
119
|
+
|
120
|
+
The following example streams the messages in a conversation
|
121
|
+
as they are generated in real-time. This feature can be useful
|
122
|
+
in case you want to see the contents of a message as it is
|
123
|
+
generated, or in case you want to avoid potential read timeouts
|
124
|
+
during the generation of a response.
|
125
|
+
|
126
|
+
The `stream` option can be set to an IO object, or the value `true`
|
127
|
+
to enable streaming – and at the end of the request, `bot.chat`
|
128
|
+
returns the same response as the non-streaming version which allows
|
129
|
+
you to process a response in the same way:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
#!/usr/bin/env ruby
|
133
|
+
require "llm"
|
134
|
+
|
135
|
+
llm = LLM.openai(key: ENV["KEY"])
|
136
|
+
bot = LLM::Bot.new(llm)
|
137
|
+
bot.chat(stream: $stdout) do |prompt|
|
138
|
+
prompt.system "You are my math assistant."
|
139
|
+
prompt.user "Tell me the answer to 5 + 15"
|
140
|
+
prompt.user "Tell me the answer to (5 + 15) * 2"
|
141
|
+
prompt.user "Tell me the answer to ((5 + 15) * 2) / 10"
|
142
|
+
end.to_a
|
118
143
|
```
|
119
144
|
|
120
145
|
### Schema
|
121
146
|
|
122
147
|
#### Structured
|
123
148
|
|
124
|
-
All LLM providers except Anthropic allow a client to describe
|
125
|
-
of a response that a LLM emits according to a schema that is
|
126
|
-
The schema lets a client describe what JSON object (or value)
|
127
|
-
and the LLM will abide by the schema
|
128
|
-
We will use the
|
129
|
-
[llmrb/json-schema](https://github.com/llmrb/json-schema)
|
130
|
-
library for the sake of the examples – the interface is designed so you
|
131
|
-
could drop in any other library in its place:
|
149
|
+
All LLM providers except Anthropic and DeepSeek allow a client to describe
|
150
|
+
the structure of a response that a LLM emits according to a schema that is
|
151
|
+
described by JSON. The schema lets a client describe what JSON object (or value)
|
152
|
+
an LLM should emit, and the LLM will abide by the schema:
|
132
153
|
|
133
154
|
```ruby
|
134
155
|
#!/usr/bin/env ruby
|
135
156
|
require "llm"
|
136
157
|
|
158
|
+
##
|
159
|
+
# Objects
|
137
160
|
llm = LLM.openai(key: ENV["KEY"])
|
138
|
-
schema = llm.schema.object(
|
139
|
-
bot = LLM::
|
161
|
+
schema = llm.schema.object(answer: llm.schema.integer.required)
|
162
|
+
bot = LLM::Bot.new(llm, schema:)
|
163
|
+
bot.chat "Does the earth orbit the sun?", role: :user
|
164
|
+
bot.messages.find(&:assistant?).content! # => {probability: 1}
|
165
|
+
|
166
|
+
##
|
167
|
+
# Enums
|
168
|
+
schema = llm.schema.object(fruit: llm.schema.string.enum("Apple", "Orange", "Pineapple"))
|
169
|
+
bot = LLM::Bot.new(llm, schema:)
|
140
170
|
bot.chat "Your favorite fruit is Pineapple", role: :system
|
141
171
|
bot.chat "What fruit is your favorite?", role: :user
|
142
172
|
bot.messages.find(&:assistant?).content! # => {fruit: "Pineapple"}
|
143
173
|
|
144
|
-
|
145
|
-
|
174
|
+
##
|
175
|
+
# Arrays
|
176
|
+
schema = llm.schema.object(answers: llm.schema.array(llm.schema.integer.required))
|
177
|
+
bot = LLM::Bot.new(llm, schema:)
|
178
|
+
bot.chat "Answer all of my questions", role: :system
|
146
179
|
bot.chat "Tell me the answer to ((5 + 5) / 2)", role: :user
|
147
|
-
bot.
|
148
|
-
|
149
|
-
|
150
|
-
bot = LLM::Chat.new(llm, schema:).lazy
|
151
|
-
bot.chat "Does the earth orbit the sun?", role: :user
|
152
|
-
bot.messages.find(&:assistant?).content! # => {probability: 1}
|
180
|
+
bot.chat "Tell me the answer to ((5 + 5) / 2) * 2", role: :user
|
181
|
+
bot.chat "Tell me the answer to ((5 + 5) / 2) * 2 + 1", role: :user
|
182
|
+
bot.messages.find(&:assistant?).content! # => {answers: [5, 10, 11]}
|
153
183
|
```
|
154
184
|
|
155
185
|
### Tools
|
156
186
|
|
157
187
|
#### Functions
|
158
188
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
189
|
+
All providers support a powerful feature known as tool calling, and although
|
190
|
+
it is a little complex to understand at first, it can be powerful for building
|
191
|
+
agents. The following example demonstrates how we can define a local function
|
192
|
+
(which happens to be a tool), and a provider (such as OpenAI) can then detect
|
193
|
+
when we should call the function.
|
164
194
|
|
165
195
|
The
|
166
|
-
[LLM::
|
196
|
+
[LLM::Bot#functions](https://0x1eef.github.io/x/llm.rb/LLM/Bot.html#functions-instance_method)
|
167
197
|
method returns an array of functions that can be called after sending a message and
|
168
198
|
it will only be populated if the LLM detects a function should be called. Each function
|
169
199
|
corresponds to an element in the "tools" array. The array is emptied after a function call,
|
@@ -192,7 +222,7 @@ tool = LLM.function(:system) do |fn|
|
|
192
222
|
end
|
193
223
|
end
|
194
224
|
|
195
|
-
bot = LLM::
|
225
|
+
bot = LLM::Bot.new(llm, tools: [tool])
|
196
226
|
bot.chat "Your task is to run shell commands via a tool.", role: :system
|
197
227
|
|
198
228
|
bot.chat "What is the current date?", role: :user
|
@@ -351,7 +381,7 @@ can be given to the chat method:
|
|
351
381
|
require "llm"
|
352
382
|
|
353
383
|
llm = LLM.openai(key: ENV["KEY"])
|
354
|
-
bot = LLM::
|
384
|
+
bot = LLM::Bot.new(llm)
|
355
385
|
file = llm.files.create(file: "/documents/openbsd_is_awesome.pdf")
|
356
386
|
bot.chat(file)
|
357
387
|
bot.chat("What is this file about?")
|
@@ -382,7 +412,7 @@ to a prompt:
|
|
382
412
|
require "llm"
|
383
413
|
|
384
414
|
llm = LLM.openai(key: ENV["KEY"])
|
385
|
-
bot = LLM::
|
415
|
+
bot = LLM::Bot.new(llm)
|
386
416
|
|
387
417
|
bot.chat [URI("https://example.com/path/to/image.png"), "Describe the image in the link"]
|
388
418
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
@@ -453,7 +483,7 @@ end
|
|
453
483
|
##
|
454
484
|
# Select a model
|
455
485
|
model = llm.models.all.find { |m| m.id == "gpt-3.5-turbo" }
|
456
|
-
bot = LLM::
|
486
|
+
bot = LLM::Bot.new(llm, model:)
|
457
487
|
bot.chat "Hello #{model.id} :)"
|
458
488
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
459
489
|
```
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class LLM::Bot
|
4
|
+
##
|
5
|
+
# @private
|
6
|
+
module Conversable
|
7
|
+
private
|
8
|
+
|
9
|
+
##
|
10
|
+
# Queues a response to be sent to the provider.
|
11
|
+
# @param [String] prompt The prompt
|
12
|
+
# @param [Hash] params
|
13
|
+
# @return [void]
|
14
|
+
def async_response(prompt, params = {})
|
15
|
+
role = params.delete(:role)
|
16
|
+
@messages << [LLM::Message.new(role, prompt), @params.merge(params), :respond]
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Queues a completion to be sent to the provider.
|
21
|
+
# @param [String] prompt The prompt
|
22
|
+
# @param [Hash] params
|
23
|
+
# @return [void]
|
24
|
+
def async_completion(prompt, params = {})
|
25
|
+
role = params.delete(:role)
|
26
|
+
@messages.push [LLM::Message.new(role, prompt), @params.merge(params), :complete]
|
27
|
+
end
|
28
|
+
|
29
|
+
include LLM
|
30
|
+
end
|
31
|
+
end
|
@@ -1,20 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module LLM::
|
4
|
-
class Completion < Struct.new(:bot)
|
3
|
+
module LLM::Bot::Prompt
|
4
|
+
class Completion < Struct.new(:bot, :defaults)
|
5
|
+
##
|
6
|
+
# @param [LLM::Bot] bot
|
7
|
+
# @param [Hash] defaults
|
8
|
+
# @return [LLM::Bot::Prompt::Completion]
|
9
|
+
def initialize(bot, defaults)
|
10
|
+
super(bot, defaults || {})
|
11
|
+
end
|
12
|
+
|
5
13
|
##
|
6
14
|
# @param [String] prompt
|
7
15
|
# @param [Hash] params (see LLM::Provider#complete)
|
8
|
-
# @return [LLM::
|
16
|
+
# @return [LLM::Bot]
|
9
17
|
def system(prompt, params = {})
|
18
|
+
params = defaults.merge(params)
|
10
19
|
bot.chat prompt, params.merge(role: :system)
|
11
20
|
end
|
12
21
|
|
13
22
|
##
|
14
23
|
# @param [String] prompt
|
15
24
|
# @param [Hash] params (see LLM::Provider#complete)
|
16
|
-
# @return [LLM::
|
25
|
+
# @return [LLM::Bot]
|
17
26
|
def user(prompt, params = {})
|
27
|
+
params = defaults.merge(params)
|
18
28
|
bot.chat prompt, params.merge(role: :user)
|
19
29
|
end
|
20
30
|
end
|
@@ -1,28 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module LLM::
|
4
|
-
class Respond < Struct.new(:bot)
|
3
|
+
module LLM::Bot::Prompt
|
4
|
+
class Respond < Struct.new(:bot, :defaults)
|
5
|
+
##
|
6
|
+
# @param [LLM::Bot] bot
|
7
|
+
# @param [Hash] defaults
|
8
|
+
# @return [LLM::Bot::Prompt::Completion]
|
9
|
+
def initialize(bot, defaults)
|
10
|
+
super(bot, defaults || {})
|
11
|
+
end
|
12
|
+
|
5
13
|
##
|
6
14
|
# @param [String] prompt
|
7
15
|
# @param [Hash] params (see LLM::Provider#complete)
|
8
|
-
# @return [LLM::
|
16
|
+
# @return [LLM::Bot]
|
9
17
|
def system(prompt, params = {})
|
18
|
+
params = defaults.merge(params)
|
10
19
|
bot.respond prompt, params.merge(role: :system)
|
11
20
|
end
|
12
21
|
|
13
22
|
##
|
14
23
|
# @param [String] prompt
|
15
24
|
# @param [Hash] params (see LLM::Provider#complete)
|
16
|
-
# @return [LLM::
|
25
|
+
# @return [LLM::Bot]
|
17
26
|
def developer(prompt, params = {})
|
27
|
+
params = defaults.merge(params)
|
18
28
|
bot.respond prompt, params.merge(role: :developer)
|
19
29
|
end
|
20
30
|
|
21
31
|
##
|
22
32
|
# @param [String] prompt
|
23
33
|
# @param [Hash] params (see LLM::Provider#complete)
|
24
|
-
# @return [LLM::
|
34
|
+
# @return [LLM::Bot]
|
25
35
|
def user(prompt, params = {})
|
36
|
+
params = defaults.merge(params)
|
26
37
|
bot.respond prompt, params.merge(role: :user)
|
27
38
|
end
|
28
39
|
end
|
data/lib/llm/{chat.rb → bot.rb}
RENAMED
@@ -2,47 +2,48 @@
|
|
2
2
|
|
3
3
|
module LLM
|
4
4
|
##
|
5
|
-
# {LLM::
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
5
|
+
# {LLM::Bot LLM::Bot} provides a bot object that can maintain a
|
6
|
+
# a conversation. A conversation can use the chat completions API
|
7
|
+
# that all LLM providers support or the responses API that a select
|
8
|
+
# few LLM providers support.
|
9
9
|
#
|
10
|
-
# @example
|
10
|
+
# @example example #1
|
11
11
|
# #!/usr/bin/env ruby
|
12
12
|
# require "llm"
|
13
13
|
#
|
14
14
|
# llm = LLM.openai(ENV["KEY"])
|
15
|
-
# bot = LLM::
|
15
|
+
# bot = LLM::Bot.new(llm)
|
16
16
|
# msgs = bot.chat do |prompt|
|
17
17
|
# prompt.system "Answer the following questions."
|
18
18
|
# prompt.user "What is 5 + 7 ?"
|
19
19
|
# prompt.user "Why is the sky blue ?"
|
20
20
|
# prompt.user "Why did the chicken cross the road ?"
|
21
21
|
# end
|
22
|
-
# msgs.
|
22
|
+
# msgs.each { print "[#{_1.role}]", _1.content, "\n" }
|
23
23
|
#
|
24
|
-
# @example
|
24
|
+
# @example example #2
|
25
25
|
# #!/usr/bin/env ruby
|
26
26
|
# require "llm"
|
27
27
|
#
|
28
28
|
# llm = LLM.openai(ENV["KEY"])
|
29
|
-
# bot = LLM::
|
29
|
+
# bot = LLM::Bot.new(llm)
|
30
30
|
# bot.chat "Answer the following questions.", role: :system
|
31
31
|
# bot.chat "What is 5 + 7 ?", role: :user
|
32
32
|
# bot.chat "Why is the sky blue ?", role: :user
|
33
33
|
# bot.chat "Why did the chicken cross the road ?", role: :user
|
34
|
-
# bot.messages.
|
35
|
-
class
|
36
|
-
require_relative "
|
37
|
-
require_relative "
|
38
|
-
require_relative "
|
39
|
-
require_relative "
|
34
|
+
# bot.messages.each { print "[#{_1.role}]", _1.content, "\n" }
|
35
|
+
class Bot
|
36
|
+
require_relative "bot/prompt/completion"
|
37
|
+
require_relative "bot/prompt/respond"
|
38
|
+
require_relative "bot/conversable"
|
39
|
+
require_relative "bot/builder"
|
40
40
|
|
41
41
|
include Conversable
|
42
42
|
include Builder
|
43
43
|
|
44
44
|
##
|
45
|
-
#
|
45
|
+
# Returns an Enumerable for the messages in a conversation
|
46
|
+
# @return [LLM::Buffer<LLM::Message>]
|
46
47
|
attr_reader :messages
|
47
48
|
|
48
49
|
##
|
@@ -58,72 +59,68 @@ module LLM
|
|
58
59
|
def initialize(provider, params = {})
|
59
60
|
@provider = provider
|
60
61
|
@params = {model: provider.default_model, schema: nil}.compact.merge!(params)
|
61
|
-
@
|
62
|
-
@messages = [].extend(Array)
|
62
|
+
@messages = LLM::Buffer.new(provider)
|
63
63
|
end
|
64
64
|
|
65
65
|
##
|
66
66
|
# Maintain a conversation via the chat completions API
|
67
|
-
# @
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
#
|
67
|
+
# @overload def chat(prompt, params = {})
|
68
|
+
# @param prompt (see LLM::Provider#complete)
|
69
|
+
# @param params The params
|
70
|
+
# @return [LLM::Bot]
|
71
|
+
# Returns self
|
72
|
+
# @overload def chat(prompt, params, &block)
|
73
|
+
# @param prompt (see LLM::Provider#complete)
|
74
|
+
# @param params The params
|
75
|
+
# @yield prompt Yields a prompt
|
76
|
+
# @return [LLM::Buffer]
|
77
|
+
# Returns messages
|
72
78
|
def chat(prompt = nil, params = {})
|
73
79
|
if block_given?
|
74
|
-
|
80
|
+
params = prompt
|
81
|
+
yield Prompt::Completion.new(self, params)
|
75
82
|
messages
|
76
83
|
elsif prompt.nil?
|
77
84
|
raise ArgumentError, "wrong number of arguments (given 0, expected 1)"
|
78
85
|
else
|
79
86
|
params = {role: :user}.merge!(params)
|
80
|
-
tap {
|
87
|
+
tap { async_completion(prompt, params) }
|
81
88
|
end
|
82
89
|
end
|
83
90
|
|
84
91
|
##
|
85
92
|
# Maintain a conversation via the responses API
|
86
|
-
# @
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
#
|
93
|
+
# @overload def respond(prompt, params = {})
|
94
|
+
# @param prompt (see LLM::Provider#complete)
|
95
|
+
# @param params The params
|
96
|
+
# @return [LLM::Bot]
|
97
|
+
# Returns self
|
98
|
+
# @overload def respond(prompt, params, &block)
|
99
|
+
# @note Not all LLM providers support this API
|
100
|
+
# @param prompt (see LLM::Provider#complete)
|
101
|
+
# @param params The params
|
102
|
+
# @yield prompt Yields a prompt
|
103
|
+
# @return [LLM::Buffer]
|
104
|
+
# Returns messages
|
91
105
|
def respond(prompt = nil, params = {})
|
92
106
|
if block_given?
|
93
|
-
|
107
|
+
params = prompt
|
108
|
+
yield Prompt::Respond.new(self, params)
|
94
109
|
messages
|
95
110
|
elsif prompt.nil?
|
96
111
|
raise ArgumentError, "wrong number of arguments (given 0, expected 1)"
|
97
112
|
else
|
98
113
|
params = {role: :user}.merge!(params)
|
99
|
-
tap {
|
114
|
+
tap { async_response(prompt, params) }
|
100
115
|
end
|
101
116
|
end
|
102
117
|
|
103
|
-
##
|
104
|
-
# Enables lazy mode for the conversation.
|
105
|
-
# @return [LLM::Chat]
|
106
|
-
def lazy
|
107
|
-
tap do
|
108
|
-
next if lazy?
|
109
|
-
@lazy = true
|
110
|
-
@messages = LLM::Buffer.new(@provider)
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
##
|
115
|
-
# @return [Boolean]
|
116
|
-
# Returns true if the conversation is lazy
|
117
|
-
def lazy?
|
118
|
-
@lazy
|
119
|
-
end
|
120
|
-
|
121
118
|
##
|
122
119
|
# @return [String]
|
123
120
|
def inspect
|
124
121
|
"#<#{self.class.name}:0x#{object_id.to_s(16)} " \
|
125
122
|
"@provider=#{@provider.class}, @params=#{@params.inspect}, " \
|
126
|
-
"@messages=#{@messages.inspect}
|
123
|
+
"@messages=#{@messages.inspect}>"
|
127
124
|
end
|
128
125
|
|
129
126
|
##
|
@@ -135,20 +132,5 @@ module LLM
|
|
135
132
|
.flat_map(&:functions)
|
136
133
|
.select(&:pending?)
|
137
134
|
end
|
138
|
-
|
139
|
-
private
|
140
|
-
|
141
|
-
##
|
142
|
-
# @private
|
143
|
-
module Array
|
144
|
-
def find(...)
|
145
|
-
reverse_each.find(...)
|
146
|
-
end
|
147
|
-
|
148
|
-
def unread
|
149
|
-
reject(&:read?)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
private_constant :Array
|
153
135
|
end
|
154
136
|
end
|
data/lib/llm/buffer.rb
CHANGED
@@ -82,7 +82,7 @@ module LLM
|
|
82
82
|
message.content,
|
83
83
|
params.merge(role:, messages:)
|
84
84
|
)
|
85
|
-
@completed.concat([*pendings, message, completion.choices[0]])
|
85
|
+
@completed.concat([*pendings, message, *completion.choices[0]])
|
86
86
|
@pending.clear
|
87
87
|
end
|
88
88
|
|
@@ -95,7 +95,7 @@ module LLM
|
|
95
95
|
@response ? {previous_response_id: @response.id} : {}
|
96
96
|
].inject({}, &:merge!)
|
97
97
|
@response = @provider.responses.create(message.content, params.merge(role:))
|
98
|
-
@completed.concat([*pendings, message,
|
98
|
+
@completed.concat([*pendings, message, *@response.outputs[0]])
|
99
99
|
@pending.clear
|
100
100
|
end
|
101
101
|
end
|