Translation between the System One wire format and OpenAI's Decisions API
(POST /v1/decisions).
Callers keep writing System One questions (a map of id to noul / choice /
score) and get System One answers back; this module maps both directions:
| System One | OpenAI Decisions |
|---|---|
state (any JSON) | input (text; non-string state is JSON-encoded) |
images: option | a user message with input_image parts, then the state as input_text |
%{id => question} | [%{name: id, ...}] |
noul with optional true / false criteria | predicate, with the criteria folded into instructions |
choice criteria: %{value => description} | choices: [%{value, description}] |
score criteria: [description] | levels: [%{label: "Level N", description}] |
| answers keyed by id, probabilities as maps | answers array keyed by name, probabilities as arrays |
| none | refusal, decoded to %{"type" => "refusal"} |
Non-string instructions and descriptions (System One allows objects and arrays) are JSON-encoded, since OpenAI expects text.
Summary
Functions
Converts a /v1/decisions response into the System One shape:
%{"answers" => %{id => raw_answer}, "model" => _, "usage" => _}, ready for
SystemOneClient.Answer.parse_all/1.
Builds the /v1/decisions request body.
Functions
Converts a /v1/decisions response into the System One shape:
%{"answers" => %{id => raw_answer}, "model" => _, "usage" => _}, ready for
SystemOneClient.Answer.parse_all/1.
Pass the original questions so score legends carry the caller's level text instead
of the "Level N" labels sent to OpenAI.
@spec encode_request(term(), map(), String.t() | nil, [String.t()]) :: {:ok, map()} | {:error, term()}
Builds the /v1/decisions request body.
iex> {:ok, body} =
...> SystemOneClient.Wire.OpenAIDecisions.encode_request(
...> "I was charged twice.",
...> %{"urgent" => %{"type" => "noul", "instructions" => "Is this urgent?"}},
...> "gpt-6-luna"
...> )
iex> body.questions
[%{type: "predicate", name: "urgent", instructions: "Is this urgent?"}]