> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipecat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAIDecisionsClassifier

> OpenAIDecisionsClassifier and OpenAIDecisionsClient answer classifier questions through OpenAI's Decisions API.

## Overview

`OpenAIDecisionsClassifier` answers [classifier questions](/api-reference/server/classifiers/overview) through OpenAI's Decisions API. All the questions about one state go to OpenAI in one request. Structured state, instructions, and descriptions are sent as JSON.

An `OpenAIDecisionsClassifier` asks through an `OpenAIDecisionsClient`, which holds the HTTP/2 connection, adds the auth header, retries when OpenAI is busy, and counts tokens. Build the classifier with an API key to give it a client of its own, or pass an `OpenAIDecisionsClient` to share one connection between several classifiers.

## Installation

```bash theme={null}
uv add "pipecat-ai[openai]"
```

## Prerequisites

An OpenAI API key, usually set as an environment variable:

```bash theme={null}
OPENAI_API_KEY=...
```

## Configuration

### OpenAIDecisionsClassifier

```python theme={null}
from pipecat.classifiers.openai.decisions.classifier import OpenAIDecisionsClassifier
```

<ParamField path="api_key" type="str | None" default="None">
  OpenAI API key, when the classifier should have a client of its own. One of
  `api_key` and `client` is required.
</ParamField>

<ParamField path="client" type="OpenAIDecisionsClient | None" default="None">
  A client to share with other classifiers. One of `api_key` and `client` is
  required.
</ParamField>

<ParamField path="model" type="str" default="gpt-6-luna">
  The decision model a client of its own asks.
</ParamField>

<ParamField path="base_url" type="str" default="https://api.openai.com/v1">
  Where a client of its own sends its questions.
</ParamField>

<ParamField path="timeout" type="float" default="10.0">
  Seconds a client of its own waits for an answer before raising
  `ClassifierError`.
</ParamField>

<ParamField path="name" type="str | None" default="None">
  Name of the classifier, as it appears in logs and metrics.
</ParamField>

### OpenAIDecisionsClient

```python theme={null}
from pipecat.classifiers.openai.decisions.client import OpenAIDecisionsClient
```

<ParamField path="api_key" type="str" required>
  OpenAI API key.
</ParamField>

<ParamField path="base_url" type="str" default="https://api.openai.com/v1">
  Where the API is served, such as `https://eu.api.openai.com/v1` for data
  residency in Europe.
</ParamField>

<ParamField path="model" type="str" default="gpt-6-luna">
  The decision model to ask.
</ParamField>

<ParamField path="timeout" type="float" default="10.0">
  Seconds to wait for a reply before raising `ClassifierError`.
</ParamField>

<ParamField path="max_retries" type="int" default="3">
  How many times to retry a request OpenAI refused because it was busy (HTTP 429
  or 503), with exponential backoff.
</ParamField>

## Usage

### Basic Usage

```python theme={null}
import os

from pipecat.classifiers.base_classifier import YesNoQuestion
from pipecat.classifiers.openai.decisions.classifier import OpenAIDecisionsClassifier

classifier = OpenAIDecisionsClassifier(api_key=os.getenv("OPENAI_API_KEY"))

results = await classifier.yes_no(
    "Hi, you've reached Dana. Leave a message.",
    {"voicemail": YesNoQuestion(instructions="is this a voicemail?")},
)
results["voicemail"].is_yes
results["voicemail"].probability
```

Most of the time you don't call the classifier yourself: you pass it to a component that asks it, such as [`VoicemailDetector`](/api-reference/server/extensions/voicemail) or [`UIWorker`](/api-reference/server/workers/ui-worker).

### Sharing a Client

Several classifiers can share one `OpenAIDecisionsClient`, and with it one connection pool and one token count:

```python theme={null}
from pipecat.classifiers.openai.decisions.classifier import OpenAIDecisionsClassifier
from pipecat.classifiers.openai.decisions.client import OpenAIDecisionsClient

client = OpenAIDecisionsClient(api_key=os.getenv("OPENAI_API_KEY"))

voicemail = VoicemailDetector(classifier=OpenAIDecisionsClassifier(client=client))
ui_worker = MyUIWorker("ui", llm=ui_llm, classifier=OpenAIDecisionsClassifier(client=client))
```

A client the classifier created is closed in the classifier's `cleanup()`. A shared client is left open, so close it yourself with `await client.close()` when every classifier using it is done.

### Asking About Images

The Decisions API can see images. Pass them as `images=` to `ask()`, `yes_no()`, `choice()` or `score()`, as [`ClassifierImage`](/api-reference/server/classifiers/overview#classifierimage) objects:

```python theme={null}
from pipecat.classifiers.base_classifier import ClassifierImage

image = await ClassifierImage.from_frame(frame)  # e.g. a frame from the user's camera
results = await classifier.yes_no(
    "A frame from the user's camera.",
    {"person": YesNoQuestion(instructions="Is there a person in the frame?")},
    images=[image],
)
```

The images are sent inline as base64 data URLs, ahead of the state, in one user message. Without images, the state is sent as plain text.

### Opening the Connection Early

`setup()` opens the connection to OpenAI ahead of the first question, so the first answer does not pay for the TLS handshake. Components that own a classifier, such as `VoicemailDetector`, call it for you. If the connection cannot be opened at setup, a warning is logged and the first question opens it instead.

## OpenAIDecisionsClassifier Properties

| Property | Type | Description |
| - | - | - |
| `client` | `OpenAIDecisionsClient` | The client this classifier asks through. |
| `model` | `str` | The decision model the questions go to. |
| `supports_images` | `bool` | Always `True`: the Decisions API can see images. |

## OpenAIDecisionsClient Reference

### Properties

| Property | Type | Description |
| - | - | - |
| `model` | `str` | The decision model the questions go to. |
| `usage` | `OpenAIDecisionsUsage` | Tokens used so far over every request, as `input_tokens` and `output_tokens`. |

### Methods

#### connect

```python theme={null}
await client.connect()
```

Opens the connection to OpenAI by looking up the model, a cheap request that also checks the key and its access to the model. It connects once: a client shared by several classifiers is connected by each of them, and only the first call sends anything. Raises `ClassifierError` if OpenAI could not be reached or refused the request.

#### close

```python theme={null}
await client.close()
```

Closes the connection pool.

## Metrics

After every call, an `OpenAIDecisionsClassifier` reports the time it took as `ProcessingMetricsData` and the tokens used as `LLMUsageMetricsData` through its [`on_metrics`](/api-reference/server/classifiers/overview#on_metrics) event.

## Notes

* **Limits**: a choice question takes at most 255 options, available as `OPENAI_DECISIONS_MAX_CHOICE_OPTIONS` in `pipecat.classifiers.openai.decisions.classifier`. A question with more raises `ClassifierError` before anything is sent.
* **Images**: a call takes up to 128 images, available as `OPENAI_DECISIONS_MAX_IMAGES` in `pipecat.classifiers.openai.decisions.classifier`. More than 128 raises `ClassifierError` before anything is sent. The API's `detail` option is not exposed, so OpenAI uses its default, `auto`.
* **Errors**: a rejected request, a busy OpenAI after every retry, an unreachable server, a refusal to answer a question, or a reply missing an answer all raise `ClassifierError`.
* **Idle connections** are kept open for 240 seconds, so a gap between questions does not cost a new TLS handshake.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.