Metadata-Version: 2.4
Name: llmtrim
Version: 0.3.2
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Text Processing
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
Summary: Static, deterministic LLM prompt/payload compression that cuts input tokens 30-90% with zero extra model calls.
Keywords: llm,tokens,compression,openai,anthropic
Home-Page: https://github.com/fkiene/llmtrim
License: MPL-2.0
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Repository, https://github.com/fkiene/llmtrim

# llmtrim-uniffi

[UniFFI](https://mozilla.github.io/uniffi-rs/) bindings over [`llmtrim-core`]: one Rust
definition, idiomatic in-process bindings for **Python, Ruby, Swift and Kotlin**. The
compression runs natively in the caller's process (no server, no async).

## API

A deliberately flat surface over the engine:

```rust
fn compress(
    input: String,                 // a provider-shaped request body (JSON)
    provider: Option<Provider>,    // OpenAi | Anthropic | Google, or None to auto-detect
    preset: Option<String>,        // "aggressive" | "agent" | "code" | "rag" | "safe" | …
                                   // None = config from the environment / config file
) -> Result<CompressOutput, LlmtrimError>
```

`CompressOutput` carries the compressed `request_json`, the resolved `provider`/`model`,
the tokenizer label/exactness, and the before/after/frozen input-token counts. Embedders
that need the full rehydration plan or per-stage reports should depend on [`llmtrim-core`]
directly in Rust.

## In-process vs. the proxy

llmtrim has two integration routes:

- **The proxy** (`HTTPS_PROXY=127.0.0.1:8788 llmtrim`) intercepts your existing traffic
  and compresses it in flight. Nothing in your code changes, but the client has to route
  through the proxy and trust its CA.
- **These bindings** compress in your process. You call `compress()` on the request body,
  then send the result with your own HTTP client. No proxy, no CA, no env-var setup.

Use the in-process path when the proxy can't run:

- **Sandboxed / serverless** functions where you can't set a process-wide `HTTPS_PROXY`
  or run a side process.
- **Certificate-pinned clients** that reject a MITM CA, so the proxy's interception fails.
- Anywhere you'd rather not add a network hop or an extra moving part.

It replaces a per-framework adapter: instead of wiring a hook into each SDK, you compress
the body once and POST it yourself. Runnable end-to-end examples (compress, then send with
your own client) are in [`examples/`](examples).

## Python

```bash
# Build a self-contained wheel (cdylib + generated glue):
crates/llmtrim-uniffi/scripts/build-wheel.sh --release
pip install target/wheels/llmtrim-*.whl
```

```python
import llmtrim, json

req = json.dumps({"model": "gpt-4o",
                  "messages": [{"role": "user", "content": "…"}]})
out = llmtrim.compress(req, llmtrim.Provider.OPEN_AI, "aggressive")
print(out.input_tokens_before, "->", out.input_tokens_after)
# send out.request_json to the provider
```

> **Why `build-wheel.sh` and not plain `maturin build`:** maturin's `bindings = "uniffi"`
> auto-packaging is sensitive to the maturin↔uniffi version pair. With maturin 1.14 +
> uniffi 0.31 it builds the native library into the wheel but omits the generated Python
> glue (empty package `__init__.py`). The script runs maturin, then injects the freshly
> generated bindings and repacks the wheel with valid RECORD hashes. Remove it once the
> auto path packages cleanly.

## Ruby / Swift / Kotlin

All targets generate from the same built library, no extra Rust. The generated glue is a
build artifact (its checksums are pinned to the library ABI), so it is regenerated per
release rather than committed:

```bash
crates/llmtrim-uniffi/scripts/generate-bindings.sh out/   # python, ruby, swift, kotlin
```

> **Generation needs an unstripped library.** Library-mode `uniffi-bindgen` reads metadata
> symbols from the cdylib, but the workspace release profile sets `strip = true`. The script
> therefore generates from the (unstripped) debug build; the native library you *ship* can be
> a stripped `cargo build --release -p llmtrim-uniffi` cdylib; the glue loads it by name.

Ruby (verified). This is the **raw generated binding** (module `LlmtrimFfi`), for a
source build with `libllmtrim_ffi.so` on the load path. The published **gem** aliases it to
`Llmtrim` (`require "llmtrim"` then `Llmtrim.compress(...)`); see
[`packaging/ruby`](packaging/ruby).

```ruby
require_relative "llmtrim_ffi"
require "json"
out = LlmtrimFfi.compress(
  JSON.generate({model: "gpt-4o", messages: [{role: "user", content: "…"}]}),
  LlmtrimFfi::Provider::OPEN_AI, "aggressive")
puts "#{out.input_tokens_before} -> #{out.input_tokens_after}"
```

Swift emits `llmtrim_ffi.swift` + an FFI header and modulemap; Kotlin emits
`uniffi/.../llmtrim_ffi.kt` (which loads the cdylib via JNA). CI compiles and runs a smoke
for both: Swift on macOS (`swiftc` against the modulemap), Kotlin on a JVM (`kotlinc` +
JNA), so a binding break is caught in all four languages (see `tests/swift`, `tests/kotlin`
and the `bindings*` jobs in `.github/workflows/ci.yml`).

## Publishable packages

Each ships the compiled engine bundled, so consumers need no Rust toolchain:

| Target | Build | Package | Verified |
|--------|-------|---------|----------|
| Python (PyPI) | `scripts/build-wheel.sh` | wheel | locally |
| Ruby (gem) | `scripts/build-gem.sh` | `packaging/ruby/` | locally |
| Kotlin/JVM (Maven) | `scripts/build-maven.sh` | `packaging/kotlin/` | locally |
| Swift (SwiftPM) | `scripts/build-xcframework.sh` | `packaging/swift/` | macOS CI only |

Each `packaging/<lang>/README.md` has the usage + publish details.

[`llmtrim-core`]: ../llmtrim-core

