llm.rb 4.16.1 → 4.17.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 +45 -0
- data/README.md +70 -24
- data/lib/llm/active_record/acts_as_agent.rb +177 -0
- data/lib/llm/active_record/acts_as_llm.rb +66 -1
- data/lib/llm/active_record.rb +1 -0
- data/lib/llm/agent.rb +127 -16
- data/lib/llm/sequel/plugin.rb +66 -1
- data/lib/llm/version.rb +1 -1
- data/llm.gemspec +11 -17
- metadata +13 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d834b30dd18d6bf83ccd2334bbedba0ee5a9f75d0d6d0616aefb7baa6be68ad0
|
|
4
|
+
data.tar.gz: d8c8a1e43cc89d888ab1ea008baddce7e1427fc3598daf68ca04fd0eff1351b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f9fbe6f7080294c0500153dbfeb3c721407553289a2f34cd37e63552a84171bdc1bc130fb325aaf164f099c5fd6bfb417f550eea60dc126c20f940a7737e393a
|
|
7
|
+
data.tar.gz: 67cef120d84c98ea695caab059d2bb008ca1cbdeb15a1b2f13c819166b1edd7a523c7213acabed5b35c44e7123ae85481c0eaff815d6254088c858e5a0516ae3
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
Changes since `v4.17.0`.
|
|
6
|
+
|
|
7
|
+
## v4.17.0
|
|
8
|
+
|
|
5
9
|
Changes since `v4.16.1`.
|
|
6
10
|
|
|
11
|
+
This release expands agent support across llm.rb. It brings `LLM::Agent`
|
|
12
|
+
closer to `LLM::Context`, adds configurable automatic tool concurrency,
|
|
13
|
+
extends persisted ORM wrappers with more of the context runtime surface and
|
|
14
|
+
fiber-local tracer hooks, and introduces built-in ActiveRecord agent
|
|
15
|
+
persistence through `acts_as_agent`.
|
|
16
|
+
|
|
17
|
+
### Change
|
|
18
|
+
|
|
19
|
+
* **Add configurable tool concurrency to `LLM::Agent`** <br>
|
|
20
|
+
Add the class-level `concurrency` DSL to `LLM::Agent` so automatic
|
|
21
|
+
tool loops can run with `:call`, `:thread`, `:task`, or `:fiber`
|
|
22
|
+
instead of always executing sequentially.
|
|
23
|
+
|
|
24
|
+
* **Bring `LLM::Agent` closer to `LLM::Context`** <br>
|
|
25
|
+
Expand `LLM::Agent` so it exposes more of the same runtime surface as
|
|
26
|
+
`LLM::Context`, including returns, interruption, mode, cost, context
|
|
27
|
+
window, structured serialization, and other context-backed helpers,
|
|
28
|
+
while still auto-managing tool loops.
|
|
29
|
+
|
|
30
|
+
* **Refresh agent docs and coverage** <br>
|
|
31
|
+
Update the README and deep dive to explain the current role of
|
|
32
|
+
`LLM::Agent`, add examples that show automatic tool execution and
|
|
33
|
+
concurrency, and add focused specs for the expanded agent surface and
|
|
34
|
+
tool-loop behavior.
|
|
35
|
+
|
|
36
|
+
* **Add ORM tracer hooks for persisted contexts** <br>
|
|
37
|
+
Add `tracer:` to both the Sequel plugin and `acts_as_llm` so models
|
|
38
|
+
can resolve and assign fiber-local tracers onto the provider used by
|
|
39
|
+
their persisted `LLM::Context`.
|
|
40
|
+
|
|
41
|
+
* **Bring persisted ORM wrappers closer to `LLM::Context`** <br>
|
|
42
|
+
Expand both the Sequel plugin and `acts_as_llm` so record-backed
|
|
43
|
+
contexts expose more of the same runtime surface as `LLM::Context`,
|
|
44
|
+
including mode, returns, interruption, prompt helpers, file helpers,
|
|
45
|
+
and tracer access.
|
|
46
|
+
|
|
47
|
+
* **Add ActiveRecord agent persistence with `acts_as_agent`** <br>
|
|
48
|
+
Add `acts_as_agent` for ActiveRecord models that should wrap
|
|
49
|
+
`LLM::Agent`, reusing the same record-backed runtime shape as
|
|
50
|
+
`acts_as_llm` while letting tool execution be managed by the agent.
|
|
51
|
+
|
|
7
52
|
## v4.16.1
|
|
8
53
|
|
|
9
54
|
Changes since `v4.16.0`.
|
data/README.md
CHANGED
|
@@ -4,27 +4,24 @@
|
|
|
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.17.0-green.svg?" alt="Version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
## About
|
|
11
11
|
|
|
12
|
-
llm.rb is a runtime for building AI systems
|
|
13
|
-
application. It is not just an API wrapper. It provides a unified execution
|
|
14
|
-
model for providers, tools, MCP servers, streaming, schemas, files, and
|
|
15
|
-
state.
|
|
12
|
+
llm.rb is a lightweight runtime for building capable AI systems in Ruby.
|
|
16
13
|
|
|
17
|
-
It is
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
built-in Sequel support through `plugin :llm`, so long-lived context state can
|
|
22
|
-
be saved and restored across requests, jobs, or retries.
|
|
14
|
+
It is not just an API wrapper. llm.rb gives you one runtime for providers,
|
|
15
|
+
contexts, agents, tools, MCP servers, streaming, schemas, files, and persisted
|
|
16
|
+
state, so real systems can be built out of one coherent execution model instead
|
|
17
|
+
of a pile of adapters.
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
It stays close to Ruby, runs on the standard library by default, loads optional
|
|
20
|
+
pieces only when needed, includes built-in ActiveRecord support through
|
|
21
|
+
`acts_as_llm` and `acts_as_agent`, includes built-in Sequel support through
|
|
22
|
+
`plugin :llm`, and is designed for engineers who want control over
|
|
23
|
+
long-lived, tool-capable, stateful AI workflows instead of just
|
|
24
|
+
request/response helpers.
|
|
28
25
|
|
|
29
26
|
## Architecture
|
|
30
27
|
|
|
@@ -66,6 +63,10 @@ same context object.
|
|
|
66
63
|
- **Streaming and tool execution work together** <br>
|
|
67
64
|
Start tool work while output is still streaming so you can hide latency
|
|
68
65
|
instead of waiting for turns to finish.
|
|
66
|
+
- **Agents auto-manage tool execution** <br>
|
|
67
|
+
Use `LLM::Agent` when you want the same stateful runtime surface as
|
|
68
|
+
`LLM::Context`, but with tool loops executed automatically according to a
|
|
69
|
+
configured concurrency mode such as `:call`, `:thread`, `:task`, or `:fiber`.
|
|
69
70
|
- **Tool calls have an explicit lifecycle** <br>
|
|
70
71
|
A tool call can be executed, cancelled through
|
|
71
72
|
[`LLM::Function#cancel`](https://0x1eef.github.io/x/llm.rb/LLM/Function.html#cancel-instance_method),
|
|
@@ -88,11 +89,14 @@ same context object.
|
|
|
88
89
|
Connect to MCP servers over stdio or HTTP without bolting on a separate
|
|
89
90
|
integration stack.
|
|
90
91
|
- **ActiveRecord and Sequel persistence are built in** <br>
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
`
|
|
94
|
-
or `
|
|
95
|
-
|
|
92
|
+
llm.rb includes built-in ActiveRecord support through `acts_as_llm` and
|
|
93
|
+
`acts_as_agent`, plus built-in Sequel support through `plugin :llm`.
|
|
94
|
+
Use `acts_as_llm` when you want to wrap `LLM::Context`, `acts_as_agent`
|
|
95
|
+
when you want to wrap `LLM::Agent`, or `plugin :llm` on Sequel models to
|
|
96
|
+
persist `LLM::Context` state with sensible default columns. These
|
|
97
|
+
integrations support `provider:` and `context:` hooks, plus `format:
|
|
98
|
+
:string` for text columns or `format: :jsonb` for native PostgreSQL JSON
|
|
99
|
+
storage when ORM JSON typecasting support is enabled.
|
|
96
100
|
- **Persistent HTTP pooling is shared process-wide** <br>
|
|
97
101
|
When enabled, separate
|
|
98
102
|
[`LLM::Provider`](https://0x1eef.github.io/x/llm.rb/LLM/Provider.html)
|
|
@@ -174,7 +178,7 @@ gem install llm.rb
|
|
|
174
178
|
|
|
175
179
|
**REPL**
|
|
176
180
|
|
|
177
|
-
See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
181
|
+
This example uses [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) directly for an interactive REPL. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
178
182
|
|
|
179
183
|
```ruby
|
|
180
184
|
require "llm"
|
|
@@ -191,7 +195,7 @@ end
|
|
|
191
195
|
|
|
192
196
|
**Sequel (ORM)**
|
|
193
197
|
|
|
194
|
-
See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
198
|
+
The `plugin :llm` integration wraps [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) on a `Sequel::Model` and keeps tool execution explicit. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
195
199
|
|
|
196
200
|
```ruby
|
|
197
201
|
require "llm"
|
|
@@ -207,9 +211,10 @@ ctx.talk("Remember that my favorite language is Ruby")
|
|
|
207
211
|
puts ctx.talk("What is my favorite language?").content
|
|
208
212
|
```
|
|
209
213
|
|
|
210
|
-
**ActiveRecord (ORM)**
|
|
214
|
+
**ActiveRecord (ORM): acts_as_llm**
|
|
211
215
|
|
|
212
|
-
|
|
216
|
+
The `acts_as_llm` method wraps [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) and
|
|
217
|
+
provides full control over tool execution. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
213
218
|
|
|
214
219
|
```ruby
|
|
215
220
|
require "llm"
|
|
@@ -225,6 +230,47 @@ ctx.talk("Remember that my favorite language is Ruby")
|
|
|
225
230
|
puts ctx.talk("What is my favorite language?").content
|
|
226
231
|
```
|
|
227
232
|
|
|
233
|
+
**ActiveRecord (ORM): acts_as_agent**
|
|
234
|
+
|
|
235
|
+
The `acts_as_agent` method wraps [`LLM::Agent`](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html) and
|
|
236
|
+
manages tool execution for you. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
require "llm"
|
|
240
|
+
require "active_record"
|
|
241
|
+
require "llm/active_record"
|
|
242
|
+
|
|
243
|
+
class Ticket < ApplicationRecord
|
|
244
|
+
acts_as_agent provider: -> { { key: ENV["#{provider.upcase}_SECRET"], persistent: true } }
|
|
245
|
+
model "gpt-5.4-mini"
|
|
246
|
+
instructions "You are a concise support assistant."
|
|
247
|
+
tools SearchDocs, Escalate
|
|
248
|
+
concurrency :thread
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
ticket = Ticket.create!(provider: "openai", model: "gpt-5.4-mini")
|
|
252
|
+
puts ticket.talk("How do I rotate my API key?").content
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Agent**
|
|
256
|
+
|
|
257
|
+
This example uses [`LLM::Agent`](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html) directly and lets the agent manage tool execution. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
258
|
+
|
|
259
|
+
```ruby
|
|
260
|
+
require "llm"
|
|
261
|
+
|
|
262
|
+
class ShellAgent < LLM::Agent
|
|
263
|
+
model "gpt-5.4-mini"
|
|
264
|
+
instructions "You are a Linux system assistant."
|
|
265
|
+
tools Shell
|
|
266
|
+
concurrency :thread
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
270
|
+
agent = ShellAgent.new(llm)
|
|
271
|
+
puts agent.talk("What time is it on this system?").content
|
|
272
|
+
```
|
|
273
|
+
|
|
228
274
|
## Resources
|
|
229
275
|
|
|
230
276
|
- [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) is the
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::ActiveRecord
|
|
4
|
+
##
|
|
5
|
+
# ActiveRecord integration for persisting {LLM::Agent LLM::Agent} state.
|
|
6
|
+
#
|
|
7
|
+
# This wrapper reuses the same record-backed runtime surface as
|
|
8
|
+
# {LLM::ActiveRecord::ActsAsLLM}, but builds an {LLM::Agent LLM::Agent}
|
|
9
|
+
# instead of an {LLM::Context LLM::Context}. Agent defaults such as model,
|
|
10
|
+
# tools, schema, instructions, and concurrency are configured on the model
|
|
11
|
+
# class and forwarded to an internal agent subclass.
|
|
12
|
+
module ActsAsAgent
|
|
13
|
+
EMPTY_HASH = LLM::ActiveRecord::ActsAsLLM::EMPTY_HASH
|
|
14
|
+
DEFAULT_USAGE_COLUMNS = LLM::ActiveRecord::ActsAsLLM::DEFAULT_USAGE_COLUMNS
|
|
15
|
+
DEFAULTS = LLM::ActiveRecord::ActsAsLLM::DEFAULTS
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
def model(model = nil)
|
|
19
|
+
return agent.model if model.nil?
|
|
20
|
+
agent.model(model)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def tools(*tools)
|
|
24
|
+
return agent.tools if tools.empty?
|
|
25
|
+
agent.tools(*tools)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def schema(schema = nil)
|
|
29
|
+
return agent.schema if schema.nil?
|
|
30
|
+
agent.schema(schema)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def instructions(instructions = nil)
|
|
34
|
+
return agent.instructions if instructions.nil?
|
|
35
|
+
agent.instructions(instructions)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def concurrency(concurrency = nil)
|
|
39
|
+
return agent.concurrency if concurrency.nil?
|
|
40
|
+
agent.concurrency(concurrency)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def agent
|
|
44
|
+
@agent ||= Class.new(LLM::Agent)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module Hooks
|
|
49
|
+
##
|
|
50
|
+
# Called when hooks are extended onto an ActiveRecord model.
|
|
51
|
+
#
|
|
52
|
+
# @param [Class] model
|
|
53
|
+
# @return [void]
|
|
54
|
+
def self.extended(model)
|
|
55
|
+
options = model.llm_agent_options
|
|
56
|
+
model.validates options[:provider_column], options[:model_column], presence: true
|
|
57
|
+
model.include LLM::ActiveRecord::ActsAsLLM::InstanceMethods unless model.ancestors.include?(LLM::ActiveRecord::ActsAsLLM::InstanceMethods)
|
|
58
|
+
model.include InstanceMethods unless model.ancestors.include?(InstanceMethods)
|
|
59
|
+
model.extend ClassMethods unless model.singleton_class.ancestors.include?(ClassMethods)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Installs the `acts_as_agent` wrapper on an ActiveRecord model.
|
|
65
|
+
#
|
|
66
|
+
# @param [Hash] options
|
|
67
|
+
# @option options [Symbol] :format
|
|
68
|
+
# Storage format for the serialized agent state. Use `:string` for text
|
|
69
|
+
# columns, or `:json` / `:jsonb` for structured JSON columns with
|
|
70
|
+
# ActiveRecord JSON typecasting enabled.
|
|
71
|
+
# @option options [Proc, LLM::Tracer, nil] :tracer
|
|
72
|
+
# Optional tracer or proc that resolves to one and is assigned through
|
|
73
|
+
# `llm.tracer = ...` on the resolved provider.
|
|
74
|
+
# @return [void]
|
|
75
|
+
def acts_as_agent(options = EMPTY_HASH)
|
|
76
|
+
options = DEFAULTS.merge(options)
|
|
77
|
+
usage_columns = DEFAULT_USAGE_COLUMNS.merge(options[:usage_columns] || EMPTY_HASH)
|
|
78
|
+
class_attribute :llm_agent_options, instance_accessor: false, default: DEFAULTS unless respond_to?(:llm_agent_options)
|
|
79
|
+
self.llm_agent_options = options.merge(usage_columns: usage_columns.freeze).freeze
|
|
80
|
+
extend Hooks
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module InstanceMethods
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Returns the resolved provider instance for this record.
|
|
88
|
+
# @return [LLM::Provider]
|
|
89
|
+
def llm
|
|
90
|
+
options = self.class.llm_agent_options
|
|
91
|
+
provider = self[columns[:provider_column]]
|
|
92
|
+
kwargs = resolve_options(options[:provider])
|
|
93
|
+
@llm ||= LLM.method(provider).call(**kwargs)
|
|
94
|
+
@llm.tracer = resolve_option(options[:tracer]) if options[:tracer]
|
|
95
|
+
@llm
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# @return [LLM::Agent]
|
|
100
|
+
def ctx
|
|
101
|
+
@ctx ||= begin
|
|
102
|
+
options = self.class.llm_agent_options
|
|
103
|
+
params = resolve_options(options[:context]).dup
|
|
104
|
+
params[:model] ||= self[columns[:model_column]]
|
|
105
|
+
ctx = self.class.agent.new(llm, params.compact)
|
|
106
|
+
data = self[columns[:data_column]]
|
|
107
|
+
if data.nil? || data == ""
|
|
108
|
+
ctx
|
|
109
|
+
else
|
|
110
|
+
case options[:format]
|
|
111
|
+
when :string then ctx.restore(string: data)
|
|
112
|
+
when :json, :jsonb then ctx.restore(data:)
|
|
113
|
+
else raise ArgumentError, "Unknown format: #{options[:format].inspect}"
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# @return [void]
|
|
121
|
+
def flush
|
|
122
|
+
attrs = {
|
|
123
|
+
columns[:data_column] => serialize_context(self.class.llm_agent_options[:format]),
|
|
124
|
+
columns[:input_tokens] => ctx.usage.input_tokens,
|
|
125
|
+
columns[:output_tokens] => ctx.usage.output_tokens,
|
|
126
|
+
columns[:total_tokens] => ctx.usage.total_tokens
|
|
127
|
+
}
|
|
128
|
+
assign_attributes(attrs)
|
|
129
|
+
save!
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# @return [Hash]
|
|
134
|
+
def resolve_option(option)
|
|
135
|
+
case option
|
|
136
|
+
when Proc then instance_exec(&option)
|
|
137
|
+
when Hash then option.dup
|
|
138
|
+
else option
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
# @return [Hash]
|
|
144
|
+
def resolve_options(option)
|
|
145
|
+
case option
|
|
146
|
+
when Proc, Hash then resolve_option(option)
|
|
147
|
+
else EMPTY_HASH.dup
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def serialize_context(format)
|
|
152
|
+
case format
|
|
153
|
+
when :string then ctx.to_json
|
|
154
|
+
when :json, :jsonb then ctx.to_h
|
|
155
|
+
else raise ArgumentError, "Unknown format: #{format.inspect}"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def columns
|
|
160
|
+
@columns ||= begin
|
|
161
|
+
options = self.class.llm_agent_options
|
|
162
|
+
usage_columns = options[:usage_columns]
|
|
163
|
+
{
|
|
164
|
+
provider_column: options[:provider_column],
|
|
165
|
+
model_column: options[:model_column],
|
|
166
|
+
data_column: options[:data_column],
|
|
167
|
+
input_tokens: usage_columns[:input_tokens],
|
|
168
|
+
output_tokens: usage_columns[:output_tokens],
|
|
169
|
+
total_tokens: usage_columns[:total_tokens]
|
|
170
|
+
}.freeze
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
::ActiveRecord::Base.extend(LLM::ActiveRecord::ActsAsAgent)
|
|
@@ -13,7 +13,8 @@ module LLM::ActiveRecord
|
|
|
13
13
|
# default) or as a structured object (`format: :json` / `:jsonb`) for
|
|
14
14
|
# databases such as PostgreSQL that can persist JSON natively.
|
|
15
15
|
# `:json` and `:jsonb` expect a real JSON column type with ActiveRecord
|
|
16
|
-
# handling JSON typecasting for the model.
|
|
16
|
+
# handling JSON typecasting for the model. A `tracer:` proc can also be
|
|
17
|
+
# configured to assign a fiber-local tracer onto the resolved provider.
|
|
17
18
|
module ActsAsLLM
|
|
18
19
|
EMPTY_HASH = {}.freeze
|
|
19
20
|
DEFAULT_USAGE_COLUMNS = {
|
|
@@ -27,6 +28,7 @@ module LLM::ActiveRecord
|
|
|
27
28
|
data_column: :data,
|
|
28
29
|
format: :string,
|
|
29
30
|
usage_columns: DEFAULT_USAGE_COLUMNS,
|
|
31
|
+
tracer: nil,
|
|
30
32
|
provider: EMPTY_HASH,
|
|
31
33
|
context: EMPTY_HASH
|
|
32
34
|
}.freeze
|
|
@@ -52,6 +54,9 @@ module LLM::ActiveRecord
|
|
|
52
54
|
# Storage format for the serialized context. Use `:string` for text
|
|
53
55
|
# columns, or `:json` / `:jsonb` for structured JSON columns with
|
|
54
56
|
# ActiveRecord JSON typecasting enabled.
|
|
57
|
+
# @option options [Proc, LLM::Tracer, nil] :tracer
|
|
58
|
+
# Optional tracer or proc that resolves to one and is assigned through
|
|
59
|
+
# `llm.tracer = ...` on the resolved provider.
|
|
55
60
|
# @return [void]
|
|
56
61
|
def acts_as_llm(options = EMPTY_HASH)
|
|
57
62
|
options = DEFAULTS.merge(options)
|
|
@@ -94,6 +99,13 @@ module LLM::ActiveRecord
|
|
|
94
99
|
ctx.call(...)
|
|
95
100
|
end
|
|
96
101
|
|
|
102
|
+
##
|
|
103
|
+
# @see LLM::Context#mode
|
|
104
|
+
# @return [Symbol]
|
|
105
|
+
def mode
|
|
106
|
+
ctx.mode
|
|
107
|
+
end
|
|
108
|
+
|
|
97
109
|
##
|
|
98
110
|
# @see LLM::Context#messages
|
|
99
111
|
# @return [Array<LLM::Message>]
|
|
@@ -116,6 +128,13 @@ module LLM::ActiveRecord
|
|
|
116
128
|
ctx.functions
|
|
117
129
|
end
|
|
118
130
|
|
|
131
|
+
##
|
|
132
|
+
# @see LLM::Context#returns
|
|
133
|
+
# @return [Array<LLM::Function::Return>]
|
|
134
|
+
def returns
|
|
135
|
+
ctx.returns
|
|
136
|
+
end
|
|
137
|
+
|
|
119
138
|
##
|
|
120
139
|
# @see LLM::Context#cost
|
|
121
140
|
# @return [LLM::Cost]
|
|
@@ -143,6 +162,50 @@ module LLM::ActiveRecord
|
|
|
143
162
|
)
|
|
144
163
|
end
|
|
145
164
|
|
|
165
|
+
##
|
|
166
|
+
# @see LLM::Context#interrupt!
|
|
167
|
+
# @return [nil]
|
|
168
|
+
def interrupt!
|
|
169
|
+
ctx.interrupt!
|
|
170
|
+
end
|
|
171
|
+
alias_method :cancel!, :interrupt!
|
|
172
|
+
|
|
173
|
+
##
|
|
174
|
+
# @see LLM::Context#prompt
|
|
175
|
+
# @return [LLM::Prompt]
|
|
176
|
+
def prompt(&)
|
|
177
|
+
ctx.prompt(&)
|
|
178
|
+
end
|
|
179
|
+
alias_method :build_prompt, :prompt
|
|
180
|
+
|
|
181
|
+
##
|
|
182
|
+
# @see LLM::Context#image_url
|
|
183
|
+
# @return [LLM::Object]
|
|
184
|
+
def image_url(...)
|
|
185
|
+
ctx.image_url(...)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
##
|
|
189
|
+
# @see LLM::Context#local_file
|
|
190
|
+
# @return [LLM::Object]
|
|
191
|
+
def local_file(...)
|
|
192
|
+
ctx.local_file(...)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# @see LLM::Context#remote_file
|
|
197
|
+
# @return [LLM::Object]
|
|
198
|
+
def remote_file(...)
|
|
199
|
+
ctx.remote_file(...)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
##
|
|
203
|
+
# @see LLM::Context#tracer
|
|
204
|
+
# @return [LLM::Tracer]
|
|
205
|
+
def tracer
|
|
206
|
+
ctx.tracer
|
|
207
|
+
end
|
|
208
|
+
|
|
146
209
|
private
|
|
147
210
|
|
|
148
211
|
##
|
|
@@ -153,6 +216,8 @@ module LLM::ActiveRecord
|
|
|
153
216
|
provider = self[columns[:provider_column]]
|
|
154
217
|
kwargs = resolve_options(options[:provider])
|
|
155
218
|
@llm ||= LLM.method(provider).call(**kwargs)
|
|
219
|
+
@llm.tracer = resolve_option(options[:tracer]) if options[:tracer]
|
|
220
|
+
@llm
|
|
156
221
|
end
|
|
157
222
|
|
|
158
223
|
##
|
data/lib/llm/active_record.rb
CHANGED
data/lib/llm/agent.rb
CHANGED
|
@@ -6,10 +6,18 @@ module LLM
|
|
|
6
6
|
# reusable, preconfigured assistants with defaults for model,
|
|
7
7
|
# tools, schema, and instructions.
|
|
8
8
|
#
|
|
9
|
+
# It wraps the same stateful runtime surface as
|
|
10
|
+
# {LLM::Context LLM::Context}: message history, usage, persistence,
|
|
11
|
+
# streaming parameters, and provider-backed requests still flow through
|
|
12
|
+
# an underlying context. The defining behavior of an agent is that it
|
|
13
|
+
# automatically resolves pending tool calls for you during `talk` and
|
|
14
|
+
# `respond`, instead of leaving tool loops to the caller.
|
|
15
|
+
#
|
|
9
16
|
# **Notes:**
|
|
10
17
|
# * Instructions are injected only on the first request.
|
|
11
|
-
# * An agent
|
|
12
|
-
# *
|
|
18
|
+
# * An agent automatically executes tool loops (unlike {LLM::Context LLM::Context}).
|
|
19
|
+
# * Tool loop execution can be configured with `concurrency :call`,
|
|
20
|
+
# `:thread`, `:task`, or `:fiber`.
|
|
13
21
|
#
|
|
14
22
|
# @example
|
|
15
23
|
# class SystemAdmin < LLM::Agent
|
|
@@ -72,6 +80,21 @@ module LLM
|
|
|
72
80
|
@instructions = instructions
|
|
73
81
|
end
|
|
74
82
|
|
|
83
|
+
##
|
|
84
|
+
# Set or get the tool execution concurrency.
|
|
85
|
+
#
|
|
86
|
+
# @param [Symbol, nil] concurrency
|
|
87
|
+
# Controls how pending tool loops are executed:
|
|
88
|
+
# - `:call`: sequential calls
|
|
89
|
+
# - `:thread`: concurrent threads
|
|
90
|
+
# - `:task`: concurrent async tasks
|
|
91
|
+
# - `:fiber`: concurrent raw fibers
|
|
92
|
+
# @return [Symbol, nil]
|
|
93
|
+
def self.concurrency(concurrency = nil)
|
|
94
|
+
return @concurrency if concurrency.nil?
|
|
95
|
+
@concurrency = concurrency
|
|
96
|
+
end
|
|
97
|
+
|
|
75
98
|
##
|
|
76
99
|
# @param [LLM::Provider] provider
|
|
77
100
|
# A provider
|
|
@@ -82,8 +105,10 @@ module LLM
|
|
|
82
105
|
# @option params [String] :model Defaults to the provider's default model
|
|
83
106
|
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
84
107
|
# @option params [#to_json, nil] :schema Defaults to nil
|
|
108
|
+
# @option params [Symbol, nil] :concurrency Defaults to the agent class concurrency
|
|
85
109
|
def initialize(llm, params = {})
|
|
86
110
|
defaults = {model: self.class.model, tools: self.class.tools, schema: self.class.schema}.compact
|
|
111
|
+
@concurrency = params.delete(:concurrency) || self.class.concurrency
|
|
87
112
|
@llm = llm
|
|
88
113
|
@ctx = LLM::Context.new(llm, defaults.merge(params))
|
|
89
114
|
end
|
|
@@ -94,7 +119,7 @@ module LLM
|
|
|
94
119
|
#
|
|
95
120
|
# @param prompt (see LLM::Provider#complete)
|
|
96
121
|
# @param [Hash] params The params passed to the provider, including optional :stream, :tools, :schema etc.
|
|
97
|
-
# @option params [Integer] :
|
|
122
|
+
# @option params [Integer] :tool_attempts The maxinum number of tool call iterations (default 10)
|
|
98
123
|
# @return [LLM::Response] Returns the LLM's response for this turn.
|
|
99
124
|
# @example
|
|
100
125
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
@@ -102,13 +127,13 @@ module LLM
|
|
|
102
127
|
# response = agent.talk("Hello, what is your name?")
|
|
103
128
|
# puts response.choices[0].content
|
|
104
129
|
def talk(prompt, params = {})
|
|
105
|
-
|
|
130
|
+
max = Integer(params.delete(:tool_attempts) || 10)
|
|
106
131
|
res = @ctx.talk(apply_instructions(prompt), params)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
res = @ctx.talk
|
|
110
|
-
i += 1
|
|
132
|
+
max.times do
|
|
133
|
+
break if @ctx.functions.empty?
|
|
134
|
+
res = @ctx.talk(call_functions, params)
|
|
111
135
|
end
|
|
136
|
+
raise LLM::ToolLoopError, "pending tool calls remain" unless @ctx.functions.empty?
|
|
112
137
|
res
|
|
113
138
|
end
|
|
114
139
|
alias_method :chat, :talk
|
|
@@ -120,7 +145,7 @@ module LLM
|
|
|
120
145
|
# @note Not all LLM providers support this API
|
|
121
146
|
# @param prompt (see LLM::Provider#complete)
|
|
122
147
|
# @param [Hash] params The params passed to the provider, including optional :stream, :tools, :schema etc.
|
|
123
|
-
# @option params [Integer] :
|
|
148
|
+
# @option params [Integer] :tool_attempts The maxinum number of tool call iterations (default 10)
|
|
124
149
|
# @return [LLM::Response] Returns the LLM's response for this turn.
|
|
125
150
|
# @example
|
|
126
151
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
@@ -128,13 +153,13 @@ module LLM
|
|
|
128
153
|
# res = agent.respond("What is the capital of France?")
|
|
129
154
|
# puts res.output_text
|
|
130
155
|
def respond(prompt, params = {})
|
|
131
|
-
|
|
156
|
+
max = Integer(params.delete(:tool_attempts) || 10)
|
|
132
157
|
res = @ctx.respond(apply_instructions(prompt), params)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
res = @ctx.respond
|
|
136
|
-
i += 1
|
|
158
|
+
max.times do
|
|
159
|
+
break if @ctx.functions.empty?
|
|
160
|
+
res = @ctx.respond(call_functions, params)
|
|
137
161
|
end
|
|
162
|
+
raise LLM::ToolLoopError, "pending tool calls remain" unless @ctx.functions.empty?
|
|
138
163
|
res
|
|
139
164
|
end
|
|
140
165
|
|
|
@@ -150,12 +175,41 @@ module LLM
|
|
|
150
175
|
@ctx.functions
|
|
151
176
|
end
|
|
152
177
|
|
|
178
|
+
##
|
|
179
|
+
# @see LLM::Context#returns
|
|
180
|
+
# @return [Array<LLM::Function::Return>]
|
|
181
|
+
def returns
|
|
182
|
+
@ctx.returns
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# @see LLM::Context#call
|
|
187
|
+
# @return [Object]
|
|
188
|
+
def call(...)
|
|
189
|
+
@ctx.call(...)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
##
|
|
193
|
+
# @see LLM::Context#wait
|
|
194
|
+
# @return [Array<LLM::Function::Return>]
|
|
195
|
+
def wait(...)
|
|
196
|
+
@ctx.wait(...)
|
|
197
|
+
end
|
|
198
|
+
|
|
153
199
|
##
|
|
154
200
|
# @return [LLM::Object]
|
|
155
201
|
def usage
|
|
156
202
|
@ctx.usage
|
|
157
203
|
end
|
|
158
204
|
|
|
205
|
+
##
|
|
206
|
+
# Interrupt the active request, if any.
|
|
207
|
+
# @return [nil]
|
|
208
|
+
def interrupt!
|
|
209
|
+
@ctx.interrupt!
|
|
210
|
+
end
|
|
211
|
+
alias_method :cancel!, :interrupt!
|
|
212
|
+
|
|
159
213
|
##
|
|
160
214
|
# @param (see LLM::Context#prompt)
|
|
161
215
|
# @return (see LLM::Context#prompt)
|
|
@@ -206,6 +260,53 @@ module LLM
|
|
|
206
260
|
@ctx.model
|
|
207
261
|
end
|
|
208
262
|
|
|
263
|
+
##
|
|
264
|
+
# @return [Symbol]
|
|
265
|
+
def mode
|
|
266
|
+
@ctx.mode
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
##
|
|
270
|
+
# Returns the configured tool execution concurrency.
|
|
271
|
+
# @return [Symbol, nil]
|
|
272
|
+
def concurrency
|
|
273
|
+
@concurrency
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
##
|
|
277
|
+
# @see LLM::Context#cost
|
|
278
|
+
# @return [LLM::Cost]
|
|
279
|
+
def cost
|
|
280
|
+
@ctx.cost
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
##
|
|
284
|
+
# @see LLM::Context#context_window
|
|
285
|
+
# @return [Integer]
|
|
286
|
+
def context_window
|
|
287
|
+
@ctx.context_window
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
##
|
|
291
|
+
# @see LLM::Context#to_h
|
|
292
|
+
# @return [Hash]
|
|
293
|
+
def to_h
|
|
294
|
+
@ctx.to_h
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
##
|
|
298
|
+
# @return [String]
|
|
299
|
+
def to_json(...)
|
|
300
|
+
to_h.to_json(...)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
##
|
|
304
|
+
# @return [String]
|
|
305
|
+
def inspect
|
|
306
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} " \
|
|
307
|
+
"@llm=#{@llm.class}, @mode=#{mode.inspect}, @messages=#{messages.inspect}>"
|
|
308
|
+
end
|
|
309
|
+
|
|
209
310
|
##
|
|
210
311
|
# @param (see LLM::Context#serialize)
|
|
211
312
|
# @return (see LLM::Context#serialize)
|
|
@@ -230,14 +331,24 @@ module LLM
|
|
|
230
331
|
instr = self.class.instructions
|
|
231
332
|
return new_prompt unless instr
|
|
232
333
|
if LLM::Prompt === new_prompt
|
|
233
|
-
|
|
334
|
+
new_prompt.system(instr) if @ctx.messages.empty?
|
|
234
335
|
new_prompt
|
|
235
336
|
else
|
|
236
337
|
prompt do
|
|
237
|
-
|
|
338
|
+
_1.system(instr) if @ctx.messages.empty?
|
|
238
339
|
_1.user(new_prompt)
|
|
239
340
|
end
|
|
240
341
|
end
|
|
241
342
|
end
|
|
343
|
+
|
|
344
|
+
##
|
|
345
|
+
# @return [Array<LLM::Function::Return>]
|
|
346
|
+
def call_functions
|
|
347
|
+
case concurrency || :call
|
|
348
|
+
when :call then call(:functions)
|
|
349
|
+
when :thread, :task, :fiber then wait(concurrency)
|
|
350
|
+
else raise ArgumentError, "Unknown concurrency: #{concurrency.inspect}. Expected :call, :thread, :task, or :fiber"
|
|
351
|
+
end
|
|
352
|
+
end
|
|
242
353
|
end
|
|
243
354
|
end
|
data/lib/llm/sequel/plugin.rb
CHANGED
|
@@ -13,7 +13,8 @@ module LLM::Sequel
|
|
|
13
13
|
# default) or as a structured object (`format: :json` / `:jsonb`) for
|
|
14
14
|
# databases such as PostgreSQL that can persist JSON natively.
|
|
15
15
|
# `:json` and `:jsonb` expect a real JSON column type with Sequel handling
|
|
16
|
-
# JSON typecasting for the model.
|
|
16
|
+
# JSON typecasting for the model. A `tracer:` proc can also be configured
|
|
17
|
+
# to assign a fiber-local tracer onto the resolved provider.
|
|
17
18
|
module Plugin
|
|
18
19
|
EMPTY_HASH = {}.freeze
|
|
19
20
|
DEFAULT_USAGE_COLUMNS = {
|
|
@@ -27,6 +28,7 @@ module LLM::Sequel
|
|
|
27
28
|
data_column: :data,
|
|
28
29
|
format: :string,
|
|
29
30
|
usage_columns: DEFAULT_USAGE_COLUMNS,
|
|
31
|
+
tracer: nil,
|
|
30
32
|
provider: EMPTY_HASH,
|
|
31
33
|
context: EMPTY_HASH
|
|
32
34
|
}.freeze
|
|
@@ -59,6 +61,9 @@ module LLM::Sequel
|
|
|
59
61
|
# Storage format for the serialized context. Use `:string` for text
|
|
60
62
|
# columns, or `:json` / `:jsonb` for structured JSON columns with Sequel
|
|
61
63
|
# JSON typecasting enabled.
|
|
64
|
+
# @option options [Proc, LLM::Tracer, nil] :tracer
|
|
65
|
+
# Optional tracer or proc that resolves to one and is assigned through
|
|
66
|
+
# `llm.tracer = ...` on the resolved provider.
|
|
62
67
|
# @return [void]
|
|
63
68
|
def self.configure(model, options = EMPTY_HASH)
|
|
64
69
|
options = DEFAULTS.merge(options)
|
|
@@ -111,6 +116,13 @@ module LLM::Sequel
|
|
|
111
116
|
ctx.call(...)
|
|
112
117
|
end
|
|
113
118
|
|
|
119
|
+
##
|
|
120
|
+
# @see LLM::Context#mode
|
|
121
|
+
# @return [Symbol]
|
|
122
|
+
def mode
|
|
123
|
+
ctx.mode
|
|
124
|
+
end
|
|
125
|
+
|
|
114
126
|
##
|
|
115
127
|
# @see LLM::Context#messages
|
|
116
128
|
# @return [Array<LLM::Message>]
|
|
@@ -134,6 +146,13 @@ module LLM::Sequel
|
|
|
134
146
|
ctx.functions
|
|
135
147
|
end
|
|
136
148
|
|
|
149
|
+
##
|
|
150
|
+
# @see LLM::Context#returns
|
|
151
|
+
# @return [Array<LLM::Function::Return>]
|
|
152
|
+
def returns
|
|
153
|
+
ctx.returns
|
|
154
|
+
end
|
|
155
|
+
|
|
137
156
|
##
|
|
138
157
|
# @see LLM::Context#cost
|
|
139
158
|
# @return [LLM::Cost]
|
|
@@ -161,6 +180,50 @@ module LLM::Sequel
|
|
|
161
180
|
)
|
|
162
181
|
end
|
|
163
182
|
|
|
183
|
+
##
|
|
184
|
+
# @see LLM::Context#interrupt!
|
|
185
|
+
# @return [nil]
|
|
186
|
+
def interrupt!
|
|
187
|
+
ctx.interrupt!
|
|
188
|
+
end
|
|
189
|
+
alias_method :cancel!, :interrupt!
|
|
190
|
+
|
|
191
|
+
##
|
|
192
|
+
# @see LLM::Context#prompt
|
|
193
|
+
# @return [LLM::Prompt]
|
|
194
|
+
def prompt(&)
|
|
195
|
+
ctx.prompt(&)
|
|
196
|
+
end
|
|
197
|
+
alias_method :build_prompt, :prompt
|
|
198
|
+
|
|
199
|
+
##
|
|
200
|
+
# @see LLM::Context#image_url
|
|
201
|
+
# @return [LLM::Object]
|
|
202
|
+
def image_url(...)
|
|
203
|
+
ctx.image_url(...)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
##
|
|
207
|
+
# @see LLM::Context#local_file
|
|
208
|
+
# @return [LLM::Object]
|
|
209
|
+
def local_file(...)
|
|
210
|
+
ctx.local_file(...)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
##
|
|
214
|
+
# @see LLM::Context#remote_file
|
|
215
|
+
# @return [LLM::Object]
|
|
216
|
+
def remote_file(...)
|
|
217
|
+
ctx.remote_file(...)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
##
|
|
221
|
+
# @see LLM::Context#tracer
|
|
222
|
+
# @return [LLM::Tracer]
|
|
223
|
+
def tracer
|
|
224
|
+
ctx.tracer
|
|
225
|
+
end
|
|
226
|
+
|
|
164
227
|
private
|
|
165
228
|
|
|
166
229
|
##
|
|
@@ -171,6 +234,8 @@ module LLM::Sequel
|
|
|
171
234
|
provider = self[columns[:provider_column]]
|
|
172
235
|
kwargs = resolve_options(options[:provider])
|
|
173
236
|
@llm ||= LLM.method(provider).call(**kwargs)
|
|
237
|
+
@llm.tracer = resolve_option(options[:tracer]) if options[:tracer]
|
|
238
|
+
@llm
|
|
174
239
|
end
|
|
175
240
|
|
|
176
241
|
##
|
data/lib/llm/version.rb
CHANGED
data/llm.gemspec
CHANGED
|
@@ -8,25 +8,19 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Antar Azri", "0x1eef", "Christos Maris", "Rodrigo Serrano"]
|
|
9
9
|
spec.email = ["azantar@proton.me", "0x1eef@hardenedbsd.org"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "
|
|
11
|
+
spec.summary = "Lightweight runtime for building capable AI systems in Ruby."
|
|
12
12
|
|
|
13
13
|
spec.description = <<~DESCRIPTION
|
|
14
|
-
llm.rb is a runtime for building AI systems
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
state
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
state across requests, jobs, or retries.
|
|
25
|
-
|
|
26
|
-
Most LLM libraries stop at request/response APIs. Building real systems
|
|
27
|
-
means stitching together streaming, tools, state, persistence, and external
|
|
28
|
-
services by hand. llm.rb provides a single execution model for all of these,
|
|
29
|
-
so they compose naturally instead of becoming separate subsystems.
|
|
14
|
+
llm.rb is a lightweight runtime for building capable AI systems in Ruby.
|
|
15
|
+
It is not just an API wrapper. llm.rb gives you one runtime for providers,
|
|
16
|
+
contexts, agents, tools, MCP servers, streaming, schemas, files, and
|
|
17
|
+
persisted state, so real systems can be built out of one coherent
|
|
18
|
+
execution model instead of a pile of adapters. It stays close to Ruby, runs
|
|
19
|
+
on the standard library by default, loads optional pieces only when needed,
|
|
20
|
+
works naturally in Rails or ActiveRecord through acts_as_llm, includes
|
|
21
|
+
built-in Sequel support through plugin :llm, and is designed for
|
|
22
|
+
engineers who want control over long-lived, tool-capable, stateful AI
|
|
23
|
+
workflows instead of just request/response helpers.
|
|
30
24
|
DESCRIPTION
|
|
31
25
|
|
|
32
26
|
spec.license = "0BSD"
|
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.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antar Azri
|
|
@@ -195,22 +195,16 @@ dependencies:
|
|
|
195
195
|
- !ruby/object:Gem::Version
|
|
196
196
|
version: '1.7'
|
|
197
197
|
description: |
|
|
198
|
-
llm.rb is a runtime for building AI systems
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
state
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
state across requests, jobs, or retries.
|
|
209
|
-
|
|
210
|
-
Most LLM libraries stop at request/response APIs. Building real systems
|
|
211
|
-
means stitching together streaming, tools, state, persistence, and external
|
|
212
|
-
services by hand. llm.rb provides a single execution model for all of these,
|
|
213
|
-
so they compose naturally instead of becoming separate subsystems.
|
|
198
|
+
llm.rb is a lightweight runtime for building capable AI systems in Ruby.
|
|
199
|
+
It is not just an API wrapper. llm.rb gives you one runtime for providers,
|
|
200
|
+
contexts, agents, tools, MCP servers, streaming, schemas, files, and
|
|
201
|
+
persisted state, so real systems can be built out of one coherent
|
|
202
|
+
execution model instead of a pile of adapters. It stays close to Ruby, runs
|
|
203
|
+
on the standard library by default, loads optional pieces only when needed,
|
|
204
|
+
works naturally in Rails or ActiveRecord through acts_as_llm, includes
|
|
205
|
+
built-in Sequel support through plugin :llm, and is designed for
|
|
206
|
+
engineers who want control over long-lived, tool-capable, stateful AI
|
|
207
|
+
workflows instead of just request/response helpers.
|
|
214
208
|
email:
|
|
215
209
|
- azantar@proton.me
|
|
216
210
|
- 0x1eef@hardenedbsd.org
|
|
@@ -229,6 +223,7 @@ files:
|
|
|
229
223
|
- data/zai.json
|
|
230
224
|
- lib/llm.rb
|
|
231
225
|
- lib/llm/active_record.rb
|
|
226
|
+
- lib/llm/active_record/acts_as_agent.rb
|
|
232
227
|
- lib/llm/active_record/acts_as_llm.rb
|
|
233
228
|
- lib/llm/agent.rb
|
|
234
229
|
- lib/llm/bot.rb
|
|
@@ -410,5 +405,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
410
405
|
requirements: []
|
|
411
406
|
rubygems_version: 3.6.9
|
|
412
407
|
specification_version: 4
|
|
413
|
-
summary:
|
|
408
|
+
summary: Lightweight runtime for building capable AI systems in Ruby.
|
|
414
409
|
test_files: []
|