Skip to Content
How-To GuidesScalaAdding LLM and AI Capabilities (Scala)

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:

  1. Use a third-party Scala library — but only if it is Scala.js-compatible and uses fetch under the hood (JVM-only HTTP clients will not work)
  2. Call the provider’s REST API directly using fetch or 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 use fetch or another browser-compatible HTTP mechanism. Libraries that depend on JVM networking (Apache HttpClient, OkHttp, sttp with JVM backends, etc.) will not work.

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 fetch or another browser-compatible HTTP mechanism — JVM HTTP clients will not work
  • Use %%% (not %%) in build.sbt for Scala.js-compatible dependencies
  • Calling the REST API directly with fetch is the most reliable approach
  • API keys should be stored as secrets using Golem’s typed config system (See the golem-add-secret-scala guide)
  • All HTTP requests are automatically durably persisted by Golem
Last updated on