opensolr-haystack 0.4.0__tar.gz → 0.5.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensolr-haystack
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Haystack integration for Opensolr — managed Apache Solr DocumentStore with server-side embeddings and hybrid BM25+kNN retrieval
5
5
  Author-email: Opensolr <support@opensolr.com>
6
6
  License: MIT
@@ -145,10 +145,37 @@ Don't need vectors? Pure keyword search skips the embedding call entirely —
145
145
  zero AI quota, and it works on **any** Opensolr index, including non-vector
146
146
  ones and older Solr versions.
147
147
 
148
+ ## Search by image
149
+
150
+ Search your index with a **photo** instead of a text query. Opensolr reads the
151
+ picture three ways — visual labels (what it depicts), OCR text (words printed on
152
+ it), and any barcode / QR code — and turns that into an ordinary search. Nothing
153
+ new is stored in Solr; the picture simply becomes words.
154
+
155
+ ```python
156
+ # What the picture reads as (labels, OCR text, barcodes) — no search yet:
157
+ read = store.image_to_words("photo.jpg") # path, bytes, or base64
158
+ # {'text': 'red running shoe', 'mode': 'clip',
159
+ # 'labels': ['running shoe', 'sneaker'], 'codes': ['0123456789012']}
160
+
161
+ # Search the index with the picture:
162
+ docs = store.search_by_image("photo.jpg", top_k=4) # engine picks the best reading
163
+ docs = store.search_by_image("photo.jpg", using="meaning") # visual labels
164
+ docs = store.search_by_image("photo.jpg", using="text") # only OCR text
165
+ docs = store.search_by_image("photo.jpg", using="code") # exact barcode / QR match
166
+ docs = store.search_by_image("photo.jpg", using="all") # labels + OCR + codes
167
+ ```
168
+
169
+ `search_by_image` forwards the same tuning as `search` — `hybrid`, `lexical`,
170
+ `alpha`, `fresh_bias`, `filters` — so an image query runs through the exact
171
+ hybrid pipeline a text query does.
172
+
148
173
  ## Your index schema
149
174
 
150
175
  Documents follow the Opensolr document model (`title`, `description`, `text`,
151
- `meta_*` custom fields). To see the full schema: **Control Panel → click your
176
+ `meta_*` custom fields). The whole schema, every field and every type suffix,
177
+ is explained in the [Index Schema Reference](https://opensolr.com/opensolr-platform-user-documentation/schema-reference).
178
+ To see your own copy: **Control Panel → click your
152
179
  index → Configuration → Edit File → schema.xml**. Prefer zero-effort data
153
180
  entry? Configure the **Web Crawler** in the Control Panel (Index Tools →
154
181
  WebCrawler): add your site URL, validate it, and Opensolr indexes the whole
@@ -127,10 +127,37 @@ Don't need vectors? Pure keyword search skips the embedding call entirely —
127
127
  zero AI quota, and it works on **any** Opensolr index, including non-vector
128
128
  ones and older Solr versions.
129
129
 
130
+ ## Search by image
131
+
132
+ Search your index with a **photo** instead of a text query. Opensolr reads the
133
+ picture three ways — visual labels (what it depicts), OCR text (words printed on
134
+ it), and any barcode / QR code — and turns that into an ordinary search. Nothing
135
+ new is stored in Solr; the picture simply becomes words.
136
+
137
+ ```python
138
+ # What the picture reads as (labels, OCR text, barcodes) — no search yet:
139
+ read = store.image_to_words("photo.jpg") # path, bytes, or base64
140
+ # {'text': 'red running shoe', 'mode': 'clip',
141
+ # 'labels': ['running shoe', 'sneaker'], 'codes': ['0123456789012']}
142
+
143
+ # Search the index with the picture:
144
+ docs = store.search_by_image("photo.jpg", top_k=4) # engine picks the best reading
145
+ docs = store.search_by_image("photo.jpg", using="meaning") # visual labels
146
+ docs = store.search_by_image("photo.jpg", using="text") # only OCR text
147
+ docs = store.search_by_image("photo.jpg", using="code") # exact barcode / QR match
148
+ docs = store.search_by_image("photo.jpg", using="all") # labels + OCR + codes
149
+ ```
150
+
151
+ `search_by_image` forwards the same tuning as `search` — `hybrid`, `lexical`,
152
+ `alpha`, `fresh_bias`, `filters` — so an image query runs through the exact
153
+ hybrid pipeline a text query does.
154
+
130
155
  ## Your index schema
131
156
 
132
157
  Documents follow the Opensolr document model (`title`, `description`, `text`,
133
- `meta_*` custom fields). To see the full schema: **Control Panel → click your
158
+ `meta_*` custom fields). The whole schema, every field and every type suffix,
159
+ is explained in the [Index Schema Reference](https://opensolr.com/opensolr-platform-user-documentation/schema-reference).
160
+ To see your own copy: **Control Panel → click your
134
161
  index → Configuration → Edit File → schema.xml**. Prefer zero-effort data
135
162
  entry? Configure the **Web Crawler** in the Control Panel (Index Tools →
136
163
  WebCrawler): add your site URL, validate it, and Opensolr indexes the whole
@@ -540,6 +540,15 @@ class OpensolrClient:
540
540
  def ai(self, method: str, **params: Any) -> Any:
541
541
  return self._request(AI_BASE, method, params)
542
542
 
543
+ def image_to_text(self, index: str, image_b64: str, top_k: int = 8) -> Dict[str, Any]:
544
+ """Turn an image into search words via the Opensolr image_to_text API.
545
+
546
+ The answer carries ``text`` (the words to search with), ``mode`` ("clip"/"ocr"),
547
+ ``labels`` (CLIP visual labels, present even in OCR mode) and ``codes``
548
+ (barcodes / QR codes). No image vector is stored: the picture becomes words.
549
+ """
550
+ return self.ai("image_to_text", index_name=index, image=image_b64, top_k=top_k)
551
+
543
552
  # ------------------------------------------------------------------ #
544
553
  # management #
545
554
  # ------------------------------------------------------------------ #
@@ -7,6 +7,8 @@ local embedder component needed at indexing time.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import base64
11
+ from pathlib import Path
10
12
  import json
11
13
  import re
12
14
  from typing import Any, Dict, List, Optional
@@ -376,6 +378,66 @@ class OpensolrDocumentStore:
376
378
  # serialization #
377
379
  # ------------------------------------------------------------------ #
378
380
 
381
+ def image_to_words(self, image, top_k: int = 8) -> Dict[str, Any]:
382
+ """Read an image and return what it was turned into, without searching yet.
383
+
384
+ Returns ``{"text", "mode", "labels", "codes"}``: the best words to search
385
+ with, whether it was read as visual labels ("clip") or OCR text ("ocr"),
386
+ the CLIP visual labels (present even in OCR mode), and any barcodes / QR codes.
387
+ ``image`` is a file path (str/Path) or raw image bytes.
388
+ """
389
+ raw = bytes(image) if isinstance(image, (bytes, bytearray)) else Path(image).read_bytes()
390
+ b64 = base64.b64encode(raw).decode("ascii")
391
+ ans = self.client.image_to_text(self._index, b64, top_k=top_k)
392
+ labels = [l["label"] for l in (ans.get("labels") or []) if isinstance(l, dict) and l.get("label")]
393
+ codes = []
394
+ for c in (ans.get("codes") or []):
395
+ text = c.get("text") if isinstance(c, dict) else (c if isinstance(c, str) else None)
396
+ if text:
397
+ codes.append(text)
398
+ return {"text": (ans.get("text") or "").strip(), "mode": ans.get("mode") or "clip", "labels": labels, "codes": codes}
399
+
400
+ def search_by_image(
401
+ self,
402
+ image,
403
+ top_k: int = 10,
404
+ using: str = "auto",
405
+ filters: Optional[Dict[str, Any]] = None,
406
+ image_top_k: int = 8,
407
+ **search_kwargs,
408
+ ) -> List[Document]:
409
+ """Search the index with a photo instead of a text query.
410
+
411
+ The image is turned into words by :meth:`image_to_words`, then those words
412
+ run through the normal :meth:`search`, so ``hybrid``, ``lexical``, ``alpha``
413
+ and ``fresh_bias`` apply via ``search_kwargs``. No image vector is stored.
414
+
415
+ ``using``: which reading to search with — ``"auto"`` (the engine's chosen
416
+ text), ``"meaning"`` (CLIP visual labels), ``"text"`` (OCR text only), or
417
+ ``"code"`` (the first barcode / QR code), or ``"all"`` (labels + OCR text + barcodes combined).
418
+ """
419
+ read = self.image_to_words(image, top_k=image_top_k)
420
+ if using == "meaning":
421
+ query = ", ".join(read["labels"])
422
+ elif using == "text":
423
+ query = read["text"] if read["mode"] == "ocr" else ""
424
+ elif using == "code":
425
+ query = read["codes"][0] if read["codes"] else ""
426
+ search_kwargs.setdefault("lexical", True)
427
+ elif using == "all":
428
+ parts = list(read["labels"])
429
+ if read["mode"] == "ocr" and read["text"]:
430
+ parts.append(read["text"])
431
+ parts.extend(read["codes"])
432
+ query = ", ".join(p for p in parts if p)
433
+ elif using == "auto":
434
+ query = read["text"]
435
+ else:
436
+ raise ValueError("using must be one of 'auto', 'meaning', 'text', 'code', 'all'")
437
+ if not query:
438
+ return []
439
+ return self.search(query, top_k=top_k, filters=filters, **search_kwargs)
440
+
379
441
  def to_dict(self) -> Dict[str, Any]:
380
442
  return default_to_dict(
381
443
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensolr-haystack
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Haystack integration for Opensolr — managed Apache Solr DocumentStore with server-side embeddings and hybrid BM25+kNN retrieval
5
5
  Author-email: Opensolr <support@opensolr.com>
6
6
  License: MIT
@@ -145,10 +145,37 @@ Don't need vectors? Pure keyword search skips the embedding call entirely —
145
145
  zero AI quota, and it works on **any** Opensolr index, including non-vector
146
146
  ones and older Solr versions.
147
147
 
148
+ ## Search by image
149
+
150
+ Search your index with a **photo** instead of a text query. Opensolr reads the
151
+ picture three ways — visual labels (what it depicts), OCR text (words printed on
152
+ it), and any barcode / QR code — and turns that into an ordinary search. Nothing
153
+ new is stored in Solr; the picture simply becomes words.
154
+
155
+ ```python
156
+ # What the picture reads as (labels, OCR text, barcodes) — no search yet:
157
+ read = store.image_to_words("photo.jpg") # path, bytes, or base64
158
+ # {'text': 'red running shoe', 'mode': 'clip',
159
+ # 'labels': ['running shoe', 'sneaker'], 'codes': ['0123456789012']}
160
+
161
+ # Search the index with the picture:
162
+ docs = store.search_by_image("photo.jpg", top_k=4) # engine picks the best reading
163
+ docs = store.search_by_image("photo.jpg", using="meaning") # visual labels
164
+ docs = store.search_by_image("photo.jpg", using="text") # only OCR text
165
+ docs = store.search_by_image("photo.jpg", using="code") # exact barcode / QR match
166
+ docs = store.search_by_image("photo.jpg", using="all") # labels + OCR + codes
167
+ ```
168
+
169
+ `search_by_image` forwards the same tuning as `search` — `hybrid`, `lexical`,
170
+ `alpha`, `fresh_bias`, `filters` — so an image query runs through the exact
171
+ hybrid pipeline a text query does.
172
+
148
173
  ## Your index schema
149
174
 
150
175
  Documents follow the Opensolr document model (`title`, `description`, `text`,
151
- `meta_*` custom fields). To see the full schema: **Control Panel → click your
176
+ `meta_*` custom fields). The whole schema, every field and every type suffix,
177
+ is explained in the [Index Schema Reference](https://opensolr.com/opensolr-platform-user-documentation/schema-reference).
178
+ To see your own copy: **Control Panel → click your
152
179
  index → Configuration → Edit File → schema.xml**. Prefer zero-effort data
153
180
  entry? Configure the **Web Crawler** in the Control Panel (Index Tools →
154
181
  WebCrawler): add your site URL, validate it, and Opensolr indexes the whole
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "opensolr-haystack"
7
- version = "0.4.0"
7
+ version = "0.5.0"
8
8
  description = "Haystack integration for Opensolr — managed Apache Solr DocumentStore with server-side embeddings and hybrid BM25+kNN retrieval"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }