Metadata-Version: 2.5
Name: llm-openai-decisions
Version: 0.1
Summary: LLM plugin for the OpenAI Decisions API
Project-URL: Homepage, https://github.com/simonw/llm-openai-decisions
Project-URL: Changelog, https://github.com/simonw/llm-openai-decisions/releases
Project-URL: Issues, https://github.com/simonw/llm-openai-decisions/issues
Project-URL: CI, https://github.com/simonw/llm-openai-decisions/actions
Author: Simon Willison
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.10
Requires-Dist: httpx2>=2.13.0
Requires-Dist: llm>=0.36
Requires-Dist: pydantic>=2
Description-Content-Type: text/markdown

# llm-openai-decisions

[![PyPI](https://img.shields.io/pypi/v/llm-openai-decisions.svg)](https://pypi.org/project/llm-openai-decisions/)
[![Changelog](https://img.shields.io/github/v/release/simonw/llm-openai-decisions?include_prereleases&label=changelog)](https://github.com/simonw/llm-openai-decisions/releases)
[![Tests](https://github.com/simonw/llm-openai-decisions/actions/workflows/test.yml/badge.svg)](https://github.com/simonw/llm-openai-decisions/actions/workflows/test.yml)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/llm-openai-decisions/blob/main/LICENSE)

Use the [OpenAI Decisions API](https://developers.openai.com/api/docs/guides/decisions) with [LLM](https://llm.datasette.io/) to evaluate text and images with predicates, choices, and scores.

## Installation

```bash
llm install llm-openai-decisions
llm keys set openai
```

You can also set the `OPENAI_API_KEY` environment variable, or use LLM's `--key` option.

## Yes/no questions

Supply the input as the prompt or on stdin, and the question using `-s`:

```bash
llm -m openai-decisions/gpt-6-luna 'Please refund my last payment.' \
  -s 'Does this message explicitly request a refund?'
```
Example output:
```json
{"name": "evaluation", "type": "predicate", "probability": 0.99}
```

The default `answer_type` is `predicate`. Its `probability` is an estimate from 0 to 1 that the condition is true. Use `-o name refund` to change the question name from `evaluation`.

## Categories

Use `answer_type choice` and supply a JSON object mapping category names to descriptions:

```bash
llm -m openai-decisions/gpt-6-luna 'I was charged twice.' \
  -s 'Which department should handle this message?' \
  -o answer_type choice \
  -o choices '{
    "billing": "Charges, invoices, and refunds",
    "technical": "Problems using the product",
    "other": null
  }'
```
Example output:
```json
{
  "type": "choice",
  "name": "evaluation",
  "choice": "billing",
  "probabilities": [
    {
      "value": "billing",
      "probability": 1.0
    },
    {
      "value": "technical",
      "probability": 0.0
    },
    {
      "value": "other",
      "probability": 0.0
    }
  ],
  "confidence": 1.0
}
```

A `null` description means the name alone is sufficient. The output includes `choice`, `confidence`, and a `probabilities` array.

For boolean choices, supply an array of objects with descriptions for `true` and `false`:

```bash
llm -m openai-decisions/gpt-6-luna 'I was charged twice.' \
  -s 'Does this require billing support?' \
  -o answer_type choice \
  -o choices '[
    {"value": true,"description": "Billing issue"},
    {"value": false,"description" :"Anything else"}
  ]'
```
Example output:
```json
{
  "type": "choice",
  "name": "evaluation",
  "choice": true,
  "probabilities": [
    {
      "value": true,
      "probability": 1.0
    },
    {
      "value": false,
      "probability": 0.0
    }
  ],
  "confidence": 1.0
}
```
## Ratings

Use `answer_type score` with ordered level labels, lowest to highest:

```bash
llm -m openai-decisions/gpt-6-luna 'Export fails in Safari but works in Chrome.' \
  -s 'How severe is this issue?' \
  -o answer_type score \
  -o levels '["Cosmetic","Workaround available","Fully blocked"]'
```

You can also supply objects with `label` and `description` fields:

```bash
llm -m openai-decisions/gpt-6-luna 'Export fails in Safari but works in Chrome.' \
  -s 'How severe is this issue?' \
  -o answer_type score \
  -o levels '[{"label":"Cosmetic","description":"Appearance only"},{"label":"Workaround","description":"Another way works"},{"label":"Blocked","description":"No workaround"}]'
```
Example output:
```json
{
  "type": "score",
  "name": "evaluation",
  "score": 0.96,
  "probabilities": [
    {
      "value": 0,
      "label": "Cosmetic",
      "probability": 0.04
    },
    {
      "value": 1,
      "label": "Workaround",
      "probability": 0.96
    },
    {
      "value": 2,
      "label": "Blocked",
      "probability": 0.0
    }
  ],
  "confidence": 0.94
}
```

Supply at least two levels. The returned `score` is the probability-weighted average of the zero-based level indices: with three levels it can range from 0 to 2. The answer also includes `confidence` and per-level `probabilities`.

## Images

Attach an image using LLM's `-a` option. Text alongside the image is optional:

```bash
llm -m openai-decisions/gpt-6-luna -a https://static.simonwillison.net/static/2025/two-pelicans.jpg \
  -s 'Does this image contain any mammals?'
```
Example outpt:
```json
{"type": "predicate", "name": "evaluation", "probability": 0.0}
```

PNG, JPEG, WebP, and GIF attachments are supported, with at most 128 images per request. You can pass a URL or a path to a local file.

## Multiple questions

Use `-o questions` with a JSON array to ask several named questions about shared input. Each question needs a unique `name`, a `type`, and `instructions`; choice questions also need `choices`, and score questions need `levels`, using the native array-of-objects formats:

```bash
llm -m openai-decisions/gpt-6-luna 'I was charged twice and need my money back.' \
  -o questions '[
    {
      "name": "refund",
      "type": "predicate",
      "instructions": "Does this request a refund?"
    },
    {
      "name": "department",
      "type": "choice",
      "instructions": "Which department should handle this?",
      "choices": [
        {
          "value": "billing"
        },
        {
          "value": "other"
        }
      ]
    }
  ]'
```
Example output:
```json
[
  {
    "type": "predicate",
    "name": "refund",
    "probability": 0.94
  },
  {
    "type": "choice",
    "name": "department",
    "choice": "billing",
    "probabilities": [
      {
        "value": "billing",
        "probability": 1.0
      },
      {
        "value": "other",
        "probability": 0.0
      }
    ],
    "confidence": 1.0
  }
]
```
This returns a JSON array of answers in question order, even if only one question is supplied. Do not combine `questions` with a system prompt, `choices`, `levels`, or non-default `answer_type` or `name` options. Put the instructions in each question instead.

A single question normally returns one JSON object. If the API declines a question, its answer is preserved as `{"name":"...","type":"refusal"}`.

## Reusable templates

```bash
llm -m openai-decisions/gpt-6-luna \
  -s 'Does this message explicitly request a refund?' --save refund-request
cat message.txt | llm -t refund-request
```

Templates, fragments, stdin, key management, and LLM's normal logging work through the regular model API.

## Python

```python
import json
import llm

model = llm.get_model("openai-decisions/gpt-6-luna")
response = model.prompt(
    "Please refund my last payment.",
    system="Does this message explicitly request a refund?",
)
print(json.loads(response.text())["probability"])
print(response.json())  # Complete provider response
print(response.usage())  # Token counts and details
```

Options accept Python lists and dictionaries as well as JSON strings. For asynchronous use:

```python
import asyncio
import llm

async def main():
    model = llm.get_async_model("openai-decisions/gpt-6-luna")
    response = model.prompt(
        "I was charged twice.",
        system="Which department should handle this?",
        answer_type="choice",
        choices={"billing": "Payments and refunds", "other": None},
    )
    print(await response.text())
    print(await response.json())

asyncio.run(main())
```

For an image in Python, pass `attachments=[llm.Attachment(path="product.png")]` to `model.prompt()`. Raw image bytes can be supplied using `llm.Attachment(content=image_bytes, type="image/png")`.

## Development

```bash
cd llm-openai-decisions
uv run pytest
uv run llm models -m openai-decisions/gpt-6-luna --options
```
