lex-llm 0.1.1

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 (135) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +7 -0
  3. data/.github/dependabot.yml +18 -0
  4. data/.github/workflows/ci.yml +16 -0
  5. data/.gitignore +19 -0
  6. data/.rubocop.yml +42 -0
  7. data/CHANGELOG.md +15 -0
  8. data/Gemfile +50 -0
  9. data/LICENSE +21 -0
  10. data/README.md +279 -0
  11. data/lex-llm.gemspec +43 -0
  12. data/lib/generators/lex_llm/agent/agent_generator.rb +36 -0
  13. data/lib/generators/lex_llm/agent/templates/agent.rb.tt +6 -0
  14. data/lib/generators/lex_llm/agent/templates/instructions.txt.erb.tt +0 -0
  15. data/lib/generators/lex_llm/chat_ui/chat_ui_generator.rb +256 -0
  16. data/lib/generators/lex_llm/chat_ui/templates/controllers/chats_controller.rb.tt +38 -0
  17. data/lib/generators/lex_llm/chat_ui/templates/controllers/messages_controller.rb.tt +21 -0
  18. data/lib/generators/lex_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  19. data/lib/generators/lex_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
  20. data/lib/generators/lex_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  21. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
  22. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
  23. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
  24. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
  25. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
  26. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
  27. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
  28. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
  29. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
  30. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
  31. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
  32. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
  33. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
  34. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
  35. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
  36. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
  37. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
  38. data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
  39. data/lib/generators/lex_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  40. data/lib/generators/lex_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  41. data/lib/generators/lex_llm/chat_ui/templates/views/chats/index.html.erb.tt +28 -0
  42. data/lib/generators/lex_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  43. data/lib/generators/lex_llm/chat_ui/templates/views/chats/show.html.erb.tt +25 -0
  44. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
  45. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
  46. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
  47. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  48. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
  49. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
  50. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -0
  51. data/lib/generators/lex_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
  52. data/lib/generators/lex_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +7 -0
  53. data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
  54. data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
  55. data/lib/generators/lex_llm/chat_ui/templates/views/models/_model.html.erb.tt +15 -0
  56. data/lib/generators/lex_llm/chat_ui/templates/views/models/index.html.erb.tt +38 -0
  57. data/lib/generators/lex_llm/chat_ui/templates/views/models/show.html.erb.tt +17 -0
  58. data/lib/generators/lex_llm/generator_helpers.rb +214 -0
  59. data/lib/generators/lex_llm/install/install_generator.rb +109 -0
  60. data/lib/generators/lex_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
  61. data/lib/generators/lex_llm/install/templates/chat_model.rb.tt +3 -0
  62. data/lib/generators/lex_llm/install/templates/create_chats_migration.rb.tt +7 -0
  63. data/lib/generators/lex_llm/install/templates/create_messages_migration.rb.tt +19 -0
  64. data/lib/generators/lex_llm/install/templates/create_models_migration.rb.tt +39 -0
  65. data/lib/generators/lex_llm/install/templates/create_tool_calls_migration.rb.tt +21 -0
  66. data/lib/generators/lex_llm/install/templates/initializer.rb.tt +20 -0
  67. data/lib/generators/lex_llm/install/templates/message_model.rb.tt +4 -0
  68. data/lib/generators/lex_llm/install/templates/model_model.rb.tt +3 -0
  69. data/lib/generators/lex_llm/install/templates/tool_call_model.rb.tt +3 -0
  70. data/lib/generators/lex_llm/schema/schema_generator.rb +26 -0
  71. data/lib/generators/lex_llm/schema/templates/schema.rb.tt +2 -0
  72. data/lib/generators/lex_llm/tool/templates/tool.rb.tt +9 -0
  73. data/lib/generators/lex_llm/tool/templates/tool_call.html.erb.tt +13 -0
  74. data/lib/generators/lex_llm/tool/templates/tool_result.html.erb.tt +13 -0
  75. data/lib/generators/lex_llm/tool/tool_generator.rb +96 -0
  76. data/lib/generators/lex_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +19 -0
  77. data/lib/generators/lex_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +50 -0
  78. data/lib/generators/lex_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
  79. data/lib/generators/lex_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
  80. data/lib/generators/lex_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
  81. data/lib/generators/lex_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +122 -0
  82. data/lib/generators/lex_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
  83. data/lib/generators/lex_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
  84. data/lib/legion/extensions/llm/provider_settings.rb +49 -0
  85. data/lib/legion/extensions/llm/transport/fleet_lane.rb +70 -0
  86. data/lib/legion/extensions/llm.rb +50 -0
  87. data/lib/lex_llm/active_record/acts_as.rb +180 -0
  88. data/lib/lex_llm/active_record/acts_as_legacy.rb +503 -0
  89. data/lib/lex_llm/active_record/chat_methods.rb +468 -0
  90. data/lib/lex_llm/active_record/message_methods.rb +131 -0
  91. data/lib/lex_llm/active_record/model_methods.rb +76 -0
  92. data/lib/lex_llm/active_record/payload_helpers.rb +26 -0
  93. data/lib/lex_llm/active_record/tool_call_methods.rb +15 -0
  94. data/lib/lex_llm/agent.rb +365 -0
  95. data/lib/lex_llm/aliases.json +436 -0
  96. data/lib/lex_llm/aliases.rb +38 -0
  97. data/lib/lex_llm/attachment.rb +223 -0
  98. data/lib/lex_llm/chat.rb +351 -0
  99. data/lib/lex_llm/chunk.rb +6 -0
  100. data/lib/lex_llm/configuration.rb +81 -0
  101. data/lib/lex_llm/connection.rb +130 -0
  102. data/lib/lex_llm/content.rb +77 -0
  103. data/lib/lex_llm/context.rb +29 -0
  104. data/lib/lex_llm/embedding.rb +29 -0
  105. data/lib/lex_llm/error.rb +112 -0
  106. data/lib/lex_llm/image.rb +105 -0
  107. data/lib/lex_llm/message.rb +107 -0
  108. data/lib/lex_llm/mime_type.rb +71 -0
  109. data/lib/lex_llm/model/info.rb +113 -0
  110. data/lib/lex_llm/model/modalities.rb +22 -0
  111. data/lib/lex_llm/model/pricing.rb +48 -0
  112. data/lib/lex_llm/model/pricing_category.rb +46 -0
  113. data/lib/lex_llm/model/pricing_tier.rb +33 -0
  114. data/lib/lex_llm/model.rb +7 -0
  115. data/lib/lex_llm/models.json +57241 -0
  116. data/lib/lex_llm/models.rb +506 -0
  117. data/lib/lex_llm/models_schema.json +168 -0
  118. data/lib/lex_llm/moderation.rb +56 -0
  119. data/lib/lex_llm/provider.rb +278 -0
  120. data/lib/lex_llm/railtie.rb +35 -0
  121. data/lib/lex_llm/routing/lane_key.rb +51 -0
  122. data/lib/lex_llm/routing/model_offering.rb +169 -0
  123. data/lib/lex_llm/routing.rb +7 -0
  124. data/lib/lex_llm/stream_accumulator.rb +203 -0
  125. data/lib/lex_llm/streaming.rb +175 -0
  126. data/lib/lex_llm/thinking.rb +49 -0
  127. data/lib/lex_llm/tokens.rb +47 -0
  128. data/lib/lex_llm/tool.rb +254 -0
  129. data/lib/lex_llm/tool_call.rb +25 -0
  130. data/lib/lex_llm/transcription.rb +35 -0
  131. data/lib/lex_llm/utils.rb +91 -0
  132. data/lib/lex_llm/version.rb +5 -0
  133. data/lib/lex_llm.rb +95 -0
  134. data/lib/tasks/lex_llm.rake +23 -0
  135. metadata +349 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 40556884bda9b7daeff510f1ee58532cfcecb01c30b942bbc17637ae2eb1aa14
4
+ data.tar.gz: 4a1448028063a4976a4dab4c5058927b3e79b4c2bbff897c44075e90c879097e
5
+ SHA512:
6
+ metadata.gz: 156e17beda598522ff95facb2eae951ccc2cc13693e4f4f11346e6f403cf73f76e358f4ef57518f2ad92dff9e63cddbc04510c9a3fce18789d90ed062084b629
7
+ data.tar.gz: adb0fca5733239dc70894d2fc606da5dc2018d4ba83f47e1cf645048c2a956d3a3da6a7f08cd946c8dfc1e473c29cbeb83675f2e452a40fb53302f5df770a8e0
@@ -0,0 +1,7 @@
1
+ # Auto-generated from team-config.yml
2
+ # Team: ai
3
+ #
4
+ # To apply: scripts/apply-codeowners.sh lex-openai
5
+
6
+ * @LegionIO/maintainers
7
+ * @LegionIO/ai
@@ -0,0 +1,18 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ day: monday
8
+ open-pull-requests-limit: 5
9
+ labels:
10
+ - "type:dependencies"
11
+ - package-ecosystem: github-actions
12
+ directory: /
13
+ schedule:
14
+ interval: weekly
15
+ day: monday
16
+ open-pull-requests-limit: 5
17
+ labels:
18
+ - "type:dependencies"
@@ -0,0 +1,16 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+
7
+ jobs:
8
+ ci:
9
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
10
+
11
+ release:
12
+ needs: ci
13
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
14
+ uses: LegionIO/.github/.github/workflows/release.yml@main
15
+ secrets:
16
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ Gemfile.lock
3
+ /.yardoc
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # rspec failure tracking
12
+ .rspec_status*
13
+
14
+ # local/editor/runtime
15
+ .env
16
+ .claude
17
+ AGENTS.md
18
+ CLAUDE.md
19
+ lib/lex_llm/models.failed.json
data/.rubocop.yml ADDED
@@ -0,0 +1,42 @@
1
+ plugins:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
6
+ AllCops:
7
+ NewCops: enable
8
+ TargetRubyVersion: 3.4
9
+ Exclude:
10
+ - docs/**/*
11
+ - vendor/**/*
12
+ - gemfiles/**/*
13
+ - lib/generators/**/templates/**/*
14
+ SuggestExtensions: false
15
+
16
+ Metrics/ClassLength:
17
+ Enabled: false
18
+ Metrics/AbcSize:
19
+ Enabled: false
20
+ Metrics/CyclomaticComplexity:
21
+ Enabled: false
22
+ Metrics/MethodLength:
23
+ Enabled: false
24
+ Metrics/ModuleLength:
25
+ Enabled: false
26
+ Performance/CollectionLiteralInLoop:
27
+ Exclude:
28
+ - spec/**/*
29
+ Performance/RedundantBlockCall:
30
+ Enabled: false # TODO: temporarily disabled to avoid potential breaking change
31
+ Performance/StringInclude:
32
+ Exclude:
33
+ - lib/lex_llm/providers/**/capabilities.rb
34
+ Performance/UnfreezeString:
35
+ Exclude:
36
+ - spec/**/*
37
+ RSpec/ExampleLength:
38
+ Enabled: false
39
+ RSpec/MultipleExpectations:
40
+ Enabled: false
41
+ RSpec/LeakyLocalVariable:
42
+ Enabled: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1 - 2026-04-27
4
+
5
+ - Remove fork-carried concrete provider implementations and VCR-backed provider specs from the base gem.
6
+ - Add fake-provider end-to-end specs for shared chat, tools, schemas, embeddings, moderation, images, transcription, model lookup, and fleet lane wiring.
7
+ - Add shared provider settings construction for `lex-llm-*` gems.
8
+ - Make base defaults provider-neutral and move provider-specific defaults into provider gems.
9
+
10
+ ## 0.1.0 - 2026-04-26
11
+
12
+ - Rename the forked base gem to `lex-llm` with `LexLLM` runtime namespaces and `Legion::Extensions::Llm` integration.
13
+ - Add provider-neutral routing metadata for concrete model offerings and shared fleet lane keys.
14
+ - Use Legion JSON/settings/logging runtime dependencies for shared extension behavior.
15
+ - Remove the upstream RubyLLM docs site and issue templates from the LegionIO fork.
data/Gemfile ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development do # rubocop:disable Metrics/BlockLength
8
+ gem 'appraisal'
9
+ gem 'async', platform: :mri
10
+ gem 'bundler', '>= 2.0'
11
+ gem 'colorize'
12
+ gem 'dotenv'
13
+ gem 'ferrum'
14
+ gem 'flay'
15
+ gem 'image_processing', '~> 1.2'
16
+ gem 'irb'
17
+ gem 'json-schema'
18
+ gem 'nokogiri'
19
+ gem 'overcommit', '>= 0.66'
20
+ gem 'pry', '>= 0.14'
21
+ gem 'rails'
22
+ gem 'rake', '>= 13.0'
23
+ gem 'reline'
24
+ gem 'rspec', '~> 3.12'
25
+ gem 'rubocop', '>= 1.0'
26
+ gem 'rubocop-performance'
27
+ gem 'rubocop-rake', '>= 0.6'
28
+ gem 'rubocop-rspec'
29
+ gem 'simplecov', '>= 0.21'
30
+ gem 'simplecov-cobertura'
31
+ gem 'test-queue'
32
+
33
+ # database drivers for MRI and JRuby
34
+ gem 'activerecord-jdbcsqlite3-adapter', platform: 'jruby'
35
+ gem 'jdbc-sqlite3', platform: 'jruby'
36
+ gem 'sqlite3', platform: 'mri'
37
+
38
+ gem 'vcr'
39
+ gem 'webmock', '~> 3.18'
40
+
41
+ # Optional dependency for Vertex AI
42
+ gem 'googleauth'
43
+
44
+ # Optional dependency for Bedrock
45
+ gem 'aws-eventstream'
46
+ end
47
+
48
+ group :development, :test do
49
+ gem 'turbo-rails'
50
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Carmine Paolino
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # lex-llm
2
+
3
+ [![CI](https://github.com/LegionIO/lex-llm/actions/workflows/ci.yml/badge.svg)](https://github.com/LegionIO/lex-llm/actions/workflows/ci.yml)
4
+
5
+ Shared LegionIO framework for LLM provider extensions.
6
+
7
+ `lex-llm` is the provider-neutral base layer for LegionIO LLM work. It defines the common Ruby API, schema bridge, model metadata, routing structures, request/response helpers, streaming helpers, and Legion extension namespace that concrete provider gems build on.
8
+
9
+ The routing principle is simple: provider is not the routing unit anymore. A concrete model offering is.
10
+
11
+ That means Legion can reason about:
12
+
13
+ - one local Ollama instance with many models
14
+ - multiple remote Ollama or vLLM instances
15
+ - several Bedrock accounts or regions exposing overlapping Anthropic models
16
+ - direct frontier providers such as OpenAI or Anthropic
17
+ - fleet workers on MacBooks, GPU servers, or cloud-side proxy nodes
18
+
19
+ ## What This Gem Owns
20
+
21
+ `lex-llm` provides shared primitives only. Provider-specific behavior belongs in provider gems.
22
+
23
+ This gem owns:
24
+
25
+ - `LexLLM`, the shared Ruby API and base provider framework
26
+ - `Legion::Extensions::Llm`, the Legion extension namespace used by autoloading and settings
27
+ - provider-neutral model metadata and capability normalization
28
+ - routing structures such as `LexLLM::Routing::ModelOffering`
29
+ - fleet lane key generation for shared RabbitMQ work lanes
30
+ - common chat, embedding, tool, streaming, ActiveRecord, and schema helpers
31
+ - shared runtime dependencies such as `legion-json`, `legion-settings`, and `legion-logging`
32
+
33
+ Concrete provider gems should depend on this gem and implement the provider-specific transport, authentication, model discovery, request translation, and response translation.
34
+
35
+ Expected provider gems include:
36
+
37
+ - `lex-llm-ollama`
38
+ - `lex-llm-vllm`
39
+ - `lex-llm-anthropic`
40
+ - `lex-llm-openai`
41
+ - `lex-llm-gemini`
42
+ - `lex-llm-mlx`
43
+ - `lex-llm-bedrock`
44
+ - `lex-llm-vertex`
45
+ - `lex-llm-azure`
46
+
47
+ ## Install
48
+
49
+ ```ruby
50
+ gem 'lex-llm'
51
+ ```
52
+
53
+ Provider extensions should declare `lex-llm` as a gemspec dependency:
54
+
55
+ ```ruby
56
+ spec.add_dependency 'lex-llm', '>= 0.1.0'
57
+ ```
58
+
59
+ For local development across LegionIO repos, prefer a local path override in the app or test `Gemfile`, not a permanent git dependency in the gemspec.
60
+
61
+ ## Namespaces
62
+
63
+ This gem exposes two runtime namespaces:
64
+
65
+ - `LexLLM` for shared Ruby classes, provider primitives, schemas, and helpers
66
+ - `Legion::Extensions::Llm` for LegionIO extension loading and default settings
67
+
68
+ Provider gems must use nested Legion extension namespaces so LegionIO autoloading can find them consistently.
69
+
70
+ Example for `lex-llm-ollama`:
71
+
72
+ ```ruby
73
+ require 'legion/extensions/llm/ollama'
74
+
75
+ module Legion
76
+ module Extensions
77
+ module Llm
78
+ module Ollama
79
+ def self.default_settings
80
+ Legion::Extensions::Llm.default_settings.merge(
81
+ provider_family: :ollama
82
+ )
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ ```
89
+
90
+ ## Model Offerings
91
+
92
+ A model offering describes one concrete model made available by one provider instance. It is the base unit for routing, filtering, fleet lane creation, health, policy, and cost decisions.
93
+
94
+ ```ruby
95
+ offering = LexLLM::Routing::ModelOffering.new(
96
+ provider_family: :ollama,
97
+ instance_id: :macbook_m4_max,
98
+ transport: :local,
99
+ tier: :local,
100
+ model: 'qwen3.6:27b-q4_K_M',
101
+ usage_type: :inference,
102
+ capabilities: %i[chat tools vision thinking],
103
+ limits: {
104
+ context_window: 32_768,
105
+ max_output_tokens: 8_192
106
+ },
107
+ health: {
108
+ ready: true,
109
+ latency_ms: 180
110
+ },
111
+ policy_tags: %i[internal_only phi_allowed],
112
+ metadata: {
113
+ enabled: true,
114
+ eligibility: {
115
+ ac_power: true
116
+ }
117
+ }
118
+ )
119
+
120
+ offering.eligible_for?(
121
+ usage_type: :inference,
122
+ required_capabilities: %i[tools],
123
+ min_context_window: 16_000,
124
+ policy_tags: %i[internal_only]
125
+ )
126
+ # => true
127
+ ```
128
+
129
+ Common offering fields:
130
+
131
+ - `provider_family`: provider implementation family, such as `:ollama`, `:vllm`, `:bedrock`, `:anthropic`, or `:openai`
132
+ - `instance_id`: concrete provider instance, account, node, region, or local runtime
133
+ - `transport`: `:local`, `:http`, `:rabbitmq`, `:sdk`, or another provider-supported transport
134
+ - `tier`: `:local`, `:private`, `:fleet`, `:cloud`, `:frontier`, or deployment-specific policy tier
135
+ - `model`: provider model name or normalized model alias
136
+ - `usage_type`: `:inference` or `:embedding`
137
+ - `capabilities`: normalized feature flags such as `:chat`, `:tools`, `:json_schema`, `:vision`, `:thinking`, or `:embedding`
138
+ - `limits`: context window, output token limits, rate limits, concurrency limits, and provider-specific bounds
139
+ - `health`: readiness, latency, recent failures, and provider-specific health metadata
140
+ - `policy_tags`: routing and compliance tags such as `:internal_only`, `:phi_allowed`, or `:hipaa`
141
+ - `metadata`: extension-specific metadata; sensitive values are excluded from fleet eligibility fingerprints
142
+
143
+ ## Fleet Lanes
144
+
145
+ Fleet routing uses shared work lanes derived from model offerings. A lane describes the work required, not the worker that happens to do it.
146
+
147
+ ```ruby
148
+ offering.lane_key
149
+ # => "llm.fleet.inference.qwen3-6-27b-q4-k-m.ctx32768"
150
+ ```
151
+
152
+ Embedding lanes omit context size:
153
+
154
+ ```ruby
155
+ LexLLM::Routing::ModelOffering.new(
156
+ provider_family: :ollama,
157
+ instance_id: :gpu_embed_01,
158
+ transport: :rabbitmq,
159
+ model: 'nomic-embed-text',
160
+ usage_type: :embedding,
161
+ capabilities: %i[embedding]
162
+ ).lane_key
163
+ # => "llm.fleet.embed.nomic-embed-text"
164
+ ```
165
+
166
+ The intent is that any eligible worker can bind to the same lane:
167
+
168
+ - local MacBook workers
169
+ - GPU servers in a datacenter
170
+ - vLLM workers
171
+ - Ollama workers
172
+ - cloud-side LegionIO workers near Bedrock, Vertex, Azure, or another provider
173
+
174
+ Busy endpoint workers should not reject/requeue in a hot loop. Endpoint fleet workers can use pull-style scheduling, while server-class workers can use normal consumers with prefetch and consumer priority.
175
+
176
+ ## Default Fleet Settings
177
+
178
+ `Legion::Extensions::Llm.default_settings` provides defaults that provider extensions inherit and override:
179
+
180
+ ```ruby
181
+ Legion::Extensions::Llm.default_settings
182
+ # => {
183
+ # fleet: {
184
+ # enabled: false,
185
+ # scheduler: :basic_get,
186
+ # consumer_priority: 0,
187
+ # queue_expires_ms: 60_000,
188
+ # message_ttl_ms: 120_000,
189
+ # queue_max_length: 100,
190
+ # delivery_limit: 3,
191
+ # consumer_ack_timeout_ms: 300_000,
192
+ # endpoint: {
193
+ # enabled: false,
194
+ # empty_lane_backoff_ms: 250,
195
+ # idle_backoff_ms: 1_000,
196
+ # max_consecutive_pulls_per_lane: 0,
197
+ # accept_when: []
198
+ # }
199
+ # }
200
+ # }
201
+ ```
202
+
203
+ The defaults are conservative:
204
+
205
+ - fleet participation is off unless configured
206
+ - endpoint fleet mode is separately disabled by default
207
+ - queue and message TTLs are bounded
208
+ - pull scheduling is the default for endpoint-style workers
209
+ - provider gems can override defaults through `Legion::Settings`
210
+
211
+ Provider gems can build a complete provider settings hash without duplicating merge logic:
212
+
213
+ ```ruby
214
+ Legion::Extensions::Llm.provider_settings(
215
+ family: :ollama,
216
+ instance: {
217
+ base_url: "http://localhost:11434",
218
+ fleet: { enabled: true, consumer_priority: 10 }
219
+ }
220
+ )
221
+ ```
222
+
223
+ ## Provider Extension Contract
224
+
225
+ A provider gem should use `lex-llm` for shared behavior and implement only the provider-specific pieces.
226
+
227
+ At minimum, a provider extension should define:
228
+
229
+ - `Legion::Extensions::Llm::<Provider>`
230
+ - provider default settings
231
+ - model discovery or a static model offering registry
232
+ - provider request translation
233
+ - provider response translation
234
+ - health and readiness checks
235
+ - embedding support separately from inference support when the provider exposes both
236
+
237
+ Provider extensions should avoid duplicating shared classes, schema logic, fleet lane construction, JSON handling, or common request/response objects.
238
+
239
+ ## Schema Status
240
+
241
+ `lex-llm` still depends on `ruby_llm-schema` because the current schema bridge exposes:
242
+
243
+ ```ruby
244
+ LexLLM::Schema
245
+ ```
246
+
247
+ as:
248
+
249
+ ```ruby
250
+ RubyLLM::Schema
251
+ ```
252
+
253
+ That dependency should stay until LegionIO owns or replaces the schema layer directly.
254
+
255
+ ## Development
256
+
257
+ Install dependencies:
258
+
259
+ ```bash
260
+ bundle install
261
+ ```
262
+
263
+ Run lint:
264
+
265
+ ```bash
266
+ bundle exec rubocop -A
267
+ ```
268
+
269
+ Run the full test suite:
270
+
271
+ ```bash
272
+ bundle exec rspec --format json --out tmp/rspec_results.json --format progress --out tmp/rspec_progress.txt
273
+ ```
274
+
275
+ `Gemfile.lock` is intentionally not committed for this repo.
276
+
277
+ ## Attribution
278
+
279
+ `lex-llm` began as a LegionIO fork of RubyLLM. RubyLLM remains credited under the MIT license in `LICENSE`.
data/lex-llm.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/lex_llm/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-llm'
7
+ spec.version = LexLLM::VERSION
8
+ spec.authors = ['LegionIO', 'Carmine Paolino']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'Shared LegionIO LLM provider framework'
12
+ spec.description = 'Provider-neutral LLM primitives, schemas, routing metadata, and shared codecs for LegionIO ' \
13
+ 'LLM provider extensions.'
14
+
15
+ spec.homepage = 'https://github.com/LegionIO/lex-llm'
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.4')
18
+
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-llm'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-llm/blob/main/CHANGELOG.md'
22
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-llm'
23
+ spec.metadata['bug_tracker_uri'] = "#{spec.metadata['source_code_uri']}/issues"
24
+
25
+ spec.metadata['rubygems_mfa_required'] = 'true'
26
+
27
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec|test|features|tmp|coverage)/}) }
28
+ spec.require_paths = ['lib']
29
+
30
+ # Runtime dependencies
31
+ spec.add_dependency 'base64'
32
+ spec.add_dependency 'event_stream_parser', '~> 1'
33
+ spec.add_dependency 'faraday', ENV['FARADAY_VERSION'] || '>= 1.10.0'
34
+ spec.add_dependency 'faraday-multipart', '>= 1'
35
+ spec.add_dependency 'faraday-net_http', '>= 1'
36
+ spec.add_dependency 'faraday-retry', '>= 1'
37
+ spec.add_dependency 'legion-json', '>= 1.2.1'
38
+ spec.add_dependency 'legion-logging', '>= 1.3.2'
39
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
40
+ spec.add_dependency 'marcel', '~> 1'
41
+ spec.add_dependency 'ruby_llm-schema', '~> 0'
42
+ spec.add_dependency 'zeitwerk', '~> 2'
43
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module LexLLM
6
+ module Generators
7
+ # Generator for LexLLM agent classes and prompt files.
8
+ class AgentGenerator < Rails::Generators::NamedBase
9
+ source_root File.expand_path('templates', __dir__)
10
+
11
+ namespace 'lex_llm:agent'
12
+
13
+ desc 'Creates a LexLLM agent class and default instructions prompt'
14
+
15
+ def create_agent_file
16
+ template 'agent.rb.tt', File.join('app/agents', class_path, "#{agent_file_name}.rb")
17
+ end
18
+
19
+ def create_prompt_file
20
+ empty_directory File.join('app/prompts', class_path, agent_file_name)
21
+ template 'instructions.txt.erb.tt',
22
+ File.join('app/prompts', class_path, agent_file_name, 'instructions.txt.erb')
23
+ end
24
+
25
+ private
26
+
27
+ def agent_class_name
28
+ class_name.end_with?('Agent') ? class_name : "#{class_name}Agent"
29
+ end
30
+
31
+ def agent_file_name
32
+ agent_class_name.demodulize.underscore
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,6 @@
1
+ class <%= agent_class_name %> < LexLLM::Agent
2
+ # Change `Chat` to your app's chat model for Rails persistence.
3
+ # Remove this line to skip persistence and use plain LexLLM chats.
4
+ chat_model Chat
5
+ instructions
6
+ end