Current section
Files
Jump to
Current section
Files
src/gloq.gleam
import gleam/hackney
import gleam/http
import gleam/http/request.{type Request}
import gleam/json.{type Json}
import gleam/list
import gleam/option.{type Option, None, Some}
/// A single message in a conversation, used for multi-turn requests.
pub type Message {
Message(role: String, content: String)
}
/// Builder for a GroqCloud chat completion request.
///
/// Construct one with `default_groq_request/0` or `new_groq_request/0`,
/// chain `with_*` setters, then call `build/1` to get an HTTP request
/// you can send with any client (e.g. `gleam_httpc` or `gleam_hackney`).
pub type GroqRequestBuilder {
GroqRequestBuilder(
key: String,
user: String,
context: String,
model: String,
frequency_penalty: Option(Float),
logprobs: Option(Bool),
max_tokens: Option(Int),
n: Option(Int),
parallel_tool_calls: Option(Bool),
presence_penalty: Option(Float),
seed: Option(Int),
stop: Option(String),
stream: Option(Bool),
temperature: Option(Float),
top_p: Option(Float),
system_prompt: Option(String),
messages: List(Message),
response_format: Option(String),
)
}
/// Creates a new `GroqRequestBuilder` with sensible defaults.
/// Default model: `llama-3.1-8b-instant`. Default user role: `"user"`.
pub fn default_groq_request() -> GroqRequestBuilder {
GroqRequestBuilder(
key: "",
user: "user",
context: "",
model: "llama-3.1-8b-instant",
frequency_penalty: Some(0.0),
logprobs: None,
max_tokens: None,
n: Some(1),
parallel_tool_calls: Some(True),
presence_penalty: Some(0.0),
seed: None,
stop: None,
stream: Some(False),
temperature: Some(1.0),
top_p: Some(1.0),
system_prompt: None,
messages: [],
response_format: None,
)
}
/// Creates a new `GroqRequestBuilder` with no preset values.
/// You must set `key`, `model`, `user`, and `context` (or `messages`) before calling `build/1`.
pub fn new_groq_request() -> GroqRequestBuilder {
GroqRequestBuilder(
key: "",
user: "",
context: "",
model: "",
frequency_penalty: None,
logprobs: None,
max_tokens: None,
n: None,
parallel_tool_calls: None,
presence_penalty: None,
seed: None,
stop: None,
stream: None,
temperature: None,
top_p: None,
system_prompt: None,
messages: [],
response_format: None,
)
}
/// Sets the GroqCloud API key.
pub fn with_key(builder: GroqRequestBuilder, key: String) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, key: key)
}
/// Sets the user role label for the single-turn message (default: `"user"`).
/// For multi-turn conversations use `add_user_message/2` instead.
pub fn with_user(
builder: GroqRequestBuilder,
user: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, user: user)
}
/// Sets the prompt text for a single-turn request.
/// For multi-turn conversations use `add_user_message/2` instead.
pub fn with_context(
builder: GroqRequestBuilder,
context: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, context: context)
}
/// Sets the model to use for inference. See `gloq/models` for available constants.
pub fn with_model(
builder: GroqRequestBuilder,
model: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, model: model)
}
/// Sets a system prompt that is prepended as the first message.
pub fn with_system_prompt(
builder: GroqRequestBuilder,
prompt: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, system_prompt: Some(prompt))
}
/// Replaces the entire conversation message list.
/// When messages is non-empty, `with_user/2` and `with_context/2` are ignored.
pub fn with_messages(
builder: GroqRequestBuilder,
messages: List(Message),
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, messages: messages)
}
/// Appends a message with the given role and content to the conversation.
pub fn add_message(
builder: GroqRequestBuilder,
role: String,
content: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(
..builder,
messages: list.append(builder.messages, [Message(role: role, content: content)]),
)
}
/// Appends a user message to the conversation.
pub fn add_user_message(
builder: GroqRequestBuilder,
content: String,
) -> GroqRequestBuilder {
add_message(builder, "user", content)
}
/// Appends an assistant message to the conversation (useful for continuing a thread).
pub fn add_assistant_message(
builder: GroqRequestBuilder,
content: String,
) -> GroqRequestBuilder {
add_message(builder, "assistant", content)
}
/// Number between -2.0 and 2.0. Positive values penalize new tokens based on
/// their existing frequency, decreasing the likelihood of repetition.
pub fn with_frequency_penalty(
builder: GroqRequestBuilder,
frequency_penalty: Float,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, frequency_penalty: Some(frequency_penalty))
}
/// Whether to return log probabilities of the output tokens.
/// Not yet supported by all models.
pub fn with_logprobs(
builder: GroqRequestBuilder,
logprobs: Bool,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, logprobs: Some(logprobs))
}
/// Maximum number of tokens to generate. When `None` the model default is used.
pub fn with_max_tokens(
builder: GroqRequestBuilder,
max_tokens: Int,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, max_tokens: Some(max_tokens))
}
/// Number of completion choices to generate. Only `n = 1` is currently supported.
pub fn with_n(builder: GroqRequestBuilder, n: Int) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, n: Some(n))
}
/// Whether to enable parallel function calling during tool use.
pub fn with_parallel_tool_calls(
builder: GroqRequestBuilder,
parallel_tool_calls: Bool,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, parallel_tool_calls: Some(parallel_tool_calls))
}
/// Number between -2.0 and 2.0. Positive values penalize tokens that have
/// already appeared, encouraging the model to discuss new topics.
pub fn with_presence_penalty(
builder: GroqRequestBuilder,
presence_penalty: Float,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, presence_penalty: Some(presence_penalty))
}
/// Setting a seed makes the system attempt to sample deterministically so that
/// repeated requests with the same seed and parameters return the same result.
pub fn with_seed(builder: GroqRequestBuilder, seed: Int) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, seed: Some(seed))
}
/// Up to 4 sequences where the API stops generating tokens. The stop sequence
/// itself is not included in the output.
pub fn with_stop(
builder: GroqRequestBuilder,
stop: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, stop: Some(stop))
}
/// When `True`, partial message deltas are sent as server-sent events.
pub fn with_stream(
builder: GroqRequestBuilder,
stream: Bool,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, stream: Some(stream))
}
/// Sampling temperature between 0 and 2. Higher values increase randomness;
/// lower values increase focus. Do not use alongside `with_top_p/2`.
pub fn with_temperature(
builder: GroqRequestBuilder,
temperature: Float,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, temperature: Some(temperature))
}
/// Nucleus sampling threshold. The model considers only the top tokens whose
/// cumulative probability exceeds this value. Do not use alongside `with_temperature/2`.
pub fn with_top_p(
builder: GroqRequestBuilder,
top_p: Float,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, top_p: Some(top_p))
}
/// Request a structured response format. Pass `"json_object"` to get valid JSON.
/// When using this, instruct the model to produce JSON in your system prompt.
pub fn with_response_format(
builder: GroqRequestBuilder,
format: String,
) -> GroqRequestBuilder {
GroqRequestBuilder(..builder, response_format: Some(format))
}
fn optional_field(
key: String,
value: Option(a),
encoder: fn(a) -> Json,
) -> Option(#(String, Json)) {
option.map(value, fn(v) { #(key, encoder(v)) })
}
/// Builds the HTTP request ready to send with your chosen HTTP client.
/// Only optional fields that have been explicitly set are included in the body.
///
/// Example:
/// ```gleam
/// import gleam/httpc
/// import gloq
///
/// let req =
/// gloq.default_groq_request()
/// |> gloq.with_key(api_key)
/// |> gloq.with_context("Hello!")
/// |> gloq.build()
///
/// let response = httpc.send(req)
/// ```
pub fn build(builder: GroqRequestBuilder) -> Request(String) {
let system_msgs = case builder.system_prompt {
None -> []
Some(prompt) -> [
json.object([
#("role", json.string("system")),
#("content", json.string(prompt)),
]),
]
}
let conversation_msgs = case builder.messages {
[] -> [
json.object([
#("role", json.string(builder.user)),
#("content", json.string(builder.context)),
]),
]
msgs ->
list.map(msgs, fn(m) {
json.object([
#("role", json.string(m.role)),
#("content", json.string(m.content)),
])
})
}
let all_messages = list.append(system_msgs, conversation_msgs)
let optional_fields =
[
optional_field("frequency_penalty", builder.frequency_penalty, json.float),
optional_field("logprobs", builder.logprobs, json.bool),
optional_field("max_tokens", builder.max_tokens, json.int),
optional_field("n", builder.n, json.int),
optional_field(
"parallel_tool_calls",
builder.parallel_tool_calls,
json.bool,
),
optional_field("presence_penalty", builder.presence_penalty, json.float),
optional_field("seed", builder.seed, json.int),
optional_field("stop", builder.stop, json.string),
optional_field("stream", builder.stream, json.bool),
optional_field("temperature", builder.temperature, json.float),
optional_field("top_p", builder.top_p, json.float),
optional_field("response_format", builder.response_format, fn(fmt) {
json.object([#("type", json.string(fmt))])
}),
]
|> list.filter_map(fn(x) { option.to_result(x, Nil) })
let required_fields = [
#("messages", json.array(all_messages, fn(x) { x })),
#("model", json.string(builder.model)),
]
let body = json.object(list.append(required_fields, optional_fields))
request.new()
|> request.set_method(http.Post)
|> request.set_host("api.groq.com")
|> request.set_path("/openai/v1/chat/completions")
|> request.set_header("Authorization", "Bearer " <> builder.key)
|> request.set_header("Content-Type", "application/json")
|> request.set_body(json.to_string(body))
}
/// Builds a request to list all available models.
///
/// > Note: `view_models` is kept for backwards compatibility.
/// > New code should prefer `list_models/1`.
pub fn view_models(api_key: String) -> Request(String) {
list_models(api_key)
}
/// Builds a GET request to list all models available on your GroqCloud account.
pub fn list_models(api_key: String) -> Request(String) {
request.new()
|> request.set_method(http.Get)
|> request.set_host("api.groq.com")
|> request.set_path("/openai/v1/models")
|> request.set_header("Authorization", "Bearer " <> api_key)
|> request.set_header("Content-Type", "application/json")
}
/// Builds a GET request to retrieve details for a single model by ID.
pub fn get_model(api_key: String, model_id: String) -> Request(String) {
request.new()
|> request.set_method(http.Get)
|> request.set_host("api.groq.com")
|> request.set_path("/openai/v1/models/" <> model_id)
|> request.set_header("Authorization", "Bearer " <> api_key)
|> request.set_header("Content-Type", "application/json")
}
/// Sends the request to the GroqCloud API using the hackney HTTP client and
/// returns the raw response body.
///
/// > **Deprecated** — send logic is intentionally left to the consumer so you
/// > can use any HTTP client. Call `build/1` instead and send the request with
/// > `gleam_httpc`, `gleam_hackney`, or your preferred client.
pub fn send(builder: GroqRequestBuilder) -> String {
let req = build(builder)
case hackney.send(req) {
Ok(r) -> r.body
Error(_) -> "Error, Request Failed"
}
}