Adding LLM and AI Capabilities (Scala)
Overview
There are no Golem-specific AI libraries for Scala. To integrate with LLM providers, you have two options:
- Use a third-party Scala library — but only if it is Scala.js-compatible and uses
fetchunder the hood (JVM-only HTTP clients will not work) - Call the provider's REST API directly using
fetchor ZIO HTTP's fetch backend (recommended)
See the golem-make-http-request-scala skill for full details on making HTTP requests from Scala agents.
Option 1: Using a Third-Party Library
If you find a Scala.js-compatible LLM client library, add it with %%% in build.sbt:
libraryDependencies += "com.example" %%% "llm-client" % "1.0.0"⚠️ Important: The library must be published for Scala.js (
_sjs1_artifact) and must usefetchor another browser-compatible HTTP mechanism. Libraries that depend on JVM networking (Apache HttpClient, OkHttp, sttp with JVM backends, etc.) will not work.
Option 2: Calling the API Directly with fetch (Recommended)
Since Golem Scala agents compile to JavaScript via Scala.js, the global fetch function is available. This is the most reliable approach:
import scala.scalajs.js
import scala.scalajs.js.Thenable.Implicits._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
def chatCompletion(prompt: String, apiKey: String): Future[String] = {
val payload = js.JSON.stringify(js.Dynamic.literal(
model = "gpt-4o",
messages = js.Array(
js.Dynamic.literal(role = "user", content = prompt)
)
))
val options = js.Dynamic.literal(
method = "POST",
headers = js.Dynamic.literal(
"Content-Type" -> "application/json",
"Authorization" -> s"Bearer $apiKey"
),
body = payload
)
for {
response <- js.Dynamic.global.fetch(
"https://api.openai.com/v1/chat/completions",
options
).asInstanceOf[js.Promise[js.Dynamic]].toFuture
text <- response.text().asInstanceOf[js.Promise[String]].toFuture
} yield {
val json = js.JSON.parse(text)
json.choices.asInstanceOf[js.Array[js.Dynamic]](0)
.message.content.asInstanceOf[String]
}
}Option 3: Using ZIO HTTP
For ZIO-based agents, use zio-http which provides a typed Scala HTTP client:
import zio._
import zio.http._
import scala.concurrent.Future
def chatCompletion(prompt: String, apiKey: String): Future[String] = {
val body = Body.fromString(
s"""{"model": "gpt-4o", "messages": [{"role": "user", "content": "$prompt"}]}"""
)
val effect =
(for {
response <- ZIO.serviceWithZIO[Client] { client =>
client
.url(URL.decode("https://api.openai.com").toOption.get)
.addHeader(Header.ContentType(MediaType.application.json))
.addHeader(Header.Authorization.Bearer(apiKey))
.batched
.post("/v1/chat/completions")(body)
}
text <- response.body.asString
} yield {
// Parse the JSON response to extract the message content
text
}).provide(ZClient.default)
Unsafe.unsafe { implicit u =>
Runtime.default.unsafe.runToFuture(effect)
}
}Setting API Keys
Store provider API keys as secrets using Golem's typed config system. See the golem-add-secret-scala skill for full details. In brief, declare the key in your config case class:
import golem.config.{Config, Secret}
import zio.blocks.schema.Schema
final case class MyAgentConfig(apiKey: Secret[String])
object MyAgentConfig {
implicit val schema: Schema[MyAgentConfig] = Schema.derived
}Then manage it via the CLI:
golem agent-secret create apiKey --secret-type string --secret-value "sk-..."Access in code with config.value.apiKey.get.
Complete Agent Example
import golem.runtime.annotations.{agentDefinition, agentImplementation, endpoint}
import golem.BaseAgent
import scala.scalajs.js
import scala.scalajs.js.Thenable.Implicits._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
@agentDefinition(mount = "/chats/{value}")
trait ChatAgent extends BaseAgent {
class Id(val value: String)
@endpoint(method = "POST", path = "/ask")
def ask(question: String): Future[String]
}
@agentImplementation()
final class ChatAgentImpl(private val chatName: String) extends ChatAgent {
private var messages: List[js.Dynamic] = List(
js.Dynamic.literal(
role = "system",
content = s"You are a helpful assistant for chat '$chatName'"
)
)
override def ask(question: String): Future[String] = {
messages = messages :+ js.Dynamic.literal(role = "user", content = question)
val apiKey = sys.env.getOrElse("OPENAI_API_KEY",
throw new RuntimeException("OPENAI_API_KEY not set"))
val payload = js.JSON.stringify(js.Dynamic.literal(
model = sys.env.getOrElse("LLM_MODEL", "gpt-4o"),
messages = js.Array(messages: _*)
))
val options = js.Dynamic.literal(
method = "POST",
headers = js.Dynamic.literal(
"Content-Type" -> "application/json",
"Authorization" -> s"Bearer $apiKey"
),
body = payload
)
for {
response <- js.Dynamic.global.fetch(
"https://api.openai.com/v1/chat/completions",
options
).asInstanceOf[js.Promise[js.Dynamic]].toFuture
text <- response.text().asInstanceOf[js.Promise[String]].toFuture
} yield {
val json = js.JSON.parse(text)
val reply = json.choices.asInstanceOf[js.Array[js.Dynamic]](0)
.message.content.asInstanceOf[String]
messages = messages :+ js.Dynamic.literal(role = "assistant", content = reply)
reply
}
}
}Key Constraints
- Golem Scala agents are compiled to JavaScript via Scala.js — only Scala.js-compatible libraries work
- Third-party libraries must use
fetchor another browser-compatible HTTP mechanism — JVM HTTP clients will not work - Use
%%%(not%%) inbuild.sbtfor Scala.js-compatible dependencies - Calling the REST API directly with
fetchis the most reliable approach - API keys should be stored as secrets using Golem's typed config system (See the
golem-add-secret-scalaguide) - All HTTP requests are automatically durably persisted by Golem