llmtrim 0.1.8-arm64-darwin-23

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fc49bdb3efb0bc80ddb884e0cbf67c8565eec22e042785775f57820b578662de
4
+ data.tar.gz: 9f1d11e3a9dbfeded463652511f66c0dc93967486477da29c67ee8126da8fa8e
5
+ SHA512:
6
+ metadata.gz: fd940debd7f9d516ac6cc0617dfac1718de27d3d3e6d386b8776d7c979df2a06378fe25a42acd4f117476f3792fcf40915117020a44e8bb5095ab83d106a4761
7
+ data.tar.gz: '087a85e49737fea85d3b50df31af9c06ffe393e18a1ceceb4b650731495103f3efea67792d0fcb59d25902b40547e2ec6b1015d5ad936e2120f435beef98e81d'
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # llmtrim (Ruby)
2
+
3
+ Native, in-process bindings to the [llmtrim](https://github.com/fkiene/llmtrim)
4
+ compression engine — cut LLM input tokens 30–90% with zero extra model calls, no network,
5
+ no server. The compiled engine is bundled in the gem, so no Rust toolchain is needed.
6
+
7
+ ```ruby
8
+ require "llmtrim"
9
+ require "json"
10
+
11
+ req = JSON.generate("model" => "gpt-4o",
12
+ "messages" => [{ "role" => "user", "content" => "…" }])
13
+ out = Llmtrim.compress(req, Llmtrim::Provider::OPEN_AI, "aggressive")
14
+ puts "#{out.input_tokens_before} -> #{out.input_tokens_after}"
15
+ # send out.request_json to the provider
16
+ ```
17
+
18
+ `compress(input, provider, preset)` — `provider` is `Llmtrim::Provider::OPEN_AI` /
19
+ `ANTHROPIC` / `GOOGLE` or `nil` to auto-detect; `preset` is a workload name
20
+ (`"aggressive"`, `"agent"`, `"code"`, `"rag"`, `"safe"`, …) or `nil` for the environment
21
+ config. Raises `Llmtrim::LlmtrimError::Compress` / `UnknownPreset` on error.
22
+
23
+ Built with `crates/llmtrim-uniffi/scripts/build-gem.sh` (platform-specific gem with the
24
+ bundled native library). License: AGPL-3.0-only.
Binary file
@@ -0,0 +1,733 @@
1
+ # This file was autogenerated by some hot garbage in the `uniffi` crate.
2
+ # Trust me, you don't want to mess with it!
3
+
4
+ # Common helper code.
5
+ #
6
+ # Ideally this would live in a separate .rb file where it can be unittested etc
7
+ # in isolation, and perhaps even published as a re-useable package.
8
+ #
9
+ # However, it's important that the details of how this helper code works (e.g. the
10
+ # way that different builtin types are passed across the FFI) exactly match what's
11
+ # expected by the rust code on the other side of the interface. In practice right
12
+ # now that means coming from the exact some version of `uniffi` that was used to
13
+ # compile the rust component. The easiest way to ensure this is to bundle the Ruby
14
+ # helpers directly inline like we're doing here.
15
+
16
+ require 'ffi'
17
+
18
+
19
+ module LlmtrimFfi
20
+ def self.uniffi_in_range(i, type_name, min, max)
21
+ raise TypeError, "no implicit conversion of #{i} into Integer" unless i.respond_to?(:to_int)
22
+ i = i.to_int
23
+ raise RangeError, "#{type_name} requires #{min} <= value < #{max}" unless (min <= i && i < max)
24
+ i
25
+ end
26
+
27
+ def self.uniffi_utf8(v)
28
+ raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str)
29
+ v = v.to_str.encode(Encoding::UTF_8)
30
+ raise Encoding::InvalidByteSequenceError, "not a valid UTF-8 encoded string" unless v.valid_encoding?
31
+ v
32
+ end
33
+
34
+ def self.uniffi_bytes(v)
35
+ raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str)
36
+ v.to_str
37
+ end
38
+
39
+ class RustBuffer < FFI::Struct
40
+ layout :capacity, :uint64,
41
+ :len, :uint64,
42
+ :data, :pointer
43
+
44
+ def self.alloc(size)
45
+ return LlmtrimFfi.rust_call(:ffi_llmtrim_ffi_rustbuffer_alloc, size)
46
+ end
47
+
48
+ def self.reserve(rbuf, additional)
49
+ return LlmtrimFfi.rust_call(:ffi_llmtrim_ffi_rustbuffer_reserve, rbuf, additional)
50
+ end
51
+
52
+ def free
53
+ LlmtrimFfi.rust_call(:ffi_llmtrim_ffi_rustbuffer_free, self)
54
+ end
55
+
56
+ def capacity
57
+ self[:capacity]
58
+ end
59
+
60
+ def len
61
+ self[:len]
62
+ end
63
+
64
+ def len=(value)
65
+ self[:len] = value
66
+ end
67
+
68
+ def data
69
+ self[:data]
70
+ end
71
+
72
+ def to_s
73
+ "RustBuffer(capacity=#{capacity}, len=#{len}, data=#{data.read_bytes len})"
74
+ end
75
+
76
+ # The allocated buffer will be automatically freed if an error occurs, ensuring that
77
+ # we don't accidentally leak it.
78
+ def self.allocWithBuilder
79
+ builder = RustBufferBuilder.new
80
+
81
+ begin
82
+ yield builder
83
+ rescue => e
84
+ builder.discard
85
+ raise e
86
+ end
87
+ end
88
+
89
+ # The RustBuffer will be freed once the context-manager exits, ensuring that we don't
90
+ # leak it even if an error occurs.
91
+ def consumeWithStream
92
+ stream = RustBufferStream.new self
93
+
94
+ yield stream
95
+
96
+ raise RuntimeError, 'junk data left in buffer after consuming' if stream.remaining != 0
97
+ ensure
98
+ free
99
+ end# The primitive String type.
100
+
101
+ def self.allocFromString(value)
102
+ RustBuffer.allocWithBuilder do |builder|
103
+ builder.write value.encode('utf-8')
104
+ return builder.finalize
105
+ end
106
+ end
107
+
108
+ def consumeIntoString
109
+ consumeWithStream do |stream|
110
+ return stream.read(stream.remaining).force_encoding(Encoding::UTF_8)
111
+ end
112
+ end
113
+
114
+ # The Record type CompressOutput.
115
+
116
+ def self.check_lower_TypeCompressOutput(v)
117
+
118
+
119
+ RustBuffer.check_lower_Optionalstring(v.model)
120
+
121
+
122
+
123
+
124
+
125
+
126
+ end
127
+
128
+ def self.alloc_from_TypeCompressOutput(v)
129
+ RustBuffer.allocWithBuilder do |builder|
130
+ builder.write_TypeCompressOutput(v)
131
+ return builder.finalize
132
+ end
133
+ end
134
+
135
+ def consumeIntoTypeCompressOutput
136
+ consumeWithStream do |stream|
137
+ return stream.readTypeCompressOutput
138
+ end
139
+ end
140
+
141
+
142
+
143
+ # The Enum type Provider.
144
+
145
+ def self.check_lower_TypeProvider(v)
146
+ end
147
+
148
+ def self.alloc_from_TypeProvider(v)
149
+ RustBuffer.allocWithBuilder do |builder|
150
+ builder.write_TypeProvider(v)
151
+ return builder.finalize
152
+ end
153
+ end
154
+
155
+ def consumeIntoTypeProvider
156
+ consumeWithStream do |stream|
157
+ return stream.readTypeProvider
158
+ end
159
+ end
160
+
161
+
162
+ # The Optional<T> type for string.
163
+
164
+ def self.check_lower_Optionalstring(v)
165
+ if not v.nil?
166
+
167
+ end
168
+ end
169
+
170
+ def self.alloc_from_Optionalstring(v)
171
+ RustBuffer.allocWithBuilder do |builder|
172
+ builder.write_Optionalstring(v)
173
+ return builder.finalize()
174
+ end
175
+ end
176
+
177
+ def consumeIntoOptionalstring
178
+ consumeWithStream do |stream|
179
+ return stream.readOptionalstring
180
+ end
181
+ end
182
+
183
+ # The Optional<T> type for TypeProvider.
184
+
185
+ def self.check_lower_OptionalTypeProvider(v)
186
+ if not v.nil?
187
+ RustBuffer.check_lower_TypeProvider(v)
188
+ end
189
+ end
190
+
191
+ def self.alloc_from_OptionalTypeProvider(v)
192
+ RustBuffer.allocWithBuilder do |builder|
193
+ builder.write_OptionalTypeProvider(v)
194
+ return builder.finalize()
195
+ end
196
+ end
197
+
198
+ def consumeIntoOptionalTypeProvider
199
+ consumeWithStream do |stream|
200
+ return stream.readOptionalTypeProvider
201
+ end
202
+ end
203
+
204
+
205
+ end
206
+
207
+ module UniFFILib
208
+ class ForeignBytes < FFI::Struct
209
+ layout :len, :int32,
210
+ :data, :pointer
211
+
212
+ def len
213
+ self[:len]
214
+ end
215
+
216
+ def data
217
+ self[:data]
218
+ end
219
+
220
+ def to_s
221
+ "ForeignBytes(len=#{len}, data=#{data.read_bytes(len)})"
222
+ end
223
+ end
224
+ end
225
+
226
+ private_constant :UniFFILib
227
+
228
+ # Helper for structured reading of values from a RustBuffer.
229
+ class RustBufferStream
230
+
231
+ def initialize(rbuf)
232
+ @rbuf = rbuf
233
+ @offset = 0
234
+ end
235
+
236
+ def remaining
237
+ @rbuf.len - @offset
238
+ end
239
+
240
+ def read(size)
241
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
242
+
243
+ data = @rbuf.data.get_bytes @offset, size
244
+
245
+ @offset += size
246
+
247
+ data
248
+ end
249
+
250
+ def readU64
251
+ unpack_from 8, 'Q>'
252
+ end
253
+
254
+ def readBool
255
+ v = unpack_from 1, 'c'
256
+
257
+ return false if v == 0
258
+ return true if v == 1
259
+
260
+ raise InternalError, 'Unexpected byte for Boolean type'
261
+ end
262
+
263
+ def readString
264
+ size = unpack_from 4, 'l>'
265
+
266
+ raise InternalError, 'Unexpected negative string length' if size.negative?
267
+
268
+ read(size).force_encoding(Encoding::UTF_8)
269
+ end
270
+
271
+ # The Record type CompressOutput.
272
+
273
+ def readTypeCompressOutput
274
+ CompressOutput.new(
275
+ request_json: readString,
276
+ provider: readString,
277
+ model: readOptionalstring,
278
+ tokenizer_label: readString,
279
+ tokenizer_exact: readBool,
280
+ input_tokens_before: readU64,
281
+ input_tokens_after: readU64,
282
+ frozen_input_tokens: readU64,
283
+ output_shaped: readBool
284
+ )
285
+ end
286
+
287
+
288
+
289
+
290
+
291
+ # The Error type LlmtrimError
292
+
293
+ def readTypeLlmtrimError
294
+ variant = unpack_from 4, 'l>'
295
+
296
+ if variant == 1
297
+ return LlmtrimError::Compress.new(
298
+ readString()
299
+ )
300
+ end
301
+ if variant == 2
302
+ return LlmtrimError::UnknownPreset.new(
303
+ readString()
304
+ )
305
+ end
306
+
307
+ raise InternalError, 'Unexpected variant tag for TypeLlmtrimError'
308
+ end
309
+
310
+
311
+
312
+
313
+ # The Enum type Provider.
314
+
315
+ def readTypeProvider
316
+ variant = unpack_from 4, 'l>'
317
+
318
+ if variant == 1
319
+ return Provider::OPEN_AI
320
+ end
321
+ if variant == 2
322
+ return Provider::ANTHROPIC
323
+ end
324
+ if variant == 3
325
+ return Provider::GOOGLE
326
+ end
327
+
328
+ raise InternalError, 'Unexpected variant tag for TypeProvider'
329
+ end
330
+
331
+
332
+
333
+ # The Optional<T> type for string.
334
+
335
+ def readOptionalstring
336
+ flag = unpack_from 1, 'c'
337
+
338
+ if flag == 0
339
+ return nil
340
+ elsif flag == 1
341
+ return readString
342
+ else
343
+ raise InternalError, 'Unexpected flag byte for Optionalstring'
344
+ end
345
+ end
346
+
347
+ # The Optional<T> type for TypeProvider.
348
+
349
+ def readOptionalTypeProvider
350
+ flag = unpack_from 1, 'c'
351
+
352
+ if flag == 0
353
+ return nil
354
+ elsif flag == 1
355
+ return readTypeProvider
356
+ else
357
+ raise InternalError, 'Unexpected flag byte for OptionalTypeProvider'
358
+ end
359
+ end
360
+
361
+
362
+
363
+ def unpack_from(size, format)
364
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
365
+
366
+ value = @rbuf.data.get_bytes(@offset, size).unpack format
367
+
368
+ @offset += size
369
+
370
+ # TODO: verify this
371
+ raise 'more than one element!!!' if value.size > 1
372
+
373
+ value[0]
374
+ end
375
+ end
376
+
377
+ private_constant :RustBufferStream
378
+
379
+ # Helper for structured writing of values into a RustBuffer.
380
+ class RustBufferBuilder
381
+ def initialize
382
+ @rust_buf = RustBuffer.alloc 16
383
+ @rust_buf.len = 0
384
+ end
385
+
386
+ def finalize
387
+ rbuf = @rust_buf
388
+
389
+ @rust_buf = nil
390
+
391
+ rbuf
392
+ end
393
+
394
+ def discard
395
+ return if @rust_buf.nil?
396
+
397
+ rbuf = finalize
398
+ rbuf.free
399
+ end
400
+
401
+ def write(value)
402
+ reserve(value.bytes.size) do
403
+ @rust_buf.data.put_array_of_char @rust_buf.len, value.bytes
404
+ end
405
+ end
406
+
407
+ def write_U64(v)
408
+ v = LlmtrimFfi::uniffi_in_range(v, "u64", 0, 2**64)
409
+ pack_into(8, 'Q>', v)
410
+ end
411
+
412
+ def write_Bool(v)
413
+ pack_into(1, 'c', v ? 1 : 0)
414
+ end
415
+
416
+ def write_String(v)
417
+ v = LlmtrimFfi::uniffi_utf8(v)
418
+ pack_into 4, 'l>', v.bytes.size
419
+ write v
420
+ end
421
+
422
+ # The Record type CompressOutput.
423
+
424
+ def write_TypeCompressOutput(v)
425
+ self.write_String(v.request_json)
426
+ self.write_String(v.provider)
427
+ self.write_Optionalstring(v.model)
428
+ self.write_String(v.tokenizer_label)
429
+ self.write_Bool(v.tokenizer_exact)
430
+ self.write_U64(v.input_tokens_before)
431
+ self.write_U64(v.input_tokens_after)
432
+ self.write_U64(v.frozen_input_tokens)
433
+ self.write_Bool(v.output_shaped)
434
+ end
435
+
436
+
437
+
438
+ # The Enum type Provider.
439
+
440
+ def write_TypeProvider(v)
441
+ pack_into(4, 'l>', v)
442
+ end
443
+
444
+
445
+ # The Optional<T> type for string.
446
+
447
+ def write_Optionalstring(v)
448
+ if v.nil?
449
+ pack_into(1, 'c', 0)
450
+ else
451
+ pack_into(1, 'c', 1)
452
+ self.write_String(v)
453
+ end
454
+ end
455
+
456
+ # The Optional<T> type for TypeProvider.
457
+
458
+ def write_OptionalTypeProvider(v)
459
+ if v.nil?
460
+ pack_into(1, 'c', 0)
461
+ else
462
+ pack_into(1, 'c', 1)
463
+ self.write_TypeProvider(v)
464
+ end
465
+ end
466
+
467
+
468
+
469
+ private
470
+
471
+ def reserve(num_bytes)
472
+ if @rust_buf.len + num_bytes > @rust_buf.capacity
473
+ @rust_buf = RustBuffer.reserve(@rust_buf, num_bytes)
474
+ end
475
+
476
+ yield
477
+
478
+ @rust_buf.len += num_bytes
479
+ end
480
+
481
+ def pack_into(size, format, value)
482
+ reserve(size) do
483
+ @rust_buf.data.put_array_of_char @rust_buf.len, [value].pack(format).bytes
484
+ end
485
+ end
486
+ end
487
+
488
+ private_constant :RustBufferBuilder
489
+
490
+ # Error definitions
491
+ class RustCallStatus < FFI::Struct
492
+ layout :code, :int8,
493
+ :error_buf, RustBuffer
494
+
495
+ def code
496
+ self[:code]
497
+ end
498
+
499
+ def error_buf
500
+ self[:error_buf]
501
+ end
502
+
503
+ def to_s
504
+ "RustCallStatus(code=#{self[:code]})"
505
+ end
506
+ end
507
+
508
+ # These match the values from the uniffi::rustcalls module
509
+ CALL_SUCCESS = 0
510
+ CALL_ERROR = 1
511
+ CALL_PANIC = 2
512
+
513
+
514
+ module LlmtrimError
515
+ class Compress < StandardError
516
+ def initialize(detail)
517
+ @detail = detail
518
+ super()
519
+ end
520
+
521
+ attr_reader :detail
522
+
523
+
524
+ def to_s
525
+ "#{self.class.name}(detail=#{@detail.inspect})"
526
+ end
527
+ end
528
+ class UnknownPreset < StandardError
529
+ def initialize(name)
530
+ @name = name
531
+ super()
532
+ end
533
+
534
+ attr_reader :name
535
+
536
+
537
+ def to_s
538
+ "#{self.class.name}(name=#{@name.inspect})"
539
+ end
540
+ end
541
+
542
+ end
543
+
544
+
545
+
546
+ # Map error modules to the RustBuffer method name that reads them
547
+ ERROR_MODULE_TO_READER_METHOD = {
548
+
549
+ LlmtrimError => :readTypeLlmtrimError,
550
+
551
+
552
+ }
553
+
554
+ private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC,
555
+ :RustCallStatus
556
+
557
+ def self.consume_buffer_into_error(error_module, rust_buffer)
558
+ rust_buffer.consumeWithStream do |stream|
559
+ reader_method = ERROR_MODULE_TO_READER_METHOD[error_module]
560
+ return stream.send(reader_method)
561
+ end
562
+ end
563
+
564
+ class InternalError < StandardError
565
+ end
566
+
567
+ def self.rust_call(fn_name, *args)
568
+ # Call a rust function
569
+ rust_call_with_error(nil, fn_name, *args)
570
+ end
571
+
572
+ def self.rust_call_with_error(error_module, fn_name, *args)
573
+ # Call a rust function and handle errors
574
+ #
575
+ # Use this when the rust function returns a Result<>. error_module must be the error_module that corresponds to that Result.
576
+
577
+
578
+ # Note: RustCallStatus.new zeroes out the struct, which is exactly what we
579
+ # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0,
580
+ # data=NULL))
581
+ status = RustCallStatus.new
582
+ args << status
583
+
584
+ result = UniFFILib.public_send(fn_name, *args)
585
+
586
+ case status.code
587
+ when CALL_SUCCESS
588
+ result
589
+ when CALL_ERROR
590
+ if error_module.nil?
591
+ status.error_buf.free
592
+ raise InternalError, "CALL_ERROR with no error_module set"
593
+ else
594
+ raise consume_buffer_into_error(error_module, status.error_buf)
595
+ end
596
+ when CALL_PANIC
597
+ # When the rust code sees a panic, it tries to construct a RustBuffer
598
+ # with the message. But if that code panics, then it just sends back
599
+ # an empty buffer.
600
+ if status.error_buf.len > 0
601
+ raise InternalError, status.error_buf.consumeIntoString()
602
+ else
603
+ raise InternalError, "Rust panic"
604
+ end
605
+ else
606
+ raise InternalError, "Unknown call status: #{status.code}"
607
+ end
608
+ end
609
+
610
+ private_class_method :consume_buffer_into_error
611
+
612
+ # This is how we find and load the dynamic library provided by the component.
613
+ # For now we just look it up by name.
614
+ module UniFFILib
615
+ extend FFI::Library
616
+
617
+
618
+ ffi_lib File.expand_path('libllmtrim_ffi.dylib', __dir__)
619
+
620
+
621
+ attach_function :uniffi_llmtrim_ffi_fn_func_compress,
622
+ [RustBuffer.by_value, RustBuffer.by_value, RustBuffer.by_value, RustCallStatus.by_ref],
623
+ RustBuffer.by_value
624
+ attach_function :ffi_llmtrim_ffi_rustbuffer_alloc,
625
+ [:uint64, RustCallStatus.by_ref],
626
+ RustBuffer.by_value
627
+ attach_function :ffi_llmtrim_ffi_rustbuffer_from_bytes,
628
+ [ForeignBytes, RustCallStatus.by_ref],
629
+ RustBuffer.by_value
630
+ attach_function :ffi_llmtrim_ffi_rustbuffer_free,
631
+ [RustBuffer.by_value, RustCallStatus.by_ref],
632
+ :void
633
+ attach_function :ffi_llmtrim_ffi_rustbuffer_reserve,
634
+ [RustBuffer.by_value, :uint64, RustCallStatus.by_ref],
635
+ RustBuffer.by_value
636
+ attach_function :uniffi_llmtrim_ffi_checksum_func_compress,
637
+ [RustCallStatus.by_ref],
638
+ :uint16
639
+ attach_function :ffi_llmtrim_ffi_uniffi_contract_version,
640
+ [RustCallStatus.by_ref],
641
+ :uint32
642
+
643
+ end
644
+
645
+ # Public interface members begin here.
646
+
647
+
648
+
649
+
650
+
651
+
652
+ class Provider
653
+ OPEN_AI = 1
654
+ ANTHROPIC = 2
655
+ GOOGLE = 3
656
+
657
+ end
658
+
659
+
660
+
661
+ # Record type CompressOutput
662
+ class CompressOutput
663
+ attr_reader :request_json, :provider, :model, :tokenizer_label, :tokenizer_exact, :input_tokens_before, :input_tokens_after, :frozen_input_tokens, :output_shaped
664
+
665
+ def initialize(request_json:, provider:, model:, tokenizer_label:, tokenizer_exact:, input_tokens_before:, input_tokens_after:, frozen_input_tokens:, output_shaped:)
666
+ @request_json = request_json
667
+ @provider = provider
668
+ @model = model
669
+ @tokenizer_label = tokenizer_label
670
+ @tokenizer_exact = tokenizer_exact
671
+ @input_tokens_before = input_tokens_before
672
+ @input_tokens_after = input_tokens_after
673
+ @frozen_input_tokens = frozen_input_tokens
674
+ @output_shaped = output_shaped
675
+ end
676
+
677
+ def ==(other)
678
+ if @request_json != other.request_json
679
+ return false
680
+ end
681
+ if @provider != other.provider
682
+ return false
683
+ end
684
+ if @model != other.model
685
+ return false
686
+ end
687
+ if @tokenizer_label != other.tokenizer_label
688
+ return false
689
+ end
690
+ if @tokenizer_exact != other.tokenizer_exact
691
+ return false
692
+ end
693
+ if @input_tokens_before != other.input_tokens_before
694
+ return false
695
+ end
696
+ if @input_tokens_after != other.input_tokens_after
697
+ return false
698
+ end
699
+ if @frozen_input_tokens != other.frozen_input_tokens
700
+ return false
701
+ end
702
+ if @output_shaped != other.output_shaped
703
+ return false
704
+ end
705
+
706
+ true
707
+ end
708
+ end
709
+
710
+
711
+
712
+
713
+
714
+ def self.compress(input, provider, preset)
715
+ input = LlmtrimFfi::uniffi_utf8(input)
716
+
717
+
718
+ provider = (provider ? provider : nil)
719
+ RustBuffer.check_lower_OptionalTypeProvider(provider)
720
+
721
+ preset = (preset ? LlmtrimFfi::uniffi_utf8(preset) : nil)
722
+ RustBuffer.check_lower_Optionalstring(preset)
723
+
724
+ result = LlmtrimFfi.rust_call_with_error(LlmtrimError,:uniffi_llmtrim_ffi_fn_func_compress,RustBuffer.allocFromString(input),RustBuffer.alloc_from_OptionalTypeProvider(provider),RustBuffer.alloc_from_Optionalstring(preset))
725
+ return result.consumeIntoTypeCompressOutput
726
+ end
727
+
728
+
729
+
730
+
731
+
732
+ end
733
+
data/lib/llmtrim.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # llmtrim — static, deterministic LLM prompt/payload compression.
4
+ #
5
+ # Thin entry point: load the UniFFI-generated bindings (which the build step patched to
6
+ # load the native library bundled inside this gem) and re-expose them under `Llmtrim`.
7
+ #
8
+ # require "llmtrim"
9
+ # out = Llmtrim.compress(request_json, Llmtrim::Provider::OPEN_AI, "aggressive")
10
+ # out.input_tokens_before # => Integer
11
+ #
12
+ # The compression runs natively in-process via the bundled `llmtrim-core` engine.
13
+
14
+ require_relative "llmtrim/llmtrim_ffi"
15
+
16
+ # `LlmtrimFfi` is the module name UniFFI derives from the crate; alias it to the friendlier
17
+ # `Llmtrim` without breaking the generated internals.
18
+ Llmtrim = LlmtrimFfi
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: llmtrim
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.8
5
+ platform: arm64-darwin-23
6
+ authors:
7
+ - François Kiene
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ description: Native in-process bindings to the llmtrim-core compression engine (no
28
+ network, no extra model calls), generated via UniFFI.
29
+ email:
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - README.md
35
+ - lib/llmtrim.rb
36
+ - lib/llmtrim/libllmtrim_ffi.dylib
37
+ - lib/llmtrim/llmtrim_ffi.rb
38
+ homepage: https://github.com/fkiene/llmtrim
39
+ licenses:
40
+ - AGPL-3.0-only
41
+ metadata:
42
+ source_code_uri: https://github.com/fkiene/llmtrim
43
+ rubygems_mfa_required: 'true'
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '3.0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 3.5.22
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: Static, deterministic LLM prompt/payload compression — cut input tokens 30-90%.
63
+ test_files: []