llm.rb 13.1.0 → 14.0.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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +320 -0
  3. data/README.md +340 -31
  4. data/bin/llm.rb +36 -12
  5. data/data/anthropic.json +206 -263
  6. data/data/bedrock.json +2138 -1860
  7. data/data/deepinfra.json +1003 -624
  8. data/data/deepseek.json +38 -34
  9. data/data/google.json +1079 -371
  10. data/data/mistral.json +448 -368
  11. data/data/moonshot.json +384 -0
  12. data/data/openai.json +974 -1343
  13. data/data/xai.json +154 -126
  14. data/data/zai.json +191 -191
  15. data/lib/llm/agent.rb +47 -14
  16. data/lib/llm/context.rb +71 -88
  17. data/lib/llm/cost.rb +23 -17
  18. data/lib/llm/error.rb +0 -8
  19. data/lib/llm/function/async/task.rb +2 -0
  20. data/lib/llm/function/fiber/task.rb +2 -0
  21. data/lib/llm/function/fork/task.rb +2 -0
  22. data/lib/llm/function/ractor/task.rb +2 -0
  23. data/lib/llm/function/sequential/group.rb +4 -1
  24. data/lib/llm/function/sequential/task.rb +1 -1
  25. data/lib/llm/function/task.rb +4 -0
  26. data/lib/llm/function/thread/task.rb +2 -0
  27. data/lib/llm/function.rb +32 -4
  28. data/lib/llm/guard/loop.rb +89 -0
  29. data/lib/llm/guard/null.rb +19 -0
  30. data/lib/llm/guard.rb +61 -0
  31. data/lib/llm/provider.rb +36 -0
  32. data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
  33. data/lib/llm/providers/anthropic.rb +1 -8
  34. data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
  35. data/lib/llm/providers/bedrock.rb +1 -8
  36. data/lib/llm/providers/google/stream_parser.rb +1 -0
  37. data/lib/llm/providers/google.rb +1 -8
  38. data/lib/llm/providers/moonshot.rb +76 -0
  39. data/lib/llm/providers/ollama.rb +1 -8
  40. data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
  41. data/lib/llm/providers/openai/responses.rb +6 -8
  42. data/lib/llm/providers/openai/stream_parser.rb +1 -0
  43. data/lib/llm/providers/openai.rb +3 -10
  44. data/lib/llm/repl/bar.rb +4 -3
  45. data/lib/llm/repl/buffer.rb +42 -15
  46. data/lib/llm/repl/color.rb +78 -0
  47. data/lib/llm/repl/input/char.rb +46 -0
  48. data/lib/llm/repl/input/row.rb +39 -0
  49. data/lib/llm/repl/input.rb +251 -66
  50. data/lib/llm/repl/markdown/table.rb +6 -2
  51. data/lib/llm/repl/markdown.rb +31 -5
  52. data/lib/llm/repl/status.rb +38 -3
  53. data/lib/llm/repl/stream.rb +16 -4
  54. data/lib/llm/repl/walker.rb +3 -2
  55. data/lib/llm/repl/window.rb +25 -5
  56. data/lib/llm/repl.rb +29 -13
  57. data/lib/llm/stream.rb +8 -7
  58. data/lib/llm/tool.rb +29 -0
  59. data/lib/llm/transformer/null.rb +21 -0
  60. data/lib/llm/transformer.rb +55 -0
  61. data/lib/llm/version.rb +1 -1
  62. data/lib/llm.rb +12 -2
  63. data/llm.gemspec +1 -0
  64. data/resources/deepdive/advanced/cancellation.md +74 -0
  65. data/resources/deepdive/advanced/compaction.md +83 -0
  66. data/resources/deepdive/advanced/context.md +267 -0
  67. data/resources/deepdive/advanced/guard.md +371 -0
  68. data/resources/deepdive/advanced/tracer.md +180 -0
  69. data/resources/deepdive/advanced/transformer.md +67 -0
  70. data/resources/deepdive/advanced/transports.md +45 -0
  71. data/resources/deepdive/everything_else/audio.md +122 -0
  72. data/resources/deepdive/everything_else/cost.md +99 -0
  73. data/resources/deepdive/everything_else/images.md +89 -0
  74. data/resources/deepdive/everything_else/object.md +108 -0
  75. data/resources/deepdive/everything_else/ocr.md +48 -0
  76. data/resources/deepdive/fundamentals/agents.md +202 -0
  77. data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
  78. data/resources/deepdive/fundamentals/concurrency.md +104 -0
  79. data/resources/deepdive/fundamentals/database.md +449 -0
  80. data/resources/deepdive/fundamentals/embeddings.md +157 -0
  81. data/resources/deepdive/fundamentals/repl.md +87 -0
  82. data/resources/deepdive/fundamentals/schema.md +61 -0
  83. data/resources/deepdive/fundamentals/skills.md +106 -0
  84. data/resources/deepdive/fundamentals/stream.md +110 -0
  85. data/resources/deepdive/fundamentals/tools.md +265 -0
  86. data/resources/deepdive/protocols/a2a.md +106 -0
  87. data/resources/deepdive/protocols/mcp.md +111 -0
  88. data/resources/deepdive.md +7 -1
  89. metadata +36 -3
  90. data/lib/llm/loop_guard.rb +0 -107
@@ -0,0 +1,111 @@
1
+
2
+ ## MCP
3
+
4
+ ### Introduction
5
+
6
+ #### Overview
7
+
8
+ The Model Context Protocol (MCP) connects agents to external tools
9
+ and data sources through a standardized interface. Instead of
10
+ wiring each service directly into your agent, you run an MCP
11
+ server that exposes its capabilities. The runtime translates the
12
+ server's tool list into
13
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
14
+ subclasses the model can call.
15
+
16
+ #### How it works
17
+
18
+ The stdio transport runs the server as a child process and
19
+ communicates over stdin/stdout. Use
20
+ [`LLM::MCP#session`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html#session)
21
+ to avoid launching the same process multiple times.
22
+
23
+ ```ruby
24
+ require "llm"
25
+
26
+ llm = LLM.deepseek(key: ENV["KEY"])
27
+ mcp = LLM::MCP.stdio(argv: ["npx", "-y", "@forgejo/mcp-server"])
28
+ agent = LLM::Agent.new(llm)
29
+
30
+ mcp.session do
31
+ agent.talk "What's happening on forgejo?", tools: mcp.tools
32
+ end
33
+ ```
34
+
35
+ #### Why would I use it?
36
+
37
+ MCP decouples tool implementation from the agent. Add tools by
38
+ launching a new MCP server. Update them by restarting an existing
39
+ one. Remove them without touching the agent's code.
40
+
41
+ #### Notes
42
+
43
+ For stdio, use
44
+ [`LLM::MCP#session`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html#session)
45
+ to avoid launching the same process multiple times. For HTTP,
46
+ [`LLM::MCP#session`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html#session)
47
+ carries little benefit.
48
+
49
+ ### HTTP
50
+
51
+ #### Overview
52
+
53
+ The HTTP transport connects to a remote MCP server over HTTP.
54
+ It is the right choice for cloud-hosted servers like GitHub's
55
+ MCP endpoint. Tools exposed by the server become
56
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
57
+ subclasses the model can call. The server does not need to run
58
+ locally or even on the same machine. Configure headers for
59
+ authentication and pick a transport backend for connection
60
+ management.
61
+
62
+ #### How it works
63
+
64
+ When you want to connect to a remote MCP server, provide a URL and
65
+ optional headers. The server's tool list is fetched and translated
66
+ into
67
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) subclasses the model can call.
68
+
69
+ ```ruby
70
+ require "llm"
71
+
72
+ llm = LLM.deepseek(key: ENV["KEY"])
73
+ mcp = LLM::MCP.http(
74
+ url: "https://api.githubcopilot.com/mcp/",
75
+ headers: {
76
+ "Authorization" => "Bearer #{ENV.fetch('GITHUB_PAT')}"
77
+ },
78
+ transport: :net_http_persistent
79
+ )
80
+ agent = LLM::Agent.new(llm)
81
+ agent.talk "What's happening on GitHub?", tools: mcp.tools
82
+ ```
83
+
84
+ #### Why would I use it?
85
+
86
+ The HTTP transport connects to remote MCP servers. This matters
87
+ when the server is not running locally or when tools are maintained
88
+ by a different team and exposed as a service.
89
+
90
+ #### Notes
91
+
92
+ For HTTP,
93
+ [`LLM::MCP#session`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html#session)
94
+ carries little benefit.
95
+
96
+ ##### Persistent connections
97
+
98
+ Set `persistent: true` to reuse HTTP connections across requests
99
+ to the same MCP server. This uses
100
+ [`Net::HTTP::Persistent`](https://github.com/drbrain/net-http-persistent)
101
+ under the hood and avoids the overhead of opening a new TCP
102
+ connection for every request.
103
+
104
+ ```ruby
105
+ mcp = LLM::MCP.http(
106
+ url: "https://api.githubcopilot.com/mcp/",
107
+ headers: {"Authorization" => "Bearer #{ENV.fetch('GITHUB_PAT')}"},
108
+ persistent: true
109
+ )
110
+ ```
111
+
@@ -53,12 +53,14 @@ place to start if you are new to llm.rb.
53
53
 
54
54
  - [Agents](deepdive/fundamentals/agents.md)
55
55
  - [Tools](deepdive/fundamentals/tools.md)
56
+ - [Built-in tools](deepdive/fundamentals/builtin_tools.md)
56
57
  - [Skills](deepdive/fundamentals/skills.md)
57
58
  - [Schema](deepdive/fundamentals/schema.md)
58
59
  - [Stream](deepdive/fundamentals/stream.md)
59
60
  - [Database](deepdive/fundamentals/database.md)
60
61
  - [Concurrency](deepdive/fundamentals/concurrency.md)
61
62
  - [REPL](deepdive/fundamentals/repl.md)
63
+ - [Embeddings](deepdive/fundamentals/embeddings.md)
62
64
 
63
65
  ## Advanced
64
66
 
@@ -66,6 +68,8 @@ place to start if you are new to llm.rb.
66
68
  - [Compaction](deepdive/advanced/compaction.md)
67
69
  - [Cancellation](deepdive/advanced/cancellation.md)
68
70
  - [Transports](deepdive/advanced/transports.md)
71
+ - [Transformer](deepdive/advanced/transformer.md)
72
+ - [Guard](deepdive/advanced/guard.md)
69
73
  - [Tracer](deepdive/advanced/tracer.md)
70
74
 
71
75
  ## Protocols
@@ -77,4 +81,6 @@ place to start if you are new to llm.rb.
77
81
 
78
82
  - [Images](deepdive/everything_else/images.md)
79
83
  - [Audio](deepdive/everything_else/audio.md)
80
- - [OCR](deepdive/everything_else/ocr.md)
84
+ - [OCR](deepdive/everything_else/ocr.md)
85
+ - [LLM::Object](deepdive/everything_else/object.md)
86
+ - [LLM::Cost](deepdive/everything_else/cost.md)
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: 13.1.0
4
+ version: 14.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - robert
@@ -299,6 +299,7 @@ files:
299
299
  - data/deepseek.json
300
300
  - data/google.json
301
301
  - data/mistral.json
302
+ - data/moonshot.json
302
303
  - data/openai.json
303
304
  - data/xai.json
304
305
  - data/zai.json
@@ -357,8 +358,10 @@ files:
357
358
  - lib/llm/function/thread/group.rb
358
359
  - lib/llm/function/thread/task.rb
359
360
  - lib/llm/function/tracing.rb
361
+ - lib/llm/guard.rb
362
+ - lib/llm/guard/loop.rb
363
+ - lib/llm/guard/null.rb
360
364
  - lib/llm/json_adapter.rb
361
- - lib/llm/loop_guard.rb
362
365
  - lib/llm/mcp.rb
363
366
  - lib/llm/mcp/command.rb
364
367
  - lib/llm/mcp/error.rb
@@ -435,6 +438,7 @@ files:
435
438
  - lib/llm/providers/mistral.rb
436
439
  - lib/llm/providers/mistral/request_adapter.rb
437
440
  - lib/llm/providers/mistral/request_adapter/completion.rb
441
+ - lib/llm/providers/moonshot.rb
438
442
  - lib/llm/providers/ollama.rb
439
443
  - lib/llm/providers/ollama/error_handler.rb
440
444
  - lib/llm/providers/ollama/models.rb
@@ -478,11 +482,14 @@ files:
478
482
  - lib/llm/repl.rb
479
483
  - lib/llm/repl/bar.rb
480
484
  - lib/llm/repl/buffer.rb
485
+ - lib/llm/repl/color.rb
481
486
  - lib/llm/repl/command.rb
482
487
  - lib/llm/repl/commands/compact.rb
483
488
  - lib/llm/repl/commands/exit.rb
484
489
  - lib/llm/repl/commands/help.rb
485
490
  - lib/llm/repl/input.rb
491
+ - lib/llm/repl/input/char.rb
492
+ - lib/llm/repl/input/row.rb
486
493
  - lib/llm/repl/markdown.rb
487
494
  - lib/llm/repl/markdown/table.rb
488
495
  - lib/llm/repl/node.rb
@@ -537,6 +544,8 @@ files:
537
544
  - lib/llm/tracer/null.rb
538
545
  - lib/llm/tracer/pretty_logger.rb
539
546
  - lib/llm/tracer/telemetry.rb
547
+ - lib/llm/transformer.rb
548
+ - lib/llm/transformer/null.rb
540
549
  - lib/llm/transport.rb
541
550
  - lib/llm/transport/curb.rb
542
551
  - lib/llm/transport/execution.rb
@@ -557,6 +566,30 @@ files:
557
566
  - lib/sequel/plugins/llm.rb
558
567
  - llm.gemspec
559
568
  - resources/deepdive.md
569
+ - resources/deepdive/advanced/cancellation.md
570
+ - resources/deepdive/advanced/compaction.md
571
+ - resources/deepdive/advanced/context.md
572
+ - resources/deepdive/advanced/guard.md
573
+ - resources/deepdive/advanced/tracer.md
574
+ - resources/deepdive/advanced/transformer.md
575
+ - resources/deepdive/advanced/transports.md
576
+ - resources/deepdive/everything_else/audio.md
577
+ - resources/deepdive/everything_else/cost.md
578
+ - resources/deepdive/everything_else/images.md
579
+ - resources/deepdive/everything_else/object.md
580
+ - resources/deepdive/everything_else/ocr.md
581
+ - resources/deepdive/fundamentals/agents.md
582
+ - resources/deepdive/fundamentals/builtin_tools.md
583
+ - resources/deepdive/fundamentals/concurrency.md
584
+ - resources/deepdive/fundamentals/database.md
585
+ - resources/deepdive/fundamentals/embeddings.md
586
+ - resources/deepdive/fundamentals/repl.md
587
+ - resources/deepdive/fundamentals/schema.md
588
+ - resources/deepdive/fundamentals/skills.md
589
+ - resources/deepdive/fundamentals/stream.md
590
+ - resources/deepdive/fundamentals/tools.md
591
+ - resources/deepdive/protocols/a2a.md
592
+ - resources/deepdive/protocols/mcp.md
560
593
  homepage: https://r.uby.dev/llm/
561
594
  licenses:
562
595
  - MIT
@@ -584,7 +617,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
584
617
  - !ruby/object:Gem::Version
585
618
  version: '0'
586
619
  requirements: []
587
- rubygems_version: 4.0.6
620
+ rubygems_version: 4.0.16
588
621
  specification_version: 4
589
622
  summary: Ruby's capable AI runtime
590
623
  test_files: []
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- ##
4
- # {LLM::LoopGuard LLM::LoopGuard} is the built-in implementation of
5
- # llm.rb's `guard` capability.
6
- #
7
- # A guard is a context-level supervisor for agentic execution. It can inspect
8
- # the current runtime state and return a warning string when pending tool work
9
- # should be blocked before the loop keeps going.
10
- #
11
- # {LLM::LoopGuard LLM::LoopGuard} detects when a context is repeating the same
12
- # tool-call pattern instead of making progress.
13
- #
14
- # The public interface is intentionally small:
15
- # - `call(ctx)` returns `nil` when no intervention is needed
16
- # - `call(ctx)` returns a warning string when pending tool execution should be blocked
17
- #
18
- # {LLM::Context LLM::Context} can use that warning to return in-band
19
- # {LLM::GuardError LLM::GuardError} tool errors, and
20
- # {LLM::Agent LLM::Agent} enables this guard by default through its wrapped
21
- # context.
22
- #
23
- class LLM::LoopGuard
24
- ##
25
- # The default number of repeated tool-call patterns required before
26
- # the guard intervenes.
27
- # @return [Integer]
28
- DEFAULT_THRESHOLD = 3
29
-
30
- ##
31
- # Returns the repetition threshold.
32
- # @return [Integer]
33
- attr_reader :threshold
34
-
35
- ##
36
- # @param [Hash] config
37
- # @option config [Integer] :threshold
38
- # How many repeated tool-call patterns must appear at the tail of the
39
- # sequence before the guard returns a warning.
40
- def initialize(config = {})
41
- @threshold = config.fetch(:threshold, DEFAULT_THRESHOLD)
42
- end
43
-
44
- ##
45
- # Checks the current context for repeated tool-call patterns.
46
- #
47
- # This method inspects assistant tool calls only. It reduces each call to a
48
- # `[tool_name, arguments]` signature and checks whether the tail of the
49
- # sequence is repeating.
50
- #
51
- # @param [LLM::Context] ctx
52
- # @return [String, nil]
53
- # Returns a warning string when pending tool execution should be blocked,
54
- # or `nil` when execution should continue.
55
- def call(ctx)
56
- repetitions = detect(ctx.messages.to_a)
57
- repetitions ? warning(repetitions) : nil
58
- end
59
-
60
- private
61
-
62
- def detect(messages)
63
- signatures = extract_signatures(messages)
64
- return if signatures.size < threshold
65
- check_repeating_pattern(signatures)
66
- end
67
-
68
- def warning(repetitions)
69
- <<~MSG
70
- SYSTEM NOTICE: Repeated tool-call pattern detected - the same pattern has repeated #{repetitions} times.
71
- You are stuck in a loop and not making progress. Stop and try a fundamentally different approach:
72
- - Re-read the relevant context before retrying
73
- - Try a different tool or strategy
74
- - Break the problem into smaller steps
75
- - If a tool keeps failing, investigate why before retrying
76
- MSG
77
- end
78
-
79
- def extract_signatures(messages)
80
- messages
81
- .select { _1.respond_to?(:functions) && _1.assistant? }
82
- .flat_map { |message| message.functions.map { [_1.name.to_s, _1.arguments.to_s] } }
83
- end
84
-
85
- def check_repeating_pattern(sequence)
86
- max_pattern_len = sequence.size / threshold
87
- (1..max_pattern_len).each do |pattern_len|
88
- count = count_tail_repetitions(sequence, pattern_len)
89
- return count if count >= threshold
90
- end
91
- nil
92
- end
93
-
94
- def count_tail_repetitions(sequence, length)
95
- return 0 if sequence.size < length
96
- pattern = sequence.last(length)
97
- count = 1
98
- pos = sequence.size - length
99
- while pos >= length
100
- candidate = sequence[(pos - length)...pos]
101
- break unless candidate == pattern
102
- count += 1
103
- pos -= length
104
- end
105
- count
106
- end
107
- end