Current section
Files
Jump to
Current section
Files
src/llmgleam@gemini.erl
-module(llmgleam@gemini).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/llmgleam/gemini.gleam").
-export([generate_content/3]).
-export_type([gemini_client_internal/0, content/0, part/0, generate_content_request/0, generate_content_response/0, candidate/0]).
-type gemini_client_internal() :: {gemini_client_internal, binary()}.
-type content() :: {content,
list(part()),
gleam@option:option(llmgleam@types:role())}.
-type part() :: {text_part, binary()}.
-type generate_content_request() :: {generate_content_request, list(content())}.
-type generate_content_response() :: {generate_content_response,
list(candidate())}.
-type candidate() :: {candidate, content(), gleam@option:option(binary())}.
-file("src/llmgleam/gemini.gleam", 79).
-spec chat_messages_to_request(list(llmgleam@types:chat_message())) -> generate_content_request().
chat_messages_to_request(Messages) ->
Contents = gleam@list:map(
Messages,
fun(Msg) ->
{content,
[{text_part, erlang:element(2, Msg)}],
{some, erlang:element(3, Msg)}}
end
),
{generate_content_request, Contents}.
-file("src/llmgleam/gemini.gleam", 89).
-spec encode_part(part()) -> gleam@json:json().
encode_part(Part) ->
case Part of
{text_part, Text} ->
gleam@json:object([{<<"text"/utf8>>, gleam@json:string(Text)}])
end.
-file("src/llmgleam/gemini.gleam", 108).
-spec role_to_str(llmgleam@types:role()) -> binary().
role_to_str(Role) ->
case Role of
system ->
<<"system"/utf8>>;
user ->
<<"user"/utf8>>
end.
-file("src/llmgleam/gemini.gleam", 95).
-spec encode_content(content()) -> gleam@json:json().
encode_content(Content) ->
Parts_json = gleam@json:array(erlang:element(2, Content), fun encode_part/1),
case erlang:element(3, Content) of
{some, Role} ->
gleam@json:object(
[{<<"parts"/utf8>>, Parts_json},
{<<"role"/utf8>>, gleam@json:string(role_to_str(Role))}]
);
none ->
gleam@json:object(
[{<<"parts"/utf8>>, Parts_json},
{<<"role"/utf8>>, gleam@json:string(<<"user"/utf8>>)}]
)
end.
-file("src/llmgleam/gemini.gleam", 115).
-spec encode_request(generate_content_request()) -> gleam@json:json().
encode_request(Request) ->
gleam@json:object(
[{<<"contents"/utf8>>,
gleam@json:array(
erlang:element(2, Request),
fun encode_content/1
)}]
).
-file("src/llmgleam/gemini.gleam", 150).
-spec response_to_completion(generate_content_response()) -> {ok,
llmgleam@types:completion()} |
{error, llmgleam@types:completion_error()}.
response_to_completion(Response) ->
case erlang:element(2, Response) of
[Candidate | _] ->
case erlang:element(2, erlang:element(2, Candidate)) of
[{text_part, Text} | _] ->
{ok, {completion, Text}};
[] ->
{error,
{api_error,
<<"The gemini API returned incomplete candidates"/utf8>>}}
end;
[] ->
{error,
{api_error,
<<"The gemini API returned incomplete candidates"/utf8>>}}
end.
-file("src/llmgleam/gemini.gleam", 165).
-spec str_to_role(binary()) -> gleam@option:option(llmgleam@types:role()).
str_to_role(Role_str) ->
case Role_str of
<<"user"/utf8>> ->
{some, user};
<<"system"/utf8>> ->
{some, system};
_ ->
none
end.
-file("src/llmgleam/gemini.gleam", 119).
-spec decode_response(binary()) -> {ok, generate_content_response()} |
{error, gleam@json:decode_error()}.
decode_response(Json_string) ->
Part_decoder = begin
gleam@dynamic@decode:field(
<<"text"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Text) -> gleam@dynamic@decode:success({text_part, Text}) end
)
end,
Content_decoder = begin
gleam@dynamic@decode:field(
<<"role"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Role) ->
gleam@dynamic@decode:field(
<<"parts"/utf8>>,
gleam@dynamic@decode:list(Part_decoder),
fun(Parts) ->
gleam@dynamic@decode:success(
{content, Parts, str_to_role(Role)}
)
end
)
end
)
end,
Candidate_decoder = begin
gleam@dynamic@decode:field(
<<"content"/utf8>>,
Content_decoder,
fun(Content) ->
gleam@dynamic@decode:field(
<<"finishReason"/utf8>>,
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Finish_reason) ->
gleam@dynamic@decode:success(
{candidate, Content, {some, Finish_reason}}
)
end
)
end
)
end,
Response_decoder = begin
gleam@dynamic@decode:field(
<<"candidates"/utf8>>,
gleam@dynamic@decode:list(Candidate_decoder),
fun(Candidates) ->
gleam@dynamic@decode:success(
{generate_content_response, Candidates}
)
end
)
end,
gleam@json:parse(Json_string, Response_decoder).
-file("src/llmgleam/gemini.gleam", 35).
-spec generate_content(
gemini_client_internal(),
binary(),
list(llmgleam@types:chat_message())
) -> {ok, llmgleam@types:completion()} |
{error, llmgleam@types:completion_error()}.
generate_content(Client, Model, Messages) ->
Request_body = chat_messages_to_request(Messages),
Json_body = encode_request(Request_body),
Req = begin
_pipe = gleam@http@request:new(),
_pipe@1 = gleam@http@request:set_method(_pipe, post),
_pipe@2 = gleam@http@request:set_host(
_pipe@1,
<<"generativelanguage.googleapis.com"/utf8>>
),
_pipe@3 = gleam@http@request:set_path(
_pipe@2,
<<<<"/v1/models/"/utf8, Model/binary>>/binary,
":generateContent"/utf8>>
),
_pipe@4 = gleam@http@request:set_query(
_pipe@3,
[{<<"key"/utf8>>, erlang:element(2, Client)}]
),
_pipe@5 = gleam@http@request:set_header(
_pipe@4,
<<"content-type"/utf8>>,
<<"application/json"/utf8>>
),
gleam@http@request:set_body(_pipe@5, gleam@json:to_string(Json_body))
end,
case gleam@hackney:send(Req) of
{ok, Resp} ->
case erlang:element(2, Resp) of
200 ->
case decode_response(erlang:element(4, Resp)) of
{ok, Response} ->
response_to_completion(Response);
{error, Err} ->
{error,
{json_error,
<<"Failed to decode response "/utf8,
(gleam@string:inspect(Err))/binary>>}}
end;
_ ->
{error,
{api_error,
<<<<<<"API returned status "/utf8,
(gleam@string:inspect(
erlang:element(2, Resp)
))/binary>>/binary,
": "/utf8>>/binary,
(erlang:element(4, Resp))/binary>>}}
end;
{error, Err@1} ->
{error,
{http_error,
<<"HTTP request failed: "/utf8,
(gleam@string:inspect(Err@1))/binary>>}}
end.