This guide is for application authors using clojure-llm-sdk as a library. The SDK normalizes provider wire formats into one Clojure surface while preserving provider-specific replay state such as reasoning signatures, encrypted reasoning items, citations, and tool-call ids.
Add the library to deps.edn:
{:deps {com.deadmeme5441/clojure-llm-sdk
{:git/url "https://github.com/DeadMeme5441/clojure-llm-sdk"
:git/sha "LATEST_SHA"}}}
Then require the public namespace:
(require '[llm.sdk :as sdk])
Set only the credentials for providers you call. For local live smoke tests, copy the template:
cp .env.example .env
Example shell setup:
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-api03-...
export KIMI_API_KEY=...
The SDK does not load .env files by itself. Your application or shell should load them before invoking the library.
(sdk/complete
:openai
{:request/model "gpt-4o-mini"
:request/messages [{:message/role :user
:message/content "Reply with the single word: ok"}]})
The response is canonical:
{:response/provider :openai
:response/model "gpt-4o-mini"
:response/parts [{:part/type :text
:text "ok"}]
:response/finish-reason :stop
:response/usage {...}
:response/cost {...}
:response/cache {...}
:response/raw {...}}
See canonical-response.md for the full response contract.
Pass :stream? true and optionally :on-event:
(sdk/complete
:openai
{:request/model "gpt-4o-mini"
:request/messages [{:message/role :user
:message/content "Count to three"}]}
:stream? true
:on-event (fn [event]
(when (= :stream/content-delta (:event/type event))
(print (:event/delta event)))))
Stream events use a provider-neutral taxonomy: content deltas, reasoning deltas, tool-call deltas, citations, usage, provider-state, errors, and end events. The final response is still stamped with usage, cost, and cache data when the provider reports enough information.
Tools use OpenAI-style function tool shape at the canonical layer:
(def weather-tool
{:type :function
:function {:name "get_weather"
:description "Get current weather for a location"
:parameters {:type :object
:required ["location"]
:properties {:location {:type :string}}}}})
(def first-turn
(sdk/complete
:openai
{:request/model "gpt-4o"
:request/messages [{:message/role :user
:message/content "Weather in New York?"}]
:request/tools [weather-tool]}))
When :response/finish-reason is :tool-calls, execute the requested tool in your application and send a tool result message:
(let [call (first (:response/tool-calls first-turn))]
(sdk/complete
:openai
{:request/model "gpt-4o"
:request/messages
[{:message/role :user
:message/content "Weather in New York?"}
{:message/role :assistant
:message/content ""
:message/tool-calls (:response/tool-calls first-turn)}
{:message/role :tool
:message/tool-call-id (:tool-call/id call)
:message/content "{\"temperature\":72,\"condition\":\"clear\"}"}]
:request/tools [weather-tool]}))
The SDK preserves provider-specific tool-call ids and replay fields so multi-turn requests can be reconstructed correctly.
The same public namespace exposes non-chat modalities:
(sdk/embed :openai
{:embed/model "text-embedding-3-small"
:embed/inputs ["clojure" "lisp"]})
(sdk/moderate :openai
{:moderation/inputs ["text to classify"]})
(sdk/rerank :cohere
{:rerank/model "rerank-english-v3.0"
:rerank/query "JVM Lisp"
:rerank/documents ["Python" "Clojure" "JavaScript"]})
(sdk/generate-image :openai
{:image/model "dall-e-3"
:image/prompt "a product photo of a brass desk lamp"})
(sdk/transcribe :openai
{:transcribe/model "whisper-1"
:transcribe/file (java.io.File. "clip.wav")})
(sdk/speak :openai
{:speak/model "tts-1"
:speak/voice "alloy"
:speak/input "Hello"})
See api-reference.md for the request keys each modality accepts.
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 |