Installation
Get LLM4S up and running in minutes.
Table of contents
- Prerequisites
- Add LLM4S to Your Project
- Quick Start with the Starter Kit
- Optional Dependencies
- For RAG, vector stores, chunking, reranking and document extraction
- For agent memory
- For MCP (Model Context Protocol)
- For speech (STT / TTS)
- For Ollama (local models)
- For Gemini and Vertex AI
- For Anthropic
- For OpenAI, Azure OpenAI and Requesty
- For Azure OpenAI with Microsoft Entra ID
- For DeepSeek, Z.ai, OpenRouter, Mistral, Cohere and any OpenAI-compatible endpoint
- For AWS Bedrock
- For Voyage AI embeddings
- For Jina AI embeddings
- For Cohere embeddings
- For the Jev decision model
- For IBM watsonx.ai
- For Perplexity
- For Java, Spring Boot, cats-effect and ZIO
- For Kotlin (coroutines and Flow)
- For image generation and vision
- For media types (
MediaType,MediaCategory) - For agents (
Agent, guardrails, handoffs, graphs) - For durable agent threads (SQLite checkpointer)
- For agent threads shared across machines (PostgreSQL checkpointer)
- For semantic memory in agents
- For built-in tools (web search, HTTP, filesystem, shell)
- For tapir endpoints as tools
- For tracing and metrics (Langfuse, OpenTelemetry, in-process trace store, Prometheus)
- For Neo4j (knowledge graph store)
- Structured output from a case class
- Typed prompts
- Capability-typed clients
- Testing your own code (the testkit)
- Checking your setup (the doctor)
- Trying llm4s in the REPL
- For provider authors (testing a provider module)
- For checkpoint store authors (testing a
Checkpointer) - For Workspace (Containerized Execution)
- Provider Setup
- Verify Installation
- Troubleshooting
- Next Steps
- Additional Resources
Prerequisites
Before installing LLM4S, ensure you have:
- Java Development Kit (JDK) 21
- Scala 3.7.x (llm4s 0.5.1 is built with 3.7.1)
- Scala 2.13: call-only, via the TASTy reader; see Using LLM4S from Scala 2.13
- sbt 1.10.6 or higher
- An API key from at least one LLM provider (OpenAI, Anthropic, Azure OpenAI, …), or a local Ollama server, which needs none
Verify Prerequisites
1
2
3
4
5
6
7
8
# Check Java version
java -version # Should show 21
# Check Scala version
scala -version # 3.7.1
# Check SBT version
sbt version # 1.10.6 or higher
Add LLM4S to Your Project
Artifact coordinates changed in 0.4.0. Every published module now carries an
llm4s-prefix (core→llm4s-core,workspaceClient→llm4s-workspace-client, and so on). Releases up to and including0.3.4remain available under the old names. See the migration guide for the full old → new table.
SBT
Add LLM4S to your build.sbt:
1
2
3
4
5
6
// Scala 3
ThisBuild / scalaVersion := "3.7.1"
libraryDependencies ++= Seq(
"org.llm4s" %% "llm4s-core" % "0.5.1",
"org.llm4s" %% "llm4s-openai" % "0.5.1" // or the provider module you use
)
llm4s-core holds no provider client: add the module of each provider you call (llm4s-openai,
llm4s-anthropic, llm4s-gemini, llm4s-ollama, …; see Optional Dependencies).
Without one, loading a provider section fails because no module registers that provider. The Maven and Gradle
snippets below show llm4s-core only; add the provider module the same way, with the _3 suffix
(llm4s-openai_3).
Maven
1
2
3
4
5
6
7
<!-- For Scala 3 -->
<dependency>
<groupId>org.llm4s</groupId>
<artifactId>llm4s-core_3</artifactId>
<version>0.5.1</version>
</dependency>
For Java, the Maven quick start has a complete pom.xml for llm4s-java-api_3.
Gradle (Kotlin DSL)
Gradle does not resolve Scala cross-version suffixes automatically — you must append the _3 suffix explicitly (LLM4S is Scala 3 only):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// build.gradle.kts
repositories { mavenCentral() }
dependencies {
implementation("org.llm4s:llm4s-core_3:0.5.1")
}
// Pin the Scala 3 library (scala3-library_3 only; scala-library stays at 2.13.x)
configurations.all {
resolutionStrategy.eachDependency {
if (requested.group == "org.scala-lang" && requested.name == "scala3-library_3") {
useVersion("3.7.1")
}
}
}
Gradle (Groovy DSL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// build.gradle
repositories { mavenCentral() }
dependencies {
implementation 'org.llm4s:llm4s-core_3:0.5.1'
}
configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'org.scala-lang' && details.requested.name == 'scala3-library_3') {
details.useVersion '3.7.1'
}
}
}
Using Spring Boot, Ktor, or encountering dependency conflicts? See the full Gradle integration guide for dependency exclusion recipes and the dependency conflicts reference.
Scala CLI
No build at all: declare the dependency in the file.
1
2
3
4
//> using scala 3.7.1
//> using jvm 21
//> using dep org.llm4s::llm4s-core:0.5.1
//> using dep org.llm4s::llm4s-openai:0.5.1
Add the provider module you use next to llm4s-core (llm4s-openai above). The
Scala CLI quick start has a complete file that makes a first call.
Mill
In build.mill (Mill 1.x), :: adds the Scala suffix as %% does in sbt:
1
2
3
4
5
6
7
8
9
val llm4sVersion = "0.5.1"
object app extends ScalaModule {
def scalaVersion = "3.7.1"
def mvnDeps = Seq(
mvn"org.llm4s::llm4s-core:$llm4sVersion",
mvn"org.llm4s::llm4s-openai:$llm4sVersion"
)
}
The Mill quick start has a complete project: a first call, its configuration, and a test that needs no API key.
Multi-Module Project
If you have a multi-module project:
1
2
3
4
5
6
7
8
9
lazy val myProject = (project in file("."))
.settings(
name := "my-llm-project",
scalaVersion := "3.7.1",
libraryDependencies ++= Seq(
"org.llm4s" %% "llm4s-core" % "0.5.1",
"org.llm4s" %% "llm4s-openai" % "0.5.1"
)
)
Align versions with the BOM
A project that uses several llm4s modules (a provider, llm4s-rag, llm4s-observability) can state the
version once, in the bill of materials llm4s-bom, and leave it off every module. The BOM contains no code;
it supplies the versions released together. Explicit overrides and dependency conflict resolution can still
select different versions, so check the resolved dependency graph.
Maven and Gradle have no notion of Scala cross-versioning, so the modules are written with the Scala binary
suffix (llm4s-core_3), and the BOM itself has none (llm4s-bom).
Maven (import scope):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.llm4s</groupId>
<artifactId>llm4s-bom</artifactId>
<version>0.5.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.llm4s</groupId>
<artifactId>llm4s-core_3</artifactId>
</dependency>
<dependency>
<groupId>org.llm4s</groupId>
<artifactId>llm4s-openai_3</artifactId>
</dependency>
</dependencies>
Gradle (Kotlin DSL; in Groovy write implementation platform("org.llm4s:llm4s-bom:...")):
1
2
3
4
5
dependencies {
implementation(platform("org.llm4s:llm4s-bom:0.5.1"))
implementation("org.llm4s:llm4s-core_3")
implementation("org.llm4s:llm4s-openai_3")
}
Gradle platform(...) treats the BOM versions as recommendations that other dependencies can upgrade.
For strict version pinning in an application, use enforcedPlatform(...) instead. Publishing libraries
should generally keep platform(...) to avoid forcing their versions on consumers. See the
Gradle platform documentation.
Mill imports it with bomMvnDeps; the Mill quick start shows how.
sbt has no BOM import, so the BOM is optional there: keep one val for the version, as the sections
below do (%% adds the _3 suffix itself):
1
2
3
4
5
6
val llm4sVersion = "0.5.1"
libraryDependencies ++= Seq(
"org.llm4s" %% "llm4s-core" % llm4sVersion,
"org.llm4s" %% "llm4s-openai" % llm4sVersion
)
Unreleased changes (main)
No snapshots are published: releases are cut from tags only, and nothing publishes between them. To try
what is on main, build it and publish it to your local repository:
1
2
3
git clone https://github.com/llm4s/llm4s && cd llm4s
sbt publishLocal # for sbt; use `sbt publishM2` for Maven or Gradle (mavenLocal())
sbt version # prints the version it published, for example 0.5.1+12-abc12345-SNAPSHOT
Then depend on that version, for example
libraryDependencies += "org.llm4s" %% "llm4s-core" % "0.5.1+12-abc12345-SNAPSHOT" (use the one sbt version
printed). This guide describes the latest release; main may have modules and configuration that it does not
have yet.
Quick Start with the Starter Kit
The fastest way to a working sbt project is the llm4s.g8 template:
1
2
3
4
5
sbt new llm4s/llm4s.g8 --llm4s_version=0.5.1
# Answer the remaining prompts (name, package, scala_version, ...) or accept the defaults
cd <your project name>
sbt run
Pass --llm4s_version (or type the version at its prompt) so the project starts on
0.5.1, then check the generated build.sbt: it needs llm4s-core and at least one
provider module at that version, because since 0.5.0 llm4s-core ships no provider. The template is being
moved to 0.5.x (llm4s.g8#6), which adds llm4s-ollama and
llm4s-openai itself; a template from before that defaults to llm4s 0.4.0 and lists llm4s-core only, so add
the provider you use, for example "org.llm4s" %% "llm4s-openai" % "0.5.1".
The generated project has an sbt build, a logging backend, an application.conf with
named provider sections, a small sample program and its tests.
Optional Dependencies
llm4s-coreis the spine: types, configuration, theLLMClientinterface and the tool API. Providers, the agent runtime and everything below are separate modules, released at the same version. Check Maven Central for available artifacts.
For RAG, vector stores, chunking, reranking and document extraction
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-rag" % llm4sVersion
Brings llm4s-knowledgegraph with it, along with Tika, POI, PDFBox, jsoup and the AWS S3
client — the document-extraction and loader dependencies that llm4s-core no longer carries.
Package names are unchanged, so existing org.llm4s.rag.* imports keep working; see the
migration note.
To keep the vectors in an Elasticsearch or OpenSearch cluster you already run, add the
Elasticsearch store module (Experimental), which holds both ElasticsearchVectorStore and
OpenSearchVectorStore. It talks to the REST API through llm4s-core’s HTTP client, so it brings no
client library; see Elasticsearch and
OpenSearch.
1
2
// same version as llm4s-rag
libraryDependencies += "org.llm4s" %% "llm4s-vectorstore-elasticsearch" % llm4sVersion
To keep them in Redis (Redis Stack, or Redis 8 and later) or Valkey (with valkey-search), add the Redis store
(Experimental). It brings the Jedis client; see Redis and Valkey.
1
2
// same version as llm4s-rag
libraryDependencies += "org.llm4s" %% "llm4s-vectorstore-redis" % llm4sVersion
For agent memory
1
2
3
4
5
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-memory" % llm4sVersion
// only if you store memories in Postgres/pgvector
libraryDependencies += "org.llm4s" %% "llm4s-memory-postgres" % llm4sVersion
llm4s-memory carries MemoryStore, the memory managers, and the in-memory and SQLite-backed
stores; it adds sqlite-jdbc to your classpath and nothing else. PostgresMemoryStore lives in
llm4s-memory-postgres, which brings HikariCP and the Postgres JDBC driver — the two
dependencies llm4s-core no longer carries. Package names are unchanged, so existing
org.llm4s.agent.memory.* imports keep working; see the
migration note.
For MCP (Model Context Protocol)
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-mcp" % llm4sVersion
Carries the MCP client, server, transports (stdio, HTTP, SSE) and MCPToolRegistry. It adds no
third-party dependency of its own. Package names are unchanged, so existing org.llm4s.mcp.*
imports keep working; see the
migration note.
For speech (STT / TTS)
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-speech" % llm4sVersion
Carries speech-to-text (Vosk for offline recognition, Whisper), text-to-speech (Tacotron 2),
and the audio IO, conversion and validation helpers. This is the module that brings Vosk,
a 25 MB dependency that used to sit on every llm4s-core user’s classpath — which is much of
the point of the split. Package names are unchanged; see the
migration note.
For Ollama (local models)
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-ollama" % llm4sVersion
Carries the Ollama chat client, the Ollama embedding provider and Ollama model listing. It adds
no third-party dependency of its own. Adding it is all the registration there is: the module
declares itself to the provider registry, so provider = "ollama" and
EMBEDDING_MODEL=ollama/<model> resolve with no code change. Without it, both fail with an error
saying ollama is not registered and naming the providers that are. Package names are
unchanged; see the
migration note.
For Gemini and Vertex AI
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-gemini" % llm4sVersion
Carries both of Google’s chat providers: the Gemini API client and model listing, and the
Vertex AI client with its OAuth authentication. It adds no third-party dependency of its own -
Vertex AI authentication is implemented without a Google SDK. Adding it is all the registration
there is: the module declares itself to the provider registry, so provider = "gemini" (or
"google") and provider = "vertexai" (or "vertex") resolve with no code change. Without it,
they fail with an error saying the provider is not registered and naming the providers that
are. Package names are unchanged; see the
migration note.
For Anthropic
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-anthropic" % llm4sVersion
Carries the Anthropic Claude chat client and Anthropic model listing. This is the module that
brings the Anthropic Java SDK (com.anthropic:anthropic-java), which used to sit on every
llm4s-core user’s classpath. Adding it is all the registration there is: the module declares
itself to the provider registry, so provider = "anthropic" resolves with no code change.
Without it, it fails with an error saying the provider is not registered and naming the
providers that are. Package names are unchanged; see the
migration note.
For OpenAI, Azure OpenAI and Requesty
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-openai" % llm4sVersion
Carries the three providers that share OpenAIClient - OpenAI, Azure OpenAI and Requesty - the
OpenAI embedding provider, and OpenAI and Requesty model listing. This is the module that
brings OpenAI’s Java SDK (com.openai:openai-java, with OkHttp, Jackson and the Kotlin
standard library), which serves Azure OpenAI as well. It replaced Microsoft’s deprecated
com.azure:azure-ai-openai, which used to sit on every llm4s-core user’s classpath;
llm4s-core depends on no vendor SDK. Adding it is
all the registration there is: the module declares itself to the provider registry, so
provider = "openai", "azure" and "requesty", and EMBEDDING_MODEL=openai/<model>, resolve
with no code change. Without it, they fail with an error saying the provider is not registered
and naming the providers that are.
OpenRouter, DeepSeek and Z.ai speak the OpenAI wire format with no SDK, and are in
llm4s-openai-compatible (below), which this module depends on for OpenAIConfig.
llm4s-rag’s RAGConfig.default embeds with openai, so a RAG pipeline built from the default
needs this module too, or .withEmbeddings(...) naming a provider you do ship. Package names are
unchanged; see the migration note.
For Azure OpenAI with Microsoft Entra ID
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-azure-identity" % llm4sVersion
Keyless authentication for Azure OpenAI (#2274): an
azure section with authentication = "entra-id" takes Microsoft Entra ID tokens from Azure’s
DefaultAzureCredential (a managed identity, workload identity or az login) instead of an API key,
and AzureEntraId turns any TokenCredential into the tokens a config or embedding provider takes.
It brings Azure’s azure-identity (MIT, with azure-core, MSAL4J, Netty and Reactor), which is why
it is not part of llm4s-openai. See
Enterprise authentication.
For DeepSeek, Z.ai, OpenRouter, Mistral, Cohere and any OpenAI-compatible endpoint
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-openai-compatible" % llm4sVersion
One SDK-free client for the OpenAI /chat/completions API, and the providers that run on it:
DeepSeek (provider = "deepseek"), Z.ai ("zai"), OpenRouter ("openrouter"), Mistral
("mistral"), Cohere ("cohere", through Cohere’s OpenAI-compatibility API), xAI ("xai"), and a generic
"openai-compatible" provider for any other endpoint that speaks the API - Groq, Together,
Fireworks, NVIDIA NIM, OrcaRouter, a vLLM, LM Studio or llama.cpp server, an internal
gateway - configured with a baseUrl and a model, and no code. See
OpenAI-compatible endpoints for a
configuration recipe per provider. The module
brings no dependency beyond llm4s-core, and adding it registers the providers. Package names
are unchanged; see the
migration note, and for Mistral and
Cohere this one.
For AWS Bedrock
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-bedrock" % llm4sVersion
The AWS Bedrock chat provider (provider = "bedrock"), over the Converse and ConverseStream APIs,
so one client reaches Claude, Llama, Mistral, Nova and Titan models. It brings the AWS SDK for
Java v2 bedrockruntime artifact (Apache-2.0, the same SDK release train llm4s-rag uses for S3);
llm4s-core gains no dependency. A section needs a region and a model:
1
2
3
4
5
llm4s.providers.claude-bedrock {
provider = "bedrock"
model = "anthropic.claude-3-5-sonnet-20241022-v2:0"
region = ${?AWS_REGION}
}
Credentials come from the AWS default credential chain (environment variables, ~/.aws, an
instance or task role), or from a profile, or from explicit accessKeyId / secretAccessKey
(plus sessionToken for temporary credentials).
For Voyage AI embeddings
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-voyage" % llm4sVersion
The Voyage AI embedding provider (EMBEDDING_MODEL=voyage/<model>), with its
llm4s.embeddings.voyage config block. It is the first community provider module under
modules/providers/, and brings no dependency beyond llm4s-core.
For Jina AI embeddings
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-jina" % llm4sVersion
The Jina AI embedding provider (EMBEDDING_MODEL=jina/<model>), with its
llm4s.embeddings.jina config block and a typed JinaTask (retrieval.query,
retrieval.passage, …). A community provider module under modules/providers/; it brings no
dependency beyond llm4s-core.
For Cohere embeddings
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-cohere" % llm4sVersion
The Cohere embedding provider (EMBEDDING_MODEL=cohere/<model>), on Cohere’s native /v2/embed,
with its llm4s.embeddings.cohere config block and a typed CohereInputType (search_document,
search_query, …). Cohere chat is a different module: llm4s-openai-compatible. A community
provider module under modules/providers/; it brings no dependency beyond llm4s-core.
For the Jev decision model
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-jev" % llm4sVersion
A typed client for TypeSafe’s Jev decision model: send a state and typed questions (Noul, Choice, Score), get typed answers.
It is not a chat provider and returns no Completion. Configured under llm4s.jev, with the key in TYPESAFE_API_KEY. A
community module under modules/providers/; it brings no dependency beyond llm4s-core. See
Jev decision model.
For IBM watsonx.ai
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-watsonx" % llm4sVersion
The IBM watsonx.ai chat provider (provider = "watsonx"), brings no dependency beyond
llm4s-core. See IBM watsonx.ai.
For Perplexity
1
libraryDependencies += "org.llm4s" %% "llm4s-perplexity" % llm4sVersion
The Perplexity Agent API chat provider (provider = "perplexity"): answers grounded in web search, with
citations. It brings no dependency beyond llm4s-core. See Perplexity.
For Java, Spring Boot, cats-effect and ZIO
1
2
3
4
5
6
7
8
9
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-java-api" % llm4sVersion // Java facade
libraryDependencies += "org.llm4s" %% "llm4s-java-rag" % llm4sVersion // Java facade for RAG (Experimental)
libraryDependencies += "org.llm4s" %% "llm4s-reactor" % llm4sVersion // Reactor Flux over the Java facade (Experimental)
libraryDependencies += "org.llm4s" %% "llm4s-spring-boot-starter" % llm4sVersion // Spring Boot auto-configuration
libraryDependencies += "org.llm4s" %% "llm4s-effect" % llm4sVersion // cats-effect 3 / fs2
libraryDependencies += "org.llm4s" %% "llm4s-zio" % llm4sVersion // ZIO 2 / ZIO Streams
libraryDependencies += "org.llm4s" %% "llm4s-pekko" % llm4sVersion // Apache Pekko Streams
libraryDependencies += "org.llm4s" %% "llm4s-ox" % llm4sVersion // Ox direct style (Experimental)
llm4s-java-api is the Java-friendly facade (a runnable Gradle sample is in
modules/samples/gradle-java, and a Quarkus
application with a CDI producer in modules/samples/quarkus-java,
see Quarkus) and the base of the Spring starter, which adds the
llm4s.* properties, an LLM4STemplate and an Actuator health indicator. llm4s-java-rag puts llm4s-rag’s
pipeline behind one Java class, JRag (Java RAG); it is a separate artifact so that a Java
facade user who only calls a model does not receive document parsing and the database drivers. llm4s-reactor turns
a streamed reply into a Reactor Flux<String> (Java Reactor), apart from the facade so that it
does not bring reactor-core to an application without Reactor. llm4s-effect,
llm4s-zio and llm4s-pekko wrap LLMClient and Agent for their effect and streaming systems. llm4s-ox adds an
Ox Flow of a streamed reply, bounded fan-out, races and retries to direct-style code (Direct style).
For Kotlin (coroutines and Flow)
llm4s-kotlin-api is the Kotlin coroutine API over the Java facade: LLMClientKt and AgentKt with suspend
functions, and Flows of an agent turn’s events. It is a Kotlin artifact, so it has no _3 suffix, and it is
released at the same version as every other llm4s module, so the BOM manages it too:
llm4s-kotlin-api0.5.1 is not on Maven Central yet. A fault in the release workflow left it out of the 0.5.0 and 0.5.1 releases; the fix is pending (#1976). Until a release includes it, the coordinates below do not resolve (the BOM lists it, but every other entry of the BOM resolves). Build it locally instead, as described below the snippet; it then depends on the releasedllm4s-java-api_3.
1
2
3
4
5
6
// build.gradle.kts
dependencies {
implementation(platform("org.llm4s:llm4s-bom:0.5.1"))
implementation("org.llm4s:llm4s-kotlin-api") // brings llm4s-java-api_3 and kotlinx-coroutines-core
implementation("org.llm4s:llm4s-ollama_3") // the provider module you call
}
Without the BOM, write the version on it: implementation("org.llm4s:llm4s-kotlin-api:0.5.1").
It runs on JDK 21, like the Java facade it wraps. The module is built by Gradle (modules/kotlin-api), not sbt, so
sbt publishLocal does not build it. To build it, check out the release tag (git checkout v0.5.1)
and run gradle publishToMavenLocal -Pllm4sVersion=0.5.1 in modules/kotlin-api,
then add mavenLocal() to your repositories; it depends on llm4s-java-api_3 at that version, which is on Maven
Central. Without -Pllm4sVersion it builds at 0.1.0-SNAPSHOT; to try it from main, run sbt publishM2 first and
pass the version sbt published.
Its entry point is org.llm4s.kotlin.Llm4s: createDefaultClient() builds an LLMClientKt from application.conf,
and wrapClient(JLlmClient) wraps one configured in code with the Java facade’s builders (see the Java guide’s
Configure in code); createAgent and wrapAgent give an AgentKt. A Kotlin DSL
declares tools (tools { tool(...) { ... } }), completion options (completionOptions { ... }) and clients
(Llm4s.openAi { ... }); see the Java guide’s Kotlin DSL.
For image generation and vision
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-image" % llm4sVersion
Carries image generation (org.llm4s.imagegeneration — Stable Diffusion, Stability AI,
Hugging Face, OpenAI) and image processing (org.llm4s.imageprocessing — the OpenAI and
Anthropic vision clients plus a local javax.imageio processor). It adds no third-party
dependency of its own, and brings llm4s-media with it.
Package names are unchanged, so existing org.llm4s.imagegeneration.* and
org.llm4s.imageprocessing.* imports keep working — but note that image formats did change,
in llm4s-media. See the migration note.
For media types (MediaType, MediaCategory)
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-media" % llm4sVersion
The shared vocabulary the multimodal modules speak: MediaType (MIME string, canonical
extension, category, and lookups by extension, path or MIME type) and MediaCategory. It has
no dependencies at all and does no I/O — deciding what a file is from its bytes needs Tika and
lives in llm4s-rag.
You will usually get it transitively, from llm4s-core or llm4s-rag; declare it directly
only if you name these types in your own signatures. It replaces three overlapping image-format
types that used to ship in llm4s-core, which is a source break — see the
migration note.
For agents (Agent, guardrails, handoffs, graphs)
1
libraryDependencies += "org.llm4s" %% "llm4s-agent" % llm4sVersion // same version as llm4s-core
The agent runtime - org.llm4s.agent (Agent, AgentResult, guardrails, handoffs,
the graph runtime) and the console assistant, org.llm4s.assistant - lives here.
Agent memory is the separate llm4s-memory; the ready-made tools are llm4s-agent-tools.
llm4s-core keeps what the agent is built on: LLMClient, the tool API and the tracing
contract. Package names are unchanged. See the
migration note.
For durable agent threads (SQLite checkpointer)
1
libraryDependencies += "org.llm4s" %% "llm4s-agent-checkpoint-sqlite" % llm4sVersion // same version as llm4s-agent
SqliteCheckpointer (org.llm4s.agent.graph.sqlite) keeps graph and agent threads - checkpoints, pending
writes, event logs and run claims - in one SQLite file, so a run survives a restart and recover continues it
in a new process. It brings the SQLite JDBC driver (org.xerial:sqlite-jdbc), which llm4s-agent itself does
not depend on. See Durable Checkpointers.
For agent threads shared across machines (PostgreSQL checkpointer)
1
libraryDependencies += "org.llm4s" %% "llm4s-agent-checkpoint-postgres" % llm4sVersion // same version as llm4s-agent
PostgresCheckpointer (org.llm4s.agent.graph.postgres) keeps the same threads in a PostgreSQL schema that
runtimes on several machines share, with claims and retention judged by the database’s clock. It brings the
PostgreSQL JDBC driver and HikariCP. See Durable Checkpointers.
For semantic memory in agents
1
libraryDependencies += "org.llm4s" %% "llm4s-agent-memory" % llm4sVersion // same version as llm4s-agent
SemanticMemoryMiddleware (org.llm4s.agent.harness.memory) recalls a tenant’s memories before each turn and
records the turn after it, on an llm4s-memory store. It depends on llm4s-agent and llm4s-memory. See
Semantic Memory Middleware.
For built-in tools (web search, HTTP, filesystem, shell)
1
libraryDependencies += "org.llm4s" %% "llm4s-agent-tools" % llm4sVersion // same version as llm4s-core
BuiltinTools and everything under org.llm4s.toolapi.builtin - DateTime, Calculator, UUID,
JSON, file access, HTTP, shell, and the Brave, DuckDuckGo and Exa search tools - live here, with
ToolsConfigLoader and the llm4s.tools settings the search tools read. llm4s-core keeps the
tool API they implement (ToolFunction, ToolRegistry). Package names are unchanged. See the
migration note.
For tapir endpoints as tools
1
libraryDependencies += "org.llm4s" %% "llm4s-tapir" % llm4sVersion // same version as llm4s-core
EndpointTools turns tapir endpoints into tools, each schema derived from its endpoint’s inputs, called through
sttp client 3. It depends on tapir-core 1.11, not on the agent runtime (Experimental). See
Tapir Endpoints as Tools.
For tracing and metrics (Langfuse, OpenTelemetry, in-process trace store, Prometheus)
1
2
3
4
5
6
7
// same version as llm4s-core
libraryDependencies ++= Seq(
"org.llm4s" %% "llm4s-observability" % llm4sVersion, // TRACING_MODE=langfuse
"org.llm4s" %% "llm4s-observability-otel" % llm4sVersion, // TRACING_MODE=opentelemetry
"org.llm4s" %% "llm4s-observability-prometheus" % llm4sVersion, // llm4s.metrics.enabled = true
"org.llm4s" %% "llm4s-observability-micrometer" % llm4sVersion // MicrometerMetrics over your MeterRegistry
)
llm4s-core builds only the console (default) and noop tracers. llm4s-observability
registers Langfuse and carries the in-process trace collector and store
(TraceCollectorTracing, org.llm4s.trace.store, org.llm4s.trace.model) and CostTracker,
with no third-party dependency; llm4s-observability-otel registers OpenTelemetry and brings the
OpenTelemetry SDK. Adding the dependency is all the registration there is, and the
LANGFUSE_* / OTEL_* variables keep their names. llm4s-observability-prometheus carries
PrometheusMetrics, the /metrics endpoint and MetricsConfigLoader, with the Prometheus client
and HTTP server; core keeps only the MetricsCollector interface every client accepts. Package
names are unchanged. See the migration notes for
Langfuse and the collector
and for Prometheus.
llm4s-observability-micrometer is the Micrometer counterpart: a MetricsCollector that records into a MeterRegistry
you already have, with the same series names as the Prometheus module. It depends on llm4s-core and micrometer-core.
See Micrometer metrics.
For Neo4j (knowledge graph store)
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-knowledgegraph-neo4j" % llm4sVersion
Neo4jGraphStore, a graph store for llm4s-knowledgegraph backed by Neo4j. It depends on llm4s-core
and llm4s-knowledgegraph and brings the Neo4j driver.
Structured output from a case class
1
2
3
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-schema-derivation" % llm4sVersion
libraryDependencies += "org.llm4s" %% "llm4s-schema-iron" % llm4sVersion // bounds from Iron refinement types
SchemaOf and client.completeStructuredOf[A]: the JSON schema of completeStructured, derived from a case class
with derives. It is experimental, and depends on llm4s-core only. See Derived Schemas.
llm4s-schema-iron (experimental) adds the bounds of Iron refinement types, such
as Int :| Interval.Closed[1, 5], to the derived schema and refuses a reply that breaks them; it brings Iron. See
Iron refinements.
Typed prompts
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-prompt" % llm4sVersion
The prompt"..." and system"..." interpolators: each value is rendered by a Promptable checked at compile time,
and Untrusted input is fenced. It is experimental, and depends on llm4s-core only. See
Typed Prompts.
Capability-typed clients
1
2
// same version as llm4s-core
libraryDependencies += "org.llm4s" %% "llm4s-capabilities" % llm4sVersion
client.requireCapabilities[Vision & Tools](provider, model) and Capable: check a model’s capabilities once, and passing a
client not checked for one fails to compile. It is experimental, and depends on llm4s-core only. See
Capability-Typed Clients.
Testing your own code (the testkit)
1
2
// same version as llm4s-core; test scope only
libraryDependencies += "org.llm4s" %% "llm4s-testkit" % llm4sVersion % Test
A scriptable fake LLMClient for the tests of code that calls llm4s: canned replies, scripted tool calls,
injected errors and recorded requests, with no network and no API key. It depends on llm4s-core only. See
Testing with the Testkit.
Checking your setup (the doctor)
1
2
// same version as llm4s-core; test scope only
libraryDependencies += "org.llm4s" %% "llm4s-doctor" % llm4sVersion % Test
DoctorCli checks your JDK, application.conf, default provider, provider module, API key and, with --local or
--live, the model server, and says what to fix: sbt "Test/runMain org.llm4s.doctor.DoctorCli". It depends on
llm4s-core only. See Doctor for Maven, Gradle and Coursier.
Trying llm4s in the REPL
1
2
// same version as llm4s-core; for the REPL, worksheets and scripts, not for production code
libraryDependencies += "org.llm4s" %% "llm4s-repl" % llm4sVersion
ask, extract, chat(), history and lastUsage behind one import org.llm4s.repl.*, with an offline session
that needs no API key. It is experimental, and depends on llm4s-core, llm4s-schema-derivation and
llm4s-testkit. See Try llm4s in the REPL.
For provider authors (testing a provider module)
1
2
// same version as llm4s-core; test scope only
libraryDependencies += "org.llm4s" %% "llm4s-provider-testkit" % llm4sVersion % Test
The checks a provider module’s own Llm4s<Name>ModuleSpec makes: discovery, sole ownership of its ids,
explicit registration and the config-to-client round trip. It requires JDK 21: its interruption checks
and its local test server run on virtual threads. See
Writing a provider.
For checkpoint store authors (testing a Checkpointer)
1
2
// same version as llm4s-agent; test scope only
libraryDependencies += "org.llm4s" %% "llm4s-agent-testkit" % llm4sVersion % Test
CheckpointerContract, the suite every Checkpointer must pass: commits, event numbering, compaction, run-claim
leases and fencing, and two runtimes contending over one store. It requires JDK 21, as llm4s-agent does. See
Durable Checkpointers.
For Workspace (Containerized Execution)
1
libraryDependencies += "org.llm4s" %% "llm4s-workspace-client" % "0.5.1"
And install Docker:
1
2
3
4
5
6
7
8
# macOS
brew install docker
# Ubuntu/Debian
sudo apt-get install docker.io
# Verify
docker --version
llm4s-workspace-client brings llm4s-workspace-shared, the wire protocol the client and the runner image
speak, so you depend on it directly only if you implement the runner side.
Provider Setup
llm4s reads provider configuration from named sections in your application.conf, not from
environment variables of its own. Keep secrets in environment variables and bind them into the
section with ${?VAR}:
1
2
3
4
5
6
7
8
9
10
11
# src/main/resources/application.conf
llm4s {
providers {
provider = "openai-main" # the default: the name of a section below
openai-main {
provider = "openai"
model = "gpt-4o-mini"
}
}
}
1
export OPENAI_API_KEY=sk-...
The section needs no apiKey line: llm4s-openai binds OPENAI_API_KEY to OpenAI’s shared
key, which a section without a key of its own uses (see
API keys for a second account). Nothing reads LLM_MODEL. The Configuration guide
shows sections for every provider, how to switch between them,
and which environment variables llm4s reads
without any binding.
If you keep your variables in a .env file (add it to .gitignore!), load it before running:
1
2
source .env
sbt run
Get API Keys
OpenAI
- Go to platform.openai.com
- Sign up or log in
- Navigate to API Keys
- Click Create new secret key
- Copy the key (starts with
sk-)
Anthropic
- Go to console.anthropic.com
- Sign up or log in
- Navigate to API Keys
- Click Create Key
- Copy the key (starts with
sk-ant-)
Azure OpenAI
- Create an Azure account
- Navigate to Azure OpenAI Service
- Create a resource
- Deploy a model (e.g., gpt-4o)
- Copy the API Key and Endpoint
Ollama (Local)
- Install Ollama: ollama.com
- Pull a model:
ollama pull llama2 - Start server:
ollama serve - No API key needed!
Verify Installation
Create a simple test file VerifyInstall.scala:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import org.llm4s.config.Llm4sConfig
import org.llm4s.llmconnect.LLMConnect
import org.llm4s.llmconnect.model.{ Conversation, UserMessage }
import org.llm4s.model.ModelRegistryService
object VerifyInstall extends App {
println("Testing LLM4S installation...")
val result = for {
providerConfig <- Llm4sConfig.defaultProvider()
registry <- Llm4sConfig.modelRegistryService()
given ModelRegistryService = registry
client <- LLMConnect.getClient(providerConfig)
response <- client.complete(Conversation(Seq(UserMessage("Say 'LLM4S is working!'"))))
} yield response
result match {
case Right(completion) =>
println("✅ Success!")
println(s"Response: ${completion.content}")
case Left(error) =>
println("❌ Error:")
println(error)
}
}
Run it:
1
sbt run
Expected output:
1
2
3
Testing LLM4S installation...
✅ Success!
Response: LLM4S is working!
Troubleshooting
“missing required fields: apiKey”
Problem: the provider section has no API key.
Solution:
- Check the vendor’s variable is set in the shell that starts the JVM:
echo $OPENAI_API_KEY(if you use a.envfile,source .envfirst), and that the provider’s module (llm4s-openai) is a dependency - it is what binds the variable - If the section sets its own
apiKey = ${?SOME_VAR}, check that variable too - Check the error names the section you meant to load - only that section is validated, so
if it names another,
llm4s.providers.providerpoints somewhere you did not expect
“Configured provider ‘…’ was not found” or an unknown provider id
Problem: llm4s.providers.provider names a section that does not exist, or a section’s
provider = "..." id is not registered.
Solution: make llm4s.providers.provider match a section name exactly, and add the module
for the provider id (for example llm4s-openai for provider = "openai"; see
Optional Dependencies). Model names go in the section’s model
without a provider prefix: model = "gpt-4o" for OpenAI, not openai/gpt-4o.
Compilation Errors
Problem: Scala version mismatch.
Solution:
1
2
3
# Clean and recompile
sbt clean
sbt compile
Dependency Resolution Issues
Problem: Can’t resolve LLM4S dependency.
Solution:
- Releases are on Maven Central, so no additional resolver is needed
- With Maven or Gradle, write the Scala suffix yourself:
llm4s-core_3, notllm4s-core(the BOM,llm4s-bom, has none) - No snapshots are published: to use
main, build it locally (see Unreleased changes) llm4s-kotlin-apiis not on Maven Central for 0.5.1 yet (see For Kotlin)
Next Steps
Now that LLM4S is installed:
- Write your first program → - Create a simple LLM application
- Configure providers → - Set up multiple LLM providers
- Explore examples → - Browse 69 working examples
Additional Resources
- GitHub Repository: llm4s/llm4s
- Starter Kit: llm4s.g8
- Discord Community: Join us
- API Reference: Core API
Installation complete! Ready to write your first program →