Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ OpenGradient enables developers to build AI applications with verifiable executi
### Key Features

- **Verifiable LLM Inference**: Drop-in replacement for OpenAI and Anthropic APIs with cryptographic attestation
- **Confidential Inference (Oblivious HTTP)**: End-to-end encrypted chat through an untrusted relay, with the enclave signature verified client-side before any content is returned — no wallet needed on the caller
- **Multi-Provider Support**: Access models from OpenAI, Anthropic, Google, and xAI through a unified interface
- **Native Web Search**: Opt-in `web_search` flag enables each provider's built-in web search, billed per search
- **Image Generation**: Native image-output models ("nano banana") return generated images directly on the response
Expand Down Expand Up @@ -215,6 +216,52 @@ for i, image in enumerate(result.images or []):
f.write(base64.b64decode(payload))
```

### Confidential Inference (Oblivious HTTP)

`og.ConfidentialLLM` sends chat completions end-to-end encrypted to a TEE through an untrusted relay — the same Oblivious HTTP path the OpenGradient chat app uses in the browser. The request is HPKE-encrypted so the relay only ever sees ciphertext, and the enclave's signature is **verified client-side before any content is returned**. Unlike `og.LLM`, this path needs **no wallet on the caller**: the relay holds the x402 account and pays per request.

It auto-resolves an OHTTP-capable TEE from the on-chain registry and appends the confidential-inference path (`/api/v1/chat/ohttp`) to the relay URL for you. `chat()` is synchronous.

```python
import opengradient as og

client = og.ConfidentialLLM(
relay_url="https://chat-api.opengradient.ai",
# If the relay requires authentication, provide a per-request header callable:
auth_headers=lambda: {"Authorization": "Bearer <token>"},
)

result = client.chat(
model=og.TEE_LLM.CLAUDE_HAIKU_4_5,
messages=[{"role": "user", "content": "In one sentence, what is a TEE?"}],
max_tokens=200,
)

print(result.content) # verified assistant text
print(result.proof.tee_id) # the attested enclave that produced it
```

Set `stream=True` to buffer, verify, and return the decrypted SSE frames in `result.stream_frames`. To target a self-hosted or pre-selected enclave and skip the registry lookup, use `og.ConfidentialLLM.from_tee(relay_url, tee)`.

#### Sign in with your Chat account

Instead of managing a relay URL and token yourself, `og.login_chat_account()` signs you in through the browser — the same CLI-auth flow other OpenGradient tools use. It opens `chat.opengradient.ai/cli-auth`, waits for you to authorize on a short-lived loopback listener (the session is only ever posted back to `127.0.0.1`, never to a remote host), and returns the access token together with the relay URL to use:

```python
auth = og.login_chat_account() # opens the browser, waits for sign-in

client = og.ConfidentialLLM(
relay_url=auth.chat_api_base_url, # relay URL from the account config
auth_headers=auth.auth_headers, # Bearer <access_token>, refreshed per request
)

result = client.chat(
model=og.TEE_LLM.CLAUDE_HAIKU_4_5,
messages=[{"role": "user", "content": "Hello!"}],
)
print(result.content)
```

### Verifiable LangChain Integration

Use OpenGradient as a drop-in LLM provider for LangChain agents with network-verified execution:
Expand Down
151 changes: 151 additions & 0 deletions docs/opengradient/client/chat_auth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
outline: [2,4]
---

[opengradient](../index) / [client](./index) / chat_auth

# Package opengradient.client.chat_auth

Browser-based login to an OpenGradient Chat account.

Opens the chat app's CLI-authorization page (``…/cli-auth``) in the user's
browser, runs a short-lived loopback HTTP listener to receive the session the
page hands back, and returns it. This mirrors the flow the chat app implements
in ``app/(standalone)/cli-auth`` (``CliAuth.tsx``): the page only ever POSTs the
session to a loopback (``127.0.0.1`` / ``localhost``) address, so the token never
leaves the user's machine.

The returned `ChatAccountAuth` carries the access token **and** the public client
config the SDK needs to reach the confidential-inference relay, so it drops
straight into [ConfidentialLLM](./confidential_llm):

```python
import opengradient as og

auth = og.login_chat_account() # opens the browser, waits for sign-in
client = og.ConfidentialLLM(
relay_url=auth.chat_api_base_url, # from the returned config
auth_headers=auth.auth_headers, # Bearer <access_token>
)
result = client.chat(
model=og.TEE_LLM.CLAUDE_HAIKU_4_5,
messages=[{"role": "user", "content": "Hello!"}],
)
print(result.content)
```

## Functions

---

### `login_chat_account()`

```python
def login_chat_account(
chat_app_url: str = 'https://chat.opengradient.ai',
*,
app_name: str = 'opengradient-sdk',
timeout: float = 300.0,
open_browser: bool = True,
host: str = '127.0.0.1',
port: int = 0,
on_ready: Optional[Callable[[str], None]] = None
) ‑> `ChatAccountAuth`
```
Sign in to an OpenGradient Chat account via the browser and return the session.

Starts a loopback HTTP listener, opens the chat app's ``/cli-auth`` page
pointed at it, and blocks until the page posts back the session bundle (after
the user signs in and authorizes) or ``timeout`` elapses.

**Arguments**

* **`chat_app_url`**: Base URL of the chat app hosting ``/cli-auth``. Defaults to
``https://chat.opengradient.ai``.
* **`app_name`**: Name shown to the user on the authorization page.
* **`timeout`**: Seconds to wait for the user to authorize before giving up.
* **`open_browser`**: Open the authorization URL in the default browser. When
False (or if no browser is available), the URL is surfaced via
``on_ready`` / logged so the user can open it manually.
* **`host`**: Loopback host to bind the listener to (``127.0.0.1`` or
``localhost`` — the page rejects any non-loopback callback).
* **`port`**: Loopback port to bind. ``0`` picks a free ephemeral port.
* **`on_ready`**: Optional callback invoked with the full authorization URL once
the listener is up. Defaults to logging it. Use this to surface the
URL yourself (e.g. print a QR code).

**Returns**

A `ChatAccountAuth` with the session tokens and client config.

**`ChatAccountAuth` fields:**

* **`access_token`**: The bearer token to authenticate to the chat-api relay.
* **`refresh_token`**: The Supabase refresh token (for obtaining a new access
token when this one expires).
* **`token_type`**: The token type reported by Supabase (typically ``"bearer"``).
* **`expires_at`**: Unix seconds at which ``access_token`` expires, if known.
* **`expires_in`**: Seconds until expiry at issue time, if known.
* **`user`**: The signed-in user record (``id``, ``email``, ``is_anonymous``).
* **`config`**: Public client configuration (``chat_api_base_url``,
``tee_registry_rpc_url``, ``tee_registry_address``, ...).
* **`raw`**: The full bundle exactly as received, for forward compatibility.

**Raises**

* **`TimeoutError`**: If the user did not authorize within ``timeout`` seconds.
* **`ValueError`**: If the received bundle was malformed.

## Classes

### `ChatAccountAuth`

A signed-in OpenGradient Chat session handed back by the browser.

Mirrors the chat app's ``CliAuthBundle``. Besides the Supabase session
tokens, it carries the public ``config`` the CLI/SDK needs to talk to
chat-api and read the TEE registry — a single source of truth so nothing has
to be hardcoded.

**Attributes**

* **`access_token`**: The bearer token to authenticate to the chat-api relay.
* **`refresh_token`**: The Supabase refresh token (for obtaining a new access
token when this one expires).
* **`token_type`**: The token type reported by Supabase (typically ``"bearer"``).
* **`expires_at`**: Unix seconds at which ``access_token`` expires, if known.
* **`expires_in`**: Seconds until expiry at issue time, if known.
* **`user`**: The signed-in user record (``id``, ``email``, ``is_anonymous``).
* **`config`**: Public client configuration (``chat_api_base_url``,
``tee_registry_rpc_url``, ``tee_registry_address``, ...).
* **`raw`**: The full bundle exactly as received, for forward compatibility.

#### Constructor

```python
def __init__(
access_token: str,
refresh_token: str,
token_type: str,
expires_at: Optional[int],
expires_in: Optional[int],
user: Dict[str, Any] = &lt;factory&gt;,
config: Dict[str, Any] = &lt;factory&gt;,
raw: Dict[str, Any] = &lt;factory&gt;
)
```

#### Methods

---

#### `auth_headers()`

```python
def auth_headers(self) ‑> Dict[str, str]
```
Return the ``Authorization`` header for the relay.

Shaped as a zero-argument callable so it can be handed directly to
`ConfidentialLLM(auth_headers=...)` / `OhttpRelayClient`, which call it
per request.
Loading
Loading