//// 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) }