Scala CLI Quick Start
A first LLM call from a single file, with no sbt project.
Table of contents
- Before you start
- The files
- Run it
- What the script does
- If something goes wrong
- The dependencies
- Next steps
Scala CLI runs a Scala file with its dependencies declared in the file itself, so you can try LLM4S without creating a build. This page is the shortest path from nothing to an answer; when you want a real project, use the starter kit or add LLM4S to your build.
Before you start
- Scala CLI, installed as described on its install page.
- A JDK is optional: the script’s
//> using jvm 21line makes Scala CLI use JDK 21, and download it the first time if you do not have it. LLM4S needs JDK 21 (see Installation). - An API key for OpenAI, or a local Ollama (no key).
The files
Make a directory with these files. The same files are in
modules/samples/scala-cli.
1
2
3
4
5
hello-llm4s/
hello.scala
logback.xml
resources/application.conf
resources/ollama.conf (only for the no-key Ollama run)
hello.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
//> 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
//> using dep org.llm4s::llm4s-ollama:0.5.1
//> using dep ch.qos.logback:logback-classic:1.5.34
//> using resourceDir resources
//> using javaOpt -Dlogback.configurationFile=logback.xml
import org.llm4s.config.Llm4sConfig
import org.llm4s.llmconnect.LLMConnect
import org.llm4s.llmconnect.model.{ Conversation, UserMessage }
import org.llm4s.model.ModelRegistryService
@main def hello(): Unit = {
val answer = for {
providerConfig <- Llm4sConfig.defaultProvider()
registry <- Llm4sConfig.modelRegistryService()
given ModelRegistryService = registry
client <- LLMConnect.getClient(providerConfig)
completion <- client.complete(Conversation(Seq(UserMessage("In one sentence, what is Scala?"))))
} yield completion.content
answer.fold(error => println(s"Error: ${error.formatted}"), text => println(text))
}
resources/application.conf names the provider the script uses: the section that provider names. The
Configuration guide covers the other providers:
1
2
3
4
5
6
7
8
9
10
11
llm4s {
providers {
provider = "openai-main"
openai-main {
provider = "openai"
model = "gpt-4o-mini"
apiKey = ${?OPENAI_API_KEY}
}
}
}
resources/ollama.conf is the no-key alternative, a local Ollama (it needs ollama serve
and ollama pull llama3). It is a separate file so that each run reads only its own provider:
1
2
3
4
5
6
7
8
9
10
11
12
llm4s {
providers {
provider = "ollama-local"
ollama-local {
provider = "ollama"
model = "llama3"
baseUrl = "http://localhost:11434"
baseUrl = ${?OLLAMA_BASE_URL}
}
}
}
logback.xml keeps the library’s own logging out of your first answer (without it, logback prints
every debug message):
1
2
3
4
5
6
7
<configuration>
<appender name="STDERR" class="ch.qos.logback.core.ConsoleAppender">
<target>System.err</target>
<encoder><pattern>%d{HH:mm:ss} %-5level %logger{20} - %msg%n</pattern></encoder>
</appender>
<root level="WARN"><appender-ref ref="STDERR"/></root>
</configuration>
Run it
Run from inside the directory: the logback.xml path in the script is relative to where you run it.
1
2
3
cd hello-llm4s
export OPENAI_API_KEY=sk-...
scala-cli run .
With a local Ollama instead, no key needed (-Dconfig.resource tells the configuration library to read
ollama.conf in place of application.conf):
1
scala-cli run . --java-opt -Dconfig.resource=ollama.conf
The first run downloads the dependencies, which takes a while. You should see one sentence, for example:
1
Scala is a language that blends object-oriented and functional programming on the JVM.
The wording depends on the model.
What the script does
Llm4sConfig.defaultProvider()reads the section thatllm4s.providers.providernames. This is the one place configuration is read.LLMConnect.getClientbuilds the client for that provider.client.completesends the conversation. Every step returns aResult, so theforstops at the first error and the last line prints it instead of throwing.
If something goes wrong
Error: ConfigurationError: Provider 'openai-main' (provider = openai) is missing required fields: followed by
- apiKey: set OPENAI_API_KEY, or set apiKey under llm4s.providers.openai-main in application.conf: the key is not set
in the shell that runs scala-cli. Check echo $OPENAI_API_KEY, or use the Ollama command above.
Error: NetworkError: POST http://localhost:11434/api/chat: connection failed (the Ollama run): no Ollama server
is listening. Start it with ollama serve, or point OLLAMA_BASE_URL at the one you have.
A wall of DEBUG lines before the answer: logback.xml was not found. Run from the directory that
contains it, and check the name and the javaOpt line.
The version: the //> using dep lines pin the release. Change the number on every org.llm4s line together
to move to another one: the modules are released at the same version.
The dependencies
llm4s-core holds no provider client, so the script adds the provider modules it uses, llm4s-openai and
llm4s-ollama; keep only the one you use. llm4s-core brings no logging backend either, so the script adds
logback (see the migration guide). The logback
line is a Java dependency (a single colon, unlike the :: Scala form); without a backend SLF4J prints a single
“no SLF4J providers were found” warning and logback.xml has no effect.
The apiKey = ${?OPENAI_API_KEY} line in the section is optional: llm4s-openai binds OPENAI_API_KEY itself,
and a section’s own apiKey takes precedence over it.
Up to 0.4.x, llm4s-core included the provider clients and a logging backend, so a script for 0.4.x needs only the
llm4s-core line, and the apiKey line in the section.
Next steps
- First Example: the same call in a project, with conversation context, tools and streaming
- Configuration: all providers and how keys are found
- Ollama Quick Start: local models