Skip to content

Python API

The LocalLens class is the main entry point for all operations.

python
from locallens import LocalLens

Constructor

python
LocalLens(
    path: str | Path | None = None,
    collection_name: str = "locallens",
    data_dir: str | Path | None = None,
    embedding_model: str = "all-MiniLM-L6-v2",
    ollama_url: str = "http://localhost:11434",
    ollama_model: str = "qwen2.5:3b",
)
ParameterTypeDefaultDescription
pathstr | Path | NoneNoneFolder to index and search. Can be set later via index().
collection_namestr"locallens"Qdrant collection name
data_dirstr | Path | None~/.locallensWhere to store the Qdrant Edge shard and BM25 index
embedding_modelstr"all-MiniLM-L6-v2"Sentence-transformers model name
ollama_urlstr"http://localhost:11434"Ollama server URL (for ask())
ollama_modelstr"qwen2.5:3b"Ollama model name (for ask())

index()

Index files in the configured path.

python
def index(
    self,
    force: bool = False,
    callback: Callable[[str, str, float], None] | None = None,
) -> IndexResult
ParameterTypeDefaultDescription
forceboolFalseRe-index all files regardless of content hash
callbackCallable | NoneNoneOptional callback(event_type, message, progress)

Returns: IndexResult

python
lens = LocalLens("~/Documents")
result = lens.index()
print(f"Indexed {result.total_files} files ({result.total_chunks} chunks) in {result.duration_seconds}s")

Search indexed files by semantic meaning, keywords, or hybrid.

python
def search(
    self,
    query: str,
    top_k: int = 5,
    mode: str = "hybrid",
    file_type: str | None = None,
    path_prefix: str | None = None,
) -> list[SearchResult]
ParameterTypeDefaultDescription
querystr(required)Search query text. Supports query arithmetic: use + to add concepts and - to subtract (e.g. "pricing +recent -draft")
top_kint5Maximum results to return
modestr"hybrid""semantic", "keyword", or "hybrid"
file_typestr | NoneNoneFilter by extension (e.g. ".pdf")
path_prefixstr | NoneNoneFilter by file path prefix

Returns: list[SearchResult]

python
# Hybrid search (default)
results = lens.search("quarterly revenue")

# Semantic only
results = lens.search("quarterly revenue", mode="semantic")

# Filter by file type
results = lens.search("quarterly revenue", file_type=".pdf")

ask()

Ask a question about indexed files using RAG.

python
def ask(
    self,
    question: str,
    top_k: int = 3,
) -> AskResult
ParameterTypeDefaultDescription
questionstr(required)Natural language question
top_kint3Number of context chunks to retrieve

Returns: AskResult

Raises: OllamaUnavailableError when Ollama is not running.

python
result = lens.ask("What was the Q3 revenue?")
print(result.answer)
print(f"Model: {result.model}, took {result.duration_seconds}s")
for source in result.sources:
    print(f"  Source: {source.file_name}")

ask_stream()

Stream a RAG answer token by token.

python
def ask_stream(
    self,
    question: str,
    top_k: int = 3,
) -> Generator[AskStreamEvent, None, None]

Yields: AskStreamEvent with event_type of "token" (data=str) or "sources" (data=list[SearchResult]).

Raises: OllamaUnavailableError when Ollama is not running.

python
for event in lens.ask_stream("What was the Q3 revenue?"):
    if event.event_type == "token":
        print(event.data, end="", flush=True)
    elif event.event_type == "sources":
        print(f"\n\nSources: {[s.file_name for s in event.data]}")

stats()

Get collection statistics.

python
def stats(self) -> StatsResult

Returns: StatsResult

python
stats = lens.stats()
print(f"{stats.total_files} files, {stats.total_chunks} chunks")
print(f"File types: {stats.file_types}")

files()

List all indexed files.

python
def files(self) -> list[FileInfo]

Returns: list[FileInfo]

python
for f in lens.files():
    print(f"{f.file_name} ({f.file_type}) — {f.chunk_count} chunks")

Refine a search by boosting or suppressing specific result texts. This is the programmatic equivalent of clicking +/- buttons on search results.

python
def refine_search(
    self,
    base_query: str,
    add_texts: list[str] | None = None,
    subtract_texts: list[str] | None = None,
    top_k: int = 5,
    file_type: str | None = None,
    path_prefix: str | None = None,
) -> list[SearchResult]
ParameterTypeDefaultDescription
base_querystr(required)The original search query (may include +/- arithmetic)
add_textslist[str] | NoneNoneChunk texts to boost (add to query direction)
subtract_textslist[str] | NoneNoneChunk texts to suppress (subtract from query direction)
top_kint5Maximum results
file_typestr | NoneNoneFilter by extension
path_prefixstr | NoneNoneFilter by path prefix

Returns: list[SearchResult]

python
# First search
results = lens.search("pricing strategy")

# Boost results like the first hit, suppress results like the third
refined = lens.refine_search(
    "pricing strategy",
    add_texts=[results[0].chunk_text],
    subtract_texts=[results[2].chunk_text],
)

delete()

Delete a file and all its chunks from the index.

python
def delete(self, file_path: str) -> bool

Returns: True if deleted, False on error.

python
lens.delete("/Users/me/Documents/old-report.pdf")

doctor()

Run health checks on all dependencies.

python
def doctor(self) -> list[DoctorCheck]

Returns: list[DoctorCheck]

python
for check in lens.doctor():
    print(f"{check.name}: {check.status}{check.message}")

Complete example

python
from locallens import LocalLens, OllamaUnavailableError

# Initialize
lens = LocalLens("~/Documents", ollama_model="qwen2.5:3b")

# Index
result = lens.index()
print(f"Indexed {result.total_files} files")

# Search
results = lens.search("budget projections", top_k=3)
for r in results:
    print(f"  {r.file_name} (score: {r.score})")

# Ask with error handling
try:
    answer = lens.ask("What are the budget projections for next quarter?")
    print(f"\nAnswer: {answer.answer}")
    print(f"Sources: {[s.file_name for s in answer.sources]}")
except OllamaUnavailableError:
    print("Ollama is not running. Start it with: ollama serve")

# Stats
stats = lens.stats()
print(f"\n{stats.total_files} files, {stats.total_chunks} chunks")

# Health check
for check in lens.doctor():
    icon = "✓" if check.status == "ok" else "✗"
    print(f"  {icon} {check.name}: {check.message}")

Released under the MIT License.