Metadata-Version: 2.4
Name: django-ask-ai
Version: 0.1.0
Summary: Ask your Django project questions in plain language. LLM translates; Django validates and executes under Group scopes. Pluggable into any app.
Project-URL: Documentation, https://mabukhashabeh.github.io/django-ask-ai/
Project-URL: Source, https://github.com/mabukhashabeh/django-ask-ai
Project-URL: Changelog, https://github.com/mabukhashabeh/django-ask-ai/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/mabukhashabeh/django-ask-ai/issues
Author: Mohammad Abu Khashabeh
License-Expression: MIT
License-File: LICENSE
Keywords: ai,analytics,anthropic,chatbot,django,llm,natural-language,ollama,openai,orm
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 5.2
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: django>=4.2
Provides-Extra: all
Requires-Dist: anthropic>=0.30; extra == 'all'
Requires-Dist: boto3>=1.34; extra == 'all'
Requires-Dist: celery>=5.3; extra == 'all'
Requires-Dist: cohere>=5.0; extra == 'all'
Requires-Dist: djangorestframework>=3.14; extra == 'all'
Requires-Dist: google-genai>=1.0; extra == 'all'
Requires-Dist: mistralai>=1.0; extra == 'all'
Requires-Dist: openai>=1.30; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.30; extra == 'anthropic'
Provides-Extra: bedrock
Requires-Dist: boto3>=1.34; extra == 'bedrock'
Provides-Extra: celery
Requires-Dist: celery>=5.3; extra == 'celery'
Provides-Extra: cohere
Requires-Dist: cohere>=5.0; extra == 'cohere'
Provides-Extra: dev
Requires-Dist: django-stubs>=5.0; extra == 'dev'
Requires-Dist: djangorestframework>=3.14; extra == 'dev'
Requires-Dist: hypothesis>=6.100; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest-django>=4.8; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: drf
Requires-Dist: djangorestframework>=3.14; extra == 'drf'
Provides-Extra: gemini
Requires-Dist: google-genai>=1.0; extra == 'gemini'
Provides-Extra: groq
Provides-Extra: mistral
Requires-Dist: mistralai>=1.0; extra == 'mistral'
Provides-Extra: openai
Requires-Dist: openai>=1.30; extra == 'openai'
Description-Content-Type: text/markdown

# django-ask-ai

**Let people ask your Django app questions in plain language — without giving AI a database cursor, your source code, or the power to change anything.**

Staff, support, and product people already live in Django admin and your product UI. They want answers like *“how many open invoices this month?”* or *“what do the status values mean?”* Engineers want that too — but not by wiring ChatGPT to SQL, pasting the schema into a vendor chat, or letting an agent “just run tools” against production.

`django-ask-ai` is the drop-in middle path:

> **The LLM only translates. Django validates, scopes, executes read-only, logs, and caches. Your Groups decide who sees what.**

```python
Invoice.objects.ask("how many open invoices this month?", user=request.user)
# → Group scope → allowlisted ORM → aggregate → plain-language answer
```

```console
$ python manage.py ask_ai "what do the status values mean on Invoice?"
$ python manage.py ask_ai --doctor
```

---

## Who is this for?

| Person | What they get |
|---|---|
| **End users / staff** | Talk to *their* data in ordinary language — from **admin**, **CLI**, **API**, widget, or Python |
| **Engineers** | One package, not a custom agent stack. Plug in **Ollama / OpenAI / OpenRouter / Anthropic / Gemini / …** |
| **Security / compliance** | No SQL generation. No writes. Model never sees a cursor or your source tree. Every ask is in **`AskLog`** |

**Real use case:** a support lead opens Invoice admin, types “how many overdue?”, gets a number under their Group’s visible models and excluded fields. A manager with `aggregates_only` never gets raw customer rows. “Delete all unpaid orders” is refused and logged. The same question works from `manage.py ask_ai` or a DRF client when a product UI needs “talk to your data” over HTTP.

---

## Why this exists (and why not “just an AI agent”?)

| Concern with AI agents / text-to-SQL | What django-ask-ai does |
|---|---|
| “What if the model invents SQL?” | It never runs SQL. It proposes ORM JSON; an **AST allowlist** decides |
| “What if it runs destructive actions?” | Writes are **not allowlisted** — `delete()` / `update()` / `create()` / `raw()` blocked in the validator (string + AST), not by asking the model nicely |
| “What if my codebase / settings leak into the prompt?” | The model sees **schema + vocabulary you choose** — not your repo, secrets, or connection strings |
| “What if support sees another tenant’s rows?” | **Scopes** (Django Groups): schema allowlist, field `exclude`, `row_filter`, `aggregates_only` — enforced in code |
| “Agents are heavy and every ask burns tokens” | Schema, relations, today/yesterday, and host **metrics** often answer with **0 tokens**. Aggregate answers **cache** |

**One sentence:** *The LLM is a translator. Django is the executor. Your Groups are the permission system.*

---

## Out of the box

No custom agent framework. After install + mixin:

| Feature | What you get |
|---|---|
| **`AskAdminMixin`** | Ask panel on each ModelAdmin (changelist + change form), suggestion chips from `_meta` |
| **`manage.py ask_ai`** | CLI one-shot + REPL; `--doctor` for install health |
| **DRF API** | HTTP “talk to your data” — sessions, `/ask/capabilities/` |
| **Widget / console** | `{% ask_widget %}` / `{% ask_console %}` in your pages |
| **`.objects.ask()`** | Same pipeline from Python views and services |
| **Pluggable LLMs** | Ollama (default), OpenAI, **OpenRouter** (OpenAI-compatible `BASE_URL`), Anthropic, Gemini, Groq, Mistral, Cohere, Together, Azure, Bedrock |
| Schema / relations / choices | Live `_meta` — often **no LLM call** |
| Today / yesterday · status / period | Deterministic paths from your fields & choices |
| Clarify unknown terms | From **your** visible models |
| Novel data questions | LLM → AST validate → cost gate → execute under scope |
| **Audit logging (`AskLog`)** | Every ask — answers *and* refusals — with scope, expression, tokens, stage |
| **Answer cache** | Aggregate hits via Django cache (`CACHE_ALIAS` → Redis/Memcached in prod) |
| **API sessions** | Multi-turn conversation state in cache (not package DB tables) |
| Charts · follow-ups | Group-by chart payloads + “Why?” drill-down suggestions |
| Certified questions · metrics | Freeze weekly asks → **0-token** replay |
| Knowledge providers | Local docs + optional MCP for structure/knowledge asks |
| `ask_eval` / `ask_watch` | Regression suites + anomaly watch on metrics |
| Celery schedules | Optional recurring asks + webhooks |

Optional (5 minutes): teach **your** words in `ask_context.py`. The engine stays domain-agnostic — shop, library, billing, HR all plug in the same way.

---

## Quickstart

```console
pip install django-ask-ai
```

```python
INSTALLED_APPS = [..., "django_ask_ai"]

from django_ask_ai import AskMixin

class Invoice(AskMixin, models.Model):
    ask_exclude = ["internal_notes"]
    ask_business = "An invoice is overdue if due_at < today and status != paid."
```

```python
# admin.py — plug and play per model
from django.contrib import admin
from django_ask_ai import AskAdminMixin

@admin.register(Invoice)
class InvoiceAdmin(AskAdminMixin, admin.ModelAdmin):
    list_display = ("id", "status", "total")
```

### Local (Ollama) or cloud (OpenRouter / OpenAI / …)

```python
# settings.py — Ollama (default, offline)
ASK_AI = {
    "BACKENDS": {
        "default": {
            "PROVIDER": "ollama",
            "MODEL": "llama3.1",
            "BASE_URL": "http://localhost:11434",
        },
    },
    "SCOPES": {
        "support": {
            "schema": ["billing.Subscription", "billing.Invoice"],
            "exclude": ["customer__email"],
        },
        "managers": {"schema": ["billing"], "data": "aggregates_only"},
    },
    # "CACHE_ALIAS": "default",  # point at Redis in production
}
```

```python
# OpenRouter (or any OpenAI-compatible gateway) — same provider, different BASE_URL
ASK_AI = {
    "BACKENDS": {
        "default": {
            "PROVIDER": "openai",
            "MODEL": "openai/gpt-4o-mini",
            "API_KEY": "…",
            "BASE_URL": "https://openrouter.ai/api/v1",
        },
    },
}
```

```console
python manage.py migrate
python manage.py ask_ai --doctor
python manage.py ask_ai "how many open invoices?"
```

```django
{% load ask_ai %}
{% ask_console %}   {# or {% ask_widget %} #}
```

### Teach weekly questions (optional, high value)

```python
# myapp/ask_context.py — autodiscovered
ASK_CONTEXT = {
    "glossary": {"ARR": "billing.Subscription"},
    "rules": ["Trial accounts are excluded from ARR."],
    "metrics": {
        "arr": {
            "description": "Monthly recurring revenue from active subscriptions",
            "expression": (
                'Subscription.objects.filter(status="active")'
                '.aggregate(v=Sum("mrr"))'
            ),
            "aliases": ["ARR", "monthly recurring revenue"],
            "model": "billing.Subscription",
        },
    },
}
```

---

## How a question is answered

```
question
   │
   ├─► scope (Django Groups) ──► intent (data|knowledge|decision|structure)
   │
   ├─► aggregate cache hit? ──► return + AskLog
   │
   ├─► library? (metric / certified / temporal / breakdown) ──► 0 tokens
   │
   ├─► structure / definition from host metadata
   │
   └─► LLM translate → AST validate → cost gate → execute → format
              → cache aggregates → follow_ups + citations + AskLog
```

- **Logging:** `AskLog` records user, scope, surface, question, expression, tokens, duration, block stage — admin changelist included.
- **Caching:** repeated aggregate answers and API session turns live in Django’s cache framework — no package-owned chat tables.
- **Safety:** 150+ hostile-expression corpus in CI. Full threat model: [`docs/security-model.md`](docs/security-model.md).

---

## Surfaces (same safe pipeline)

| Surface | Typical use |
|---|---|
| **`AskAdminMixin`** | Staff already in admin |
| **`manage.py ask_ai`** | Engineers / ops on the command line |
| **DRF API** | Product UI, mobile, or another service over HTTP |
| **Widget / console** | Embed chat in your own templates |
| **`.objects.ask()`** | Views, jobs, notebooks |
| Celery schedules + webhooks | Optional recurring asks |

## Providers (pluggable)

**Ollama** (default) · **OpenAI** · **OpenRouter** (via OpenAI-compatible `BASE_URL`) · **Anthropic** · **Gemini** · **Groq** · **Mistral** · **Cohere** · Together · Azure OpenAI · Bedrock — routing, fallbacks, `mock_llm()` for CI. Django is the only hard dependency.

## Offline demo in under 5 minutes (Ollama + SQLite)

No cloud keys. Stage-safe.

```console
ollama pull llama3.1
cd example_project
pip install -e "..[dev,drf]"
unset ASK_AI_PROVIDER ASK_AI_API_KEY ASK_AI_BASE_URL OPENROUTER_API_KEY
python manage.py migrate && python manage.py seed_demo
python manage.py ask_ai --doctor          # backend ollama: ok
python manage.py ask_ai "orders by status"
python manage.py runserver                # admin → Order → Ask AI
```

Full talk script: [`example_project/OFFLINE_DEMO.md`](example_project/OFFLINE_DEMO.md).

## Example hosts

`example_project/` is a shop dogfood host. A second library/loans host proves the engine is **not** shop-specific.

## Production

Safe by default. Valuable when taught. Freeze weekly asks as **metrics** or
**certified questions**; run `ask_eval` before demos; put `CACHE_ALIAS` on Redis.
Playbook: [`docs/production.md`](docs/production.md).

## Docs

| Topic | Path |
|---|---|
| Quickstart | [`docs/quickstart.md`](docs/quickstart.md) |
| Offline demo | [`example_project/OFFLINE_DEMO.md`](example_project/OFFLINE_DEMO.md) |
| Scopes / Group cookbook | [`docs/scopes.md`](docs/scopes.md) |
| Compatibility vs others | [`COMPAT.md`](COMPAT.md) |
| Production | [`docs/production.md`](docs/production.md) |
| Security | [`docs/security-model.md`](docs/security-model.md) |
| Market | [`docs/market.md`](docs/market.md) |
| Vision | [`docs/vision.md`](docs/vision.md) |
| Site | https://mabukhashabeh.github.io/django-ask-ai/ |

## License

MIT
