Current section
Files
Jump to
Current section
Files
src/gopenai@chat.erl
-module(gopenai@chat).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([role_decoder/1, completion_from_json/1, request_to_json/1, completion/2]).
-export_type([role/0, message/0, request/0, finish_reason/0, choice/0, usage/0, completion_response/0, completion_error/0, chat_error/0]).
-type role() :: system | assistant | user | tool | function.
-type message() :: {system_message, binary(), gleam@option:option(binary())} |
{user_message, binary(), gleam@option:option(binary())} |
{assistant_message,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(list(gopenai@tool:tool_call()))} |
{tool_message, binary(), binary()}.
-type request() :: {request,
gopenai@model:model(),
list(message()),
float(),
gleam@option:option(list(gopenai@tool:tool()))}.
-type finish_reason() :: stop |
length |
content_filter |
tool_calls |
function_call.
-type choice() :: {choice, finish_reason(), integer(), message()}.
-type usage() :: {usage, integer(), integer(), integer()}.
-type completion_response() :: {completion_response,
binary(),
list(choice()),
integer(),
binary(),
binary(),
usage()}.
-type completion_error() :: {completion_error,
binary(),
binary(),
binary(),
binary()}.
-type chat_error() :: {dynamic_error, gleam@dynamic:dynamic_()} |
{json_decode_error, gleam@json:decode_error()} |
{bad_request_error, completion_error()}.
-spec role_to_json(role()) -> gleam@json:json().
role_to_json(Role) ->
_pipe = case Role of
system ->
<<"system"/utf8>>;
assistant ->
<<"assistant"/utf8>>;
user ->
<<"user"/utf8>>;
tool ->
<<"tool"/utf8>>;
function ->
<<"function"/utf8>>
end,
gleam@json:string(_pipe).
-spec role_from_json(gleam@dynamic:dynamic_()) -> {ok, role()} |
{error, list(gleam@dynamic:decode_error())}.
role_from_json(Data) ->
case gleam@dynamic:string(Data) of
{ok, Str} ->
case Str of
<<"system"/utf8>> ->
{ok, system};
<<"assistant"/utf8>> ->
{ok, assistant};
<<"user"/utf8>> ->
{ok, user};
<<"function"/utf8>> ->
{ok, function};
Default ->
{error,
[{decode_error, <<"valid role"/utf8>>, Default, []}]}
end;
{error, E} ->
{error, E}
end.
-spec role_decoder(gleam@dynamic:dynamic_()) -> {ok, role()} |
{error, list(gleam@dynamic:decode_error())}.
role_decoder(Data) ->
role_from_json(Data).
-spec object(list({binary(), gleam@json:json()})) -> gleam@json:json().
object(Entries) ->
gleam@json:object(
begin
_pipe = Entries,
gleam@list:filter(
_pipe,
fun(E) ->
{_, V} = E,
gleam@json:to_string(V) /= <<"null"/utf8>>
end
)
end
).
-spec message_encoder(message()) -> gleam@json:json().
message_encoder(Message) ->
case Message of
{system_message, Content, Name} ->
object(
[{<<"role"/utf8>>, role_to_json(system)},
{<<"content"/utf8>>, gleam@json:string(Content)},
{<<"name"/utf8>>,
gleam@json:nullable(Name, fun gleam@json:string/1)}]
);
{user_message, Content@1, Name@1} ->
object(
[{<<"role"/utf8>>, role_to_json(user)},
{<<"content"/utf8>>, gleam@json:string(Content@1)},
{<<"name"/utf8>>,
gleam@json:nullable(Name@1, fun gleam@json:string/1)}]
);
{assistant_message, Content@2, Name@2, Tool_calls} ->
object(
[{<<"role"/utf8>>, role_to_json(assistant)},
{<<"content"/utf8>>,
gleam@json:nullable(Content@2, fun gleam@json:string/1)},
{<<"name"/utf8>>,
gleam@json:nullable(Name@2, fun gleam@json:string/1)},
{<<"tool_calls"/utf8>>,
gleam@json:nullable(
Tool_calls,
fun(Tc) ->
gleam@json:array(
Tc,
fun gopenai@tool:tool_call_encoder/1
)
end
)}]
);
{tool_message, Content@3, Tool_call_id} ->
object(
[{<<"role"/utf8>>, role_to_json(tool)},
{<<"content"/utf8>>, gleam@json:string(Content@3)},
{<<"tool_call_id"/utf8>>, gleam@json:string(Tool_call_id)}]
)
end.
-spec finish_reason_from_json(gleam@dynamic:dynamic_()) -> {ok, finish_reason()} |
{error, list(gleam@dynamic:decode_error())}.
finish_reason_from_json(Data) ->
case gleam@dynamic:string(Data) of
{ok, Str} ->
case Str of
<<"stop"/utf8>> ->
{ok, stop};
<<"length"/utf8>> ->
{ok, length};
<<"content_filter"/utf8>> ->
{ok, content_filter};
<<"tool_calls"/utf8>> ->
{ok, tool_calls};
<<"function_call"/utf8>> ->
{ok, function_call};
Default ->
{error,
[{decode_error,
<<"valid finish reason"/utf8>>,
Default,
[]}]}
end;
{error, E} ->
{error, E}
end.
-spec completion_from_json(binary()) -> {ok, completion_response()} |
{error, gleam@json:decode_error()}.
completion_from_json(Data) ->
_pipe = Data,
gleam@json:decode(
_pipe,
gleam@dynamic:decode6(
fun(Field@0, Field@1, Field@2, Field@3, Field@4, Field@5) -> {completion_response, Field@0, Field@1, Field@2, Field@3, Field@4, Field@5} end,
gleam@dynamic:field(<<"id"/utf8>>, fun gleam@dynamic:string/1),
gleam@dynamic:field(
<<"choices"/utf8>>,
gleam@dynamic:list(
gleam@dynamic:decode3(
fun(Field@0, Field@1, Field@2) -> {choice, Field@0, Field@1, Field@2} end,
gleam@dynamic:field(
<<"finish_reason"/utf8>>,
fun finish_reason_from_json/1
),
gleam@dynamic:field(
<<"index"/utf8>>,
fun gleam@dynamic:int/1
),
gleam@dynamic:field(
<<"message"/utf8>>,
gleam@dynamic:decode3(
fun(Field@0, Field@1, Field@2) -> {assistant_message, Field@0, Field@1, Field@2} end,
gleam@dynamic:optional_field(
<<"content"/utf8>>,
fun gleam@dynamic:string/1
),
gleam@dynamic:optional_field(
<<"name"/utf8>>,
fun gleam@dynamic:string/1
),
gleam@dynamic:optional_field(
<<"tool_calls"/utf8>>,
gleam@dynamic:list(
gleam@dynamic:decode3(
fun(Field@0, Field@1, Field@2) -> {tool_call, Field@0, Field@1, Field@2} end,
gleam@dynamic:field(
<<"id"/utf8>>,
fun gleam@dynamic:string/1
),
gleam@dynamic:field(
<<"type"/utf8>>,
fun gopenai@tool:tool_type_from_json/1
),
gleam@dynamic:field(
<<"function"/utf8>>,
fun gopenai@tool:function_call_from_json/1
)
)
)
)
)
)
)
)
),
gleam@dynamic:field(<<"created"/utf8>>, fun gleam@dynamic:int/1),
gleam@dynamic:field(<<"model"/utf8>>, fun gleam@dynamic:string/1),
gleam@dynamic:field(<<"object"/utf8>>, fun gleam@dynamic:string/1),
gleam@dynamic:field(
<<"usage"/utf8>>,
gleam@dynamic:decode3(
fun(Field@0, Field@1, Field@2) -> {usage, Field@0, Field@1, Field@2} end,
gleam@dynamic:field(
<<"prompt_tokens"/utf8>>,
fun gleam@dynamic:int/1
),
gleam@dynamic:field(
<<"completion_tokens"/utf8>>,
fun gleam@dynamic:int/1
),
gleam@dynamic:field(
<<"total_tokens"/utf8>>,
fun gleam@dynamic:int/1
)
)
)
)
).
-spec request_to_json(request()) -> binary().
request_to_json(Req) ->
_pipe = gleam@json:object(
[{<<"model"/utf8>>, gopenai@model:model_to_json(erlang:element(2, Req))},
{<<"messages"/utf8>>,
gleam@json:array(erlang:element(3, Req), fun message_encoder/1)},
{<<"temperature"/utf8>>, gleam@json:float(erlang:element(4, Req))},
{<<"tools"/utf8>>,
gopenai@tool:tools_to_json(erlang:element(5, Req))}]
),
gleam@json:to_string(_pipe).
-spec completion(gopenai@client:client(), request()) -> {ok,
completion_response()} |
{error, chat_error()}.
completion(Client, Req) ->
_assert_subject = gleam@http@request:to(
gleam@string:concat(
[erlang:element(2, Client),
<<"/v1/"/utf8>>,
begin
_pipe = completions,
gopenai@client:endpoint_to_str(_pipe)
end]
)
),
{ok, Request} = case _assert_subject of
{ok, _} -> _assert_subject;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Assertion pattern match failed"/utf8>>,
value => _assert_fail,
module => <<"gopenai/chat"/utf8>>,
function => <<"completion"/utf8>>,
line => 254})
end,
_pipe@1 = Request,
_pipe@2 = gleam@http@request:set_method(_pipe@1, post),
_pipe@3 = gleam@http@request:prepend_header(
_pipe@2,
<<"Content-Type"/utf8>>,
<<"application/json"/utf8>>
),
_pipe@4 = gleam@http@request:prepend_header(
_pipe@3,
<<"Authorization"/utf8>>,
<<"Bearer "/utf8, (erlang:element(3, Client))/binary>>
),
_pipe@6 = gleam@http@request:set_body(
_pipe@4,
begin
_pipe@5 = Req,
request_to_json(_pipe@5)
end
),
_pipe@7 = gleam@httpc:send(_pipe@6),
_pipe@8 = gleam@result:map_error(_pipe@7, fun(E) -> {dynamic_error, E} end),
_pipe@12 = gleam@result:map(
_pipe@8,
fun(Response) -> case erlang:element(2, Response) of
200 ->
_pipe@9 = completion_from_json(erlang:element(4, Response)),
gleam@result:map_error(
_pipe@9,
fun(E@1) -> {json_decode_error, E@1} end
);
_ ->
case begin
_pipe@10 = erlang:element(4, Response),
_pipe@11 = gleam@json:decode(
_pipe@10,
gleam@dynamic:decode1(
fun(Field@0) -> {bad_request_error, Field@0} end,
gleam@dynamic:field(
<<"error"/utf8>>,
gleam@dynamic:decode4(
fun(Field@0, Field@1, Field@2, Field@3) -> {completion_error, Field@0, Field@1, Field@2, Field@3} end,
gleam@dynamic:field(
<<"message"/utf8>>,
fun gleam@dynamic:string/1
),
gleam@dynamic:field(
<<"type"/utf8>>,
fun gleam@dynamic:string/1
),
gleam@dynamic:field(
<<"param"/utf8>>,
fun gleam@dynamic:string/1
),
gleam@dynamic:field(
<<"code"/utf8>>,
fun gleam@dynamic:string/1
)
)
)
)
),
gleam@result:map_error(
_pipe@11,
fun(E@2) -> {json_decode_error, E@2} end
)
end of
{ok, E@3} ->
{error, E@3};
{error, E@4} ->
{error, E@4}
end
end end
),
gleam@result:flatten(_pipe@12).