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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ Once a skill is installed, your agent will activate it automatically when you sa
- "Create an Opper index and add this markdown file to it."

**`opper-sdks`**
- "Add an Opper `call` to this Python script that returns structured output."
- "Make this Python script return structured output through Opper."
- "Migrate this `opper.call` code to the compat chat completions endpoint."
- "Wire up streaming with the TypeScript SDK."
- "Build an Opper agent with a `get_weather` tool."
- "Wrap this pipeline in an Opper trace so I can see the steps."
Expand Down
13 changes: 9 additions & 4 deletions opper-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ description: >
gateways. Use this skill whenever the user asks about Opper concepts, what
models Opper supports, API endpoints or signatures, raw HTTP integration,
gateway behaviour, integrating Opper with a coding assistant, or
compatibility / migration from OpenAI / OpenRouter / Anthropic — even if they
only say "Opper" without "API". For media generation (images, audio, video,
compatibility / migration from OpenAI / OpenRouter / Anthropic, or migrating
off Opper's own legacy /v3/call (opper.call) — even if they only say "Opper"
without "API". For media generation (images, audio, video,
OCR), files, and realtime voice, use the `opper-multimodal` skill instead.
For any endpoint signature or payload question always recommend fetching the
live OpenAPI spec at https://api.opper.ai/v3/openapi.yaml first.
Expand Down Expand Up @@ -41,6 +42,7 @@ One gateway, one key. Pick the endpoint by what you're building — the routing,

- **Text generation / chat / migrating from OpenAI, Anthropic, Google, or OpenResponses**: the compat endpoints under **`/v3/compat/...`**. Point your SDK's base URL there, keep your code, swap the key. This is the main path. See [references/compatibility.md](references/compatibility.md) and [references/migration.md](references/migration.md).
- **Structured output** (typed JSON out of a model): the standard **`response_format: {type: "json_schema"}`** on any compat chat endpoint — no separate endpoint.
- **Still on Opper's own `/call`** (`POST /v3/call`, or the legacy v2 Python SDK's `opper.call`)? It's **being sunset** — migrate to a compat chat endpoint with `response_format`. The field-by-field mapping is in [references/migration.md](references/migration.md).
- **Media generation** (images, audio / TTS / STT, video, OCR), **files**, and **realtime voice**: a separate surface — switch to the **`opper-multimodal` skill**. Quick map: images `POST /v3/images`, audio `POST /v3/audio/{speech,transcriptions}`, video `POST /v3/videos` (async), OCR `POST /v3/ocr`, files `/v3/files`, realtime `wss://api.opper.ai/v3/realtime`.
- **Roundtable** (fan one prompt out to several models, then consolidate or compare): **`POST /v3/roundtable`** (beta).
- **Building an agent** (multi-step reasoning, tool use, multi-agent, MCP): switch to the **`opper-sdks` skill** and use the Agent SDK — `Agent`, `tool`, `Conversation` ship in the unified `opperai` package for both Python and TypeScript.
Expand Down Expand Up @@ -192,17 +194,20 @@ The v3 spec also covers tracing (`/v3/spans`, `/v3/traces`), generations, files

Knowledge bases (indexes) live at `/v2/knowledge/...`, served from the same host with the same Bearer auth. They are **not** in `/v3/openapi.yaml`. For raw HTTP, fetch `https://api.opper.ai/v2/openapi.json`. For application code, the SDKs and CLI abstract this: `opper.knowledge.*` (Python / TS) and `opper indexes ...`.

## Migration from another LLM gateway
## Migration from another gateway, or from Opper's own `/call`

Moving from OpenRouter, OpenAI, Anthropic, or similar: see [references/migration.md](references/migration.md). Recap: point the base URL at `https://api.opper.ai/v3/compat` and use your Opper API key.

Moving off Opper's own legacy `/call` (`POST /v3/call` or the v2 Python SDK's `opper.call`): same file — it opens with the field-by-field `/call` → chat-completions mapping (`name` → `X-Opper-Name` header, `instructions` → system message, `output_schema` → `response_format`, `data` → parsed `choices[0].message.content`).

## Coding assistant integrations

For wiring Opper into Claude Code, Cursor, Copilot, Continue, etc., see the up-to-date list at [docs.opper.ai/overview/integrations](https://docs.opper.ai/overview/integrations).

## Non-obvious gotchas

- **One gateway, many endpoints.** Text/chat is `/v3/compat/...`; media generation, files, and realtime are a separate surface (the `opper-multimodal` skill). All share the same key and governance.
- **`/v3/call` is legacy and being sunset.** Never point a new integration at it, and don't use it in examples. Existing `/call` users (including legacy v2 Python SDK `opper.call`) migrate to compat + `response_format` — see [references/migration.md](references/migration.md).
- **Compat endpoints live under `/v3/compat/...`**, not at `/v3/...`. SDK migrations should point base URL at `https://api.opper.ai/v3/compat`.
- **Auth is `Authorization: Bearer ...` only.** Anthropic SDKs send `x-api-key` by default — set the `Authorization` header explicitly when migrating.
- **Structured output is a parameter, not an endpoint** — use `response_format: {type: "json_schema"}` on a compat chat call.
Expand All @@ -225,6 +230,6 @@ For wiring Opper into Claude Code, Cursor, Copilot, Continue, etc., see the up-t
| Browsable model catalog (for user-facing recommendations) | [opper.ai/models](https://opper.ai/models) |
| Worked recipes in many languages | [github.com/opper-ai/opper-cookbook](https://github.com/opper-ai/opper-cookbook) |
| Provider-compatible endpoints, deeper | [references/compatibility.md](references/compatibility.md) |
| Migrating from OpenRouter / OpenAI / Anthropic | [references/migration.md](references/migration.md) |
| Migrating from OpenRouter / OpenAI / Anthropic / Opper's legacy `/call` | [references/migration.md](references/migration.md) |
| Calling the API from Python or TypeScript | the `opper-sdks` skill |
| Calling the API from a terminal | the `opper-cli` skill |
100 changes: 98 additions & 2 deletions opper-api/references/migration.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,104 @@
# Migrating to Opper from another LLM gateway
# Migrating to Opper's compat endpoints

Most migrations are a **base URL + API key swap**. The compat tree at `/v3/compat/...` is shaped so that pointing a stock provider SDK at `https://api.opper.ai/v3/compat` works without changing your call sites.

For exact provider-side payload shapes, follow each provider's own spec. This file documents only what differs at the Opper boundary.
For exact provider-side payload shapes, follow each provider's own spec. This file documents only what differs at the Opper boundary — plus the one migration that isn't a base-URL swap: moving off Opper's own legacy `/call`.

## From Opper's own `/call` — `POST /v3/call` or the legacy v2 Python SDK

`/call` (Opper's task-completion endpoint, also what the legacy v2 Python SDK's `opper.call(...)` hits) is **being sunset**. The replacement is a compat chat endpoint with structured output as a parameter — same models, same tracing and control plane, standard OpenAI shape. Don't point new integrations at `/call`; migrate existing ones.

Before — a typical structured `/v3/call`:

```bash
curl -s -X POST https://api.opper.ai/v3/call \
-H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
-d '{
"name": "extract_room_details",
"instructions": "Extract details about the room described in the text.",
"model": "openai/gpt-5-mini",
"input": {"text": "The corner office on floor 3 fits 8 people and has a projector."},
"output_schema": {"type": "object", "properties": {
"floor": {"type": "integer"},
"capacity": {"type": "integer"},
"equipment": {"type": "array", "items": {"type": "string"}}
}, "required": ["floor", "capacity", "equipment"]}
}'
```

After — the same task on `/v3/compat/chat/completions`:

```bash
curl -s -X POST https://api.opper.ai/v3/compat/chat/completions \
-H "Authorization: Bearer $OPPER_API_KEY" -H "Content-Type: application/json" \
-H "X-Opper-Name: extract_room_details" \
-d '{
"model": "openai/gpt-5-mini",
"messages": [
{"role": "system", "content": "Extract details about the room described in the text."},
{"role": "user", "content": "The corner office on floor 3 fits 8 people and has a projector."}
],
"response_format": {"type": "json_schema", "json_schema": {
"name": "room_details",
"schema": {"type": "object", "properties": {
"floor": {"type": "integer"},
"capacity": {"type": "integer"},
"equipment": {"type": "array", "items": {"type": "string"}}
}, "required": ["floor", "capacity", "equipment"]}
}}
}'
```

Field-by-field:

| `/v3/call` | Compat chat completions |
|---|---|
| `name` | `X-Opper-Name` request header — keeps named-function tracing and guardrail function-scope filtering |
| `instructions` | `system` message |
| `input` | `user` message — JSON-encode structured input into the message content |
| `output_schema` | `response_format: {type: "json_schema", json_schema: {name, schema}}` |
| `model` (string or fallback array) | `model` — same `provider/model` form; for fallback chains, define a Route alias in the platform |
| response `data` | `choices[0].message.content` — a JSON **string**; parse it |
| `meta.cost` | `usage.opper.cost.total` in the body, and the `X-Opper-Cost` response header |
| `meta.trace_uuid` | `meta.trace_uuid` — traces work identically, visible at [platform.opper.ai](https://platform.opper.ai) |

In Python, the legacy SDK call maps onto the stock OpenAI SDK:

```python
# Before (legacy opper.call)
result = opper.call(
"extract_room_details",
instructions="Extract details about the room described in the text.",
input={"text": "..."},
output_schema=RoomDetails, # Pydantic model or JSON Schema
)
room = result.data

# After (OpenAI SDK against /v3/compat)
import os
from openai import OpenAI

client = OpenAI(
base_url="https://api.opper.ai/v3/compat",
api_key=os.environ["OPPER_API_KEY"],
default_headers={"X-Opper-Name": "extract_room_details"},
)
resp = client.chat.completions.parse( # .parse() takes the Pydantic model directly
model="openai/gpt-5-mini",
messages=[
{"role": "system", "content": "Extract details about the room described in the text."},
{"role": "user", "content": "..."},
],
response_format=RoomDetails,
)
room = resp.choices[0].message.parsed
```

Notes:

- **`.parse()` vs `.create()`** — the OpenAI SDK's `parse()` accepts a Pydantic model and returns `message.parsed`, the closest ergonomic match to `result.data`. With plain `create()` + a raw `json_schema`, parse `choices[0].message.content` yourself.
- **`strict: true`** on `json_schema` follows OpenAI's rules: every property must be listed in `required` and objects need `additionalProperties: false`. Leave `strict` off to keep a `/call`-style loose schema.
- **Any compat surface works** — Anthropic Messages with a forced tool schema, OpenAI Responses with `text.format`, etc. Chat completions is the shortest hop from `/call`.

## From OpenRouter (OpenAI-compatible)

Expand Down
3 changes: 2 additions & 1 deletion opper-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Run `opper` with no args for an interactive menu (Account · Agents · Skills ·
|---|---|
| `login` / `logout` / `whoami` | OAuth device flow + slot inspection. |
| `config list/get/remove` | Inspect or remove auth slots (slots are created by `opper login`). |
| `call <name> <instructions> [input]` | Run an Opper function. Stdin if `input` omitted; `--model`, `--stream`. |
| `call <name> <instructions> [input]` | Run an Opper function. Stdin if `input` omitted; `--model`, `--stream`. **Legacy** — rides the `/v3/call` surface being sunset; don't build on it (see gotchas). |
| `functions list/get/delete` | Manage saved functions. |
| `indexes list/get/create/delete/add/query` | Knowledge bases (a.k.a. indexes). |
| `models list/create/get/delete` | Built-in + custom (LiteLLM-backed) models. |
Expand Down Expand Up @@ -103,6 +103,7 @@ Under the hood these all route to `/v3/compat/...` (see the `opper-api` skill).
## Non-obvious gotchas

- **`OPPER_API_KEY` env var beats `--key`.** When the env var is set, the `--key <slot>` flag has no effect. Unset it to use a slot.
- **`opper call` rides the legacy `/v3/call` surface, which is being sunset.** Fine for a quick ad-hoc poke, but never the pattern to build on or copy into application code — that's a compat chat endpoint with `response_format` (see the `opper-api` skill and its `references/migration.md`).
- **`opper call` argument order is `<function> <instructions> <input>`** — easy to flip.
- **Model identifiers use `provider/<id-with-dashes>`** (e.g. `anthropic/claude-sonnet-4-6`, `anthropic/claude-opus-4-7`, `openai/gpt-4o`) — **dashes, not dots**, even for versions. Custom models registered with `opper models create` are LiteLLM-backed. List the live set with `opper models list` or `curl -s https://api.opper.ai/v3/models`.
- **`indexes` is the CLI name for knowledge bases.** `opper indexes add <name> <content>` takes content as a positional arg (use `-` to read from stdin), not a `--content` flag.
Expand Down
Loading