lex-transformer 0.1.4 → 0.3.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/.github/workflows/ci.yml +16 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +37 -21
- data/CHANGELOG.md +54 -0
- data/CLAUDE.md +135 -0
- data/Dockerfile +1 -1
- data/Gemfile +13 -0
- data/README.md +121 -19
- data/Rakefile +2 -0
- data/docker_deploy.rb +1 -0
- data/lex-transformer.gemspec +13 -17
- data/lib/legion/extensions/transformer/actors/transform.rb +20 -14
- data/lib/legion/extensions/transformer/client.rb +104 -0
- data/lib/legion/extensions/transformer/definitions.rb +56 -0
- data/lib/legion/extensions/transformer/engines/base.rb +19 -0
- data/lib/legion/extensions/transformer/engines/erb.rb +37 -0
- data/lib/legion/extensions/transformer/engines/jsonpath.rb +41 -0
- data/lib/legion/extensions/transformer/engines/liquid.rb +31 -0
- data/lib/legion/extensions/transformer/engines/llm.rb +153 -0
- data/lib/legion/extensions/transformer/engines/registry.rb +51 -0
- data/lib/legion/extensions/transformer/engines/static.rb +21 -0
- data/lib/legion/extensions/transformer/helpers/schema_validator.rb +48 -0
- data/lib/legion/extensions/transformer/runners/transform.rb +79 -52
- data/lib/legion/extensions/transformer/transport/exchanges/task.rb +10 -4
- data/lib/legion/extensions/transformer/transport/messages/message.rb +27 -17
- data/lib/legion/extensions/transformer/transport/queues/transform.rb +12 -6
- data/lib/legion/extensions/transformer/transport.rb +19 -13
- data/lib/legion/extensions/transformer/version.rb +3 -1
- data/lib/legion/extensions/transformer.rb +3 -0
- metadata +29 -76
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile.lock +0 -58
- data/bitbucket-pipelines.yml +0 -19
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 111582b1dfcbe44b2cf91d3b316bd6394eb09e4d750a7b19be8844e91257e2c5
|
|
4
|
+
data.tar.gz: 6efebcb5ea769c9426e17e6be93aa1cc0ca1bf5093e02d9f5ea0fe5254809760
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8af3daa3467d9e37e571319c876a17ff772e3558c0510fcca75a633e970ed32b0497c1e31f63faa047e0eb2f6e8488d8a55820d0d5e46394450957228f4ec3af
|
|
7
|
+
data.tar.gz: a8dc1caf0ce16261c8eecf6d6ed37b2af4ff1cf4736465047a67cfd96eeb431a1454d4a31f2c34861219fea481b8d79b42d13b860a3935d240f81b89669c870d
|
|
@@ -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
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,33 +1,49 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 3.4
|
|
3
|
+
NewCops: enable
|
|
4
|
+
SuggestExtensions: false
|
|
5
|
+
|
|
1
6
|
Layout/LineLength:
|
|
2
|
-
Max:
|
|
7
|
+
Max: 160
|
|
8
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
|
9
|
+
EnforcedStyle: space
|
|
10
|
+
Layout/HashAlignment:
|
|
11
|
+
EnforcedHashRocketStyle: table
|
|
12
|
+
EnforcedColonStyle: table
|
|
13
|
+
|
|
3
14
|
Metrics/MethodLength:
|
|
4
15
|
Max: 50
|
|
5
16
|
Metrics/ClassLength:
|
|
6
17
|
Max: 1500
|
|
18
|
+
Metrics/ModuleLength:
|
|
19
|
+
Max: 1500
|
|
7
20
|
Metrics/BlockLength:
|
|
8
|
-
Max:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Metrics/CyclomaticComplexity:
|
|
12
|
-
Max: 117
|
|
21
|
+
Max: 40
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'spec/**/*'
|
|
13
24
|
Metrics/AbcSize:
|
|
14
|
-
Max:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
Max: 60
|
|
26
|
+
Metrics/CyclomaticComplexity:
|
|
27
|
+
Max: 15
|
|
28
|
+
Metrics/PerceivedComplexity:
|
|
29
|
+
Max: 17
|
|
30
|
+
Metrics/ParameterLists:
|
|
31
|
+
Enabled: false
|
|
32
|
+
|
|
22
33
|
Style/Documentation:
|
|
23
34
|
Enabled: false
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
NewCops: enable
|
|
27
|
-
SuggestExtensions: false
|
|
35
|
+
Style/SymbolArray:
|
|
36
|
+
Enabled: true
|
|
28
37
|
Style/FrozenStringLiteralComment:
|
|
29
|
-
Enabled:
|
|
38
|
+
Enabled: true
|
|
39
|
+
EnforcedStyle: always
|
|
40
|
+
|
|
30
41
|
Naming/FileName:
|
|
31
42
|
Enabled: false
|
|
32
|
-
|
|
33
|
-
Enabled: false
|
|
43
|
+
Naming/PredicateMethod:
|
|
44
|
+
Enabled: false
|
|
45
|
+
Naming/PredicatePrefix:
|
|
46
|
+
Enabled: false
|
|
47
|
+
|
|
48
|
+
Gemspec/DevelopmentDependencies:
|
|
49
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.0] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- LLM engine error handling with categorized retry (timeout/network retry, auth errors raise, provider errors return failure)
|
|
7
|
+
- LLM engine model/provider/temperature/system_prompt kwargs via `engine_options`
|
|
8
|
+
- LLM engine structured output support (`structured: true` + `schema:`)
|
|
9
|
+
- LLM engine JSON response validation with correction prompt on retry
|
|
10
|
+
- Settings-based LLM defaults (`lex-transformer.llm.*`)
|
|
11
|
+
- Named transform definitions via Settings (`lex-transformer.definitions.*`)
|
|
12
|
+
- `Definitions` class for Settings-based definition lookup
|
|
13
|
+
- `name:` parameter on `Client#transform` for named definition execution
|
|
14
|
+
- `engine_options:` parameter on `Client#transform` and `Client#transform_chain`
|
|
15
|
+
- Conditioner integration for named definitions with conditions
|
|
16
|
+
- LLM failure hash passthrough (no dispatch on engine failure)
|
|
17
|
+
- Integration specs for LLM engine with client, chain, and named definitions
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- All engine `render` signatures accept `**opts` (backward-compatible, non-LLM engines ignore)
|
|
21
|
+
|
|
22
|
+
## [0.2.1] - 2026-03-17
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- LLM transform engine (`engines/llm.rb`) — provider-agnostic natural language transformation via `Legion::LLM.chat`
|
|
26
|
+
- Registered as `:llm` in engine registry (explicit only, no auto-detection)
|
|
27
|
+
|
|
28
|
+
## [0.2.0] - 2026-03-17
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- Pluggable engine system with abstract `Engines::Base` and `Engines::Registry`
|
|
32
|
+
- 4 template engines: ERB (extracted from runner), Static (JSON passthrough), Liquid, JSONPath (dot-notation extraction)
|
|
33
|
+
- `Engines::Registry.detect` auto-selects ERB or static engine based on template content
|
|
34
|
+
- `render_transformation` accepts optional `engine:` keyword to force a specific engine
|
|
35
|
+
- `Helpers::SchemaValidator` validates transformed output against declared schemas (required keys, type checks)
|
|
36
|
+
- `transform` accepts optional `schema:` parameter for post-render validation
|
|
37
|
+
- `transform_chain` method for sequential multi-step pipelines with per-step engine selection and schema validation
|
|
38
|
+
- Standalone `Client` class for framework-independent usage with `transform` and `transform_chain` methods
|
|
39
|
+
- SimpleCov coverage reporting
|
|
40
|
+
- Modern packaging: grouped test dependencies, `require_relative` in gemspec, `rubocop-rspec`
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
- `dispatch_multiplied` payload mutation bug: `new_payload = payload` replaced with `new_payload = payload.dup`
|
|
44
|
+
- Spec filename typo: `tranformer_spec.rb` renamed to `transformer_spec.rb`
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- ERB rendering logic extracted from runner into `Engines::Erb` class
|
|
48
|
+
- `build_template_variables` moved to `Engines::Erb` as private `build_variables`
|
|
49
|
+
- Runner no longer directly requires `tilt` (delegated to `Engines::Erb`)
|
|
50
|
+
|
|
51
|
+
## [0.1.4] - 2026-03-13
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
- Initial release
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# lex-transformer: Payload Transformation Engine for LegionIO
|
|
2
|
+
|
|
3
|
+
**Repository Level 3 Documentation**
|
|
4
|
+
- **Parent**: `/Users/miverso2/rubymine/legion/extensions-core/CLAUDE.md`
|
|
5
|
+
- **Grandparent**: `/Users/miverso2/rubymine/legion/CLAUDE.md`
|
|
6
|
+
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Legion Extension that transforms task payloads between services in a relationship chain. Uses pluggable template engines (ERB, Static, Liquid, JSONPath, LLM) to map data from one task's output into the format expected by the next task's input. Supports single-hash output (1:1 dispatch), array output (fan-out/multiply), schema validation, and sequential transform chains. Requires `legion-data` (`data_required? true`).
|
|
10
|
+
|
|
11
|
+
**GitHub**: https://github.com/LegionIO/lex-transformer
|
|
12
|
+
**License**: MIT
|
|
13
|
+
**Version**: 0.2.1
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Legion::Extensions::Transformer
|
|
19
|
+
├── Actors/
|
|
20
|
+
│ └── Transform # Subscription actor consuming transform requests
|
|
21
|
+
├── Engines/
|
|
22
|
+
│ ├── Base # Abstract engine interface
|
|
23
|
+
│ ├── Registry # Maps engine name symbols to engine classes
|
|
24
|
+
│ ├── Erb # ERB rendering via tilt
|
|
25
|
+
│ ├── Static # JSON passthrough (no templating)
|
|
26
|
+
│ ├── Liquid # Liquid template rendering
|
|
27
|
+
│ ├── Jsonpath # Dot-notation value extraction from payload
|
|
28
|
+
│ └── Llm # Natural language transformation via Legion::LLM
|
|
29
|
+
├── Helpers/
|
|
30
|
+
│ └── SchemaValidator # Validates transform output against required_keys/types schema
|
|
31
|
+
├── Runners/
|
|
32
|
+
│ └── Transform # Executes template-based payload transformation
|
|
33
|
+
│ ├── transform # Entry point: render + dispatch or validate
|
|
34
|
+
│ ├── transform_chain # Sequential pipeline: N steps, output feeds next
|
|
35
|
+
│ ├── render_transformation # Engine dispatch (ERB/Static/Liquid/JSONPath/LLM)
|
|
36
|
+
│ ├── build_template_variables # Inject crypt/settings/cache/task into scope
|
|
37
|
+
│ ├── dispatch_transformed # Route Hash (single) or Array (fan-out) results
|
|
38
|
+
│ ├── dispatch_multiplied # Fan-out: create a new task per array element
|
|
39
|
+
│ └── send_task # Publish transformed payload to next runner
|
|
40
|
+
├── Transport/
|
|
41
|
+
│ ├── Exchanges/Task # Publishes to the task exchange
|
|
42
|
+
│ ├── Queues/Transform # Subscribes to transformation queue
|
|
43
|
+
│ └── Messages/Message # Transform request message format
|
|
44
|
+
└── Client # Standalone client: transform + transform_chain (uses Engines::Registry directly)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Key Files
|
|
48
|
+
|
|
49
|
+
| Path | Purpose |
|
|
50
|
+
|------|---------|
|
|
51
|
+
| `lib/legion/extensions/transformer.rb` | Entry point (`data_required? true`) |
|
|
52
|
+
| `lib/legion/extensions/transformer/runners/transform.rb` | Core transformation logic |
|
|
53
|
+
| `lib/legion/extensions/transformer/actors/transform.rb` | AMQP subscription actor |
|
|
54
|
+
| `lib/legion/extensions/transformer/client.rb` | Standalone client (transform, transform_chain) |
|
|
55
|
+
| `lib/legion/extensions/transformer/engines/registry.rb` | Engine name -> class lookup + auto-detection |
|
|
56
|
+
| `lib/legion/extensions/transformer/helpers/schema_validator.rb` | Output schema validation |
|
|
57
|
+
| `lib/legion/extensions/transformer/transport.rb` | Transport setup |
|
|
58
|
+
|
|
59
|
+
## Template Engines
|
|
60
|
+
|
|
61
|
+
| Engine | Name | Detection | Description |
|
|
62
|
+
|--------|------|-----------|-------------|
|
|
63
|
+
| ERB | `:erb` | `<%` or `%>` in template | Full ERB template rendering via `tilt` |
|
|
64
|
+
| Static | `:static` | Default (no ERB markers) | Plain JSON passthrough |
|
|
65
|
+
| Liquid | `:liquid` | Explicit only | Liquid template rendering (`{{ var }}`) |
|
|
66
|
+
| JSONPath | `:jsonpath` | Explicit only | Dot-notation value extraction from payload |
|
|
67
|
+
| LLM | `:llm` | Explicit only | Natural language transformation via `Legion::LLM` |
|
|
68
|
+
|
|
69
|
+
Auto-detection: ERB when template contains `<%` or `%>`, otherwise Static. Pass `engine:` to force a specific engine.
|
|
70
|
+
|
|
71
|
+
The LLM engine requires `legion-llm` to be started; it is provider-agnostic (Ollama, Bedrock, Anthropic, OpenAI, Gemini).
|
|
72
|
+
|
|
73
|
+
## Template Variables Available in ERB
|
|
74
|
+
|
|
75
|
+
| Variable | Available when |
|
|
76
|
+
|----------|---------------|
|
|
77
|
+
| All payload keys | Always (splatted into template scope) |
|
|
78
|
+
| `crypt` | Template string contains `'crypt'` |
|
|
79
|
+
| `settings` | Template string contains `'settings'` |
|
|
80
|
+
| `cache` | Template string contains `'cache'` |
|
|
81
|
+
| `task` | Template string contains `'task'` and payload has `task_id` |
|
|
82
|
+
|
|
83
|
+
## Schema Validation
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
schema = {
|
|
87
|
+
required_keys: [:name, :email],
|
|
88
|
+
types: { name: String, email: String, age: Integer }
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
On failure: `{ success: false, status: 'transformer.validation_failed', errors: [...] }` — no dispatch.
|
|
93
|
+
|
|
94
|
+
## Dispatch Behavior
|
|
95
|
+
|
|
96
|
+
- **Hash result**: Updates task to `transformer.succeeded`, dispatches single task, updates to `task.queued`
|
|
97
|
+
- **Array result**: Fan-out via `dispatch_multiplied` — creates a new task record per element, dispatches each, marks original as `task.multiplied`
|
|
98
|
+
- **Schema failure**: `transformer.validation_failed`, no dispatch
|
|
99
|
+
|
|
100
|
+
## Transform Chains
|
|
101
|
+
|
|
102
|
+
`transform_chain(steps:, **payload)` runs steps sequentially. Each step specifies `:transformation`, optional `:engine`, and optional `:schema`. Output of step N is merged into the running payload for step N+1. Stops on first schema failure.
|
|
103
|
+
|
|
104
|
+
## Standalone Client
|
|
105
|
+
|
|
106
|
+
`Legion::Extensions::Transformer::Client` includes the Transform runner:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
require 'legion/extensions/transformer/client'
|
|
110
|
+
client = Legion::Extensions::Transformer::Client.new
|
|
111
|
+
result = client.transform(transformation: '{"x":"<%= y %>"}', payload: { y: 'hello' })
|
|
112
|
+
result[:success] # => true
|
|
113
|
+
result[:result] # => { x: "hello" }
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Dependencies
|
|
117
|
+
|
|
118
|
+
| Gem | Purpose |
|
|
119
|
+
|-----|---------|
|
|
120
|
+
| `tilt` (>= 2.3) | Template engine abstraction for ERB rendering |
|
|
121
|
+
| `legion-data` | Required — task record creation for fan-out |
|
|
122
|
+
|
|
123
|
+
## Testing
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
bundle install
|
|
127
|
+
bundle exec rspec # 86 examples, 0 failures
|
|
128
|
+
bundle exec rubocop # 0 offenses
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Spec files: `spec/legion/extensions/tranformer_spec.rb` (note: typo in filename is intentional in repo), `spec/legion/extensions/transform_runner_spec.rb`
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
**Maintained By**: Matthew Iverson (@Esity)
|
data/Dockerfile
CHANGED
data/Gemfile
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
source 'https://rubygems.org'
|
|
2
4
|
|
|
3
5
|
gemspec
|
|
6
|
+
|
|
7
|
+
group :test do
|
|
8
|
+
gem 'base64'
|
|
9
|
+
gem 'liquid', '~> 5.0'
|
|
10
|
+
gem 'rake'
|
|
11
|
+
gem 'rspec', '~> 3.13'
|
|
12
|
+
gem 'rspec_junit_formatter'
|
|
13
|
+
gem 'rubocop', '~> 1.75'
|
|
14
|
+
gem 'rubocop-rspec'
|
|
15
|
+
gem 'simplecov'
|
|
16
|
+
end
|
data/README.md
CHANGED
|
@@ -1,38 +1,140 @@
|
|
|
1
|
-
#
|
|
1
|
+
# lex-transformer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Payload transformation engine for [LegionIO](https://github.com/LegionIO/LegionIO). Transforms task payloads between services in a relationship chain using pluggable template engines. Maps data from one task's output into the format expected by the next task's input.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
gem install lex-transformer
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or add to your Gemfile:
|
|
8
12
|
|
|
9
13
|
```ruby
|
|
10
14
|
gem 'lex-transformer'
|
|
11
15
|
```
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
## Standalone Client
|
|
18
|
+
|
|
19
|
+
Use the transformer without the full LegionIO framework:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require 'legion/extensions/transformer/client'
|
|
23
|
+
|
|
24
|
+
client = Legion::Extensions::Transformer::Client.new
|
|
25
|
+
|
|
26
|
+
# Single transform
|
|
27
|
+
result = client.transform(
|
|
28
|
+
transformation: '{"greeting":"hello <%= name %>"}',
|
|
29
|
+
payload: { name: 'world' }
|
|
30
|
+
)
|
|
31
|
+
result[:success] # => true
|
|
32
|
+
result[:result] # => { greeting: "hello world" }
|
|
33
|
+
|
|
34
|
+
# With explicit engine
|
|
35
|
+
result = client.transform(
|
|
36
|
+
transformation: '{"greeting":"hello {{ name }}"}',
|
|
37
|
+
payload: { name: 'world' },
|
|
38
|
+
engine: :liquid
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# With schema validation
|
|
42
|
+
result = client.transform(
|
|
43
|
+
transformation: '{"name":"test"}',
|
|
44
|
+
payload: {},
|
|
45
|
+
schema: { required_keys: [:name, :email] }
|
|
46
|
+
)
|
|
47
|
+
result[:success] # => false
|
|
48
|
+
result[:status] # => "transformer.validation_failed"
|
|
49
|
+
result[:errors] # => ["missing required key: email"]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Transform Chains
|
|
53
|
+
|
|
54
|
+
Pipe data through sequential transformation steps:
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
result = client.transform_chain(
|
|
58
|
+
steps: [
|
|
59
|
+
{ transformation: '{"user":"<%= login %>"}', schema: { required_keys: [:user] } },
|
|
60
|
+
{ transformation: '{"message":"Welcome, <%= user %>!"}' }
|
|
61
|
+
],
|
|
62
|
+
payload: { login: 'alice' }
|
|
63
|
+
)
|
|
64
|
+
result[:success] # => true
|
|
65
|
+
result[:result][:args] # => { message: "Welcome, alice!" }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Each step's output merges into the running payload, so subsequent steps can reference keys from earlier steps.
|
|
69
|
+
|
|
70
|
+
## Engines
|
|
71
|
+
|
|
72
|
+
| Engine | Name | Detection | Description |
|
|
73
|
+
|--------|------|-----------|-------------|
|
|
74
|
+
| ERB | `:erb` | `<%` or `%>` in template | Full ERB template rendering via `tilt` |
|
|
75
|
+
| Static | `:static` | Default (no ERB markers) | Plain JSON passthrough |
|
|
76
|
+
| Liquid | `:liquid` | Explicit only | Liquid template rendering (`{{ var }}`) |
|
|
77
|
+
| JSONPath | `:jsonpath` | Explicit only | Dot-notation value extraction from payload |
|
|
78
|
+
| LLM | `:llm` | Explicit only | Natural language transformation via Legion::LLM |
|
|
14
79
|
|
|
15
|
-
|
|
80
|
+
Auto-detection selects ERB when the template contains ERB markers, otherwise falls back to Static. Use the `engine:` parameter to force a specific engine.
|
|
16
81
|
|
|
17
|
-
|
|
82
|
+
### LLM Engine
|
|
18
83
|
|
|
19
|
-
|
|
84
|
+
The LLM engine uses natural language instructions as the "template":
|
|
20
85
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
86
|
+
```ruby
|
|
87
|
+
client.transform(
|
|
88
|
+
transformation: "Summarize this webhook payload into a Slack notification with the PR title and author",
|
|
89
|
+
payload: { action: "opened", pull_request: { title: "Fix auth", user: { login: "alice" } } },
|
|
90
|
+
engine: :llm
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Requires `legion-llm` to be started. Provider-agnostic — uses whatever LLM provider is configured (Ollama, Bedrock, Anthropic, OpenAI, Gemini).
|
|
95
|
+
|
|
96
|
+
## Schema Validation
|
|
97
|
+
|
|
98
|
+
Validate transformed output against a declared schema:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
schema = {
|
|
102
|
+
required_keys: [:name, :email], # keys that must be present
|
|
103
|
+
types: { name: String, email: String, age: Integer } # optional type checks
|
|
30
104
|
}
|
|
31
105
|
```
|
|
32
106
|
|
|
33
|
-
|
|
34
|
-
|
|
107
|
+
When validation fails, the transform returns `{ success: false, status: 'transformer.validation_failed', errors: [...] }`.
|
|
108
|
+
|
|
109
|
+
## Runners
|
|
110
|
+
|
|
111
|
+
### Transform
|
|
112
|
+
|
|
113
|
+
#### `transform(transformation:, engine: nil, schema: nil, **payload)`
|
|
114
|
+
|
|
115
|
+
Renders the transformation template against the payload, optionally validates the result, then dispatches:
|
|
116
|
+
|
|
117
|
+
- Hash result: routes to next task (`task.queued`)
|
|
118
|
+
- Array result: fan-out via `dispatch_multiplied` (one task per element, marked `task.multiplied`)
|
|
119
|
+
- Schema failure: `transformer.validation_failed`, no dispatch
|
|
120
|
+
|
|
121
|
+
#### `transform_chain(steps:, **payload)`
|
|
122
|
+
|
|
123
|
+
Sequential pipeline. Each step has `:transformation`, optional `:engine`, optional `:schema`. Output of step N feeds into step N+1. Stops on first schema failure.
|
|
124
|
+
|
|
125
|
+
## Transport
|
|
126
|
+
|
|
127
|
+
- **Exchange**: `task` (inherits from `Legion::Transport::Exchanges::Task`)
|
|
128
|
+
- **Queue**: `task.transform`
|
|
129
|
+
- **Routing keys**: `task.subtask`, `task.subtask.transform`
|
|
130
|
+
|
|
131
|
+
## Requirements
|
|
132
|
+
|
|
133
|
+
- Ruby >= 3.4
|
|
134
|
+
- [LegionIO](https://github.com/LegionIO/LegionIO) framework (for AMQP actor mode)
|
|
135
|
+
- `tilt` >= 2.3
|
|
136
|
+
- Standalone Client works without the framework
|
|
35
137
|
|
|
36
138
|
## License
|
|
37
139
|
|
|
38
|
-
|
|
140
|
+
MIT
|
data/Rakefile
CHANGED
data/docker_deploy.rb
CHANGED
data/lex-transformer.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/transformer/version'
|
|
4
4
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = 'lex-transformer'
|
|
@@ -8,27 +8,23 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ['Esity']
|
|
9
9
|
spec.email = ['matthewdiverson@gmail.com']
|
|
10
10
|
|
|
11
|
-
spec.summary = '
|
|
12
|
-
spec.description = '
|
|
13
|
-
spec.homepage = 'https://
|
|
11
|
+
spec.summary = 'Payload transformation engine for LegionIO task chains'
|
|
12
|
+
spec.description = 'Template-based payload transformation between tasks with multiple engines, schema validation, and composition'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-transformer'
|
|
14
14
|
spec.license = 'MIT'
|
|
15
|
-
spec.required_ruby_version =
|
|
15
|
+
spec.required_ruby_version = '>= 3.4'
|
|
16
16
|
|
|
17
17
|
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
-
spec.metadata['source_code_uri'] = 'https://
|
|
19
|
-
spec.metadata['documentation_uri'] = 'https://
|
|
20
|
-
spec.metadata['changelog_uri'] = 'https://
|
|
21
|
-
spec.metadata['bug_tracker_uri'] = 'https://
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-transformer'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-transformer'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-transformer/blob/main/CHANGELOG.md'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-transformer/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
22
23
|
|
|
23
24
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
25
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
25
26
|
end
|
|
26
27
|
spec.require_paths = ['lib']
|
|
27
28
|
|
|
28
|
-
spec.add_dependency 'tilt'
|
|
29
|
-
|
|
30
|
-
spec.add_development_dependency 'bundler', '>= 2'
|
|
31
|
-
spec.add_development_dependency 'rake'
|
|
32
|
-
spec.add_development_dependency 'rspec'
|
|
33
|
-
spec.add_development_dependency 'rubocop'
|
|
29
|
+
spec.add_dependency 'tilt', '>= 2.3'
|
|
34
30
|
end
|
|
@@ -1,20 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
module Actor
|
|
3
|
-
class Transform < Legion::Extensions::Actors::Subscription
|
|
4
|
-
def runner_function
|
|
5
|
-
'transform'
|
|
6
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Transformer
|
|
6
|
+
module Actor
|
|
7
|
+
class Transform < Legion::Extensions::Actors::Subscription
|
|
8
|
+
def runner_function
|
|
9
|
+
'transform'
|
|
10
|
+
end
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
def check_subtask?
|
|
13
|
+
false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate_task?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
def use_runner?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
18
24
|
end
|
|
19
25
|
end
|
|
20
26
|
end
|