Metadata-Version: 2.4
Name: aiyer
Version: 0.1.0a3
Summary: Structured image analysis using LLMs. Define a Pydantic model, send an image, get structured data.
Project-URL: Homepage, https://github.com/ASCII125/aiyer-object-viewer
Project-URL: Repository, https://github.com/ASCII125/aiyer-object-viewer
Project-URL: Issues, https://github.com/ASCII125/aiyer-object-viewer/issues
Author: ASCII125
License-Expression: MIT
License-File: LICENSE
Keywords: groq,image-analysis,llm,ollama,pydantic,vision
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.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Scientific/Engineering :: Image Recognition
Requires-Python: >=3.11
Requires-Dist: asyncer>=0.0.17
Requires-Dist: pydantic>=2.12.5
Provides-Extra: all
Requires-Dist: groq>=1.1.2; extra == 'all'
Requires-Dist: ollama>=0.6.1; extra == 'all'
Provides-Extra: groq
Requires-Dist: groq>=1.1.2; extra == 'groq'
Provides-Extra: ollama
Requires-Dist: ollama>=0.6.1; extra == 'ollama'
Description-Content-Type: text/markdown

# Aiyer

> **Alpha** – Functional demonstration. API may change.

Aiyer is a lightweight Python library for structured image analysis using LLMs. Define a Pydantic model, send an image, and get back structured data.

It works with any LLM provider through adapters (Ollama, Groq) and supports multiple analysis strategies with different speed/quality trade-offs.

## Overview

This library is designed to support systems that integrate image analysis capabilities, such as inventory management, people tracking, kitchen monitoring, access control (turnstiles), and parking management.

As illustrated in the example below, it can be used to analyze and estimate inventory quantities from visual data.

## 📦 Stock View

<table>
<tr>
<td width="40%">

<img src="https://github.com/user-attachments/assets/42a6bb59-6976-4f17-9a5f-4635b87f2643" width="100%"/>

</td>
<td width="60%">

### Stock Analysis: example1.jpg

The stock situation appears to be nearly depleted with many shelves empty or low on stock.

**Overall stock:** 🔴 Critical  

#### Products
- ● Cookies: low  
- ● Canned Goods: empty  
- ● Toiletries: empty  
- ● Baking Goods: empty  
- ● Snacks: low  

#### Restock Actions
1. Immediate restocking of all shelves  
2. Prioritize Canned Goods and Toiletries  
3. Order emergency shipment of essential items  

</td>
</tr>
</table>

---

<table>
<tr>
<td width="40%">

<img src="https://github.com/user-attachments/assets/03ca903d-617a-46d5-968e-fdfb1a7f3e00" width="100%"/>

</td>
<td width="60%">

### Stock Analysis: example2.jpg

The image shows a well-stocked shelf with various snacks and biscuits.

**Overall stock:** 🟢 Adequate  

#### Products
- ○ Biscuits: full  
- ○ Chocolates: full  
- ○ Candies: medium  

#### Restock Actions
1. No immediate restocking needed  
2. Monitor candy stock levels  

</td>
</tr>
</table>


## Installation

```bash
pip install aiyer            # Core only
pip install aiyer[ollama]    # With Ollama support (local LLMs)
pip install aiyer[groq]      # With Groq support (cloud API)
pip install aiyer[all]       # All providers
```

## Quick Start

```python
import asyncio
from pydantic import BaseModel, Field
from typing import List, Optional, Literal

from aiyer.adapters.ollama import OllamaAdapter
from aiyer.modules import AiyerLite


# Define your schema
class SceneAnalysis(BaseModel):
    summary: str = Field(description="General description of the scene")
    objects: List[str] = Field(description="List of detected objects")
    environment: Optional[str] = Field(description="Environment type")
    danger_level: Literal["low", "medium", "high"] = Field(description="Danger level")


async def main():
    # Create an adapter
    model = OllamaAdapter(
        model="qwen3.5:4b",
        ollama_ip="localhost",
    )

    # Initialize the analyzer
    aiyer = AiyerLite(model=model)

    # Analyze an image
    with open("photo.jpg", "rb") as f:
        result = await aiyer.view(f.read(), SceneAnalysis)

    # result is a VisionResponse[SceneAnalysis]
    # result.image_bytes -> the original image as bytes
    # result.view        -> your SceneAnalysis instance with the LLM output

    print(result.view.summary)
    print(result.view.objects)
    print(result.view.danger_level)


asyncio.run(main())
```

## VisionResponse

Every call to `view()` or `get_result()` returns a `VisionResponse[T]`:

```python
class VisionResponse(BaseModel, Generic[T]):
    image_bytes: bytes   # The original image
    view: T              # Your typed Pydantic model with the LLM analysis
```

`T` is the schema you passed in. So if you call `aiyer.view(img, SceneAnalysis)`, you get back a `VisionResponse[SceneAnalysis]` where `result.view` is a `SceneAnalysis` instance.

## Adapters

**Ollama** (local):

```python
from aiyer.adapters.ollama import OllamaAdapter

model = OllamaAdapter(
    model="qwen3.5:4b",
    ollama_ip="localhost",
    ollama_port=11434,       # optional, default 11434
    ollama_api_key=None,     # optional
    https=False,             # optional
)
```

**Groq** (cloud):

```python
from aiyer.adapters.groq import GroqAdapter

model = GroqAdapter(
    model="meta-llama/llama-4-scout-17b-16e-instruct",
    api_key="your-api-key",
    timeout=30.0,            # optional
    max_retries=2,           # optional
    think=False,             # optional, enables reasoning
)
```

## Analysis Modes

| Mode | LLM Calls | Speed | Quality | Use Case |
|------|-----------|-------|---------|----------|
| `AiyerZero` | 1 (resized image) | Fastest | Basic | Quick triage, real-time |
| `AiyerLite` | 1 | Fast | Good | General use, best cost/benefit |
| `AiyerMedium` | 2 (analysis + enrichment) | Slower | Best | When accuracy matters |

```python
from aiyer.modules import AiyerZero, AiyerLite, AiyerMedium

# Fastest – resizes image before sending
aiyer = AiyerZero(model=model, max_image_size=384)

# Balanced – single LLM call, full resolution
aiyer = AiyerLite(model=model)

# Best quality – LLM analyzes, then reviews its own output
aiyer = AiyerMedium(model=model)

result = await aiyer.view(image_bytes, YourSchema)
```

## ContextChat

Use `view_chat` to add context before getting results:

```python
from pydantic import BaseModel, Field
from typing import Literal

class GateStatus(BaseModel):
    status: Literal["open", "closed", "partially_open"] = Field(description="Gate status")
    description: str = Field(description="Gate description")

result = await aiyer.view_chat(image_bytes, GateStatus) \
    .add("Focus on the gate in the center of the image.") \
    .add("Is it open or closed?") \
    .get_result()

# Same VisionResponse – result.view is a GateStatus
print(result.view.status)       # "partially_open"
print(result.view.description)
```

## Schema Features

Aiyer generates smart examples from your Pydantic schema to guide the LLM:

```python
class Report(BaseModel):
    weather: Literal["sunny", "cloudy", "rainy"] = Field(description="Weather condition")
    count: int = Field(description="Number of people")
    items: List[str] = Field(description="Detected items")
```

The LLM receives:
```json
{
  "weather": "<one of: sunny, cloudy, rainy>",
  "count": "<Number of people>",
  "items": ["<Detected items>"]
}
```

`Literal`, `Optional`, `Union`, nested models, and all standard types are supported.

## Custom Adapters

Implement `ILLModel` to add any LLM provider:

```python
from aiyer.interfaces.models import ILLModel, Message

class MyAdapter(ILLModel):
    async def achat(self, messages: list[Message], **kwargs) -> Message:
        # Call your LLM here
        ...
        return Message(role="assistant", content=response_text)
```

## Requirements

- Python >= 3.11
- pydantic >= 2.12

## License

MIT
