Metadata-Version: 2.4
Name: search-expert
Version: 0.1.0
Summary: Parse natural language search queries into structured fields using fine-tuned Qwen3.5-0.8B LoRA adapters.
Author: Sarthak Rastogi
License: MIT
Project-URL: Homepage, https://github.com/sarthakrastogi/search-expert
Project-URL: Repository, https://github.com/sarthakrastogi/search-expert
Project-URL: Issues, https://github.com/sarthakrastogi/search-expert/issues
Keywords: search,nlp,query-parsing,llm,structured-extraction,fine-tuning
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: torch>=2.0
Requires-Dist: transformers>=5.5
Requires-Dist: pyyaml>=6.0
Requires-Dist: huggingface-hub>=0.22
Requires-Dist: unsloth>=2026.4
Provides-Extra: unsloth
Requires-Dist: unsloth; extra == "unsloth"
Provides-Extra: peft
Requires-Dist: peft>=0.10; extra == "peft"
Requires-Dist: bitsandbytes>=0.43; extra == "peft"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: types-PyYAML; extra == "dev"

# search-expert

Parse natural language search queries into structured fields using fine-tuned Qwen3.5-0.8B LoRA adapters.

```python
from search_expert import SearchExpert

expert = SearchExpert()
result = expert.parse("noise cancelling headphones under $200 with fast charging")

print(result.fields)
# {
#   "domain": "ecommerce",
#   "product": "headphones",
#   "feature": "noise cancelling",
#   "price": "lt:200"
# }

print(result.get_numeric_constraint("price"))
# {'operator': 'lt', 'value': 200.0, 'value_hi': None}
```

---

## Models

Two fine-tuned LoRA adapters trained on top of `Qwen3.5-0.8B`:

| Adapter | HuggingFace repo | Output format |
|---------|-----------------|---------------|
| JSON    | `sarthakrastogi/search-expert-json-0.8b` | JSON |
| YAML    | `sarthakrastogi/search-expert-yaml-0.8b` | YAML |

Both adapters return the same Python dict regardless of which one you use — the format only affects the model's internal output language.

---

## Installation

```bash
pip install search-expert
```

**With GPU (recommended) — use unsloth for fast loading:**
```bash
pip install "search-expert[unsloth]"
```

**Without unsloth — use standard HF PEFT:**
```bash
pip install "search-expert[peft]"
```

---

## Usage

### Basic

```python
from search_expert import SearchExpert, ModelFormat

# JSON adapter (default)
expert = SearchExpert()
result = expert.parse("3BR house in Austin under $600k with pool")

print(result.fields)
print(result.to_json(indent=2))
print(result.to_yaml())
```

### YAML adapter

```python
expert = SearchExpert(fmt=ModelFormat.YAML)
result = expert.parse("remote senior ML engineer job paying over $150k")
print(result.fields)
```

### Numeric constraints

Numeric fields are returned with operator prefixes so downstream search logic can apply filters directly:

| Operator | Example value | Meaning |
|----------|--------------|---------|
| `lt:N`   | `lt:200`     | < 200 |
| `lte:N`  | `lte:200`    | ≤ 200 |
| `gt:N`   | `gt:150000`  | > 150,000 |
| `gte:N`  | `gte:150000` | ≥ 150,000 |
| `approx:N` | `approx:300` | ≈ 300 |
| `between:Lo:Hi` | `between:80000:120000` | 80,000 – 120,000 |

```python
result = expert.parse("jobs paying between $80k and $120k in NYC")
salary = result.get_numeric_constraint("salary")
# {'operator': 'between', 'value': 80000.0, 'value_hi': 120000.0}

# Decode all numeric fields at once
print(result.numeric_fields())
```

### Batch parsing

```python
queries = [
    "Python ML course for beginners under $30",
    "5-star hotel in Paris with breakfast under $400/night",
    "Taylor Swift concert in London in July",
]
results = expert.parse_batch(queries)
for r in results:
    print(r.query, "→", r.fields)
```

### Custom adapter

```python
expert = SearchExpert(model_id="your-org/your-fine-tuned-adapter")
```

### Custom generation config

```python
expert = SearchExpert(
    generation_config={"temperature": 0.0, "max_new_tokens": 128}
)
```

### Eager loading

By default the model loads on the first `.parse()` call. Pass `eager=True` to load immediately:

```python
expert = SearchExpert(eager=True)   # loads model in __init__
```

---

## Supported domains

| Domain | Example query |
|--------|--------------|
| `real_estate` | "2BR apartment in Austin under $1500/month" |
| `ecommerce` | "Sony noise cancelling headphones under $300" |
| `jobs` | "Remote senior ML engineer paying over $150k" |
| `flights` | "Non-stop business class JFK to Tokyo under $3000" |
| `hotels` | "5-star hotel in Paris with breakfast under $400/night" |
| `cars` | "Electric SUV with 300+ mile range under $50k" |
| `restaurants` | "Vegan Italian in NYC with outdoor seating under $40" |
| `movies` | "Thriller on Netflix with 8+ IMDB rating" |
| `healthcare` | "Female therapist in Chicago accepting Aetna" |
| `courses` | "Python ML course for beginners under $30" |
| `events` | "Taylor Swift concert in London in July" |

---

## Repo structure

```
search-expert/
├── search_expert/          # Library source
│   ├── __init__.py
│   ├── expert.py           # SearchExpert class (main API)
│   ├── config.py           # Model IDs, prompts, format enum
│   ├── loader.py           # HF model loading (unsloth / peft / plain)
│   ├── parser.py           # Raw output → dict parsers
│   ├── result.py           # ParseResult dataclass
│   └── exceptions.py       # Custom exceptions
├── training/               # Fine-tuning pipeline
│   ├── finetune.py         # Training script
│   └── evaluate.py         # Format comparison leaderboard
├── tests/
│   └── test_search_expert.py
├── examples/
│   └── basic_usage.py
├── pyproject.toml
└── README.md
```

---

## Development

```bash
git clone https://github.com/sarthakrastogi/search-expert
cd search-expert
pip install -e ".[dev]"
pytest tests/ -v                    # unit tests (no GPU needed)
SEARCH_EXPERT_RUN_MODEL_TESTS=1 pytest tests/ -v   # includes model tests
```

---

## License

MIT
