Metadata-Version: 2.5
Name: unsure_client
Version: 0.2.0
Summary: Python client for Unsure classifications, review, and prompt evaluation
Project-URL: Homepage, https://unsure.dev
Project-URL: Documentation, https://app.unsure.dev/docs
Requires-Python: >=3.12
Requires-Dist: httpx<1,>=0.28
Requires-Dist: pydantic<3,>=2
Description-Content-Type: text/markdown

# Unsure Python client

You can't optimize what you don't measure. Unsure runs structured classifications on Jev in a few hundred milliseconds, records confidence per field, queues uncertain answers for review, and replays new prompts against those corrections before they ship.

```sh
pip install unsure_client
```

```python
from typing import Literal

from pydantic import BaseModel
from unsure_client import Unsure


class Triage(BaseModel):
    urgent: bool
    team: Literal["billing", "support", "sales"]


client = Unsure(api_key="YOUR_UNSURE_KEY")
decision = client.decide(
    prompt="I paid the invoice, but my account is still locked.",
    schema=Triage,
)
print(decision.data)  # A Triage instance, with typed .urgent and .team fields.

# Correct a field using its name as the keyword.
client.feedback(decision_id=decision.id, team="support")

# Replay an improved prompt against reviewed decisions in the same group.
group = client.group(decision.group_id)["group"]
run = client.prototype(
    decision.group_id,
    template=group["template"] + "\nPaid but locked out? Route to support.",
)
print(client.prototype_run(run["id"]))
```

No context manager is required. Reuse the client across calls and call `client.close()` when finished to release its connections. `with Unsure(...)` is also supported for automatic cleanup.

For async applications, use `AsyncUnsure` and await the same methods. Both clients use HTTPX: `httpx.Client` for `Unsure` and `httpx.AsyncClient` for `AsyncUnsure`.

```python
from unsure_client import AsyncUnsure


async def classify_ticket(text: str) -> Triage:
    client = AsyncUnsure(api_key="YOUR_UNSURE_KEY")
    try:
        decision = await client.decide(prompt=text, schema=Triage)
        print(decision.data)  # Still a typed Triage instance.
        await client.feedback(decision_id=decision.id, team="support")
        return decision.data
    finally:
        await client.aclose()
```

Reuse `AsyncUnsure` across requests in your async application. Call `await client.aclose()` (or `await client.close()`) at shutdown, or use `async with AsyncUnsure(...)` for automatic cleanup. Pipeline runs, prompt replays, reviews, and account methods all have the same arguments and results as the synchronous client.

The default base URL is `https://app.unsure.dev`. For a local or self-hosted instance, pass `base_url="http://localhost:5080"`.

Passing `schema=Triage` returns `DecisionResult[Triage]`: `decision.data` is a validated `Triage` instance, so editors and type checkers know its fields. Use `decision.data.model_dump()` or `.model_dump_json()` to serialize it. With a JSON Schema dictionary, `decision.data` is a dictionary instead. `decision.output` still contains the raw output, including the server-added `decision_id`; the typed model receives only the schema's payload, and the ID is available as `decision.id`.

`Noul` and `Score` describe additional Jev output types:

```python
from typing import Annotated
from unsure_client import Noul, Score


class Review(BaseModel):
    needs_human: Noul
    severity: Annotated[float, Score("Low", "Medium", "High")]
```

For a saved pipeline, call `client.run(pipeline="PIPELINE_ID", input={...})`. Read the result from `result["output"]`; its `decision_ids` can be reviewed with `client.feedback(...)`.

For a choice that changes per request, declare the pipeline output as `action: Annotated[str, InferenceField()]` (import `InferenceField` from `unsure_client`). Plain `str` and `list[str]` remain text-model fields. Then supply the current candidates:

```python
result = client.run(
    pipeline="PIPELINE_ID",
    input={"page": "Name input and Save button"},
    output_values={"action": ["fill:name", "click:save", "done"]},
)
print(result["output"]["action"])  # one of those strings
```

Every `InferenceField` needs 1–128 unique strings on each run; nested keys use dot paths. Candidates apply only to that run and are retained in its decision schema. Conditional text steps can use `operator="contains"` with `value="fill:"` to match an action substring (`contains` also supports exact string membership in lists).

Requests raise `httpx.HTTPStatusError` for API errors, including HTTP 402 when credits are required. `decision_id` and `note` are reserved feedback metadata; pass nested corrections with `**{"routing.team": "support"}`. Each call corrects one field.

Python 3.12 or newer is required. See the [API reference](https://app.unsure.dev/docs).
