Metadata-Version: 2.4
Name: notpixel
Version: 0.6.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'
Provides-Extra: local
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

### Direct SDK installation

For local processing (the default):

```bash
python -m pip install 'notpixel'
```

Supply your publisher ID in `Ads` or `AsyncAds`, as in the quick start below.
For remote processing, use the same installation and import from
`notpixel.remote`. Pip installation has no prompts, and SDK usage does
not require CLI login.

### Optional assisted installation

```bash
python -m pip install notpixel
python -m notpixel install
```

The English assistant offers login or account creation, asks local/remote,
uses the same package for both modes and generates configuration. Both installation
paths work with new and existing accounts. Python never requires Node.js.

## 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. Local embedding is included in the default package.
Choose `embedding_mode="remote"` explicitly to process sanitized context through NotPixel.

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.

The local embedding model and precision are managed by NotPixel. Choose processing location with `embedding_mode="local"` (default) or
`embedding_mode="remote"`; model and precision are not configurable. Use `ads.embedding.preload()` (or
`await ads.embedding.preload()` with `AsyncAds`) to load the model at startup.
Direct REST integrations must generate the compatible vector in their own
environment; see the [HTTP API guide](https://docs.notpixel.ai/http-api).

`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.

## Optional assisted setup and remote processing

For assisted setup, bootstrap with `python -m pip install notpixel`, then run
`python -m notpixel install`.
The English assistant offers **Log in** or **Create an account**, then asks where
to process context. Local installation adds `notpixel`; remote installation
keeps model loading lazy. Use `init`, `login`, `logout` and `whoami`
through the same module command. Python needs no Node.js installation.

`Ads(..., embedding_mode="remote")` and `AsyncAds(..., embedding_mode="remote")`
send sanitized context to NotPixel and its Cloudflare inference infrastructure.
Neither initializes FastEmbed. Local remains the default, with no automatic remote
fallback. Remote ad errors preserve the LLM answer. Raw `pip install` does not
run an interactive wizard. CLI login is optional for SDK usage; configure your publisher ID directly when
using manual installation. CLI authorization never becomes an application secret.

## Default local installation

`pip install notpixel` includes local inference; `from notpixel import Ads` uses it by default.
Use `from notpixel.remote import Ads, AsyncAds` to explicitly select remote processing.
Both use the same installation. The local model is loaded lazily and never initialized in remote mode.
