diff --git a/lib/open_ai.ex b/lib/open_ai.ex index 33bd70cf..422ef61b 100644 --- a/lib/open_ai.ex +++ b/lib/open_ai.ex @@ -1,8 +1,10 @@ defmodule OpenAI do + alias OpenAI.API + def gpt(completion) do {:ok, collector} = Agent.start_link(fn -> "" end) - gpt_stream(completion, fn data -> + API.gpt_stream(completion, fn data -> case get_in(data, ["choices", Access.at(0), "delta", "content"]) do nil -> :ok data -> Agent.update(collector, fn current -> current <> data end) @@ -13,58 +15,4 @@ defmodule OpenAI do Agent.stop(collector) {:ok, result} end - - # Lifted from https://fly.io/phoenix-files/streaming-openai-responses/ - defp gpt_stream(completion, cb) do - fun = fn request, finch_request, finch_name, finch_options -> - fun = fn - {:status, status}, response -> - %{response | status: status} - - {:headers, headers}, response -> - %{response | headers: headers} - - {:data, data}, response -> - body = - data - |> String.split("data: ") - |> Enum.map(fn str -> - str - |> String.trim() - |> decode_body(cb) - end) - |> Enum.filter(fn d -> d != :ok end) - - old_body = if response.body == "", do: [], else: response.body - - %{response | body: old_body ++ body} - end - - case Finch.stream(finch_request, finch_name, Req.Response.new(), fun, finch_options) do - {:ok, response} -> {request, response} - {:error, exception} -> {request, exception} - end - end - - Req.post!("https://api.openai.com/v1/chat/completions", - json: %{ - model: "gpt-4o-mini", - messages: [Map.take(completion, [:content, :role])], - response_format: %{type: "json_object"}, - stream: true, - temperature: completion.temperature - }, - auth: {:bearer, api_key()}, - finch_request: fun - ) - end - - defp decode_body("", _), do: :ok - defp decode_body("[DONE]", _), do: :ok - defp decode_body(json, cb), do: cb.(Jason.decode!(json)) - - defp api_key do - Application.get_env(:music_library, OpenAI) - |> Keyword.fetch!(:api_key) - end end diff --git a/lib/open_ai/api.ex b/lib/open_ai/api.ex new file mode 100644 index 00000000..d1ad4523 --- /dev/null +++ b/lib/open_ai/api.ex @@ -0,0 +1,73 @@ +defmodule OpenAI.API do + # Lifted from https://fly.io/phoenix-files/streaming-openai-responses/ + def gpt_stream(completion, cb) do + fun = fn request, finch_request, finch_name, finch_options -> + fun = fn + {:status, status}, response -> + %{response | status: status} + + {:headers, headers}, response -> + %{response | headers: headers} + + {:data, data}, response -> + body = + data + |> String.split("data: ") + |> Enum.map(fn str -> + str + |> String.trim() + |> decode_body(cb) + end) + |> Enum.filter(fn d -> d != :ok end) + + old_body = if response.body == "", do: [], else: response.body + + %{response | body: old_body ++ body} + end + + case Finch.stream(finch_request, finch_name, Req.Response.new(), fun, finch_options) do + {:ok, response} -> {request, response} + {:error, exception} -> {request, exception} + end + end + + Req.post!("https://api.openai.com/v1/chat/completions", + json: %{ + model: "gpt-4o-mini", + messages: [Map.take(completion, [:content, :role])], + response_format: %{type: "json_object"}, + stream: true, + temperature: completion.temperature + }, + auth: {:bearer, api_key()}, + finch_request: fun + ) + end + + def get_embeddings(text) do + resp = + Req.post!("https://api.openai.com/v1/embeddings", + json: %{ + input: text, + model: "text-embedding-3-small" + }, + auth: {:bearer, api_key()} + ) + + if resp.status == 200 do + embeddings = get_in(resp.body, ["data", Access.at(0), "embedding"]) + {:ok, embeddings} + else + {:error, resp.body} + end + end + + defp decode_body("", _), do: :ok + defp decode_body("[DONE]", _), do: :ok + defp decode_body(json, cb), do: cb.(Jason.decode!(json)) + + defp api_key do + Application.get_env(:music_library, OpenAI) + |> Keyword.fetch!(:api_key) + end +end