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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +320 -0
- data/README.md +340 -31
- data/bin/llm.rb +36 -12
- data/data/anthropic.json +206 -263
- data/data/bedrock.json +2138 -1860
- data/data/deepinfra.json +1003 -624
- data/data/deepseek.json +38 -34
- data/data/google.json +1079 -371
- data/data/mistral.json +448 -368
- data/data/moonshot.json +384 -0
- data/data/openai.json +974 -1343
- data/data/xai.json +154 -126
- data/data/zai.json +191 -191
- data/lib/llm/agent.rb +47 -14
- data/lib/llm/context.rb +71 -88
- data/lib/llm/cost.rb +23 -17
- data/lib/llm/error.rb +0 -8
- data/lib/llm/function/async/task.rb +2 -0
- data/lib/llm/function/fiber/task.rb +2 -0
- data/lib/llm/function/fork/task.rb +2 -0
- data/lib/llm/function/ractor/task.rb +2 -0
- data/lib/llm/function/sequential/group.rb +4 -1
- data/lib/llm/function/sequential/task.rb +1 -1
- data/lib/llm/function/task.rb +4 -0
- data/lib/llm/function/thread/task.rb +2 -0
- data/lib/llm/function.rb +32 -4
- data/lib/llm/guard/loop.rb +89 -0
- data/lib/llm/guard/null.rb +19 -0
- data/lib/llm/guard.rb +61 -0
- data/lib/llm/provider.rb +36 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
- data/lib/llm/providers/anthropic.rb +1 -8
- data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
- data/lib/llm/providers/bedrock.rb +1 -8
- data/lib/llm/providers/google/stream_parser.rb +1 -0
- data/lib/llm/providers/google.rb +1 -8
- data/lib/llm/providers/moonshot.rb +76 -0
- data/lib/llm/providers/ollama.rb +1 -8
- data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
- data/lib/llm/providers/openai/responses.rb +6 -8
- data/lib/llm/providers/openai/stream_parser.rb +1 -0
- data/lib/llm/providers/openai.rb +3 -10
- data/lib/llm/repl/bar.rb +4 -3
- data/lib/llm/repl/buffer.rb +42 -15
- data/lib/llm/repl/color.rb +78 -0
- data/lib/llm/repl/input/char.rb +46 -0
- data/lib/llm/repl/input/row.rb +39 -0
- data/lib/llm/repl/input.rb +251 -66
- data/lib/llm/repl/markdown/table.rb +6 -2
- data/lib/llm/repl/markdown.rb +31 -5
- data/lib/llm/repl/status.rb +38 -3
- data/lib/llm/repl/stream.rb +16 -4
- data/lib/llm/repl/walker.rb +3 -2
- data/lib/llm/repl/window.rb +25 -5
- data/lib/llm/repl.rb +29 -13
- data/lib/llm/stream.rb +8 -7
- data/lib/llm/tool.rb +29 -0
- data/lib/llm/transformer/null.rb +21 -0
- data/lib/llm/transformer.rb +55 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +12 -2
- data/llm.gemspec +1 -0
- data/resources/deepdive/advanced/cancellation.md +74 -0
- data/resources/deepdive/advanced/compaction.md +83 -0
- data/resources/deepdive/advanced/context.md +267 -0
- data/resources/deepdive/advanced/guard.md +371 -0
- data/resources/deepdive/advanced/tracer.md +180 -0
- data/resources/deepdive/advanced/transformer.md +67 -0
- data/resources/deepdive/advanced/transports.md +45 -0
- data/resources/deepdive/everything_else/audio.md +122 -0
- data/resources/deepdive/everything_else/cost.md +99 -0
- data/resources/deepdive/everything_else/images.md +89 -0
- data/resources/deepdive/everything_else/object.md +108 -0
- data/resources/deepdive/everything_else/ocr.md +48 -0
- data/resources/deepdive/fundamentals/agents.md +202 -0
- data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
- data/resources/deepdive/fundamentals/concurrency.md +104 -0
- data/resources/deepdive/fundamentals/database.md +449 -0
- data/resources/deepdive/fundamentals/embeddings.md +157 -0
- data/resources/deepdive/fundamentals/repl.md +87 -0
- data/resources/deepdive/fundamentals/schema.md +61 -0
- data/resources/deepdive/fundamentals/skills.md +106 -0
- data/resources/deepdive/fundamentals/stream.md +110 -0
- data/resources/deepdive/fundamentals/tools.md +265 -0
- data/resources/deepdive/protocols/a2a.md +106 -0
- data/resources/deepdive/protocols/mcp.md +111 -0
- data/resources/deepdive.md +7 -1
- metadata +36 -3
- 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
|
+
|
data/resources/deepdive.md
CHANGED
|
@@ -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:
|
|
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.
|
|
620
|
+
rubygems_version: 4.0.16
|
|
588
621
|
specification_version: 4
|
|
589
622
|
summary: Ruby's capable AI runtime
|
|
590
623
|
test_files: []
|
data/lib/llm/loop_guard.rb
DELETED
|
@@ -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
|