---
title: "NL Query (Read)"
amsdal_docs:
  target: framework/plugins/amsdal-ml/nl-query.md
  nav_title: "Query (Read)"
  nav_section: Framework
  nav_path: ["Plugins & Extensions", "ML", "Natural Language CRUD"]
  nav_order: 1
---

# Natural Language Query (Read)

`NLQueryRetrieverExecutor` converts plain English questions into structured database filters, executes them against your AMSDAL models, and returns matching records.

## Basic Usage

```python
from amsdal_ml.ml_models.openai import OpenAIModel
from amsdal_ml.nlquery.retriever import NLQueryRetrieverExecutor

llm = OpenAIModel(model_name='gpt-5.1')
await llm.setup()

executor = NLQueryRetrieverExecutor(llm=llm, queryset=Customer.objects.all())

# Analyze and execute
analysis = await executor.analyze('active customers in New York')
print(analysis.filters)   # generated ORM filters

results = await executor.execute(analysis)
for record in results:
    print(record)
```

## Two-Step: Analyze Then Execute

For preview or confirmation workflows:

```python
# Step 1: Generate filters without executing
analysis = await executor.analyze('customers who signed up last month')
print(analysis.filters)  # inspect the generated filters

# Step 2: Execute from analysis
results = await executor.execute(analysis)
```

## How It Works

1. **Schema introspection** — the executor inspects your model's fields and builds a search schema describing available filters
2. **LLM analysis** — the query is sent to the LLM along with the schema, and the LLM generates structured filters
3. **Filter execution** — generated filters are applied to the model's QuerySet via the AMSDAL ORM

### Supported Field Types

The executor supports filtering on:

- Primitives: `str`, `int`, `float`, `bool`
- Date/time: `date`, `datetime`
- Enums and Literals
- Unions of supported types
- Foreign key relationships (nested model fields)

Unsupported types (dicts, lists, callables) are skipped during schema generation.

## Document-Oriented Facade

`NLQueryRetriever` wraps the executor and returns serialized `Document` objects instead of model instances:

```python
from amsdal_ml.nlquery.retriever import NLQueryRetriever

retriever = NLQueryRetriever(llm=llm, queryset=Customer.objects.all())

analysis = await retriever.analyze('VIP customers')
documents = await retriever.invoke(analysis)

for doc in documents:
    print(doc.page_content)  # serialized record
    print(doc.metadata)      # object class, id, etc.
```

## Authorization

Pass a callback to check permissions on each loaded object:

```python
executor = NLQueryRetrieverExecutor(
    llm=llm,
    queryset=Customer.objects.all(),
    on_object_loaded=my_auth_callback,
)
```

## Base Filters

Pre-apply filters that always run (e.g., tenant isolation):

```python
executor = NLQueryRetrieverExecutor(
    llm=llm,
    queryset=Customer.objects.all(),
    base_filters={'is_active': True},
)
```
