A Clojure library inspired by DSPy, providing a declarative approach to building and optimizing language model pipelines.
DSCloj leverages litellm-clj to provide a unified interface for working with various LLM providers while bringing DSPy's powerful programming model to the Clojure ecosystem.
DSCloj brings the power of declarative LLM programming to Clojure. Inspired by Stanford's DSPy framework.
Add DSCloj to your deps.edn:
{:deps {tech.unravel/dscloj {:mvn/version "0.1.0-alpha.1"}}}
DSCloj works by defining modules - declarative specifications of LLM tasks with typed inputs and outputs.
(require '[dscloj.core :as dscloj])
;; 1. Define a module with Malli specs
(def qa-module
{:inputs [{:name :question
:spec :string
:description "The question to answer"}]
:outputs [{:name :answer
:spec :string
:description "The answer to the question"}]
:instructions "Provide concise and accurate answers."})
;; 2. Register a provider (one-time setup)
(dscloj/register-provider! :gpt4
{:provider :openai
:model "gpt-4"
:config {:api-key (System/getenv "OPENAI_API_KEY")}})
;; Or use quick-setup to register common providers from environment
(dscloj/quick-setup!) ; Registers :openai, :anthropic, :gemini, etc.
;; 3. Use the module with predict
(def result (dscloj/predict :gpt4 qa-module
{:question "What is the capital of France?"}
:gpt4)) ; provider-config
;; 4. Access the structured output
(:answer result)
;; => "Paris"
Modules are maps with:
:inputs - Vector of input field definitions:outputs - Vector of output field definitions:instructions - Optional string describing the task instructions, rules, and examplesFields are maps with:
:name - Keyword identifier:spec - Malli spec (e.g., :string, :int, :double, :boolean, or more complex specs like [:int {:min 0 :max 100}]):description - Human-readable descriptionProvider Management:
register-provider! - Register named provider configurationsquick-setup! - Auto-register providers from environment variableslist-providers - List all registered providersget-provider - Retrieve a specific provider configThe predict function:
DSCloj uses litellm-clj's router API for flexible provider management:
;; Option 1: Register providers (recommended)
(dscloj/register-provider! :gpt4
{:provider :openai
:model "gpt-4"
:config {:api-key (System/getenv "OPENAI_API_KEY")}})
(dscloj/register-provider! :claude
{:provider :anthropic
:model "claude-3-5-sonnet-20241022"
:config {:api-key (System/getenv "ANTHROPIC_API_KEY")}})
;; Use registered providers
(dscloj/predict :gpt4 qa-module {:question "..."} :gpt4)
(dscloj/predict :gpt4 qa-module {:question "..."} :claude)
;; Option 2: Quick setup from environment
(dscloj/quick-setup!) ; Reads OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.
;; Option 3: Ad-hoc provider (no registration)
(dscloj/predict :gpt4 qa-module
{:question "..."}
{:provider :openai
:model "gpt-4"
:config {:api-key (System/getenv "OPENAI_API_KEY")}})
DSCloj uses Malli specs for defining field types with automatic validation:
;; Simple specs
(def qa-module
{:inputs [{:name :question
:spec :string
:description "The question to answer"}]
:outputs [{:name :answer
:spec :string
:description "The answer"}]
:instructions "Provide concise and accurate answers."})
;; Complex specs with constraints
(def constrained-module
{:inputs [{:name :age
:spec [:int {:min 0 :max 150}]}
{:name :email
:spec [:string {:pattern #"^[^@]+@[^@]+$"}]}]
:outputs [{:name :message
:spec :string}]})
;; Reusable specs
(def QuestionSpec [:string {:min 1 :description "The question"}])
(def AnswerSpec [:string {:min 1 :description "The answer"}])
(def module-with-reusable-specs
{:inputs [{:name :question :spec QuestionSpec}]
:outputs [{:name :answer :spec AnswerSpec}]})
;; Setup provider
(dscloj/register-provider! :gpt4
{:provider :openai :model "gpt-4"
:config {:api-key (System/getenv "OPENAI_API_KEY")}})
;; Invalid inputs/outputs are automatically validated
(dscloj/predict :gpt4 qa-module
{:question 123} ; Throws validation error - should be string
:gpt4)
;; Disable validation if needed
(dscloj/predict :gpt4 qa-module
{:question "..."}
:gpt4
{:validate? false}) ; Skip validation (4th argument for options)
DSCloj supports streaming structured output with progressive parsing and validation:
(require '[dscloj.core :as dscloj]
'[clojure.core.async :refer [go-loop <!]])
;; Define a module with structured outputs
(def whales-module
{:inputs [{:name :query :spec :string}]
:outputs [{:name :markdown_output :spec :string}]
:instructions "Generate whale species information in markdown table format."})
;; Register provider
(dscloj/register-provider! :gpt4
{:provider :openai
:model "gpt-4"
:config {:api-key (System/getenv "OPENAI_API_KEY")}})
;; Stream predictions
(let [stream-ch (dscloj/predict-stream
whales-module
{:query "Tell me about 3 whale species."}
:gpt4 ; provider-config
{:debounce-ms 100})] ; options
;; Consume the stream progressively
(go-loop []
(when-let [parsed (<! stream-ch)]
(println "Received update:" parsed)
(recur))))
Streaming Features:
See examples/streaming_whales.clj for a complete example inspired by Pydantic AI's streaming example.
One of the key benefits of the router API is easy provider switching:
;; Register multiple providers
(dscloj/register-provider! :simple-llm
{:provider :openai :model "gpt-4"
:config {:api-key (System/getenv "OPENAI_API_KEY")}})
(def openai-result (dscloj/predict :simple-llm qa-module {:question "What is AI?"}))
(dscloj/register-provider! :simple-llm
{:provider :anthropic :model "claude-haiku-4-5"
:config {:api-key (System/getenv "ANTHROPIC_API_KEY")}})
(def anthropic-result (dscloj/predict :simple-llm qa-module {:question "What is AI?"}))
;; Compare results
(println "OpenAI:" (:answer openai-result))
(println "Anthropic:" (:answer anthropic-result))
See examples/basic_usage.clj for:
See examples/streaming_whales.clj for:
DSCloj uses litellm-clj and supports:
This project is licensed under the MIT License - see the LICENSE file for details.
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 |