case Code.ensure_compiled(ReqLLM.Provider) do
{:module, ReqLLM.Provider} ->
defmodule Pi.ReqLLM.Provider do
@moduledoc "ReqLLM provider for the active Pi model."
@behaviour ReqLLM.Provider
alias Pi.Protocol.LLM.Message
alias ReqLLM.Context
alias ReqLLM.Response
def provider_id, do: :pi
def prepare_request(:chat, model, %Context{} = context, opts) do
case Pi.LLM.complete(messages(context), opts) do
{:ok, text} -> {:ok, request_for(response(model, context, text))}
{:error, reason} -> {:error, RuntimeError.exception(message: inspect(reason))}
end
end
def prepare_request(:chat, model, messages, opts) do
with {:ok, context} <- Context.normalize(messages, opts) do
prepare_request(:chat, model, context, opts)
end
end
def prepare_request(_operation, _model, _input, _opts) do
{:error, RuntimeError.exception(message: "Pi ReqLLM provider only supports chat")}
end
def attach(request, _model, _opts), do: request
def encode_body(request), do: request
def build_body(_request), do: %{}
def decode_response({request, response}), do: {request, response}
defp messages(%Context{} = context) do
Enum.map(context.messages, fn message ->
Message.from_map!(%{
role: message.role,
content: text_content(message.content)
})
end)
end
defp text_content(content) when is_list(content) do
Enum.map_join(content, fn
%{type: :text, text: text} when is_binary(text) -> text
part -> inspect(part)
end)
end
defp request_for(response) do
req_response = %Req.Response{status: 200, body: response}
Req.new()
|> Req.Request.append_request_steps(
pi_complete: fn request -> {request, req_response} end
)
end
defp response(model, context, text) do
message = Context.assistant(text)
%Response{
id: "pi_#{System.unique_integer([:positive])}",
model: model.id || model.model || "current",
context: Context.append(context, message),
message: message,
usage: nil,
finish_reason: :stop,
provider_meta: %{provider: :pi}
}
end
end
_ ->
:ok
end