Packages

Gleam library for interacting with OpenAI-compatible APIs.

Current section

Files

Jump to
gllm src gllm.gleam
Raw

src/gllm.gleam

//// The main gllm module.
//// This module contains high-level functions and some types for interacting with OpenAI compatible APIs.
////
//// It includes functionality for creating chat completion requests, handling responses,
//// and managing API authentication. The client supports customizable base URLs
//// and temperature parameters for controlling response randomness.
////
//// ## Example
//// ```gleam
//// import gllm
//// import gllm/types/message
//// import glenvy/dotenv
//// import glenvy/env
//// import gllm
////
//// let assert Ok(api_key) = env.string("OPENROUTER_API_KEY")
//// let base_url = "https://openrouter.ai/api/v1"
////
//// let client = gllm.Client(api_key, base_url)
////
//// let messages = [
//// gllm.new_message("system", "You are a helpful assistant."),
//// gllm.new_message("user", "Hello, how are you?")
//// ]
////
//// gllm.completion(client, "openai/gpt-oss:20b", messages, 0.7)
//// ```
import gleam/http
import gleam/http/request
import gleam/httpc
import gleam/json
import gleam/list
import gleam/option
import gleam/result
import gleam/uri
import gllm/types/api_error
import gllm/types/chat_completion
import gllm/types/message
/// Represents a client for making API requests to OpenAI-compatible endpoints.
/// ## Parameters
///
/// - `api_key`: API KEY from an OpenAI compatible API
/// - `base_url`: URL of OpenAI compatible API (example: "https://openrouter.ai/api/v1")
pub type Client {
Client(api_key: String, base_url: String)
}
/// Creates a new message with the given role and content.
///
/// ## Parameters
///
/// - `role`: The role of the message sender (e.g., "system", "user", "assistant")
/// - `content`: The text content of the message
///
/// ## Returns
///
/// Returns a `Message` type with optional fields set to `None`.
///
/// ## Examples
///
/// ```
/// new_message("user", "Hello, world!")
/// // -> Message("user", "Hello, world!", None, None, None)
/// ```
pub fn new_message(
role role: String,
content content: String,
) -> message.Message {
message.Message(role, content, option.None, option.None, option.None)
}
/// Converts a Message type to a JSON object for API requests.
///
/// ## Parameters
///
/// - `message`: The Message to convert
///
/// ## Returns
///
/// Returns a JSON object containing the role and content fields.
///
/// ## Examples
///
/// ```
/// let msg = new_message("user", "Hello")
/// message_to_json(msg)
/// // -> json.object([#("role", json.string("user")), #("content", json.string("Hello"))])
/// ```
pub fn message_to_json(message: message.Message) -> json.Json {
json.object([
#("role", json.string(message.role)),
#("content", json.string(message.content)),
])
}
/// Sends a chat completion request to the API.
///
/// ## Parameters
///
/// - `client`: The API client containing authentication and endpoint information
/// - `model`: The model identifier to use for completion (e.g., "gpt-3.5-turbo")
/// - `messages`: A list of messages representing the conversation history
/// - `temperature`: Controls randomness in the response (0.0 to 2.0, lower = more focused)
///
/// ## Returns
///
/// Returns `Ok(ChatCompletion)` on success, or `Error(ApiError)` on failure.
///
/// ## Errors
///
/// - `ApiError`: Unexpected JSON response from the API or Http error
///
/// ## Example
///
/// ```
/// let client = Client("api-key", "https://api.openai.com")
/// let messages = [new_message("user", "Hello")]
///
/// completion(client, "gpt-3.5-turbo", messages, 0.7)
/// // -> Ok(ChatCompletion(...))
/// ```
pub fn completion(
client client: Client,
model model: String,
messages messages: List(message.Message),
temperature temperature: Float,
) -> Result(chat_completion.ChatCompletion, api_error.ApiError) {
let assert Ok(base_url) = uri.parse(client.base_url)
let body =
json.object([
#("model", json.string(model)),
#(
"messages",
json.array(list.map(messages, message_to_json), of: fn(x) { x }),
),
#("temperature", json.float(temperature)),
])
|> json.to_string
let req =
request.new()
|> request.set_method(http.Post)
|> request.set_host(option.unwrap(base_url.host, "api.openai.com"))
|> request.set_path({
case base_url.path {
"" -> "/v1"
_ -> base_url.path
}
<> "/chat/completions"
})
|> request.set_body(body)
|> request.prepend_header("Content-Type", "application/json")
|> request.prepend_header("Authorization", "Bearer " <> client.api_key)
use resp <- result.try(
httpc.send(req)
|> result.map_error(api_error.HttpError),
)
use completion <- result.try(
chat_completion.parse_chat_completion(resp.body)
|> result.map_error(api_error.JsonDecodeError),
)
Ok(completion)
}