{"id":49046838,"url":"https://github.com/bicardinal/brinicle","last_synced_at":"2026-06-05T11:00:15.447Z","repository":{"id":329972998,"uuid":"1121183308","full_name":"bicardinal/brinicle","owner":"bicardinal","description":"A resource-efficient C++ vector index engine built for low-RAM production workloads","archived":false,"fork":false,"pushed_at":"2026-05-28T15:48:03.000Z","size":874,"stargazers_count":20,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T17:26:20.211Z","etag":null,"topics":["brinicle","embeddings","hnsw","rag","retrieval","vector-search"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bicardinal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-22T15:12:59.000Z","updated_at":"2026-05-28T15:29:56.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bicardinal/brinicle","commit_stats":null,"previous_names":["bicardinal/brinicle"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/bicardinal/brinicle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bicardinal%2Fbrinicle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bicardinal%2Fbrinicle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bicardinal%2Fbrinicle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bicardinal%2Fbrinicle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bicardinal","download_url":"https://codeload.github.com/bicardinal/brinicle/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bicardinal%2Fbrinicle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33939226,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["brinicle","embeddings","hnsw","rag","retrieval","vector-search"],"created_at":"2026-04-19T18:00:35.902Z","updated_at":"2026-06-05T11:00:15.360Z","avatar_url":"https://github.com/bicardinal.png","language":"Python","funding_links":[],"categories":["Multidimensional data / Vectors"],"sub_categories":[],"readme":"\n![Version 0.0.9](https://img.shields.io/badge/Version-0.0.9-green.svg)\n![Python 3.12.x](https://img.shields.io/badge/Python-3.12.x-green.svg)\n![Apache-2.0 License](https://img.shields.io/badge/License-Apache2.0-green.svg)\n\n# brinicle\n\nbrinicle is a disk-first HNSW retrieval engine for vector search, structured item search, hybrid search, and autocomplete.\nIt gives you a simple Python API for building search over embeddings, product catalogs, structured records, and query suggestions without running a heavy search database.\n\nOn 1.2 Million Amazon products, brinicle achieved sub-millisecond p99 latency and 1,731 MB peak search memory, the lowest among brinicle, Meilisearch, OpenSearch, Typesense, and Weaviate. It also achieved the best Hit@1 and nDCG@10 in that benchmark.\n\n```bash\npip install brinicle\n```\n\n```python\nimport numpy as np\nimport brinicle\n\nD = 384\nn = 1000\n\nX = np.random.randn(n, D).astype(np.float32)\nq = np.random.randn(D).astype(np.float32)\n\nengine = brinicle.VectorEngine(\"vector_index\", dim=D)\n\nengine.init(mode=\"build\")\n\nfor i in range(n):\n    engine.ingest(str(i), X[i])\n\nengine.finalize()\n\nprint(engine.search(q, k=10))\n[\"42\", \"318\", \"7\", \"901\", \"114\", \"68\", \"529\", \"203\", \"771\", \"16\"]\n```\n---\n\n## Benchmark\n\nbrinicle has two public benchmark suites:\n\n* [Vector search benchmark](https://brinicle.bicardinal.com/benchmark): compares brinicle with Chroma, Weaviate, Milvus, Qdrant, FAISS, and hnswlib on vector search workloads.\n* [Hybrid search benchmark](https://brinicle.bicardinal.com/search_benchmark): compares brinicle with Meilisearch, OpenSearch, Typesense, and Weaviate on hybrid product search over Amazon ESCI and WANDS.\n\nIn a 256MB RAM / 1 CPU container using MNIST 60K vectors:\n\n| System | Outcome |\n|---|---|\n| brinicle | PASS |\n| chroma | PASS |\n| qdrant | OOMKilled |\n| weaviate | OOMKilled |\n| milvus | OOMKilled |\n\nOn SIFT 1M vectors, using the same in-process deployment model as FAISS and hnswlib:\n\n| System | Build (s) | Recall@10 | Avg latency (ms) | QPS |\n|---|---:|---:|---:|---:|\n| faiss | 237.282 | 0.96999 | 0.092 | 10857.43 |\n| hnswlib | 241.301 | 0.96364 | 0.093 | 10711.86 |\n| brinicle | 243.75 | 0.96989 | 0.103 | 9730.65 |\n\nIn this benchmark suite, brinicle stays close to FAISS and hnswlib latency while using a disk-backed index design.\n\n![Memory usage comparison](https://brinicle.bicardinal.com/blow/memory_bars.png)\n\n---\n## Install\n\nInstall from PyPI:\n\n```bash\npip install brinicle\n```\n\nOr build from source:\n\n```bash\ngit clone https://github.com/bicardinal/brinicle.git\ncd brinicle\npip install -e .\n```\n\n---\n\n## Engines\n\nbrinicle exposes three high-level engines with the same lifecycle:\n\n```python\nengine.init(...)\nengine.ingest(...)\nengine.finalize()\nengine.search(...)\n```\n\n| Engine               | Use case                                  | Input                                                      |\n| -------------------- | ----------------------------------------- | ---------------------------------------------------------- |\n| `VectorEngine`       | Raw ANN vector search                     | `float32` vectors                                          |\n| `ItemSearchEngine`   | Lexical, semantic, and hybrid item search | title, category, subcategory, attributes, optional vectors |\n| `AutocompleteEngine` | Query/title suggestions                   | suggestion text                                            |\n\nAll engines support the same operational model:\n\n| Operation            | `VectorEngine` | `ItemSearchEngine` | `AutocompleteEngine` |\n| -------------------- | -------------: | -----------------: | -------------------: |\n| Build                |            Yes |                Yes |                  Yes |\n| Insert               |            Yes |                Yes |                  Yes |\n| Upsert               |            Yes |                Yes |                  Yes |\n| Delete               |            Yes |                Yes |                  Yes |\n| Search               |            Yes |                Yes |                  Yes |\n| Batch search         |            Yes |                Yes |                  Yes |\n| Search with distance |            Yes |                Yes |                  Yes |\n| Compact rebuild      |            Yes |                Yes |                  Yes |\n| Graph optimization   |            Yes |                Yes |                  Yes |\n\n---\n\n## Features\n\n* Disk-first HNSW search\n* Low-RAM indexing and querying\n* Streaming-first ingest: one vector, item, or suggestion at a time\n* Raw vector search through `VectorEngine`\n* Structured item search through `ItemSearchEngine`\n* Lexical, semantic, and hybrid item search through one HNSW index\n* Alpha-controlled item search: lexical-only, semantic-only, or hybrid\n* Autocomplete and query suggestion search through `AutocompleteEngine`\n* Insert, upsert, delete, compact rebuild, and graph optimization\n* Simple Python API backed by a C++ search core\n\n---\n\n## Core lifecycle\n\nbrinicle uses the same lifecycle across all engines.\n\nBuild a new index:\n\n```python\nengine.init(mode=\"build\")\n\nfor record in records:\n    engine.ingest(...)\n\nengine.finalize()\n```\n\nInsert new records into an existing index:\n\n```python\nengine.init(mode=\"insert\")\n\nfor record in new_records:\n    engine.ingest(...)\n\nengine.finalize()\n```\n\nUpsert records by external id:\n\n```python\nengine.init(mode=\"upsert\")\n\nfor record in updated_records:\n    engine.ingest(...)\n\nengine.finalize()\n```\n\nSearch after the index is finalized:\n\n```python\nresults = engine.search(query, k=10)\n```\n\nDelete records by external id:\n\n```python\ndeleted_count, not_found = engine.delete_items(\n    [\"id1\", \"id2\", \"missing-id\"],\n    return_not_found=True,\n)\n```\n\nCompact and rebuild the index when needed:\n\n```python\nif engine.needs_rebuild():\n    engine.rebuild_compact()\n```\n\nOptimize graph layout:\n\n```python\nengine.optimize_graph()\n```\n\n---\n\n## Vector search\n\nUse `VectorEngine` for vector search.\n\n```python\nimport numpy as np\nimport brinicle\n\nD = 384\nn = 1000\n\nX = np.random.randn(n, D).astype(np.float32)\nq = np.random.randn(D).astype(np.float32)\n\nengine = brinicle.VectorEngine(\n    \"vector_index\",\n    dim=D,\n)\n\nengine.init(mode=\"build\")\n\nfor i in range(n):\n    engine.ingest(str(i), X[i])\n\nengine.finalize()\n\nresults = engine.search(q, k=10)\n\nprint(results)\n```\n\n`search(...)` returns external ids:\n\n```python\n[\"42\", \"318\", \"7\", \"901\", \"114\", \"68\", \"529\", \"203\", \"771\", \"16\"]\n```\n\nTo return distances too:\n\n```python\nresults = engine.search_with_distance(q, k=10)\n\nprint(results)\n```\n\nExample output:\n\n```python\n[\n    (\"42\", 0.1842),\n    (\"318\", 0.2075),\n    (\"7\", 0.2198),\n]\n```\n\nTo run batch search:\n\n```python\nQ = np.random.randn(32, D).astype(np.float32)\n\nresults = engine.search_batch(\n    Q,\n    k=10,\n    n_jobs=4,\n)\n\nprint(results)\n```\n\n---\n\n### Vector insert\n\nUse `insert` mode to add new vectors to an existing index.\n\n```python\nY = np.random.randn(5, D).astype(np.float32)\n\nengine.init(mode=\"insert\")\n\nfor i in range(5):\n    engine.ingest(f\"new-{i}\", Y[i])\n\nengine.finalize()\n\nprint(engine.search(q, k=10))\n```\n\n---\n\n### Vector upsert\n\nUse `upsert` mode to replace existing records or insert them if they do not exist.\n\n```python\nY = np.random.randn(5, D).astype(np.float32)\n\nengine.init(mode=\"upsert\")\n\nfor i in range(5):\n    engine.ingest(str(i), Y[i])\n\nengine.finalize()\n\nprint(engine.search(q, k=10))\n```\n\n---\n\n### Vector delete\n\nDelete records by external id:\n\n```python\ndeleted_count, not_found = engine.delete_items(\n    [\"1\", \"4\", \"missing\"],\n    return_not_found=True,\n)\n\nprint(deleted_count)\nprint(not_found)\n```\n\n---\n\n### Vector rebuild and optimize\n\nAfter many inserts, upserts, or deletes, the index may need a compact rebuild.\n\n```python\nif engine.needs_rebuild():\n    engine.rebuild_compact(\n        M=48,\n        ef_construction=1024,\n        ef_search=512,\n        build_n_threads=4,\n    )\n```\n\nYou can also optimize the graph:\n\n```python\nengine.optimize_graph()\n```\n\n---\n\n### Vector configuration\n\n`VectorEngine` exposes common HNSW parameters:\n\n```python\nengine = brinicle.VectorEngine(\n    \"vector_index\",\n    dim=384,\n    M=48,\n    ef_construction=1024,\n    ef_search=512,\n    delta_ratio=0.1,\n)\n```\n\n| Parameter         | Meaning                                           |\n| ----------------- | ------------------------------------------------- |\n| `dim`             | Vector dimensionality                             |\n| `M`               | Maximum graph degree used by HNSW                 |\n| `ef_construction` | Construction-time search width                    |\n| `ef_search`       | Query-time search width                           |\n| `build_n_threads` | Build n threads, higher, faster build             |\n| `delta_ratio`     | Delta segment ratio before rebuild is recommended |\n\n\n## Item search\n\nUse `ItemSearchEngine` for catalog-like records with titles, metadata, and optional semantic vectors.\n\nEach item can contain:\n\n* `title`\n* `category`\n* `subcategory`\n* `attributes`\n* an optional semantic vector\n\nOnly `title` is required.\n\n`ItemSearchEngine` supports three practical modes:\n\n| Mode                      | How to use it                                                       |\n| ------------------------- | ------------------------------------------------------------------- |\n| Lexical-only item search  | Use structured fields only and set `alpha=0.0`                      |\n| Semantic-only item search | Provide vectors and set `alpha=1.0`                                 |\n| Hybrid item search        | Provide structured fields and vectors, then use `0.0 \u003c alpha \u003c 1.0` |\n\nbrinicle does not build separate lexical and vector indexes for item search. Structured lexical signals and optional semantic vectors are encoded into one numeric representation and searched through the same HNSW graph.\n\n---\n\n### Lexical item search\n\nUse lexical item search when you want structured catalog search without external embeddings.\n\n```python\nimport brinicle\n\nengine = brinicle.ItemSearchEngine(\n    \"item_index\",\n    dim=96,\n    alpha=0.0,  # lexical-only\n)\n\nengine.init(mode=\"build\")\n\nengine.ingest(\n    external_id=\"p1\",\n    title=\"Apple iPhone 15 Pro Max 256GB Natural Titanium\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Apple\",\n        \"storage\": \"256GB\",\n        \"color\": \"Natural Titanium\",\n    },\n)\n\nengine.ingest(\n    external_id=\"p2\",\n    title=\"Samsung Galaxy S24 Ultra 512GB Black\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Samsung\",\n        \"storage\": \"512GB\",\n        \"color\": \"Black\",\n    },\n)\n\nengine.finalize()\n\nprint(engine.search(\"iphone 15 pro max\", k=10))\n```\n\nExample output:\n\n```python\n[\"p1\", \"p2\"]\n```\n\nYou can also pass query-side metadata:\n\n```python\nresults = engine.search(\n    \"iphone 15 pro max\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Apple\",\n        \"storage\": \"256GB\",\n    },\n    k=10,\n)\n```\n\n---\n\n### Hybrid item search\n\nUse hybrid item search when you want exact structured signals and semantic similarity in the same retrieval path.\n\n```python\nimport numpy as np\nimport brinicle\n\nVECTOR_DIM = 384\n\nengine = brinicle.ItemSearchEngine(\n    \"hybrid_item_index\",\n    dim=96,\n    vector_dim=VECTOR_DIM,\n    alpha=0.95,  # mostly semantic, with lexical correction\n    vector_normalized=True,\n    M=48,\n    ef_construction=1024,\n    ef_search=512,\n)\n\nengine.init(mode=\"build\")\n\nengine.ingest(\n    external_id=\"p1\",\n    title=\"Apple iPhone 15 Pro Max 256GB Natural Titanium\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Apple\",\n        \"storage\": \"256GB\",\n        \"color\": \"Natural Titanium\",\n    },\n    vector=np.random.randn(VECTOR_DIM).astype(\"float32\"),\n    normalize=True,\n)\n\nengine.ingest(\n    external_id=\"p2\",\n    title=\"Samsung Galaxy S24 Ultra 512GB Black\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Samsung\",\n        \"storage\": \"512GB\",\n        \"color\": \"Black\",\n    },\n    vector=np.random.randn(VECTOR_DIM).astype(\"float32\"),\n    normalize=True,\n)\n\nengine.finalize()\n\nquery_vector = np.random.randn(VECTOR_DIM).astype(\"float32\")\n\nresults = engine.search(\n    \"iphone 15 pro max\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Apple\",\n    },\n    vector=query_vector,\n    normalize=True,\n    k=10,\n)\n\nprint(results)\n```\n\n---\n\n### Item batch search\n\n`ItemSearchEngine.search_batch(...)` runs multiple independent item searches.\n\nFor text-only batch search:\n\n```python\nqueries = [\n    \"iphone 15 pro max\",\n    \"samsung s24 ultra\",\n    \"wireless mouse\",\n]\n\nresults = engine.search_batch(\n    queries,\n    k=10,\n    n_jobs=4,\n)\n```\n\nFor batch search with per-query metadata:\n\n```python\nqueries = [\n    \"iphone 15 pro max\",\n    \"running shoes size 42\",\n    \"wireless mouse\",\n]\n\ncategories = [\n    \"Electronics\",\n    \"Fashion\",\n    \"Electronics\",\n]\n\nsubcategories = [\n    \"Smartphones\",\n    \"Shoes\",\n    \"Computer Accessories\",\n]\n\nattributes_list = [\n    {\"brand\": \"Apple\", \"storage\": \"256GB\"},\n    {\"size\": \"42\", \"gender\": \"men\"},\n    {\"connection\": \"wireless\"},\n]\n\nresults = engine.search_batch(\n    queries,\n    categories=categories,\n    subcategories=subcategories,\n    attributes_list=attributes_list,\n    k=10,\n    n_jobs=4,\n)\n```\n\nFor hybrid batch search, pass one vector per query:\n\n```python\nvectors = np.random.randn(len(queries), VECTOR_DIM).astype(\"float32\")\n\nresults = engine.search_batch(\n    queries,\n    categories=categories,\n    subcategories=subcategories,\n    attributes_list=attributes_list,\n    vectors=vectors,\n    normalize=True,\n    k=10,\n    n_jobs=4,\n)\n```\n\n---\n\n### Item insert\n\nUse `insert` mode to add new items to an existing index.\n\n```python\nengine.init(mode=\"insert\")\n\nengine.ingest(\n    external_id=\"p3\",\n    title=\"Google Pixel 8 Pro 256GB Bay\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Google\",\n        \"storage\": \"256GB\",\n        \"color\": \"Bay\",\n    },\n)\n\nengine.finalize()\n```\n\n---\n\n### Item upsert\n\nUse `upsert` mode to replace existing items or insert them if they do not exist.\n\n```python\nengine.init(mode=\"upsert\")\n\nengine.ingest(\n    external_id=\"p1\",\n    title=\"Apple iPhone 15 Pro Max 512GB Natural Titanium\",\n    category=\"Electronics\",\n    subcategory=\"Smartphones\",\n    attributes={\n        \"brand\": \"Apple\",\n        \"storage\": \"512GB\",\n        \"color\": \"Natural Titanium\",\n    },\n)\n\nengine.finalize()\n```\n\n---\n\n### Item delete, rebuild, and optimize\n\nDelete items by external id:\n\n```python\ndeleted_count, not_found = engine.delete_items(\n    [\"p1\", \"p9\"],\n    return_not_found=True,\n)\n```\n\nCompact the index when needed:\n\n```python\nif engine.needs_rebuild():\n    engine.rebuild_compact(\n        M=48,\n        ef_construction=1024,\n        ef_search=512,\n        build_n_threads=4,\n    )\n```\n\nOptimize graph layout:\n\n```python\nengine.optimize_graph()\n```\n\n---\n\n### Understanding `alpha`\n\n`alpha` controls the balance between semantic vector similarity and structured lexical matching.\n\n| `alpha` | Behavior                                 |\n| ------: | ---------------------------------------- |\n|   `0.0` | lexical-only                             |\n|   `0.5` | balanced lexical + semantic              |\n|  `0.95` | mostly semantic, with lexical correction |\n|   `1.0` | semantic-only                            |\n\nFor semantic-only and hybrid search, pass `vector_dim` during engine construction and provide vectors during `ingest(...)` and `search(...)`.\n\nChoose `alpha` before building the index. In brinicle, `alpha` affects graph construction as well as search scoring; it is not only a query-time reranking parameter.\n\n---\n\n## Autocomplete\n\nUse `AutocompleteEngine` for low-RAM autocomplete and query suggestion search.\n\nIt can be used to index:\n\n* popular queries\n* item titles\n* category names\n* curated suggestions\n\n```python\nimport brinicle\n\nac = brinicle.AutocompleteEngine(\n    \"autocomplete_index\",\n    dim=48,\n)\n\nac.init(mode=\"build\")\n\nac.ingest(\"iphone 15 pro max\", \"iphone 15 pro max\")\nac.ingest(\"iphone 15 case\", \"iphone 15 case\")\nac.ingest(\"samsung s24 ultra\", \"samsung s24 ultra\")\n\nac.finalize()\n\nprint(ac.search(\"iph\", k=5))\n```\n\nExample output:\n\n```python\n[\"iphone 15 pro max\", \"iphone 15 case\"]\n```\n\nAutocomplete currently works best for prefix-aligned query and title suggestions.\n\n---\n\n### Autocomplete batch search\n\n```python\nqueries = [\n    \"iph\",\n    \"sams\",\n    \"iphone ca\",\n]\n\nresults = ac.search_batch(\n    queries,\n    k=5,\n    n_jobs=4,\n)\n\nprint(results)\n```\n\n---\n\n### Autocomplete insert\n\nUse `insert` mode to add suggestions to an existing autocomplete index.\n\n```python\nac.init(mode=\"insert\")\n\nac.ingest(\"iphone 16 pro\", \"iphone 16 pro\")\nac.ingest(\"iphone 16 pro case\", \"iphone 16 pro case\")\n\nac.finalize()\n```\n\n---\n\n### Autocomplete upsert\n\nUse `upsert` mode to replace existing suggestions or insert them if they do not exist.\n\n```python\nac.init(mode=\"upsert\")\n\nac.ingest(\"iphone 15 pro max\", \"iphone 15 pro max 256gb\")\n\nac.finalize()\n```\n\n---\n\n### Autocomplete delete, rebuild, and optimize\n\nDelete suggestions by external id:\n\n```python\ndeleted_count, not_found = ac.delete_items(\n    [\"iphone 15 case\", \"missing-suggestion\"],\n    return_not_found=True,\n)\n```\n\nCompact the index when needed:\n\n```python\nif ac.needs_rebuild():\n    ac.rebuild_compact(\n        M=32,\n        ef_construction=512,\n        ef_search=128,\n        build_n_threads=4,\n    )\n```\n\nOptimize graph layout:\n\n```python\nac.optimize_graph()\n```\n\n---\n\n## Streaming-first ingest\n\nbrinicle ingests records one at a time, so the full dataset does not need to fit in memory.\n\n```python\nengine.init(mode=\"build\")\n\nfor item in stream_items():\n    engine.ingest(...)\n\nengine.finalize()\n```\n\nThis applies to all engines:\n\n* `VectorEngine`\n* `ItemSearchEngine`\n* `AutocompleteEngine`\n\n---\n\n## Configuration\n\nbrinicle exposes common HNSW parameters:\n\n```python\nengine = brinicle.VectorEngine(\n    \"vector_index\",\n    dim=384,\n    M=48,\n    ef_construction=1024,\n    ef_search=512,\n    delta_ratio=0.1,\n)\n```\n\n| Parameter         | Meaning                                           |\n| ----------------- | ------------------------------------------------- |\n| `M`               | Maximum graph degree used by HNSW                 |\n| `ef_construction` | Construction-time search width                    |\n| `ef_search`       | Query-time search width                           |\n| `delta_ratio`     | Delta segment ratio before rebuild is recommended |\n\n`ItemSearchEngine` supports alpha-controlled lexical, semantic, and hybrid scoring.\n\n```python\nengine = brinicle.ItemSearchEngine(\n    \"item_index\",\n    dim=96,\n    vector_dim=384,\n    alpha=0.95,\n)\n```\n\nAdvanced users can pass a custom `LexicalConfig`.\n\n```python\ncfg = brinicle.LexicalConfig()\n\ncfg.search_title_weight = 0.60\ncfg.search_category_weight = 0.15\ncfg.search_subcategory_weight = 0.15\ncfg.search_attr_weight = 0.10\n\ncfg.build_title_weight = 0.60\ncfg.build_category_weight = 0.15\ncfg.build_subcategory_weight = 0.15\ncfg.build_attr_weight = 0.10\n\nengine = brinicle.ItemSearchEngine(\n    \"item_index\",\n    dim=96,\n    lexical_config=cfg,\n)\n```\n\n`AutocompleteEngine` also supports its own scoring configuration.\n\n```python\ncfg = brinicle.AutocompleteConfig()\n\ncfg.search_position_decay = 0.5\ncfg.search_length_penalty = 0.2\n\nac = brinicle.AutocompleteEngine(\n    \"autocomplete_index\",\n    dim=48,\n    autocomplete_config=cfg,\n)\n```\n\n---\n\n## Index files\n\nFor an index path such as:\n\n```python\nengine = brinicle.VectorEngine(\"my_index\", dim=128)\n```\n\nbrinicle stores index files beside that base path:\n\n```text\nmy_index.main\nmy_index.delta\nmy_index.lock\n```\n\n---\n\n## Which engine should I use?\n\nUse `VectorEngine` when you already have embeddings or numeric vectors.\n\nUse `ItemSearchEngine` for catalog-like records with titles, metadata, and optional semantic vectors:\n\n* `alpha=0.0` for lexical-only search\n* `alpha=1.0` for semantic-only search\n* `0.0 \u003c alpha \u003c 1.0` for hybrid search\n\nUse `AutocompleteEngine` for low-RAM query or title suggestions.\n\n---\n\n## License\n\nbrinicle is licensed under the Apache License, Version 2.0.\n\nSee the [LICENSE](https://github.com/bicardinal/brinicle/blob/main/LICENSE) file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbicardinal%2Fbrinicle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbicardinal%2Fbrinicle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbicardinal%2Fbrinicle/lists"}