Metadata-Version: 2.5
Name: pnotp
Version: 0.4.1
Summary: Official Python client for P¬P: zero-retention inference for frontier models, with a signed receipt for every call.
Project-URL: Homepage, https://pnotp.ai
Project-URL: Documentation, https://docs.pnotp.ai
Project-URL: Studio, https://studio.pnotp.ai
Author-email: PnotP <support@pnotp.ai>
Requires-Python: >=3.9
Requires-Dist: httpx<0.29,>=0.24
Provides-Extra: private
Requires-Dist: numpy>=1.23; extra == 'private'
Provides-Extra: server
Requires-Dist: fastapi<1.0,>=0.110; extra == 'server'
Requires-Dist: pillow>=9.0; extra == 'server'
Requires-Dist: pydantic<3.0,>=2.0; extra == 'server'
Requires-Dist: python-multipart>=0.0.6; extra == 'server'
Requires-Dist: uvicorn<0.29,>=0.23; extra == 'server'
Description-Content-Type: text/markdown

# pnotp

Official Python client for **P¬P**: zero-retention inference for frontier models.
AI off the record.

- **Frontier models, one key.** Claude, Gemini, Gemma, DeepSeek, Kimi, Qwen and GLM
  through `https://api.pnotp.ai/v1`, priced per model.
- **Zero retention.** P¬P never stores or logs your prompts or the answers. The
  models run on managed providers under zero-retention terms.
- **A receipt for every call.** Signed with Ed25519 and checkable against a public
  key.

Docs: [docs.pnotp.ai](https://docs.pnotp.ai) · Studio:
[studio.pnotp.ai](https://studio.pnotp.ai) · R package: `install.packages("pnotp")`

## Install

```bash
pip install pnotp
```

Requires Python 3.9 or later. The only hard dependency is `httpx`.

## Authenticate

Mint an account key at **[studio.pnotp.ai/api-keys](https://studio.pnotp.ai/api-keys)**
(it starts with `pnotp_sk_` and is shown once) and add credits on Studio's Billing
page. A new account starts with none; a call on an empty balance raises
`pnotp.InsufficientCreditsError` (402).

```bash
export PNOTP_API_KEY="pnotp_sk_..."
```

```python
import pnotp
px = pnotp.Client()                       # reads PNOTP_API_KEY
# or: px = pnotp.Client(api_key="pnotp_sk_...")
```

## Your first call

```python
out = px.chat("Summarize this clause in one sentence: ...", model="gemini-3.1-flash-lite")

out["completion"]                         # the answer
out["usage"]                              # {"input_tokens": ..., "output_tokens": ...}
out["receipt"]["zero_retention"]          # True
out["receipt"]["served_by"]               # who ran the call, and who governs retention there
```

Change `model` to use any model on the list. A few starting points:

| `model` | Use it for | $ in / out per 1M tokens |
|---|---|---|
| `gemini-3.1-flash-lite` | The default: fast, reads images, tool calling | $0.25 / $1.50 |
| `gemma-4-26b` | The lowest price on the list | $0.15 / $0.60 |
| `deepseek-v4.1-flash` | Agents and 1M-token context at a low price | $0.30 / $1.20 |
| `claude-sonnet-5-5` | Coding, agents and knowledge work, 1M context | $2.00 / $10.00 |
| `claude-opus-5-5` | Frontier coding and long-horizon agent work | $4.00 / $20.00 |

The live list, with every price and context window, is public:
`GET https://api.pnotp.ai/v1/llm/pricing`. Gemini and Gemma run on Google Cloud
Vertex AI, Claude on Amazon Bedrock, and DeepSeek, Kimi, Qwen and GLM on
Fireworks AI.

## Tools, images and documents

```python
out = px.chat("Book a 3pm slot with Dr. Reyes", tools=[{"type": "function", "function": {...}}])
out.get("tool_calls")                     # [{"id", "name", "arguments": {...}}]

px.chat("What is in this diagram?", image="diagram.png")       # on a model with vision
px.chat("Summarize this contract", file="contract.pdf")        # parsed to text
```

A model sees images natively when its `GET /v1/llm/pricing` entry says
`vision: true`. Audio is not enabled today (a clean 422).

## The receipt

Every call returns a receipt next to the answer: the retention basis, the model,
the tokens, the cost and who served it. It carries no prompt and no answer. P¬P
keeps no copy of the call, so store the receipts you need.

The receipt is signed with Ed25519. The public key is at
`GET https://api.pnotp.ai/v1/llm/attestation`, and the verification recipe is on
[docs.pnotp.ai/receipts](https://docs.pnotp.ai/receipts).

## Already on the OpenAI SDK?

The API speaks OpenAI's Chat Completions. Change two lines and keep your code:

```python
from openai import OpenAI

client = OpenAI(base_url="https://api.pnotp.ai/v1", api_key="pnotp_sk_...")
resp = client.chat.completions.create(model="claude-sonnet-5-5", messages=[...], tools=[...])
resp.receipt                              # the signed receipt
```

Streaming, `tool_choice`, the Vercel AI SDK and LangChain work the same way. See
[docs.pnotp.ai/migrating](https://docs.pnotp.ai/migrating).

## Laya: typed decisions

Laya answers typed questions (choice, score, yes/no) about a ticket, an email or a
conversation with calibrated probabilities, and writes no text.

```python
result = px.decide(
    "Checkout has been down for an hour and we're losing sales. Fix it or we cancel.",
    {
        "queue": {"type": "choice", "instructions": "Which team owns this ticket?",
                  "criteria": {"infrastructure": "outages, downtime, errors",
                               "billing": "refunds, invoices, charges"}},
        "churn_risk": {"type": "noul", "instructions": "Does the customer threaten to cancel?"},
    },
)
result["answers"]["queue"]["choice"]      # "infrastructure"
```

Guide: [docs.pnotp.ai/laya](https://docs.pnotp.ai/laya).

## Hosted agents

A hosted agent runs a tool-calling loop with a zero-retention model as its brain.
A data-compliant run keeps no question, no intermediate reasoning and no loop
state: only metered metadata and the encrypted final answer, held for 24 hours.

```python
a = px.agents.create("Advisor")
px.agents.publish(a["id"], model="pnotp-compliant", tools=["programs_db"])
run = px.agents.run(a["id"], "I love AI and maths. Which programs fit?", wait=True)
print(run.final_answer())
```

## SDK agents (bring your own brain)

`pnotp.Agent` is a bounded, observable tool-calling loop that runs in your
process. The brain can be your own provider key (any OpenAI-style
`/chat/completions` with tool calling) or a text model you deployed on P¬P. With
your own key, tokens go to that provider, not through P¬P's zero-retention layer.

```python
import pnotp

agent = pnotp.Agent("gemini:gemini-2.5-pro",          # reads GEMINI_API_KEY
                    system="You are a careful research assistant.")

@agent.tool
def search_pubmed(query: str, max_results: int = 5) -> list[dict]:
    "Search PubMed and return the top hits."          # docstring -> tool description
    return my_client.search(query, k=max_results)     # JSON Schema is derived from the signature

run = agent.run("Recent trials on pediatric pneumonia from chest X-rays?")
print(run.output, run.stop_reason)                    # final answer + "stop" | "max_steps"

for event in agent.stream("..."):                     # live UI events
    ...   # TextDelta / ToolCallStarted / ToolCallFinished / StepBoundary / Done
```

The loop is bounded: `max_steps`, per-tool `tool_timeout`, `max_tool_retries` (a
schema-invalid or timed-out call is re-asked without spending the step budget),
and `on_tool_error` (`"return"` feeds the error back to the brain, or `"raise"`).
Pass `response_format=MyDataclass` for a structured final answer.

## Neural networks (an extra)

Training and deploying your own models is a side feature that still works end to
end:

```python
project = px.projects.create(name="Chest X-ray", task_type="binary_image_classification")
model = px.models.create(
    project["id"], name="cnn-v1",
    architecture=pnotp.architectures.image_classifier(num_classes=2),
)

# dataset = a .zip of class-named folders (image) or a .csv (tabular)
job = px.train(model.id, dataset="train.zip", epochs=5)
job.wait(on_progress=lambda j: print(j.status))      # job.pause() / resume() / cancel()

dep = px.deployments.upload(project["id"], name="prod", checkpoint="model.pt", compliant=True)
print(px.predict(dep["deployment"]["id"], "xray.jpg"))
```

CPU training is free; GPU training bills $1.00 per hour on an NVIDIA L4, per second
of compute. `compliant=True` makes an endpoint data-compliant: the input is deleted
before the response returns, error text stays out of the stats log, and each
response carries a `compliance` block. External consumers can call an endpoint with
a per-deployment `pnp_...` key (`px.deployments.create_key(...)`). Guide:
[docs.pnotp.ai/neural-networks](https://docs.pnotp.ai/neural-networks).

## Help and Toby

The SDK documents itself, offline, with no key and no network:

```python
pnotp.help()                       # overview + the list of topics
pnotp.help("quickstart")           # a topic page
pnotp.help.search("stream a chat") # search topics + the live API reference
pnotp.help(pnotp.Client.chat)      # any symbol's real signature + docstring
pnotp.help.errors                  # the exception catalog
```

**Toby**, the hosted assistant, answers in plain English (needs your key and a
positive balance; it falls back to local doc search otherwise, so it never raises):

```python
print(pnotp.help.toby("Which model should I use for long contracts?"))
```

## Surface

| Area | Calls |
|---|---|
| LLM | `chat(text_or_messages, model=, tools=, image=, file=, ...)` returns `{completion, usage, model, receipt}` |
| Laya | `decide(state, questions, model="laya", min_confidence=)` |
| Hosted agents | `agents.create/get/list/publish(model="pnotp-compliant", tools=, ...)/run(..., wait=)` · `run.final_answer/steps/progress` |
| SDK agents | `Agent(model, tools=, system=, ...)` · `agent.run/stream` · `@agent.tool` / `@pnotp.tool` · `pnotp.endpoints.*` · `pnotp.model` |
| Credits | `credits.balance()` |
| API keys | `api_keys.create/list/revoke` |
| Projects and models | `projects.list/create/get` · `models.create/list/get/add_version/versions` |
| Training | `train(...)` returns a `TrainingJob` · `job.wait/progress/pause/resume/cancel` · `jobs.list/get` |
| Deployments | `deployments.upload/list/get/predict/stats/pause/resume/delete` · `create_key/list_keys/revoke_key` |
| Help | `help()` / `help("topic")` / `help(symbol)` · `help.search/topics/errors` · `help.toby(...)` · `help.as_tool()` |

Errors raise `pnotp.APIError` subclasses: `AuthError` (401/403), `NotFoundError`
(404), `InsufficientCreditsError` (402). Agent-loop failures raise
`pnotp.AgentError` subclasses: `MaxStepsExceeded`, `ToolError`, `ModelEndpointError`.

---

The legacy `pnotp_api` package (a standalone PathoVision wrapper) still ships in
this distribution and is unrelated to the client above.
