defmodule Pi.LLM do @moduledoc "BEAM API for model calls backed by the active pi session." alias Pi.LLM.Broker alias Pi.Protocol.LLM.Message def complete(messages, opts \\ []) do with {:ok, result} <- complete_with_usage(messages, opts) do {:ok, result_text(result)} end end def complete_with_usage(messages, opts \\ []) do messages |> normalize_messages() |> Broker.complete(opts) |> normalize_completion_result() end def stream(messages, opts \\ []) do messages |> normalize_messages() |> Broker.stream(opts) end def complete!(messages, opts \\ []) do case complete(messages, opts) do {:ok, result} -> result {:error, reason} -> raise RuntimeError, message: to_string(reason) end end defp normalize_completion_result({:ok, result}), do: {:ok, completion_result(result)} defp normalize_completion_result({:error, reason}), do: {:error, reason} defp completion_result(%{"text" => text} = result) when is_binary(text) do %{text: text, usage: result["usage"], model: result["model"], provider: result["provider"]} end defp completion_result(%{text: text} = result) when is_binary(text) do %{text: text, usage: result[:usage], model: result[:model], provider: result[:provider]} end defp completion_result(text) when is_binary(text), do: %{text: text, usage: nil} defp completion_result(result), do: %{text: inspect(result), usage: nil} defp result_text(%{text: text}) when is_binary(text), do: text defp normalize_messages(messages) when is_binary(messages), do: [Message.from_map!(%{role: :user, content: messages})] defp normalize_messages(messages) when is_list(messages), do: Enum.map(messages, &normalize_message/1) defp normalize_message(%Message{} = message), do: message defp normalize_message(%{} = message), do: Message.from_map!(message) defp normalize_message(message) when is_binary(message), do: Message.from_map!(%{role: :user, content: message}) end