Metadata-Version: 2.4
Name: notpixel
Version: 0.3.0
Summary: NotPixel Python SDK — Privacy-first contextual ads for LLM applications (FastAPI, Django, LangChain, CrewAI)
Project-URL: Homepage, https://notpixel.ai
Project-URL: Documentation, https://docs.notpixel.ai/http-api
Project-URL: Repository, https://github.com/Cocco-Enterprises/notpixel
Author-email: NotPixel <dev@notpixel.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: Contextual-ads,ads,ai,fastapi,langchain,llm,monetization
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: fastembed<1,>=0.7.0
Requires-Dist: httpx>=0.25.0
Provides-Extra: dev
Requires-Dist: django<5.3,>=5.2; extra == 'dev'
Requires-Dist: fastapi<1,>=0.115; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Description-Content-Type: text/markdown

# NotPixel Python SDK (`notpixel`)

Official Python SDK for **NotPixel** — privacy-first contextual monetization for LLM applications, agents, and backends (**FastAPI, Django, Flask, LangChain, LlamaIndex, CrewAI**).

## Installation

```bash
pip install "notpixel>=0.3.0"
```

## Quick Start (Async — FastAPI / LangChain)

```python
from notpixel import AsyncAds

# Ad matching uses local embeddings automatically.
# Prompt text is not sent to the NotPixel API.
ads = AsyncAds(publisher_id="pub_10565")

# 1. Fetch a contextual ad + run your LLM in parallel (best-effort)
result = await ads.respond(
    input="How do I optimize PostgreSQL queries with indexes?",
    llm=lambda: call_my_llm("How do I optimize PostgreSQL queries with indexes?"),
)
print(result.text)  # LLM response + sponsored ad block (if matched)

# 2. Track events
if result.ad:
    await ads.impression(result.ad)
    await ads.track_click(result.ad, metadata={"source": "fastapi_chat"})
    await ads.track_conversion(
        result.ad,
        type="signup",
        value_cents=2900,
        external_id="order_12345",
    )
```

## Synchronous Usage (Django WSGI / Scripts)

```python
from notpixel import Ads

ads = Ads(publisher_id="pub_10565")
ad = ads.get_ad(input="Best serverless Postgres database")
if ad:
    print(ad.text)
    ads.track_click(ad)
```

## Privacy

Ad matching uses local embeddings automatically. The SDK sends a numerical vector
to NotPixel instead of the prompt text. No privacy flag is needed.

If the local model cannot generate an embedding, `get_ad()` reports the error through
`on_error` and returns `None`, without sending the prompt to NotPixel.

Your LLM calls remain under your application's control and follow your provider's
own data handling rules. Keep personal data out of optional context and event metadata.

## All-in-one inference

```python
from notpixel import Ads, AsyncAds

# Set NOTPIXEL_MODEL=provider/model and its API key, or pass model= explicitly.
ads = Ads(publisher_id="pub_xxx", model="openai/your-model")
result = ads.offer(input="How can I optimize PostgreSQL?")
print(result.text)

# Same contract, asynchronous I/O.
async_ads = AsyncAds(publisher_id="pub_xxx", model="anthropic/your-model")
result = await async_ads.offer(input="How can I optimize PostgreSQL?")
```

`offer()` runs inference and ad selection concurrently. The original prompt goes
only to your selected LLM provider. Only a local embedding goes to NotPixel. A
sponsored block is appended after inference; no ad is injected into the LLM prompt.
When no ad is available, the exact LLM output is returned unchanged.

Supported prefixes: `openai`, `openrouter`, `anthropic`, `google`, `xai`, `deepseek`,
`groq`, `mistral`, `perplexity`, `together`, `github`, `nvidia`, `alibaba`, `minimax`,
`llama`, `lmstudio`, `vercel`, `netlify`. Unknown prefixes and identifiers without
a prefix route through OpenRouter. Gateway model suffixes may contain slashes.
Availability of any particular model depends on your provider account.

## Streaming

```python
from contextlib import aclosing, closing

with closing(ads.stream(input="Explain database indexes")) as stream:
    for chunk in stream:
        if chunk.type == "text":
            print(chunk.content, end="", flush=True)
        elif chunk.type == "ad":
            print(chunk.content)  # Structured ad is also available in chunk.ad.

async with aclosing(async_ads.stream(input="Explain database indexes")) as stream:
    async for chunk in stream:
        if chunk.type == "text":
            print(chunk.content, end="", flush=True)
```

Events are `text`, then optionally one `ad`, then `done`. Provider failures emit
an `error` event and raise `LLMError` when iteration resumes, without a successful
`done`. Configuration errors raise before streaming starts. Ad failures never
interrupt LLM output. An ad is emitted only after the text stream finishes.

Use `closing`/`aclosing` when breaking out early. Cancelling the consuming asyncio
task closes upstream HTTP streams and releases ad work owned by that consumer.
Shared ad requests continue while another consumer is waiting. A synchronous
thread already computing an embedding cannot be forcibly interrupted; it finishes
in the background when its work/network timeout completes.

The default ad timeout is 1,500 ms; inference uses a separate configurable
`llm_timeout_ms=60000` network timeout. Inference is never automatically retried,
including after partial output. The ad `retries` option applies only to NotPixel
requests and preserves the request ID across attempts.

## Batch, prefetch and callbacks

```python
batch = ads.ads_batch(inputs=["SQL indexes", "Cloud hosting"], limit=1)
batch = await async_ads.ads_batch(inputs=["SQL indexes", "Cloud hosting"],
                                  max_concurrency=8, dedupe=True)
await async_ads.prefetch(["SQL indexes", "Cloud hosting"])
await async_ads.embedding.preload()
```

Both batch clients default to eight concurrent operations, preserve input order,
count every outcome before deduplication, and apply `limit` only to the final ad
list. Empty lists return empty results. Prefetch enables cache automatically;
`ads.cache.stats()`, `clear()` and `cleanup()` are available on both clients.

`respond()` continues to accept your own LLM callable. Its optional `context`,
`publisher_id` and `request_id` are forwarded to ad selection. `AsyncAds` awaits
async LLM/callback functions and moves synchronous LLM work off the event loop.
Analytics hooks can be synchronous in both clients or asynchronous in `AsyncAds`.
Async hooks finish before the operation returns; hook failures do not fail serving.
The synchronous client requires synchronous callbacks.

## Configuration and adapters

Precedence is call arguments, then constructor configuration, then environment
(where an environment equivalent exists). Input has no environment default.

| Variable | Purpose |
| --- | --- |
| `NOTPIXEL_MODEL` | Default `provider/model` |
| `NOTPIXEL_LOCALE` | Default locale (`pt`) |
| `NOTPIXEL_BASE_URL` | NotPixel API origin |
| `NOTPIXEL_TIMEOUT_MS` | Ad HTTP timeout (default 1500) |
| `NOTPIXEL_RETRIES` | Ad retries (default 0) |
| `NOTPIXEL_MIN_SCORE` | Optional lexical post-filter (default 0, disabled) |
| `NOTPIXEL_MOCK` | `1` mocks ads; LLM inference still uses its configured transport |
| `NOTPIXEL_LOG` | `1` opts into console inference logs |

Provider keys: `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`,
`GOOGLE_API_KEY` (or `GEMINI_API_KEY`), `XAI_API_KEY`, `DEEPSEEK_API_KEY`,
`GROQ_API_KEY`, `MISTRAL_API_KEY`, `PERPLEXITY_API_KEY`, `TOGETHER_API_KEY`,
`GITHUB_TOKEN`, `NVIDIA_API_KEY`, `DASHSCOPE_API_KEY`, `MINIMAX_API_KEY`,
`LLAMA_API_KEY`, `VERCEL_API_KEY`, `NETLIFY_API_KEY`. LM Studio needs no key.

For custom endpoints or per-call credentials, use `provider_respond()` /
`async_provider_respond()` with `provider`, `model`, `input`, `api_key` and optional
`base_url`. Named helpers such as `anthropic_respond()` and
`async_anthropic_respond()` share that contract. `openai_respond_http()` uses
Responses; `openai_respond(client, ...)` accepts an existing provider SDK client.
`openai_compatible_respond(base_url="https://host/v1", ...)` and its async
counterpart support arbitrary OpenAI-compatible endpoints and custom headers.

For connection pooling, pass `llm_client=httpx.Client(...)` or `AsyncClient(...)`
to the constructor and close it in your application lifecycle. Injected clients
are owned by your application. The existing `transport` option remains exclusively
for NotPixel ad/event requests. No provider SDK is a runtime dependency.

`logger=callback` receives typed `LogEvent` objects. Logs are off by default and
contain prompts/output when enabled. API keys and raw HTTP error bodies are never
included in SDK-generated inference errors or log events.

`PrivacyConfig(dtype="q8" | "fp16" | "fp32", on_progress=callback)` selects the
default BGE artifact. Progress reports loading/ready/error states, not downloaded
byte counts. Custom FastEmbed models use their registered precision; unsupported
overrides fail explicitly. Custom `embedder` callables retain responsibility for
their own precision. Backend/model compatibility is required for custom vectors.

`auto_track="server"` (default) documents the API's serving-time impression;
`auto_track="manual"` is available for explicit `impression()` calls. This option
does not change server billing or imply viewability measurement. Track impressions
only when appropriate to your integration's lifecycle.

## Examples and validation

See [FastAPI streaming](examples/fastapi_app.py), [Django view](examples/django_view.py),
and the [backend parity matrix](PARITY.md). Install example dependencies using the
`dev` extra. These examples keep credentials and inference in your backend.

The contract suite runs against simulated HTTP transports and embeddings. It does
not certify live provider/model availability or production serving. Version 0.3.0 provides the backend capabilities described in the parity matrix.
