A production-quality Clojure SDK for LLM providers: one canonical API for chat, embeddings, moderation, rerank, image generation, audio transcription, and text-to-speech.
This is a provider SDK, not an agent framework or proxy server. It owns provider wire-format differences so your application does not have to. It does not include credential pools, budget routing, plugin loading, vector stores, MCP clients, observability sinks, or secret managers.
Add the library to deps.edn (released to Clojars):
{:deps {net.clojars.deadmeme5441/clojure-llm-sdk {:mvn/version "0.1.0"}}}
Or with Leiningen / project.clj:
[net.clojars.deadmeme5441/clojure-llm-sdk "0.1.0"]
Then require the public namespace:
(require '[llm.sdk :as sdk])
(sdk/complete
:openai
{:request/model "gpt-4o-mini"
:request/messages [{:message/role :user
:message/content "Reply with the single word: ok"}]})
Responses use the same canonical shape across providers:
{: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/provider-data {...}
:response/raw {...}}
Streaming uses the same request shape:
(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)))))
Library docs:
Project docs:
The SDK currently registers 36 provider profiles across seven canonical modalities.
| Modality | Public function | Providers |
|---|---|---|
| Chat | sdk/complete | OpenAI, Anthropic, Gemini, Vertex, OpenRouter, Codex, DeepSeek, Kimi, Kimi Code, Mistral, Groq, Cerebras, Together, xAI, HuggingFace Router, Perplexity, Bedrock, Ollama, and aggregator aliases |
| Embeddings | sdk/embed | OpenAI, Cohere, Voyage, Mistral, Together, Jina, Ollama |
| Moderation | sdk/moderate | OpenAI |
| Rerank | sdk/rerank | Cohere, Voyage, Jina |
| Image generation | sdk/generate-image | OpenAI, Vertex Imagen, Bedrock image models |
| Audio transcription | sdk/transcribe | OpenAI Whisper, Groq Whisper |
| Text-to-speech | sdk/speak | OpenAI TTS, ElevenLabs |
See Providers for the full provider matrix and credential list.
Credentials are read from environment variables. The SDK does not load .env files itself.
export OPENAI_API_KEY=sk-...
export ANTHROPIC_API_KEY=sk-ant-api03-...
export GEMINI_API_KEY=AIza...
export KIMI_API_KEY=...
Use .env.example as a non-secret template for local live smoke tests.
Some provider names are intentionally distinct:
:kimi uses Moonshot's public API and reads MOONSHOT_API_KEY.:kimi-code uses Kimi Code's coding endpoint and reads KIMI_API_KEY.:vertex-gemini uses Google Application Default Credentials or GOOGLE_OAUTH_ACCESS_TOKEN.:codex-backend reads OAuth data from the official Codex CLI auth file.Applications that manage secrets outside environment variables can pass per-call runtime config:
(sdk/complete :openai request
:config {:api-key "sk-..."
:base-url "https://api.openai.com/v1"
:timeout-ms 60000})
:cost/usd :unknown; missing cache telemetry returns :cache/status :unknown.Embeddings:
(sdk/embed
:openai
{:embed/model "text-embedding-3-small"
:embed/inputs ["clojure" "lisp" "java"]})
Rerank:
(sdk/rerank
:cohere
{:rerank/model "rerank-english-v3.0"
:rerank/query "lisp dialect on the JVM"
:rerank/documents ["Python" "Clojure" "JavaScript"]
:rerank/top-n 3})
Fallbacks:
(sdk/with-fallbacks
[[:openai "gpt-4o"]
[:anthropic "claude-haiku-4-5"]
[:groq "llama-3.1-8b-instant"]]
{:request/messages [{:message/role :user
:message/content "Reply: ok"}]})
Custom OpenAI-compatible alias:
(require '[llm.sdk.providers.openai.chat :as openai-chat])
(openai-chat/register-alias!
{:id :my-private-llm
:base-url "https://llm.example.com/v1"
:env-var-names ["MY_PRIVATE_LLM_KEY"]
:capabilities #{:chat :streaming :tools}})
Provider implementations are split by provider family, such as llm.sdk.providers.openai.chat, llm.sdk.providers.anthropic.chat, and llm.sdk.providers.openrouter.chat. Older flat namespaces remain compatibility shims for existing code.
The public CI workflow runs:
clj-kondo --lint src test
clojure -M:test
clojure -T:build jar
Live tests are explicit:
cp .env.example .env
set -a; source .env; set +a
clojure -M:live-test
MIT
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 |