Current section
Files
Jump to
Current section
Files
src/gllm.erl
-module(gllm).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gllm.gleam").
-export([new_message/2, message_to_json/1, completion/4]).
-export_type([client/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" The main gllm module.\n"
" This module contains high-level functions and some types for interacting with OpenAI compatible APIs.\n"
"\n"
" It includes functionality for creating chat completion requests, handling responses,\n"
" and managing API authentication. The client supports customizable base URLs\n"
" and temperature parameters for controlling response randomness.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" import gllm\n"
" import gllm/types/message\n"
" import glenvy/dotenv\n"
" import glenvy/env\n"
" import gllm\n"
"\n"
" let assert Ok(api_key) = env.string(\"OPENROUTER_API_KEY\")\n"
" let base_url = \"https://openrouter.ai/api/v1\"\n"
"\n"
" let client = gllm.Client(api_key, base_url)\n"
"\n"
" let messages = [\n"
" gllm.new_message(\"system\", \"You are a helpful assistant.\"),\n"
" gllm.new_message(\"user\", \"Hello, how are you?\")\n"
" ]\n"
"\n"
" gllm.completion(client, \"openai/gpt-oss:20b\", messages, 0.7)\n"
" ```\n"
).
-type client() :: {client, binary(), binary()}.
-file("src/gllm.gleam", 67).
?DOC(
" Creates a new message with the given role and content.\n"
"\n"
" ## Parameters\n"
"\n"
" - `role`: The role of the message sender (e.g., \"system\", \"user\", \"assistant\")\n"
" - `content`: The text content of the message\n"
"\n"
" ## Returns\n"
"\n"
" Returns a `Message` type with optional fields set to `None`.\n"
"\n"
" ## Examples\n"
"\n"
" ```\n"
" new_message(\"user\", \"Hello, world!\")\n"
" // -> Message(\"user\", \"Hello, world!\", None, None, None)\n"
" ```\n"
).
-spec new_message(binary(), binary()) -> gllm@types@message:message().
new_message(Role, Content) ->
{message, Role, Content, none, none, none}.
-file("src/gllm.gleam", 91).
?DOC(
" Converts a Message type to a JSON object for API requests.\n"
"\n"
" ## Parameters\n"
"\n"
" - `message`: The Message to convert\n"
"\n"
" ## Returns\n"
"\n"
" Returns a JSON object containing the role and content fields.\n"
"\n"
" ## Examples\n"
"\n"
" ```\n"
" let msg = new_message(\"user\", \"Hello\")\n"
" message_to_json(msg)\n"
" // -> json.object([#(\"role\", json.string(\"user\")), #(\"content\", json.string(\"Hello\"))])\n"
" ```\n"
).
-spec message_to_json(gllm@types@message:message()) -> gleam@json:json().
message_to_json(Message) ->
gleam@json:object(
[{<<"role"/utf8>>, gleam@json:string(erlang:element(2, Message))},
{<<"content"/utf8>>, gleam@json:string(erlang:element(3, Message))}]
).
-file("src/gllm.gleam", 124).
?DOC(
" Sends a chat completion request to the API.\n"
"\n"
" ## Parameters\n"
"\n"
" - `client`: The API client containing authentication and endpoint information\n"
" - `model`: The model identifier to use for completion (e.g., \"gpt-3.5-turbo\")\n"
" - `messages`: A list of messages representing the conversation history\n"
" - `temperature`: Controls randomness in the response (0.0 to 2.0, lower = more focused)\n"
"\n"
" ## Returns\n"
"\n"
" Returns `Ok(ChatCompletion)` on success, or `Error(ApiError)` on failure.\n"
"\n"
" ## Errors\n"
"\n"
" - `ApiError`: Unexpected JSON response from the API or Http error\n"
"\n"
" ## Example\n"
"\n"
" ```\n"
" let client = Client(\"api-key\", \"https://api.openai.com\")\n"
" let messages = [new_message(\"user\", \"Hello\")]\n"
"\n"
" completion(client, \"gpt-3.5-turbo\", messages, 0.7)\n"
" // -> Ok(ChatCompletion(...))\n"
" ```\n"
).
-spec completion(
client(),
binary(),
list(gllm@types@message:message()),
float()
) -> {ok, gllm@types@chat_completion:chat_completion()} |
{error, gllm@types@api_error:api_error()}.
completion(Client, Model, Messages, Temperature) ->
Base_url@1 = case gleam_stdlib:uri_parse(erlang:element(3, Client)) of
{ok, Base_url} -> Base_url;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"gllm"/utf8>>,
function => <<"completion"/utf8>>,
line => 130,
value => _assert_fail,
start => 3758,
'end' => 3810,
pattern_start => 3769,
pattern_end => 3781})
end,
Body = begin
_pipe = gleam@json:object(
[{<<"model"/utf8>>, gleam@json:string(Model)},
{<<"messages"/utf8>>,
gleam@json:array(
gleam@list:map(Messages, fun message_to_json/1),
fun(X) -> X end
)},
{<<"temperature"/utf8>>, gleam@json:float(Temperature)}]
),
gleam@json:to_string(_pipe)
end,
Req = begin
_pipe@1 = gleam@http@request:new(),
_pipe@2 = gleam@http@request:set_method(_pipe@1, post),
_pipe@3 = gleam@http@request:set_host(
_pipe@2,
gleam@option:unwrap(
erlang:element(4, Base_url@1),
<<"api.openai.com"/utf8>>
)
),
_pipe@4 = gleam@http@request:set_path(
_pipe@3,
(<<(case erlang:element(6, Base_url@1) of
<<""/utf8>> ->
<<"/v1"/utf8>>;
_ ->
erlang:element(6, Base_url@1)
end)/binary, "/chat/completions"/utf8>>)
),
_pipe@5 = gleam@http@request:set_body(_pipe@4, Body),
_pipe@6 = gleam@http@request:prepend_header(
_pipe@5,
<<"Content-Type"/utf8>>,
<<"application/json"/utf8>>
),
gleam@http@request:prepend_header(
_pipe@6,
<<"Authorization"/utf8>>,
<<"Bearer "/utf8, (erlang:element(2, Client))/binary>>
)
end,
gleam@result:'try'(
begin
_pipe@7 = gleam@httpc:send(Req),
gleam@result:map_error(
_pipe@7,
fun(Field@0) -> {http_error, Field@0} end
)
end,
fun(Resp) ->
gleam@result:'try'(
begin
_pipe@8 = gllm@types@chat_completion:parse_chat_completion(
erlang:element(4, Resp)
),
gleam@result:map_error(
_pipe@8,
fun(Field@0) -> {json_decode_error, Field@0} end
)
end,
fun(Completion) -> {ok, Completion} end
)
end
).