<!-- llm4s rules for release 0.5.1: https://llm4s.org/ai-assistants -->

## Rules for writing llm4s code

llm4s is Scala 3 on JDK 21. Follow these when you write or change code that uses it.

### 1. Return `Result[A]`; never throw

`Result[A]` is `Either[LLMError, A]`. A failing call is a `Left`, so compose with `for`, `map` and `fold`, and convert a
`Try` with `.toResult`.

```scala
import org.llm4s.llmconnect.LLMClient
import org.llm4s.llmconnect.model.{ Conversation, UserMessage }
import org.llm4s.types.Result

def answer(client: LLMClient, question: String): Result[String] =
  client.complete(Conversation(Seq(UserMessage(question)))).map(_.content)
```

### 2. Load configuration with `Llm4sConfig`, once, at the edge of the application

Core code receives the client it needs as a parameter.

```scala
import org.llm4s.config.Llm4sConfig
import org.llm4s.llmconnect.{ LLMClient, LLMConnect }
import org.llm4s.model.ModelRegistryService
import org.llm4s.types.Result

def startup(): Result[LLMClient] = for {
  providerConfig <- Llm4sConfig.defaultProvider()
  registry       <- Llm4sConfig.modelRegistryService()
  given ModelRegistryService = registry
  client <- LLMConnect.getClient(providerConfig)
} yield client
```

### 3. Name the provider in `application.conf`, not in code

A chat provider is a named section under `llm4s.providers`, and `llm4s.providers.provider` names the default. Load the
default with `Llm4sConfig.defaultProvider()`.

```hocon
llm4s {
  providers {
    provider = "openai-main"

    openai-main {
      provider = "openai"
      model    = "gpt-4o-mini"
    }
  }
}
```

### 4. A provider is its own dependency

`llm4s-core` ships no provider. Add the module of each provider you call (`llm4s-openai`, `llm4s-anthropic`,
`llm4s-gemini`, `llm4s-ollama`, `llm4s-openai-compatible`, ...) next to it, and other features likewise (`llm4s-agent`,
`llm4s-rag`, `llm4s-agent-tools`).

### 5. Bring a `ModelRegistryService` into scope as a `given`

Do it before `LLMConnect.getClient`, as in rule 2.

### 6. Never read `LLM_MODEL`

Nothing in the library reads it. The model is the `model` key of the provider section.

### 7. Never use `sys.env`, `System.getenv`, `ConfigFactory.load()` or `ConfigSource.default`

An API key comes from `llm4s.credentials.<provider>.apiKey`, which each provider module binds to the vendor's usual
variable (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, ...), or from the section's own `apiKey`. Environment variables reach
the configuration only through `${?VAR}` in a `.conf` file.

### 8. Build `CompletionOptions` with its `with` methods

Start from `CompletionOptions()` and chain `.withTemperature(...)`, `.withMaxTokens(...)`. Do not call `new` or `.copy`.

```scala
import org.llm4s.llmconnect.LLMClient
import org.llm4s.llmconnect.model.{ CompletionOptions, Conversation }

def ask(client: LLMClient, conversation: Conversation) = {
  val options = CompletionOptions().withTemperature(0.2).withMaxTokens(512)
  client.complete(conversation, options)
}
```

### 9. A duration is a `FiniteDuration`, not a number of milliseconds

Write `30.seconds`, never `timeoutMs = 30000`.

```scala
import scala.concurrent.duration._
import org.llm4s.toolapi.ToolExecutionConfig

val toolConfig = ToolExecutionConfig.default.withTimeout(30.seconds)
```

### 10. Use the current artifact names and the latest release version

The group is `org.llm4s`, every artifact is `llm4s-<module>` and Scala 3 only (`%%` in sbt, the `_3` suffix elsewhere);
`llm4s-core` is not `core`, and the Java API is `llm4s-java-api`. A project using several modules can import the
`llm4s-bom`. Take the version from the [installation page](/getting-started/installation), not from memory.
