ML-147: Upgrade to ServerSentEvents 1.0.0
This commit is contained in:
+2
-1
@@ -5,11 +5,12 @@ status: Done
|
|||||||
assignee:
|
assignee:
|
||||||
- Codex
|
- Codex
|
||||||
created_date: '2026-04-24 11:12'
|
created_date: '2026-04-24 11:12'
|
||||||
updated_date: '2026-04-25 06:37'
|
updated_date: '2026-04-27 21:02'
|
||||||
labels: []
|
labels: []
|
||||||
dependencies:
|
dependencies:
|
||||||
- ML-21
|
- ML-21
|
||||||
priority: low
|
priority: low
|
||||||
|
ordinal: 1000
|
||||||
---
|
---
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
---
|
---
|
||||||
id: ML-147
|
id: ML-147
|
||||||
title: Upgrade to ServerSentEvents 1.0
|
title: Upgrade to ServerSentEvents 1.0
|
||||||
status: To Do
|
status: Done
|
||||||
assignee: []
|
assignee: []
|
||||||
created_date: '2026-04-25 21:26'
|
created_date: '2026-04-25 21:26'
|
||||||
|
updated_date: '2026-04-27 21:02'
|
||||||
labels: []
|
labels: []
|
||||||
dependencies: []
|
dependencies: []
|
||||||
priority: medium
|
priority: medium
|
||||||
|
ordinal: 2000
|
||||||
---
|
---
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|||||||
+43
-27
@@ -9,6 +9,11 @@ defmodule OpenAI.API do
|
|||||||
Mid-stream failures in `chat_stream/6` come from parsed SSE event payloads
|
Mid-stream failures in `chat_stream/6` come from parsed SSE event payloads
|
||||||
rather than HTTP responses and remain as message strings passed through the
|
rather than HTTP responses and remain as message strings passed through the
|
||||||
stream callback plumbing.
|
stream callback plumbing.
|
||||||
|
|
||||||
|
`chat_stream/6` streams response chunks with `Req`'s `into: :self`, so the
|
||||||
|
calling process receives and consumes Req's async body messages while the
|
||||||
|
request is active. Call it from a short-lived worker/task process rather than
|
||||||
|
directly from a LiveView or GenServer that has unrelated mailbox traffic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias OpenAI.API.ErrorResponse
|
alias OpenAI.API.ErrorResponse
|
||||||
@@ -63,40 +68,18 @@ defmodule OpenAI.API do
|
|||||||
stream: true,
|
stream: true,
|
||||||
temperature: temperature
|
temperature: temperature
|
||||||
},
|
},
|
||||||
into: fn {:data, data}, {req, resp} ->
|
into: :self
|
||||||
buffer = Request.get_private(req, :sse_buffer, "")
|
|
||||||
{events, buffer} = ServerSentEvents.parse(buffer <> data)
|
|
||||||
req = Request.put_private(req, :sse_buffer, buffer)
|
|
||||||
|
|
||||||
decode_events(events, cb, req, resp)
|
|
||||||
end
|
|
||||||
)
|
)
|
||||||
|> do_chat_stream()
|
|> do_chat_stream(cb)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp decode_events([], _cb, req, resp), do: {:cont, {req, resp}}
|
defp do_chat_stream(req, cb) do
|
||||||
|
|
||||||
defp decode_events([%{data: json} | rest], cb, req, resp) do
|
|
||||||
case decode_responses_event(json, cb) do
|
|
||||||
{:error, message} ->
|
|
||||||
Logger.error(message)
|
|
||||||
{:halt, {req, Req.Response.put_private(resp, :error, message)}}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
decode_events(rest, cb, req, resp)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_chat_stream(req) do
|
|
||||||
case Req.post(req) do
|
case Req.post(req) do
|
||||||
{:ok, %{status: status} = resp} when status in 200..299 ->
|
{:ok, %{status: status} = resp} when status in 200..299 ->
|
||||||
if message = Req.Response.get_private(resp, :error) do
|
decode_response_stream(resp.body, cb)
|
||||||
{:error, message}
|
|
||||||
else
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, response} ->
|
{:ok, response} ->
|
||||||
|
response = decode_async_error_body(response)
|
||||||
{:error, ErrorResponse.from_response(response)}
|
{:error, ErrorResponse.from_response(response)}
|
||||||
|
|
||||||
{:error, exception} ->
|
{:error, exception} ->
|
||||||
@@ -104,6 +87,39 @@ defmodule OpenAI.API do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp decode_response_stream(stream, cb) do
|
||||||
|
stream
|
||||||
|
|> ServerSentEvents.decode_stream()
|
||||||
|
|> Enum.reduce_while(:ok, fn %{data: json}, :ok ->
|
||||||
|
case decode_responses_event(json, cb) do
|
||||||
|
{:error, message} ->
|
||||||
|
Logger.error(message)
|
||||||
|
{:halt, {:error, message}}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:cont, :ok}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode_async_error_body(%{body: %Req.Response.Async{} = body} = response) do
|
||||||
|
body =
|
||||||
|
body
|
||||||
|
|> Enum.into("")
|
||||||
|
|> decode_error_body()
|
||||||
|
|
||||||
|
%{response | body: body}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode_async_error_body(response), do: response
|
||||||
|
|
||||||
|
defp decode_error_body(body) when is_binary(body) do
|
||||||
|
case JSON.decode(body) do
|
||||||
|
{:ok, decoded} -> decoded
|
||||||
|
{:error, _reason} -> body
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec get_embeddings(String.t(), OpenAI.Config.t()) ::
|
@spec get_embeddings(String.t(), OpenAI.Config.t()) ::
|
||||||
{:ok, [float()]} | {:error, ErrorResponse.t() | Exception.t()}
|
{:ok, [float()]} | {:error, ErrorResponse.t() | Exception.t()}
|
||||||
def get_embeddings(text, config) do
|
def get_embeddings(text, config) do
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ defmodule MusicLibrary.MixProject do
|
|||||||
# HTTP Clients
|
# HTTP Clients
|
||||||
{:finch, "~> 0.21.0"},
|
{:finch, "~> 0.21.0"},
|
||||||
{:req, "~> 0.5.8"},
|
{:req, "~> 0.5.8"},
|
||||||
{:server_sent_events, "~> 0.2"},
|
{:server_sent_events, "~> 1.0"},
|
||||||
|
|
||||||
# Mailers
|
# Mailers
|
||||||
{:idna, "~> 7.1", override: true},
|
{:idna, "~> 7.1", override: true},
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
|
"req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"},
|
||||||
"rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"},
|
"rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"},
|
||||||
"rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"},
|
"rustler_precompiled": {:hex, :rustler_precompiled, "0.9.0", "3a052eda09f3d2436364645cc1f13279cf95db310eb0c17b0d8f25484b233aa0", [:mix], [{:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "471d97315bd3bf7b64623418b3693eedd8e47de3d1cb79a0ac8f9da7d770d94c"},
|
||||||
"server_sent_events": {:hex, :server_sent_events, "0.2.1", "f83b34f01241302a8bf451efc8dde3a36c533d5715463c31c653f3db8695f636", [:mix], [], "hexpm", "c8099ce4f9acd610eb7c8e0f89dba7d5d1c13300ea9884b0bd8662401d3cf96f"},
|
"server_sent_events": {:hex, :server_sent_events, "1.0.0", "e82089ac6b93ebd3c0562fd728492bbe4b5140678ffc891abfa8cce717c2c1ff", [:mix], [], "hexpm", "7899caea3e27850549f671fc9e6c53d55a8e6a78474f6b9623820aae6bb41ec7"},
|
||||||
"sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"},
|
"sobelow": {:hex, :sobelow, "0.14.1", "2f81e8632f15574cba2402bcddff5497b413c01e6f094bc0ab94e83c2f74db81", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8fac9a2bd90fdc4b15d6fca6e1608efb7f7c600fa75800813b794ee9364c87f2"},
|
||||||
"sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"},
|
"sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"},
|
||||||
"spitfire": {:hex, :spitfire, "0.3.11", "79dfcb033762470de472c1c26ea2b4e3aca74700c685dbffd9a13466272c323d", [:mix], [], "hexpm", "eb6e2dadf63214e8bfe65ca9788cef2b03b01027365d78d3c0e3d9ebd3d5b7b4"},
|
"spitfire": {:hex, :spitfire, "0.3.11", "79dfcb033762470de472c1c26ea2b4e3aca74700c685dbffd9a13466272c323d", [:mix], [], "hexpm", "eb6e2dadf63214e8bfe65ca9788cef2b03b01027365d78d3c0e3d9ebd3d5b7b4"},
|
||||||
|
|||||||
Reference in New Issue
Block a user