Metadata-Version: 2.4
Name: lens-embed
Version: 0.1.0
Summary: Shared embedding helper for the lens family — one pinned text + image model so every member's vectors are comparable (for cross-artefact consistency and cohort distinctiveness).
Project-URL: Homepage, https://github.com/michael-borck/lens-embed
Author: Michael Borck
License: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Requires-Dist: numpy>=1.24
Provides-Extra: all
Requires-Dist: open-clip-torch>=2.20; extra == 'all'
Requires-Dist: pillow>=10.0; extra == 'all'
Requires-Dist: sentence-transformers>=2.2; extra == 'all'
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: image
Requires-Dist: open-clip-torch>=2.20; extra == 'image'
Requires-Dist: pillow>=10.0; extra == 'image'
Provides-Extra: text
Requires-Dist: sentence-transformers>=2.2; extra == 'text'
Description-Content-Type: text/markdown

# lens-embed

Part of the [lens family](https://github.com/michael-borck/lens-analysers).

[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**The family's shared embedding helper.** One pinned text model and one pinned
image model, so vectors produced by *every* member land in the same space and
are directly comparable.

That comparability is the whole point. It's what makes two family-level signals
possible:

- **Cross-artefact consistency** — does the video narration match the report?
  does the code match what the reflection claims?
- **Cohort distinctiveness** — is this submission an outlier, or unusually
  similar to another in the same batch? (Surfaced as a neutral observation for a
  human to interpret — never a verdict.)

`lens-embed` is a **library** (`role: library`, like `lens-contract`), not an
analyser. It produces vectors and compares them; it makes no judgements.

## Producer vs consumer

The heavy ML backends are opt-in, because the two sides need different things:

| You are… | You install | You get |
|---|---|---|
| a **producer** (an analyser generating vectors) | `lens-embed[text]` and/or `lens-embed[image]` | `embed_text` / `embed_image` |
| a **consumer** (comparing pre-computed vectors) | `lens-embed` (numpy only) | `cosine_similarity`, `pairwise_similarity`, `most_similar` |

```bash
pip install 'lens-embed[text]'    # sentence-transformers (all-MiniLM-L6-v2)
pip install 'lens-embed[image]'   # CLIP via open_clip (ViT-B-32)
pip install 'lens-embed[all]'     # both
pip install lens-embed            # comparison helpers only (numpy)
```

## Usage

```python
from lens_embed import embed_text, cosine_similarity, most_similar

a = embed_text("Climate disclosure in annual reports")
b = embed_text("How companies report on the environment")
cosine_similarity(a, b)            # ~0.6 — L2-normalised, so this is a dot product

# Rank a cohort against one submission (distinctiveness):
cohort = [embed_text(s) for s in submissions]
most_similar(cohort[0], cohort[1:], top_k=3)   # [(idx, score), ...]
```

```python
from lens_embed import embed_image
vec = embed_image("diagram.png")   # CLIP image vector
```

Degrade gracefully when a backend isn't installed:

```python
from lens_embed import backend_available, embed_text
emb = embed_text(text) if backend_available("text") else None
```

## Model identity (the contract)

| Modality | Default | Why |
|---|---|---|
| text | `all-MiniLM-L6-v2` | the model conversation-/document-analyser already load — adopting lens-embed doesn't move the existing vector space |
| image | `ViT-B-32` / `laion2b_s34b_b79k` (CLIP) | joint image/text space for visual comparison |

The defaults are the contract — **don't diverge per analyser**, or vectors stop
being comparable. They're env-overridable (`LENS_EMBED_TEXT_MODEL`, etc.) for
experiments only.

> Note: sentence-transformers text vectors and CLIP text vectors live in
> *different* spaces. Use `embed_text` for text-to-text comparison; CLIP
> (`embed_image`) for anything visual.

## Development

```bash
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest                  # fast: comparison + contract (numpy only)
pytest -m slow          # loads real models (needs [text] / [image])
```

## License

MIT — see [LICENSE](LICENSE).
