llm.rb 15.0.1 → 15.0.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ce996689c82e7a0d62a7f09ab2545d10b36462fc75cc2feeb57ce26f6523585
4
- data.tar.gz: 74cf140b0fd11b077aea57bde027f0b00cf67f4e099ad35778bc5042b958469b
3
+ metadata.gz: b2700592b8b9fbb85694c24a0a7379a84494b0e4f2157d6302fad1552df9d684
4
+ data.tar.gz: 43864e55f008478a40860511f9ca138f86010695225f91f0b2a07fbcff1ed448
5
5
  SHA512:
6
- metadata.gz: 4274138e7054d9db54e121dcad674f307b0e1bdbfc568ac16bb3b13181e873a77a57d19f4d2eef2479216555ba0ed9213fa7372cf051d4fa73f6fe73dae594cd
7
- data.tar.gz: 12219c398f482c7f949bc9964038a2597e74a592b78023b101e96a337605c2e2fb8963f5526fbd4ca8f1ffd8b14b843e1a10334369d2f62a1e393eb409f4821d
6
+ metadata.gz: 464e203f6e4dc1fb2ea2aaa5bd4a2f95753d569190e63e4cd590b014aed4e369fa2f2835a4d6d95394b6988210c880e976abd5d8e8c69f015b057bb1f193609d
7
+ data.tar.gz: 9303d2475e2bd32dc53b65b7b9d54fa28611616a0714015bd1c0d74e8ca7cd66e6d88889b73231ed19dda38b2f2888ded08569694f23bd834a8e7c1f3fd6db35
data/CHANGELOG.md CHANGED
@@ -15,6 +15,39 @@
15
15
 
16
16
  ## What's next
17
17
 
18
+ ## v15.0.3
19
+
20
+ Changes since `v15.0.2`.
21
+
22
+ This release fixes ActiveRecord `:json`/`:jsonb` serialization so tool
23
+ call arguments round-trip as JSON objects instead of arrays of pairs.
24
+
25
+ ### Fix
26
+
27
+ * **activerecord: serialize tool call arguments properly** <br>
28
+ Fix a bug where the ActiveRecord `:json`/`:jsonb` layer serialized tool
29
+ call arguments as an array of pairs instead of a Hash. The context is
30
+ now serialized through its JSON form, so tool call arguments round-trip
31
+ as JSON objects that providers accept.
32
+
33
+ ## v15.0.2
34
+
35
+ Changes since `v15.0.1`.
36
+
37
+ This release fixes a concurrency race in MCP tool calls by
38
+ reference-counting the transport session, so overlapping tool calls
39
+ reuse the running transport instead of racing `start`/`stop`.
40
+
41
+ ### Fix
42
+
43
+ * **mcp: fix concurrent MCP tool call race** <br>
44
+ `LLM::MCP` now reference-counts its transport session: the first
45
+ caller starts the transport and the last caller stops it. Concurrent
46
+ or overlapping tool calls reuse the running transport instead of
47
+ racing `start`/`stop`, avoiding "MCP transport is not running" errors
48
+ that could occur with the `async` strategy. An externally started
49
+ transport is never stopped by a borrower.
50
+
18
51
  ## v15.0.1
19
52
 
20
53
  Changes since `v15.0.0`.
data/README.md CHANGED
@@ -15,15 +15,16 @@
15
15
  Welcome to the canonical llm.rb repository.
16
16
 
17
17
  llm.rb is an advanced runtime for building agentic AI applications
18
- on CRuby. It has zero runtime dependencies by default, it supports
18
+ on CRuby. It has zero runtime dependencies by default, supports
19
19
  concurrent and parallel tool execution and has a single coherent API
20
20
  that spans 13+ providers. Streaming, tools, guards, compaction, the
21
21
  REPL, builtin MCP/A2A support and the database integrations all build
22
22
  on the same three concepts: providers, contexts, and agents.
23
23
 
24
- Once you learn the fundamentals, everything else falls into place
25
- naturally. Some features, such as ActiveRecord support, require
26
- optional dependencies that are opt-in.
24
+ The most effective way to learn about llm.rb is to ask [the r.uby.dev chatbot](https://r.uby.dev)
25
+ a question. It is connected to the llm.rb GitHub repository, backed by
26
+ ActiveRecord and uses the builtin MCP feature to connect to GitHub. All
27
+ answers are grounded in the llm.rb source code.
27
28
 
28
29
  ## Install
29
30
 
@@ -39,8 +40,7 @@ The
39
40
  [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
40
41
  class is the default high-level interface,
41
42
  and it is recommended for most use-cases. It manages tool execution
42
- automatically and
43
- [guards against infinite loops](https://r.uby.dev/llm/deepdive/advanced/guard),
43
+ automatically and guards against infinite loops,
44
44
  manages conversation state, and much more.
45
45
 
46
46
  ```ruby
@@ -61,9 +61,6 @@ reasoning, tool calls, tool returns, and compaction.
61
61
  Streams can also observe message transformers, which rewrite
62
62
  outgoing messages before they reach the provider.
63
63
 
64
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/stream/)
65
- to learn more.
66
-
67
64
  ```ruby
68
65
  class MyStream < LLM::Stream
69
66
  # Visible assistant output.
@@ -133,7 +130,7 @@ call them on your behalf, and they're one of the most powerful features
133
130
  for extending the feature set or abilities of a model.
134
131
 
135
132
  The runtime also ships with a catalog of built-in tools for
136
- filesystem, search, and shell operations. <br> See the [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/tools) to learn more.
133
+ filesystem, search, and shell operations.
137
134
 
138
135
  ```ruby
139
136
  class ReadFile < LLM::Tool
@@ -168,8 +165,6 @@ callback hands back the subagent that ran the skill, so you can inspect
168
165
  its conversation, measure its usage, track costs or add a verification
169
166
  step (eg `subagent.talk("verify your work")`).
170
167
 
171
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/skills) to learn more.
172
-
173
168
  ##### summary.md
174
169
 
175
170
  ```markdown
@@ -209,8 +204,6 @@ with the `:fork` and `:ractor` strategies. The
209
204
  The `:fork` strategy also provides a separate process that offers
210
205
  isolation from its parent.
211
206
 
212
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/features/concurrency) to learn more.
213
-
214
207
  ```ruby
215
208
  require "llm"
216
209
  require "llm/tools"
@@ -235,8 +228,6 @@ the control channel, a ractor via message passing, and pending tools
235
228
  are stopped before they run. The in-flight HTTP request is closed
236
229
  too, so a turn you no longer want stops without burning tokens.
237
230
 
238
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/advanced/cancellation) to learn more.
239
-
240
231
  ```ruby
241
232
  llm = LLM.deepseek(key: ENV["KEY"])
242
233
  agent = LLM::Agent.new(llm)
@@ -261,8 +252,6 @@ The REPL stands out because it connects to the surrounding
261
252
  runtime and it can be extended by your code. Think of it as
262
253
  `binding.pry` but for agents.
263
254
 
264
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/features/repl) to learn more.
265
-
266
255
  ##### Demo
267
256
 
268
257
  [Watch in high quality on asciinema](https://asciinema.org/a/OsS8wwaasKasoDDz)
@@ -320,8 +309,6 @@ code. For database-backed persistence, ActiveRecord and Sequel
320
309
  integrations are also available. All persistence options use the same
321
310
  underlying serialization.
322
311
 
323
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/features/database) to learn more.
324
-
325
312
  ```ruby
326
313
  require "llm"
327
314
 
@@ -348,8 +335,6 @@ for both Rack-based / Rails-based applications. On databases
348
335
  where it is supported, such as PostgreSQL, the column can be optimized by using
349
336
  the `jsonb` type.
350
337
 
351
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/features/database) to learn more.
352
-
353
338
  ```ruby
354
339
  require "active_record"
355
340
  require "llm"
@@ -411,9 +396,6 @@ used with
411
396
  [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) or
412
397
  [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
413
398
 
414
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/protocols/mcp/), and the
415
- [deepdive.md on persistent connections](https://r.uby.dev/llm/deepdive/features/transports) to learn more.
416
-
417
399
  ```ruby
418
400
  require "llm"
419
401
 
@@ -434,9 +416,6 @@ used with
434
416
  [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) or
435
417
  [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
436
418
 
437
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/protocols/a2a/), and the
438
- [deepdive.md on persistent connections](https://r.uby.dev/llm/deepdive/features/transports) to learn more.
439
-
440
419
  ```ruby
441
420
  require "llm"
442
421
 
@@ -509,8 +488,6 @@ and implement
509
488
  The pending call arrives as `function:`. Return a value to close
510
489
  the call, or `nil` to let it run:
511
490
 
512
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/advanced/guard) to learn more.
513
-
514
491
  ```ruby
515
492
  class PolicyGuard < LLM::Guard
516
493
  def call(function:)
@@ -536,8 +513,6 @@ Create a subclass and implement `call(message:)` to scrub sensitive data,
536
513
  inject context, or normalize content. The transform runs automatically
537
514
  on every turn, so you never have to change your prompt code.
538
515
 
539
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/advanced/transformer) to learn more.
540
-
541
516
  ```ruby
542
517
  class RedactEmails < LLM::Transformer
543
518
  def call(message:)
@@ -574,8 +549,6 @@ and
574
549
  [`LLM::Stream#on_compaction_finish`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_compaction_finish)
575
550
  callbacks.
576
551
 
577
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/advanced/compaction) to learn more.
578
-
579
552
  ```ruby
580
553
  llm = LLM.deepseek(key: ENV["KEY"])
581
554
  agent = LLM::Agent.new(
@@ -624,8 +597,6 @@ exports spans via OTLP for OpenTelemetry in production.
624
597
  * [`LLM::Tracer::Logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Logger.html):
625
598
  structured JSON to stdout or a file.
626
599
 
627
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/reference/tracer) to learn more.
628
-
629
600
  ```ruby
630
601
  llm = LLM.deepseek(key: ENV["KEY"])
631
602
  agent = LLM::Agent.new(llm, tracer: LLM::Tracer::PrettyLogger.new(llm))
@@ -669,9 +640,8 @@ Each provider is constructed with a class-level factory method on
669
640
  [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
670
641
  or
671
642
  [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html). The
672
- same API drives every one of them, so switching models is a one-line
673
- change. See the [deepdive](https://r.uby.dev/llm/deepdive/fundamentals/providers)
674
- for a full provider reference.
643
+ same API drives every one of them, so switching providers is a one-line
644
+ change.
675
645
 
676
646
  #### What providers does llm.rb support?
677
647
 
@@ -734,8 +704,6 @@ modalities with the gem, sourced from [models.dev](https://models.dev).
734
704
  Reach it from any provider, context, or agent, enumerate models, or
735
705
  sort them by price.
736
706
 
737
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/reference/model_registry) to learn more.
738
-
739
707
  ```ruby
740
708
  require "llm"
741
709
 
@@ -758,8 +726,6 @@ is always available and the default, `net/http/persistent` pools
758
726
  connections for many requests to the same host, and `curb` wraps
759
727
  libcurl. They share one interface, so switching is a one-word change.
760
728
 
761
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/advanced/transports) to learn more.
762
-
763
729
  ```ruby
764
730
  llm = LLM.deepseek(
765
731
  key: ENV["KEY"],
@@ -781,8 +747,6 @@ llm.rb also includes support for OpenAI's vector store API. It
781
747
  provides a vector database as a HTTP service but we won't cover
782
748
  that here.
783
749
 
784
- See the [deepdive.md](https://r.uby.dev/llm/deepdive/features/embeddings) to learn more.
785
-
786
750
  ```ruby
787
751
  require "llm"
788
752
 
@@ -921,11 +885,11 @@ it as my primary driver for development.
921
885
 
922
886
  ## Resources
923
887
 
924
- If you like what you read so far, check out the [deepdive.md](https://r.uby.dev/llm/deepdive/)
925
- to learn more. Unfortunately it
926
- wasn't possible to cover every feature without the README becoming a small book.
927
- The [r.uby.dev](https://r.uby.dev) homepage also includes more learning material
928
- and resources.
888
+ The [r.uby.dev chatbot](https://r.uby.dev) is connected
889
+ to this very GitHub repository. It can read documentation,
890
+ source code, issues, and pull requests. The [docs/](docs/)
891
+ directory contains the full documentation and the chatbot
892
+ can find the answers to your questions there.
929
893
 
930
894
  ## License
931
895
 
@@ -39,7 +39,7 @@ module LLM::ActiveRecord
39
39
  def self.serialize_context(ctx, format)
40
40
  case format
41
41
  when :string then ctx.to_json
42
- when :json, :jsonb then ctx.to_h
42
+ when :json, :jsonb then LLM.json.load(ctx.to_json)
43
43
  else raise ArgumentError, "Unknown format: #{format.inspect}"
44
44
  end
45
45
  end
data/lib/llm/mcp.rb CHANGED
@@ -87,7 +87,10 @@ class LLM::MCP
87
87
  # @return [LLM::MCP] A new MCP instance
88
88
  def initialize(stdio: nil, http: nil, timeout: 30)
89
89
  @timeout = timeout
90
- if stdio && http
90
+ @lock = Mutex.new
91
+ @borrowers = 0
92
+ @owned = false
93
+ if stdio and http
91
94
  raise ArgumentError, "stdio and http are mutually exclusive"
92
95
  elsif stdio
93
96
  @command = Command.new(**stdio)
@@ -125,7 +128,10 @@ class LLM::MCP
125
128
  # Propagates errors raised by {#start}, the block itself, or {#stop}
126
129
  # @return [void]
127
130
  def run
128
- start
131
+ @lock.synchronize do
132
+ start
133
+ @owned = false
134
+ end
129
135
  yield
130
136
  ensure
131
137
  stop
@@ -182,13 +188,32 @@ class LLM::MCP
182
188
 
183
189
  attr_reader :command, :transport, :timeout
184
190
 
191
+ ##
192
+ # Borrows the transport for the duration of the block.
193
+ #
194
+ # The first borrower starts the transport, concurrent borrowers reuse
195
+ # it, and the last borrower stops it, so overlapping tool calls never
196
+ # race `start`/`stop` and trip over "MCP transport is not running".
197
+ # An externally started transport is never stopped by a borrower.
198
+ # @yield Runs while the transport is running
199
+ # @return [void]
185
200
  def with_session
186
- return yield if transport.running?
187
- session_started = true
188
- start
201
+ @lock.synchronize do
202
+ @borrowers += 1
203
+ unless transport.running?
204
+ start
205
+ @owned = true
206
+ end
207
+ end
189
208
  yield
190
209
  ensure
191
- stop if session_started
210
+ @lock.synchronize do
211
+ @borrowers -= 1
212
+ if @borrowers.zero? and @owned
213
+ @owned = false
214
+ stop
215
+ end
216
+ end
192
217
  end
193
218
 
194
219
  def adapt_content(content)
data/lib/llm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LLM
4
- VERSION = "15.0.1"
4
+ VERSION = "15.0.3"
5
5
  end
data/llm.gemspec CHANGED
@@ -40,13 +40,13 @@ DESCRIPTION
40
40
  spec.post_install_message = "\n" \
41
41
  "Got a question about llm.rb? " \
42
42
  "\n" \
43
- "Ask our chatbot." \
43
+ "Ask the https://r.uby.dev chatbot." \
44
44
  "\n" \
45
45
  "It is connected to the official GitHub repository." \
46
46
  "\n" \
47
47
  "100% free to use." \
48
48
  "\n" \
49
- "https://r.uby.dev" \
49
+ "Built with llm.rb and DeepSeek." \
50
50
  "\n\n"
51
51
 
52
52
  spec.add_development_dependency "webmock", "~> 3.24.0"
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: 15.0.1
4
+ version: 15.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Gleeson
@@ -652,8 +652,9 @@ metadata:
652
652
  source_code_uri: https://github.com/r-uby-dev/llm
653
653
  documentation_uri: https://r.uby.dev
654
654
  changelog_uri: https://github.com/r-uby-dev/llm/blob/main/CHANGELOG.md
655
- post_install_message: "\nGot a question about llm.rb? \nAsk our chatbot.\nIt is connected
656
- to the official GitHub repository.\n100% free to use.\nhttps://r.uby.dev\n\n"
655
+ post_install_message: "\nGot a question about llm.rb? \nAsk the https://r.uby.dev
656
+ chatbot.\nIt is connected to the official GitHub repository.\n100% free to use.\nBuilt
657
+ with llm.rb and DeepSeek.\n\n"
657
658
  rdoc_options: []
658
659
  require_paths:
659
660
  - lib