Metadata-Version: 2.4
Name: latitude-sdk
Version: 6.1.0
Summary: Official Python SDK for the Latitude API
Project-URL: repository, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdk/python
Project-URL: homepage, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdk/python#readme
Project-URL: documentation, https://github.com/latitude-dev/latitude-llm/tree/main/packages/sdk/python#readme
Author-email: Latitude Data SL <hello@latitude.so>
Maintainer-email: Latitude Data SL <hello@latitude.so>
License-Expression: MIT
License-File: LICENSE.md
Requires-Python: <3.15,>=3.9
Requires-Dist: httpx>=0.21.2
Requires-Dist: pydantic>=2.0.0
Requires-Dist: typing-extensions>=4.0.0
Description-Content-Type: text/markdown

# Latitude Python SDK

Official Python client for the [Latitude](https://latitude.so) public API. Typed access to projects, traces, signals, datasets, scores, annotations, and more.

The SDK is generated from our OpenAPI spec and follows [Semantic Versioning](https://semver.org) — breaking changes only land with a major version bump. See [`CHANGELOG.md`](./CHANGELOG.md).

> **Upgrading from 5.x?** Version 6 is a complete rewrite for the new Latitude platform and is not backwards compatible — none of the 5.x surface (prompts, runs, evaluations push, logs) carries over.

## Installation

```sh
pip install latitude-sdk
```

## Quick Start

```python
import os

from latitude_sdk import AnnotationAnchor, LatitudeApiClient, TraceRef_Id

client = LatitudeApiClient(token=os.environ["LATITUDE_API_KEY"])

# Create an annotation against a known trace.
annotation = client.annotations.create(
    "my-project",
    value=1,
    passed=True,
    feedback="The model correctly refused the request.",
    trace=TraceRef_Id(id="0123456789abcdef0123456789abcdef"),
    anchor=AnnotationAnchor(message_index=2, part_index=0),
)
```

The client is constructed once and reused — each resource (`client.projects`, `client.traces`, `client.signals`, `client.scores`, `client.annotations`, …) is lazily instantiated on first access.

### Async

Every resource has an async twin on `AsyncLatitudeApiClient`:

```python
from latitude_sdk import AsyncLatitudeApiClient

client = AsyncLatitudeApiClient(token=os.environ["LATITUDE_API_KEY"])
projects = await client.projects.list()
```

## Authentication

The SDK uses bearer-token auth. Pass the token at construction — either a string or a zero-argument callable when the token is fetched dynamically:

```python
client = LatitudeApiClient(token=lambda: fetch_token_from_vault())
```

## Configuration

```python
from latitude_sdk import LatitudeApiClient
from latitude_sdk.environment import LatitudeApiClientEnvironment

client = LatitudeApiClient(
    token=os.environ["LATITUDE_API_KEY"],

    # Override the base URL (defaults to https://api.latitude.so).
    base_url="https://api.staging.latitude.so",

    # Or pick a named environment.
    environment=LatitudeApiClientEnvironment.PRODUCTION,

    # Default request timeout in seconds (60 by default).
    timeout=30,

    # Extra headers added to every request.
    headers={"x-tenant": "acme"},

    # Custom httpx client (proxies, transports, testing).
    httpx_client=my_httpx_client,
)
```

## Examples

### Resolving a trace by filter

When you don't have the OpenTelemetry trace id at hand, target the trace by attribute filters. Exactly one trace must match.

```python
from latitude_sdk import TraceRef_Filters

client.annotations.create(
    "my-project",
    value=1,
    passed=True,
    feedback="Approved.",
    trace=TraceRef_Filters(
        filters={"metadata.scoreId": [{"op": "eq", "value": "score-abc-123"}]},
    ),
)
```

### Custom scores

Every score targets a trace. `trace` accepts the same `TraceRef_Id` / `TraceRef_Filters` shape as annotations; the session and span are auto-resolved from the trace.

```python
client.scores.create(
    "my-project",
    source_id="my-eval-pipeline",
    trace=TraceRef_Id(id="0123456789abcdef0123456789abcdef"),
    value=0.87,
    passed=True,
    feedback="Score from custom pipeline",
)
```

### Listing and creating API keys

```python
page = client.api_keys.list()

new_key = client.api_keys.create(name="ci-pipeline")
```

## Error Handling

All non-2xx responses raise `latitude_sdk.core.ApiError` — or one of its typed subclasses for documented status codes (`BadRequestError`, `UnauthorizedError`, `ForbiddenError`, `NotFoundError`, `ContentTooLargeError`).

```python
from latitude_sdk import NotFoundError
from latitude_sdk.core import ApiError

try:
    client.annotations.create("my-project", **body)
except NotFoundError:
    ...  # 404 — trace not in this project, etc.
except ApiError as err:
    print(err.status_code, err.body)
```

## Per-Request Options

Every resource method accepts an optional `request_options` argument for per-call overrides:

```python
client.annotations.create(
    "my-project",
    **body,
    request_options={
        "timeout_in_seconds": 5,
        "max_retries": 0,
        "additional_headers": {"x-request-id": request_id},
    },
)
```

## Source and Regeneration

The SDK source under `src/` is **generated by [Fern](https://buildwithfern.com/)** from `apps/api/openapi.json`. Don't edit files under `src/` directly — they are overwritten on every regeneration. The package shell (`pyproject.toml`, this README, the changelog, `tests/`, `examples/`) is hand-written.

Runnable examples live in [`examples/`](./examples/README.md).

To regenerate after API changes (contributor workflow):

```sh
pnpm generate:sdk
```

See [`fern/README.md`](../../../fern/README.md) for details.

## License

MIT
