Idiomatic Clojure wrapper over the official OpenAI Java SDK, focused on the Responses API.
deps.edn:
net.clojars.savya/openai-clj {:mvn/version "0.3.0"}
Leiningen:
[net.clojars.savya/openai-clj "0.3.0"]
Tracks com.openai/openai-java 4.41.0.
(require '[openai.core :as openai])
(def client (openai/client)) ; reads OPENAI_API_KEY
(def configured-client
(openai/client {:api-key "sk-..."
:organization "org_..."
:project "proj_..."
:base-url "https://api.openai.com/v1"
:timeout-ms 60000
:max-retries 2}))
(openai/create-response
client
{:model "gpt-5.2"
:input "Write one sentence about Clojure maps."
:instructions "Be precise."
:max-output-tokens 256
:temperature 0.2
:top-p 1.0
:metadata {:app "docs"}
:store true
:reasoning {:effort :low}})
;; => {:id "resp_..."
;; :model "gpt-5.2"
;; :status :completed
;; :output [{:type :message
;; :role :assistant
;; :id "msg_..."
;; :content [{:type :text :text "Clojure maps are ..."}]}]
;; :text "Clojure maps are ..."
;; :usage {:input-tokens 14 :output-tokens 12 :total-tokens 26}
;; :created-at 1790000000.0}
Request maps support :model, :input, :instructions,
:max-output-tokens, :max-tool-calls, :temperature, :top-p,
:top-logprobs, :metadata, :previous-response-id, :store, :user,
:reasoning, :tools, :tool-choice, :parallel-tool-calls, :background,
:include, :truncation, :prompt-cache-key, :safety-identifier,
:service-tier, :json-schema, :verbosity (:low/:medium/:high),
:conversation (a conversation id string), :stream-options
({:include-obfuscation true}), and :moderation ({:model "..."}).
Input can be a string or a vector of message items. Message content can be a string or a vector of multimodal parts:
{:model "gpt-5.2"
:input [{:role :user
:content [{:type :text :text "Summarize this image."}
{:type :image
:image-url "https://example.test/chart.png"
:detail :high}
{:type :file
:filename "notes.pdf"
:file-data "data:application/pdf;base64,..."}]}]}
Structured outputs use :json-schema:
{:model "gpt-5.2"
:input "Return an answer object."
:json-schema {:name "answer"
:description "One answer"
:strict true
:schema {:type "object"
:properties {:answer {:type "string"}}
:required ["answer"]}}}
(def weather-tool
{:type :function
:name "get_weather"
:description "Get current weather for a location"
:strict true
:parameters {:type "object"
:properties {:location {:type "string"}}
:required ["location"]}})
(def first-response
(openai/create-response
client
{:model "gpt-5.2"
:input "What is the weather in Denver?"
:tools [weather-tool]
:tool-choice :auto}))
(def call
(->> (:output first-response)
(filter #(= :function-call (:type %)))
first))
(openai/create-response
client
{:model "gpt-5.2"
:previous-response-id (:id first-response)
:input [{:type :function-call-output
:call-id (:call-id call)
:output {:temperature_f 72 :conditions "sunny"}}]})
{:tools [{:type :web-search
:search-context-size :low
:user-location {:city "Denver"
:country "US"
:region "CO"
:timezone "America/Denver"}
:allowed-domains ["example.com"]}
{:type :file-search
:vector-store-ids ["vs_123"]
:max-num-results 5
:filters {:type "eq" :key "kind" :value "docs"}
:ranking-options {:ranker "auto" :score-threshold 0.5}}
{:type :code-interpreter}
{:type :code-interpreter :container "cntr_123"}
{:type :mcp
:server-label "docs"
:server-url "https://mcp.example.test"
:allowed-tools ["search"]
:require-approval :never
:headers {"X-Trace" "1"}}]}
Tool choice accepts :auto, :required, :none, or
{:type :function :name "get_weather"}.
(openai/stream
client
{:model "gpt-5.2" :input "Count to three."}
prn)
;; prints normalized event maps and returns the concatenated output text
(openai/stream-text
client
{:model "gpt-5.2" :input "Count to three."}
print)
;; prints text deltas and returns the concatenated output text
(openai/retrieve-streaming client "resp_123" prn)
;; resumes streaming an existing background response
(openai/list-models client)
(openai/get-model client "gpt-5.2")
(openai/get-response client "resp_123")
(openai/list-input-items client "resp_123")
(openai/count-input-tokens client {:model "gpt-5.2" :input "Count me."})
(openai/compact client "resp_123")
(openai/cancel-response client "resp_123")
(openai/delete-response client "resp_123")
Output messages include text/refusal content. Text content includes
:annotations when the SDK returns URL, file, container-file, or file-path
citations, and :logprobs when requested and returned. Output item variants
currently normalized with explicit types:
:message, :function-call, :reasoning, :web-search-call,
:file-search-call, :code-interpreter-call, :image-generation-call,
:mcp-call, :mcp-list-tools, :mcp-approval-request,
:custom-tool-call, :local-shell-call, :computer-call, and :unknown.
Incomplete responses include :incomplete-details, for example
{:reason :max-output-tokens}.
All failures throw ex-info keyed :openai/error in ex-data:
{:openai/error :api-error :status <http status> :error-type <kw>} where :error-type is one of :bad-request,
:unauthorized, :permission-denied, :not-found,
:unprocessable-entity, :rate-limit, :internal-server, or
:unexpected-status. The original SDK exception is preserved as
(ex-cause e).{:openai/error :io-error}, original exception as
cause.Other SDK exceptions (e.g. OpenAIInvalidDataException) propagate unchanged.
In scope: Responses API, structured outputs, multimodal input parts, response streaming, response lifecycle subservices, response compaction, token counting, built-in Responses tools, MCP tools, client options, and models.
Out of scope: chat completions, embeddings, images API, audio, realtime, and batches.
clojure -M:test
Unit tests are no-network; ^:integration tests, if added later, should be
skipped without OPENAI_API_KEY.
Copyright © 2026 Savyasachi.
Distributed under the Eclipse Public License 2.0.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |