iil-aifw — Django AI Services Framework
DB-driven LLM provider, model & usage management for Django projects.
Installation
pip install iil-aifw
# With NL2SQL support:
pip install "iil-aifw[nl2sql]"
Extras / Optional Dependencies
| Extra | Additional deps | Purpose |
|---|---|---|
nl2sql |
(none — activation-only) | Activates aifw.nl2sql INSTALLED_APPS entry + migrations; no additional download weight |
rag |
pgvector>=0.3 |
Schema-RAG via pgvector (ADR-009 Phase 1, planned) |
dev |
pytest, hatch, ruff, iil-promptfw | Development toolchain |
Quick Start
# settings.py
INSTALLED_APPS = [
...
"aifw",
]
# Run migrations
python manage.py migrate aifw
python manage.py init_aifw_config # seed default providers & models
from aifw.service import sync_completion
result = sync_completion(
action_code="story_writing",
messages=[{"role": "user", "content": "Write a short story about a dragon."}],
)
if result.success:
print(result.content)
else:
print(result.error)
Features
- DB-driven model routing — swap LLM providers/models via Django Admin, zero code changes
- Multi-provider — OpenAI, Anthropic, Google, Ollama, any LiteLLM-compatible provider
- Quality-level routing — map subscription tiers to model quality (economy/balanced/premium)
- Async & sync —
completion()async,sync_completion()sync,completion_with_fallback() - Streaming —
sync_completion_stream()for DjangoStreamingHttpResponse - Usage logging — automatic token & latency tracking per action, per tenant
- Fallback models — configure primary + fallback model per action type
- NL2SQL — natural language → SQL engine with few-shot examples and self-healing retry
- Hybrid cache — process-local (30s) + Django cache framework (600s, Redis-aware)
Core API
from aifw.service import (
completion, # async
sync_completion, # sync wrapper
completion_with_fallback, # async, tries fallback model on error
sync_completion_with_fallback,
sync_completion_stream, # streaming generator
get_quality_level_for_tier, # tier name → QualityLevel int
check_action_code, # bool — action code exists in DB
)
Quality-Level Routing (v0.10.2)
Each AIActionType row can be scoped to a quality_level (1–9) and/or priority
('fast'|'balanced'|'quality'). A NULL in either dimension acts as catch-all.
Lookup uses a deterministic 4-step cascade: exact → ql-only → prio-only → catch-all.
from aifw.constants import QualityLevel
from aifw.service import get_quality_level_for_tier, sync_completion
# Map a subscription tier to a quality level (DB-driven via TierQualityMapping)
ql = get_quality_level_for_tier("pro") # → QualityLevel.BALANCED (5)
ql = get_quality_level_for_tier("premium") # → QualityLevel.PREMIUM (8)
ql = get_quality_level_for_tier("freemium") # → QualityLevel.ECONOMY (2)
result = sync_completion(
"story_writing",
messages,
quality_level=ql,
priority="quality",
)
Multi-Tenant Usage Tracking (v0.5.0)
result = sync_completion(
"story_writing",
messages,
tenant_id=user.organization_id,
object_id=f"story-{story.pk}",
metadata={"source": "web", "plan": "pro"},
)
Privacy-by-Design Logging (issue #8)
AIUsageLog can pseudonymise or anonymise itself at write time — PII never
reaches the database. Select the policy with one Django setting (DSGVO Art. 25 /
Art. 5 Abs. 1 lit. c):
# settings.py
from aifw.constants import PrivacyMode
AIFW_PRIVACY_MODE = PrivacyMode.PSEUDONYMOUS # "full" (default) | "pseudonymous" | "anonymous"
AIFW_PRIVACY_HMAC_SECRET = env("AIFW_PRIVACY_HMAC_SECRET") # optional; falls back to SECRET_KEY
# Call site stays the same — the hook transforms the row before it is written:
sync_completion(
"nl2sql",
messages,
user=request.user,
tenant_id=user.organization_id,
metadata={"nl_question": question},
)
| Mode | user |
metadata |
|---|---|---|
full (default) |
stored raw (FK) | stored raw |
pseudonymous |
NULL |
user_hash = HMAC(user.pk); nl_question → topic |
anonymous |
NULL |
reduced to {"day_bucket": "<ISO date>"} (tenant + tokens survive) |
The privacy_mode column records which policy was applied to each row.
Topic classification is a plain callable — aifw never imports iil-promptfw.
The built-in pseudonymous classifier emits a coarse placeholder ("unclassified");
wire a real classifier via a custom hook:
# myapp/privacy.py
from aifw.privacy import PseudonymousHook
def my_hook():
from promptfw import classify_topic
return PseudonymousHook(topic_classifier=classify_topic)
# settings.py
AIFW_PRIVACY_HOOK = "myapp.privacy:my_hook" # dotted path → PrivacyHook / subclass / factory
A custom hook receives the create-payload dict (keys: user, tenant_id,
metadata, …) and returns it rewritten. If a configured non-full hook raises,
logging fails closed — user/metadata are scrubbed rather than leaking PII.
For supervisor-facing reports, AIUsageLog.objects.aggregate_with_k_anonymity( *group_by, k=3) returns only buckets with ≥ k entries (suppresses re-identifiable
small groups).
Default-shift roadmap: the default stays
"full"in the 0.11.x line to keep existing audit views (bfagent, weltenhub, travel-beat) working unchanged. A shift to"pseudonymous"as the default is planned for 0.12.0 as a documented breaking change.
Streaming (v0.4.0)
from aifw.service import sync_completion_stream
from django.http import StreamingHttpResponse
def stream_story(request):
def generate():
for chunk in sync_completion_stream("story_writing", messages):
yield chunk
return StreamingHttpResponse(generate(), content_type="text/plain")
Django Models
| Model | Purpose |
|---|---|
LLMProvider |
Provider config (API key env var, base URL) |
LLMModel |
Model config (max tokens, cost per million tokens) |
AIActionType |
Action → model mapping; supports quality_level + priority dimensions |
AIUsageLog |
Token/latency/cost tracking per request, with tenant_id + metadata + privacy_mode |
TierQualityMapping |
Subscription tier → quality_level mapping (DB-driven) |
NL2SQL (v0.7.0+)
# settings.py
INSTALLED_APPS = [
...
"aifw",
"aifw.nl2sql", # activates NL2SQL models + migrations
]
python manage.py migrate aifw_nl2sql
python manage.py seed_nl2sql_examples # seed verified few-shot examples
from aifw.nl2sql import NL2SQLEngine
engine = NL2SQLEngine(schema_xml="<schema>...</schema>")
result = engine.query("How many open orders do we have?")
if result.success:
print(result.sql)
print(result.rows)
- Few-shot examples injected automatically from
NL2SQLExamplemodel - Self-healing retry — on SQL execution error, a second LLM call includes the error as context
- Feedback capture —
NL2SQLFeedbackauto-created on every execution error - Management commands:
seed_nl2sql_examples,promote_feedback,validate_schema
Management Commands
python manage.py init_aifw_config # seed default providers & models
python manage.py check_aifw_config # CI gate — verify all action codes have a catch-all row
python manage.py seed_nl2sql_examples # seed NL2SQL few-shot examples
python manage.py promote_feedback # promote corrected feedback → NL2SQLExample
python manage.py validate_schema # validate schema-XML against real DB (exit 1 on error)
Cache Tuning
# Environment variables (optional)
AIFW_LOCAL_CACHE_TTL=30 # process-local TTL in seconds (default: 30)
AIFW_CACHE_TTL=600 # shared cache TTL in seconds (default: 600)
from aifw.service import invalidate_action_cache, invalidate_tier_cache
invalidate_action_cache("story_writing") # clear one action's cache entries
invalidate_action_cache() # clear all
invalidate_tier_cache("pro") # clear one tier
Constants
from aifw.constants import QualityLevel
QualityLevel.ECONOMY # 2
QualityLevel.BALANCED # 5
QualityLevel.PREMIUM # 8
QualityLevel.ALL # (2, 5, 8)
Configuration
Django Settings
INSTALLED_APPS = [
"aifw", # core: LLMProvider, LLMModel, AIActionType, AIUsageLog, TierQualityMapping
"aifw.nl2sql", # optional: NL2SQL models + migrations (requires `nl2sql` extra)
]
Run once after installation to seed default providers and models:
python manage.py migrate aifw
python manage.py init_aifw_config
Environment Variables (optional)
| Variable | Default | Purpose |
|---|---|---|
AIFW_LOCAL_CACHE_TTL |
30 |
Process-local cache TTL in seconds |
AIFW_CACHE_TTL |
600 |
Shared cache TTL in seconds (Redis-backed) |
AIFW_PRIVACY_MODE |
full |
Privacy policy for AIUsageLog: full | pseudonymous | anonymous (issue #8) |
AIFW_PRIVACY_HOOK |
— | Dotted path to a custom PrivacyHook (overrides AIFW_PRIVACY_MODE) |
AIFW_PRIVACY_HMAC_SECRET |
SECRET_KEY |
HMAC key for pseudonymous user_hash |
AIFW_PRIVACY_*are read from Django settings (not env), like Django's ownSECRET_KEY. The cache TTLs above are read from the process environment.
No Django settings dict is required — all configuration is managed via Django Admin models.
Changelog
See CHANGELOG.md.
Metadata
Release files for iil-aifw 0.14.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| iil_aifw-0.14.0.tar.gz | 122.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| iil_aifw-0.14.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 214.1 kB
Release files / iil_aifw-0.14.0.tar.gz
| Download URL | iil_aifw-0.14.0.tar.gz |
|---|---|
| Size | 122.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4578d094ff0197db232156a2b643de982be08b58313c9bb318b21c10b5e27112
|
|
BLAKE2b-256 checksum How to use checksums |
1ab7cda55aeabddb63e9510ee9a111d7d572272b7dff886322dff5e9ca419827
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logRelease files / iil_aifw-0.14.0-py3-none-any.whl
| Download URL | iil_aifw-0.14.0-py3-none-any.whl |
|---|---|
| Size | 91.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
86eeb80e57cb29c24870b8247c30666ff4bf8760c4ea358de288a2f068777ad0
|
|
BLAKE2b-256 checksum How to use checksums |
d9fb729a1c3d6f7a5d092ef0b8ca0ae7eb558d6903546033c383a98c871ad8f9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logSecurity for iil-aifw 0.14.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for iil_aifw-0.14.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 31, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.