You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dispatches on embedding_mode via create_embedding_provider()
Compliant
Supports ollama, openai, gemini. Sentence-transformers falls back to OpenAI/Ollama (ZMQ removed). MLX not ported (N/A).
build_index_from_embeddings
(index_path, embeddings_file) — pickle path
(index_path, ids, embeddings) — direct data
Non-compliant
Different signatures. Rust takes IDs + embedding lists directly; Python takes a path to a pickle file containing (ids, embeddings) tuple.
update_index(index_path)
Appends passages + vectors to existing index
Not implemented
Non-compliant
Incremental update not yet ported to Rust. CLI uses sources manifest + full rebuild as workaround. Python IVF path also accepts remove_passage_ids parameter (Python-only).
Backend kwarg: M
Forwarded to HNSW builder
Extracted and applied
Compliant
Backend kwarg: efConstruction
Forwarded to HNSW builder
Extracted and applied
Compliant
Backend kwarg: is_compact
Forwarded to HNSW builder
Extracted and applied
Compliant
Backend kwarg: is_recompute
Forwarded to HNSW builder
Extracted and applied
Compliant
Backend kwarg: distance_metric
Forwarded to HNSW builder
Extracted from kwargs and applied via with_distance_metric()
Compliant
embedding_options
Stored in meta.json, used for query templates at search time
Stored via with_embedding_options(), written to meta.json
Compliant
Normalized-embedding auto-detection
Detects OpenAI/Voyage/Cohere models and sets distance_metric="cosine"
is_normalized_embeddings_model() in builder.rs
Compliant
Same known-model list + pattern matching as Python. Auto-sets cosine in constructor; explicit with_distance_metric() overrides.
LeannSearcher
Feature
Python
Rust
Status
Notes
Constructor: index_path
Required
Required
Compliant
Constructor: enable_warmup=True
Triggers background embedding server startup
Sends probe embedding request via in-process provider
Compliant
Warmup sends a test embedding request to verify provider connectivity; warns on failure. No ZMQ — uses direct provider call.
Constructor: recompute_embeddings=True
Controls recompute path at search time
Overrides meta.json value via open_with_options
Compliant
Passed through SearcherOptions to override the meta default.
Constructor: **backend_kwargs
Forwarded to backend factory
Accepted; useful kwargs handled via dedicated params
Partial
Backend abstraction exists (BackendConfig/BackendIndex in backend.rs) but searcher kwargs not dispatched through it. Warmup/recompute handled via SearcherOptions; search config via SearchConfig. Low impact: all useful kwargs are already wired.
search() signature
(query, top_k=5, complexity=64, ...) — named params
(query, top_k=5, **kwargs) — kwargs-based
Compliant
Both accept the same parameter names.
search(): complexity
Controls candidate list size
Forwarded to SearchConfig.complexity
Compliant
search(): beam_width
Parallel search paths
Forwarded to SearchConfig.beam_width
Compliant
search(): prune_ratio
Approximate distance pruning
Forwarded to SearchConfig.prune_ratio
Compliant
search(): metadata_filters
Dict-of-dicts filter spec
Extracted to SearchConfig.metadata_filters
Compliant
Nested dict unpacking via PyDict.
search(): batch_size
Controls batching
Forwarded to SearchConfig.batch_size
Compliant
search(): use_grep
Regex-based text search
Forwarded to SearchConfig.use_grep
Compliant
search(): gemma
Vector/BM25 blend weight
Forwarded to SearchConfig.gemma
Compliant
search(): expected_zmq_port
ZMQ server port
Accepted and ignored (backward compat)
Compliant
ZMQ removed; kwarg accepted silently for Python callers that still pass it.
search(): pruning_strategy
"global" / "local" / "proportional"
Extracted from kwargs and forwarded to SearchParams
Compliant
search(): provider_options
Override embedding template
Extracted from kwargs to SearchConfig.provider_options
Compliant
Wired through PyO3; stored as HashMap<String, Value>.
search(): recompute_embeddings
Per-call override (deprecated in Python)
Not supported
Non-compliant
Python deprecated this param; Rust omits it. Configure at constructor instead.
cleanup()
Stops embedding server
Calls inner cleanup
Compliant
Context manager (with statement)
__enter__ / __exit__
__enter__ returns self, __exit__ calls cleanup()
Compliant
Return order
Sorted by score descending
Same (verified)
Compliant
LeannChat
Feature
Python
Rust
Status
Notes
Constructor: index_path
Required
Required
Compliant
Constructor: llm_config
Dict with type, model, api_key, base_url, host
Dict with type, model, api_key, base_url, host
Compliant
See LlmConfig fields section for per-field breakdown.
Constructor: enable_warmup
Controls warmup (default False)
Forwarded to SearcherOptions via new_with_options
Compliant
Warmup delegated to the inner LeannSearcher.
Constructor: searcher
Accept existing LeannSearcher to share
Accepted; uses searcher's index_path to open
Compliant
Same pattern as ReActAgent: re-opens from the searcher's path.
ask() signature
Full search params + llm_kwargs
(question, top_k=5, **kwargs)
Compliant
Search kwargs and LLM kwargs both extracted from **kwargs.
ask(): llm_kwargs
Forwarded to LLM provider
Extracted (temperature, max_tokens, top_p, extras) and forwarded
Compliant
Built into LlmParams and passed to ask_with_params.
start_interactive()
REPL mode
Stdin REPL loop calling self.ask()
Compliant
Reads from stdin; exits on "quit", "exit", or EOF.
cleanup()
Stops embedding server if owns searcher
Delegates to inner searcher cleanup
Compliant
Context manager
__enter__ / __exit__
__enter__ returns self, __exit__ calls cleanup()
Compliant
ReActAgent
Feature
Python
Rust
Status
Notes
Constructor: searcher
LeannSearcher instance
LeannSearcher instance or string path
Compliant
Rust is more flexible (accepts either).
Constructor: llm / llm_config
Optional LLM and config
Accepted (unused — uses default OpenAI)
Compliant
Signature matches.
Constructor: max_iterations
Default 5
Default 5
Compliant
run(question, top_k=5)
Multi-turn reasoning
Multi-turn reasoning
Compliant
search(query, top_k=5)
Exposed as public method
Exposed in PyO3 bindings
Compliant
Opens searcher and delegates to LeannSearcher.search().
Backend Registry / Plugin System
Feature
Python
Rust
Status
Notes
BACKEND_REGISTRY dict
Module-level dict of factory instances
Static PyDict {"hnsw": "hnsw"} on module
Compliant
No dynamic plugin system, but dict is accessible.
autodiscover_backends()
Scans leann-backend-* packages via importlib.metadata
Not implemented
Non-compliant
N/A with single backend.
register_backend() decorator
Registers factory class into BACKEND_REGISTRY
Not implemented
Non-compliant
N/A with single backend.
get_registered_backends()
Reads BACKEND_REGISTRY.keys()
Returns ["hnsw"]
Compliant
Functional parity via hardcoded list.
Backend dispatch
Factory class per backend
BackendConfig/BackendIndex enums in backend.rs
Compliant
Enum dispatch matches Python's factory pattern. Adding a backend = adding an enum variant (no trait hierarchy needed).
LeannBuilder backend selection
backend_name → factory lookup
with_backend(name) → BackendConfig::from_name()
Compliant
Validates backend name and creates default config.
register_project_directory() is a CLI convenience in Python (registry.py) for leann list discovery. Not part of the core search API.
Python's interface.py defines ABCs (LeannBackendFactoryInterface, LeannBackendBuilderInterface, LeannBackendSearcherInterface) that any backend must implement. Rust uses enum-based dispatch in backend.rs instead — BackendConfig and BackendIndex enums with match arms for each backend. Adding a new backend means adding an enum variant and implementing the match arms in build_backend, read_backend_index, search_backend, and search_backend_recompute.
Search Features
These features are exposed as search() kwargs but involve significant internal machinery beyond just passing through a config value.
Feature
Python
Rust
Status
Notes
BM25 hybrid search (gemma < 1.0)
BM25Scorer in api.py, lazily initialized
BM25Scorer in bm25.rs, behind bm25 feature flag (default on)
Compliant
Both tokenize, compute IDF/TF, and blend with vector scores.
Grep search (use_grep=True)
Shells out to grep -i on .passages.jsonl
grep_search() in searcher.rs reads passages directly
Compliant
Rust uses in-process regex instead of subprocess.
Metadata filtering
MetadataFilterEngine in metadata_filter.py with 13 operators
MetadataFilterEngine in metadata_filter.rs with 13 operators
Compliant
See operator parity table below.
Metadata Filter Operators
Both implementations support identical operators with AND logic across fields.
Operator
Python
Rust
Status
==
_equals
op_equals
Compliant
!=
_not_equals
op_not_equals
Compliant
<
_less_than
op_less_than
Compliant
<=
_less_than_or_equal
op_less_than_or_equal
Compliant
>
_greater_than
op_greater_than
Compliant
>=
_greater_than_or_equal
op_greater_than_or_equal
Compliant
in
_in
op_in
Compliant
not_in
_not_in
op_not_in
Compliant
contains
_contains
op_contains
Compliant
starts_with
_starts_with
op_starts_with
Compliant
ends_with
_ends_with
op_ends_with
Compliant
is_true
_is_true
op_is_true
Compliant
is_false
_is_false
op_is_false
Compliant
Both also share: top-level field lookup with metadata fallback, null/missing fields fail all filters, numeric coercion with string fallback for comparison operators.
LlmConfig Fields
Python's get_llm() and Rust's LlmConfig struct both construct LLM providers from a config dict.
Field
Python get_llm()
Rust LlmConfig
Status
Notes
type
Dispatches to provider class
llm_type field
Compliant
model
Provider-specific model name
model: Option<String>
Compliant
api_key
Forwarded to OpenAI/Anthropic/Gemini
api_key: Option<String>
Compliant
base_url
Forwarded to OpenAI/Anthropic
base_url: Option<String>
Compliant
host
Forwarded to Ollama
host: Option<String>
Compliant
trust_remote_code
Forwarded to HuggingFace
Not implemented
Non-compliant
HuggingFace provider not ported.
On-Disk Index Format
File
Python Format
Rust Format
Status
Notes
.meta.json
JSON with version, backend_name, embedding_model, dimensions, passage_sources, embedding_mode, is_compact, is_pruned
Same fields via serde_json
Compliant
#[serde(rename = "type")] correctly serializes source_type as "type".
.passages.jsonl
One JSON object per line: {"id", "text", "metadata"}
Same format via serde_json
Compliant
.passages.idx
pickle.dump(dict[str, int]) — maps passage ID strings to byte offsets
Text file — one u64 per line (positional offsets, no IDs)
Non-compliant
BLOCKING. Python indexes by passage ID string; Rust indexes by position integer. Cross-read between implementations is not possible without a compatibility shim.
Drop trait on both LeannSearcher and LeannChat calls cleanup()
Compliant
Both classes have explicit Drop impls.
_owns_searcher guard
LeannChat.cleanup() only stops server if it created the searcher
Always cleans up (always re-opens from path)
Compliant
Rust re-opens from the searcher's index_path, so always owns its searcher.
LLM Provider Coverage
Provider
Python
Rust
Status
OpenAI
chat.pyOpenAIChat
chat.rsOpenAiChat
Compliant
Ollama
chat.pyOllamaChat
chat.rsOllamaChat
Compliant
Anthropic
chat.pyAnthropicChat
chat.rsAnthropicChat
Compliant
Gemini
chat.pyGeminiChat
chat.rsGeminiChat
Compliant
HuggingFace
chat.pyHFChat
Not implemented
N/A
Simulated (testing)
chat.pySimulatedChat
chat.rsSimulatedChat
Compliant
Note: Python's SimulatedChat exists (type "simulated" in get_llm).
Embedding Provider Coverage
Provider
Python
Rust
Status
Sentence-Transformers (via ZMQ server)
embedding_compute.py + ZMQ
Removed — falls back to OpenAI/Ollama
N/A
OpenAI Embeddings
embedding_compute.py
embedding/openai.rs — wired into LeannSearcher
Compliant
Ollama Embeddings
embedding_compute.py
embedding/ollama.rs (pipelined async) — wired into LeannSearcher
Compliant
Gemini Embeddings
embedding_compute.py
embedding/gemini.rs — wired into LeannSearcher
Compliant
MLX (Apple Silicon)
embedding_compute.py
Not implemented
N/A
ONNX Runtime (local)
Not available
Scaffold only (embedding/onnx.rs)
N/A
Note: The ZMQ embedding server/client (client.rs, server.rs) and embedding-zmq feature have been removed. Embedding providers are now in-process HTTP clients constructed from IndexMeta at LeannSearcher::open() time. The create_embedding_provider() factory in embedding/mod.rs dispatches on mode (ollama, openai, gemini), with sentence-transformers mode falling back to OpenAI then Ollama.
Backends
Backend
Python
Rust
Status
HNSW (FAISS C++ fork)
Full support
Pure-Rust HNSW (SIMD-optimized) via BackendConfig::Hnsw / BackendIndex::Hnsw
Compliant
IVF (FAISS IndexIVFFlat)
Full support (commit 5b82603)
Not implemented (backend abstraction ready for new enum variant)
N/A
DiskANN
Full support
Not implemented (backend abstraction ready for new enum variant)
N/A
Internal Implementation Parity
These are not part of the PyO3 API surface but affect build-time behavior and output quality.
Feature
Python
Rust
Status
Notes
AST chunking (tree-sitter)
astchunk library (tree-sitter) for Python, Java, C#, TS, JS
chunking/tree_sitter.rs — same 5 languages via tree-sitter-* crates
Compliant
Opt-in via tree-sitter feature flag (not in default; included in full). Falls back to heuristic chunking when disabled.
Always available; used when tree-sitter is disabled or for unsupported languages.
Sentence chunking
llama_index sentence splitter
chunking/sentence.rs — custom sentence splitter
Compliant
Priority Fixes
High (blocking cross-read)
.passages.idx format unification — Python uses pickle.dump(dict[str, int]), Rust uses text offsets. Without this, indexes built by one implementation cannot be opened by the other. Options:
Converge on a simple binary format (recommended)
Add auto-detection of format at load time
Write a migration tool
Medium (functional gaps)
build_index embedding provider flexibility — Resolved: create_embedding_provider() in embedding/mod.rs dispatches on embedding_mode (ollama, openai, gemini; sentence-transformers falls back to OpenAI/Ollama).
LeannChat.ask(): llm_kwargs — Resolved: temperature, max_tokens, top_p, and extras extracted from kwargs and forwarded via LlmParams.
search(): provider_options — Resolved: extracted from PyO3 kwargs to SearchConfig.provider_options.
distance_metric kwargs extraction — Resolved: extracted from PyO3 kwargs and applied via with_distance_metric().
pruning_strategy kwargs extraction — Resolved: extracted from kwargs and forwarded to SearchParams.