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.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +19 -0
- data/.rubocop.yml +42 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +50 -0
- data/LICENSE +21 -0
- data/README.md +279 -0
- data/lex-llm.gemspec +43 -0
- data/lib/generators/lex_llm/agent/agent_generator.rb +36 -0
- data/lib/generators/lex_llm/agent/templates/agent.rb.tt +6 -0
- data/lib/generators/lex_llm/agent/templates/instructions.txt.erb.tt +0 -0
- data/lib/generators/lex_llm/chat_ui/chat_ui_generator.rb +256 -0
- data/lib/generators/lex_llm/chat_ui/templates/controllers/chats_controller.rb.tt +38 -0
- data/lib/generators/lex_llm/chat_ui/templates/controllers/messages_controller.rb.tt +21 -0
- data/lib/generators/lex_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
- data/lib/generators/lex_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
- data/lib/generators/lex_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
- data/lib/generators/lex_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/index.html.erb.tt +28 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/chats/show.html.erb.tt +25 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +7 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/models/_model.html.erb.tt +15 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/models/index.html.erb.tt +38 -0
- data/lib/generators/lex_llm/chat_ui/templates/views/models/show.html.erb.tt +17 -0
- data/lib/generators/lex_llm/generator_helpers.rb +214 -0
- data/lib/generators/lex_llm/install/install_generator.rb +109 -0
- data/lib/generators/lex_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
- data/lib/generators/lex_llm/install/templates/chat_model.rb.tt +3 -0
- data/lib/generators/lex_llm/install/templates/create_chats_migration.rb.tt +7 -0
- data/lib/generators/lex_llm/install/templates/create_messages_migration.rb.tt +19 -0
- data/lib/generators/lex_llm/install/templates/create_models_migration.rb.tt +39 -0
- data/lib/generators/lex_llm/install/templates/create_tool_calls_migration.rb.tt +21 -0
- data/lib/generators/lex_llm/install/templates/initializer.rb.tt +20 -0
- data/lib/generators/lex_llm/install/templates/message_model.rb.tt +4 -0
- data/lib/generators/lex_llm/install/templates/model_model.rb.tt +3 -0
- data/lib/generators/lex_llm/install/templates/tool_call_model.rb.tt +3 -0
- data/lib/generators/lex_llm/schema/schema_generator.rb +26 -0
- data/lib/generators/lex_llm/schema/templates/schema.rb.tt +2 -0
- data/lib/generators/lex_llm/tool/templates/tool.rb.tt +9 -0
- data/lib/generators/lex_llm/tool/templates/tool_call.html.erb.tt +13 -0
- data/lib/generators/lex_llm/tool/templates/tool_result.html.erb.tt +13 -0
- data/lib/generators/lex_llm/tool/tool_generator.rb +96 -0
- data/lib/generators/lex_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +19 -0
- data/lib/generators/lex_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +50 -0
- data/lib/generators/lex_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
- data/lib/generators/lex_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
- data/lib/generators/lex_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
- data/lib/generators/lex_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +122 -0
- data/lib/generators/lex_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
- data/lib/generators/lex_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
- data/lib/legion/extensions/llm/provider_settings.rb +49 -0
- data/lib/legion/extensions/llm/transport/fleet_lane.rb +70 -0
- data/lib/legion/extensions/llm.rb +50 -0
- data/lib/lex_llm/active_record/acts_as.rb +180 -0
- data/lib/lex_llm/active_record/acts_as_legacy.rb +503 -0
- data/lib/lex_llm/active_record/chat_methods.rb +468 -0
- data/lib/lex_llm/active_record/message_methods.rb +131 -0
- data/lib/lex_llm/active_record/model_methods.rb +76 -0
- data/lib/lex_llm/active_record/payload_helpers.rb +26 -0
- data/lib/lex_llm/active_record/tool_call_methods.rb +15 -0
- data/lib/lex_llm/agent.rb +365 -0
- data/lib/lex_llm/aliases.json +436 -0
- data/lib/lex_llm/aliases.rb +38 -0
- data/lib/lex_llm/attachment.rb +223 -0
- data/lib/lex_llm/chat.rb +351 -0
- data/lib/lex_llm/chunk.rb +6 -0
- data/lib/lex_llm/configuration.rb +81 -0
- data/lib/lex_llm/connection.rb +130 -0
- data/lib/lex_llm/content.rb +77 -0
- data/lib/lex_llm/context.rb +29 -0
- data/lib/lex_llm/embedding.rb +29 -0
- data/lib/lex_llm/error.rb +112 -0
- data/lib/lex_llm/image.rb +105 -0
- data/lib/lex_llm/message.rb +107 -0
- data/lib/lex_llm/mime_type.rb +71 -0
- data/lib/lex_llm/model/info.rb +113 -0
- data/lib/lex_llm/model/modalities.rb +22 -0
- data/lib/lex_llm/model/pricing.rb +48 -0
- data/lib/lex_llm/model/pricing_category.rb +46 -0
- data/lib/lex_llm/model/pricing_tier.rb +33 -0
- data/lib/lex_llm/model.rb +7 -0
- data/lib/lex_llm/models.json +57241 -0
- data/lib/lex_llm/models.rb +506 -0
- data/lib/lex_llm/models_schema.json +168 -0
- data/lib/lex_llm/moderation.rb +56 -0
- data/lib/lex_llm/provider.rb +278 -0
- data/lib/lex_llm/railtie.rb +35 -0
- data/lib/lex_llm/routing/lane_key.rb +51 -0
- data/lib/lex_llm/routing/model_offering.rb +169 -0
- data/lib/lex_llm/routing.rb +7 -0
- data/lib/lex_llm/stream_accumulator.rb +203 -0
- data/lib/lex_llm/streaming.rb +175 -0
- data/lib/lex_llm/thinking.rb +49 -0
- data/lib/lex_llm/tokens.rb +47 -0
- data/lib/lex_llm/tool.rb +254 -0
- data/lib/lex_llm/tool_call.rb +25 -0
- data/lib/lex_llm/transcription.rb +35 -0
- data/lib/lex_llm/utils.rb +91 -0
- data/lib/lex_llm/version.rb +5 -0
- data/lib/lex_llm.rb +95 -0
- data/lib/tasks/lex_llm.rake +23 -0
- 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
|
data/.github/CODEOWNERS
ADDED
|
@@ -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
|
+
[](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
|
|
File without changes
|