Skip to content

feat: add OpenAI's Decisions API as a provider - #2

Merged
schainks merged 2 commits into
mainfrom
feat/openai-decisions
Oct 12, 2026
Merged

schainks merged 2 commits into
mainfrom
feat/openai-decisions

Conversation

@schainks

@schainks schainks commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Closes #1.

Adds provider: :openai for OpenAI's Decisions API (POST /v1/decisions, gpt-6-luna). The semantics match System One but the wire format doesn't, so a new SystemOneClient.Wire.OpenAIDecisions module translates requests and responses both ways. Callers keep writing the same questions and get the same Choice / Noul / Score structs.

Translation

System One OpenAI Decisions
state input (strings pass through, anything else JSON-encoded)
%{id => question} [%{name: id, ...}]
noul (+ optional true/false criteria) predicate, criteria folded into instructions
choice criteria: %{value => description} choices: [%{value, description}]
score criteria: [description] levels: [%{label: "Level N", description}]
answers map, probability maps answers array by name, probability arrays
none refusal -> %SystemOneClient.Answer.Refusal{}

Also

  • SystemOneClient.Answer.Refusal: a refused question doesn't fail the request; the other answers are still returned. (jido_ai's SystemOne transformer treats it as a missing answer and fails open.)
  • Provider results now carry wire: :system_one | :openai_decisions. Existing providers are unchanged.
  • Choice or score questions without criteria are rejected before the request.
  • README section, CHANGELOG, version 0.2.0. Not published.

Testing

  • 19 new tests (55 total), including OpenAI's three documented request/response examples and end-to-end runs through Req.Test.
  • mix format --check-formatted, compile --warnings-as-errors, mix docs (no warnings), mix hex.build.
  • Verified against the live API (gpt-6-luna): the response's top-level keys are answers, model, usage as decoded; choice, score and predicate (with criteria) all round-trip; a mixed three-question tool-routing request picked gcd at 0.99 in ~157 ms; a bad model returns {:error, {:http, 404, _}} and a bad key {:error, {:http, 401, _}}.
  • The live run caught one gap, fixed in the second commit: score legends showed the "Level N" labels sent to OpenAI. They now carry the caller's level text, like TypeSafe.

Out of scope, tracked separately: image inputs (#3) and OpenAI-Organization / OpenAI-Project headers (#4).

🤖 Generated with Claude Code

https://claude.ai/code/session_01GCRzC1xV9gCW4bf7LzBGcB

schainks and others added 2 commits October 10, 2026 22:28
provider: :openai posts to https://api.openai.com/v1/decisions (default model
gpt-6-luna, OPENAI_API_KEY). The APIs ask the same three kinds of question with
different wire formats, so SystemOneClient.Wire.OpenAIDecisions translates both
ways and callers keep the same questions and answer structs:

- noul <-> predicate (true/false criteria folded into the instructions),
  choice criteria <-> choices, score criteria <-> levels labelled "Level N"
- state -> text input (non-string state, instructions and descriptions are
  JSON-encoded)
- answers array keyed by name -> map keyed by id; probability arrays -> maps;
  score legend from OpenAI's labels
- per-question refusals -> SystemOneClient.Answer.Refusal
- choice/score without criteria rejected locally as :missing_criteria

Bumps the version to 0.2.0 and documents the provider. Closes #1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCRzC1xV9gCW4bf7LzBGcB
OpenAI requires a label per level, so requests send "Level N". Decoding now
restores the caller's original level text into the legend, matching what
TypeSafe returns. Found by a live run against /v1/decisions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCRzC1xV9gCW4bf7LzBGcB
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support OpenAI's Decisions API (POST /v1/decisions, gpt-6-luna) as a provider

1 participant