Metadata-Version: 2.5
Name: lumma-fev
Version: 0.4.0
Summary: Typed decisions with probabilities from FrontiersMind Lumma-Fev models: local inference, a TypeSafe-compatible API server and a client.
Project-URL: Homepage, https://www.frontiersmind.ai/
Project-URL: Models, https://huggingface.co/FrontiersMind
Author-email: FrontiersMind <support@frontiersmind.ai>
License-Expression: Apache-2.0 AND MIT
License-File: LICENSE
License-File: LICENSE-AUTOJEV
License-File: NOTICE
Keywords: classification,decision-model,frontiersmind,lumma,typesafe
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: huggingface-hub>=1.0
Requires-Dist: safetensors>=0.4
Requires-Dist: torch>=2.4
Requires-Dist: transformers<6,>=5.17
Provides-Extra: images
Requires-Dist: pillow>=10; extra == 'images'
Requires-Dist: torchvision>=0.19; extra == 'images'
Provides-Extra: large
Requires-Dist: accelerate>=1.0; extra == 'large'
Provides-Extra: serve
Requires-Dist: fastapi>=0.110; extra == 'serve'
Requires-Dist: pydantic>=2.6; extra == 'serve'
Requires-Dist: uvicorn>=0.29; extra == 'serve'
Description-Content-Type: text/markdown

# lumma-fev

Run FrontiersMind **Lumma-Fev** decision models: give a document (the *state*) and typed questions, and get a
probability distribution for every question in one forward pass. No text is generated, so there is nothing to parse.

- `lumma_fev.load()`: run a model locally (cuda, mps or cpu).
- `lumma-fev-serve`: a TypeSafe-compatible HTTP API (`POST /v1/systemone`), so existing TypeSafe clients work by
  changing their base URL.
- `lumma_fev.Client`: a small client for that API.

Models (pass the Hub id or the short size to `load()` and `--model`):

| Size | Hub id | Notes |
|---|---|---|
| `0.1b` | [FrontiersMind/Lumma-fev-0.1b](https://huggingface.co/FrontiersMind/Lumma-fev-0.1b) | default; runs on a CPU |
| `0.1b-v2` | [FrontiersMind/Lumma-fev-0.1B-v2](https://huggingface.co/FrontiersMind/Lumma-fev-0.1B-v2) | Nandi-Mini-150M backbone, text-only, 2,048-token context; needs lumma-fev >= 0.4 |
| `0.6b` | [FrontiersMind/Lumma-fev-0.6b](https://huggingface.co/FrontiersMind/Lumma-fev-0.6b) | |
| `4b` | [FrontiersMind/Lumma-fev-4b](https://huggingface.co/FrontiersMind/Lumma-fev-4b) | |
| `4b-v2` | [FrontiersMind/Lumma-fev-4b-v2](https://huggingface.co/FrontiersMind/Lumma-fev-4b-v2) | bidirectional attention, needs lumma-fev >= 0.3 |
| `9b` | [FrontiersMind/Lumma-fev-9b](https://huggingface.co/FrontiersMind/Lumma-fev-9b) | |
| `27b` | [FrontiersMind/Lumma-fev-27B](https://huggingface.co/FrontiersMind/Lumma-fev-27B) | about 55 GB of GPU memory in bf16; also accepts images |
| `27b-v2` | [FrontiersMind/Lumma-fev-27B-v2](https://huggingface.co/FrontiersMind/Lumma-fev-27B-v2) | same as `27b`; bidirectional attention, needs lumma-fev >= 0.3 |

## Install

```bash
pip install lumma-fev              # local inference and the client
pip install "lumma-fev[serve]"     # plus the API server
pip install "lumma-fev[large]"     # plus accelerate, for device_map="auto" (27B on several GPUs)
pip install "lumma-fev[images]"    # plus Pillow and torchvision, for image states (27B)
```

## Run locally

```python
import lumma_fev

model = lumma_fev.load()           # FrontiersMind/lumma-fev-0.1b; load("FrontiersMind/<repo>", revision=...) for others

answers = model.decide(
    state="I was charged twice for my March invoice. Please refund one of them.",
    questions={
        "billing": {"type": "noul", "instructions": "Is this about billing?"},
        "team": {"type": "choice", "instructions": "Which team should handle this?",
                 "criteria": {"billing": "Payments and refunds", "shipping": "Delivery problems", "technical": "Bugs and outages"}},
        "urgency": {"type": "score", "instructions": "How urgent is this?",
                    "criteria": ["can wait", "this week", "today"]},
    },
)
# {"billing": {"noul": 0.97}, "team": {"choice": "billing", "confidence": ..., "probabilities": {...}},
#  "urgency": {"score": ..., "confidence": ..., "legend": [...], "probabilities": {...}}}
```

`load(model, revision=None, device=None, dtype=None, device_map=None)` picks cuda, then mps, then cpu, and uses bf16
on accelerators and fp32 on cpu (27B always defaults to bf16, the dtype it is stored in). The sizes up to 9B also load
with transformers alone: `AutoModel.from_pretrained("FrontiersMind/lumma-fev-0.1b", trust_remote_code=True)`.

### Lumma-fev-27B

```python
model = lumma_fev.load("27b")                        # one GPU with about 55 GB free (an 80 GB card)
model = lumma_fev.load("27b", device_map="auto")     # or spread over several GPUs (pip install "lumma-fev[large]")

model.decide("My integration keeps failing. Please help.",
             {"team": {"type": "choice", "instructions": "Which team should handle this request?",
                       "criteria": {"billing": "Charges and refunds", "support": "Technical integration errors",
                                    "sales": "Questions about buying a product"}}})
# {"team": {"type": "choice", "choice": "support", "confidence": 0.99..., "probabilities": {...}}}

model.decide("What is shown?", {"red": {"type": "noul", "instructions": "Is the product red?"}},
             images=["photo.png"])                   # paths, PIL images or data: URLs (pip install "lumma-fev[images]")
```

27B uses a different checkpoint format from the smaller sizes (a Qwen3.5 backbone plus a readout over answer codes);
`load()` detects it from the repo's `decision_config.json`, and the call and the answers are the same. Its prompt
follows the [AutoJev](https://github.com/denis-pplx/autojev) reference code bundled in the repo, so probabilities match
it. A question plus the state must fit in 8,192 tokens (nothing is truncated: longer input raises `ValueError`), and
score questions need at least two levels. 27B was trained on noul and choice questions only, so check score answers on
your own data. For speed, install the optional `flash-linear-attention` and `causal-conv1d` kernels; without them
transformers falls back to slower PyTorch code.

Lumma-fev-0.1B-v2 (`0.1b-v2`, lumma-fev >= 0.4) uses the same readout format on the text-only Nandi-Mini-150M
backbone: a plain-text prompt (no chat template, no images), causal attention, and a 2,048-token limit per question
plus state. Its backbone ships custom modeling code, which `load()` runs with `trust_remote_code=True`.

## Serve an API

```bash
lumma-fev-serve --model FrontiersMind/lumma-fev-0.1b --host 0.0.0.0 --port 8000
lumma-fev-serve --model 27b --host 0.0.0.0 --port 8000                 # add --device-map auto for several GPUs
```

| Flag | Default | |
|---|---|---|
| `--model` | `FrontiersMind/lumma-fev-0.1b` | Hub id or local folder |
| `--revision` | latest | branch, tag or commit |
| `--name` | repo name | model name reported by the API |
| `--host`, `--port` | `127.0.0.1`, `8000` | use `0.0.0.0` to accept other machines |
| `--device`, `--dtype` | auto | `cuda`/`mps`/`cpu`, `bf16`/`fp16`/`fp32` |
| `--device-map` | none | `auto` to spread a large model over several GPUs (needs `lumma-fev[large]`) |
| `--api-key` | `$LUMMA_FEV_API_KEY` | when set, requests need `Authorization: Bearer <key>` |
| `--cors` | off | allow browser pages on any origin |

Endpoints: `POST /v1/systemone`, `GET /v1/models`, `GET /health`. Requests run one at a time. With 27B, a request
may also carry `"images": [...]`, up to four base64 PNG, JPEG or WebP `data:` URLs.

```bash
curl -s localhost:8000/v1/systemone -H 'content-type: application/json' -d '{
  "state": "I was charged twice. Please fix this ASAP.",
  "questions": {"billing": {"type": "noul", "instructions": "Is this ticket about billing?"}}
}'
# {"model": "lumma-fev-latest", "answers": {"billing": {"noul": 0.98}}, "usage": {"input_tokens": 31, "output_tokens": 9}}
```

## Call it from Python

```python
from lumma_fev import Client

with Client("http://127.0.0.1:8000", api_key=None) as client:
    print(client.decide("I was charged twice.", {"billing": {"type": "noul", "instructions": "Is this about billing?"}}))
    print(client.models())
```

`client.system_one(...)` returns the full response (model, answers, usage); errors raise `lumma_fev.LummaFevError`
with the HTTP status and detail.

The [TypeSafe SDK](https://pypi.org/project/typesafe-sdk/) works against the same server:

```python
from typesafe_sdk import Noul, TypeSafeClient

with TypeSafeClient(api_key="local", base_url="http://127.0.0.1:8000") as ts:
    result = ts.system_one(state="I was charged twice.", questions={"billing": Noul(instructions="Is this about billing?")})
    print(result.nouls["billing"].noul)
```

The SDK always sends an API key; any value works when the server has no `--api-key`.

## Questions and answers

| Type | Criteria | Answer |
|---|---|---|
| `noul` | optional `{"true": ..., "false": ...}` | `noul`: probability of yes |
| `choice` | `{name: description or null}`, 1–255 options | `choice`, `confidence`, `probabilities` by name |
| `score` | ordered list of 1–255 levels (2–255 for 27B) | `score` (expected level), `confidence`, `legend`, `probabilities` by level |

`state` and `instructions` can be text or any JSON value. Each question reads the state and its own instructions and
options only, so questions never influence each other. Up to 9B, long states are truncated to the model's context (see
the model card); 27B rejects input over 8,192 tokens instead.

## License

Apache-2.0. See `LICENSE` and `NOTICE`.
