Shrink collection summary to fit OpenAI token limit
Deduplicate records by musicbrainz_id (merging formats) and cap genres to 3 per record, reducing the summary from ~32K to ~24K estimated tokens.
This commit is contained in:
@@ -8,9 +8,10 @@ defmodule MusicLibrary.Chats.CollectionChat do
|
|||||||
alias MusicLibrary.Chats.Prompt
|
alias MusicLibrary.Chats.Prompt
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
@spec stream_response([map()], String.t(), (String.t() -> any())) :: :ok | {:error, term()}
|
@spec stream_response([map()], {String.t(), non_neg_integer()}, (String.t() -> any())) ::
|
||||||
def stream_response(messages, collection_summary, callback) do
|
:ok | {:error, term()}
|
||||||
instructions = build_instructions(collection_summary)
|
def stream_response(messages, {summary, record_count}, callback) do
|
||||||
|
instructions = build_instructions(summary, record_count)
|
||||||
|
|
||||||
OpenAI.chat_stream(messages,
|
OpenAI.chat_stream(messages,
|
||||||
on_chunk: callback,
|
on_chunk: callback,
|
||||||
@@ -19,9 +20,7 @@ defmodule MusicLibrary.Chats.CollectionChat do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_instructions(collection_summary) do
|
defp build_instructions(collection_summary, record_count) do
|
||||||
record_count = count_records(collection_summary)
|
|
||||||
|
|
||||||
Prompt.build("""
|
Prompt.build("""
|
||||||
Answer questions about the user's music collection. \
|
Answer questions about the user's music collection. \
|
||||||
Use the provided collection catalog as your primary reference. \
|
Use the provided collection catalog as your primary reference. \
|
||||||
@@ -31,12 +30,4 @@ defmodule MusicLibrary.Chats.CollectionChat do
|
|||||||
#{collection_summary}\
|
#{collection_summary}\
|
||||||
""")
|
""")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp count_records(""), do: 0
|
|
||||||
|
|
||||||
defp count_records(summary) do
|
|
||||||
summary
|
|
||||||
|> String.split("\n")
|
|
||||||
|> length()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -191,7 +191,9 @@ defmodule MusicLibrary.Collection do
|
|||||||
|> MapSet.new()
|
|> MapSet.new()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec collection_summary() :: String.t()
|
@max_genres_per_record 3
|
||||||
|
|
||||||
|
@spec collection_summary() :: {String.t(), non_neg_integer()}
|
||||||
def collection_summary do
|
def collection_summary do
|
||||||
records =
|
records =
|
||||||
from(r in Record,
|
from(r in Record,
|
||||||
@@ -201,16 +203,31 @@ defmodule MusicLibrary.Collection do
|
|||||||
)
|
)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
|
record_count = length(records)
|
||||||
|
|
||||||
|
summary =
|
||||||
records
|
records
|
||||||
|> Enum.map_join("\n", &format_record_line/1)
|
|> Enum.group_by(& &1.musicbrainz_id)
|
||||||
|
|> Enum.map(fn {_id, group} -> format_group(group) end)
|
||||||
|
|> Enum.sort()
|
||||||
|
|> Enum.join("\n")
|
||||||
|
|
||||||
|
{summary, record_count}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp format_record_line(record) do
|
defp format_group(records) do
|
||||||
|
record = hd(records)
|
||||||
artist_names = Record.artist_names(record)
|
artist_names = Record.artist_names(record)
|
||||||
genres = record.genres || []
|
formats = records |> Enum.map(& &1.format) |> Enum.uniq() |> Enum.join("/")
|
||||||
|
|
||||||
|
genres =
|
||||||
|
records
|
||||||
|
|> Enum.flat_map(&(&1.genres || []))
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> Enum.take(@max_genres_per_record)
|
||||||
|
|
||||||
base =
|
base =
|
||||||
"#{artist_names} - #{record.title} (#{record.release_date || "Unknown"}, #{record.format}, #{record.type})"
|
"#{artist_names} - #{record.title} (#{record.release_date || "Unknown"}, #{formats}, #{record.type})"
|
||||||
|
|
||||||
if genres == [] do
|
if genres == [] do
|
||||||
base
|
base
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ defmodule MusicLibrary.Chats.CollectionChatTest do
|
|||||||
summary =
|
summary =
|
||||||
"Radiohead - OK Computer (1997-06-16, cd, album) [alternative rock, art rock]\nPink Floyd - The Wall (1979-11-30, vinyl, album) [progressive rock]"
|
"Radiohead - OK Computer (1997-06-16, cd, album) [alternative rock, art rock]\nPink Floyd - The Wall (1979-11-30, vinyl, album) [progressive rock]"
|
||||||
|
|
||||||
assert :ok = CollectionChat.stream_response([], summary, fn _chunk -> :ok end)
|
assert :ok = CollectionChat.stream_response([], {summary, 2}, fn _chunk -> :ok end)
|
||||||
|
|
||||||
assert_receive {:captured_instructions, instructions}
|
assert_receive {:captured_instructions, instructions}
|
||||||
assert instructions =~ summary
|
assert instructions =~ summary
|
||||||
@@ -43,7 +43,7 @@ defmodule MusicLibrary.Chats.CollectionChatTest do
|
|||||||
test "stream_response handles empty collection" do
|
test "stream_response handles empty collection" do
|
||||||
stub_and_capture_instructions(self())
|
stub_and_capture_instructions(self())
|
||||||
|
|
||||||
assert :ok = CollectionChat.stream_response([], "", fn _chunk -> :ok end)
|
assert :ok = CollectionChat.stream_response([], {"", 0}, fn _chunk -> :ok end)
|
||||||
|
|
||||||
assert_receive {:captured_instructions, instructions}
|
assert_receive {:captured_instructions, instructions}
|
||||||
assert instructions =~ "0 records"
|
assert instructions =~ "0 records"
|
||||||
|
|||||||
@@ -298,11 +298,11 @@ defmodule MusicLibrary.CollectionTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe "collection_summary/0" do
|
describe "collection_summary/0" do
|
||||||
test "returns empty string when collection is empty" do
|
test "returns empty string and zero count when collection is empty" do
|
||||||
assert Collection.collection_summary() == ""
|
assert Collection.collection_summary() == {"", 0}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns one line per collected record" do
|
test "returns one line per collected record with record count" do
|
||||||
record_with_artist("Radiohead", %{
|
record_with_artist("Radiohead", %{
|
||||||
title: "OK Computer",
|
title: "OK Computer",
|
||||||
format: :cd,
|
format: :cd,
|
||||||
@@ -321,9 +321,10 @@ defmodule MusicLibrary.CollectionTest do
|
|||||||
purchased_at: ~U[2024-01-02 00:00:00Z]
|
purchased_at: ~U[2024-01-02 00:00:00Z]
|
||||||
})
|
})
|
||||||
|
|
||||||
summary = Collection.collection_summary()
|
{summary, record_count} = Collection.collection_summary()
|
||||||
lines = String.split(summary, "\n")
|
lines = String.split(summary, "\n")
|
||||||
|
|
||||||
|
assert record_count == 2
|
||||||
assert length(lines) == 2
|
assert length(lines) == 2
|
||||||
|
|
||||||
assert Enum.any?(lines, fn line ->
|
assert Enum.any?(lines, fn line ->
|
||||||
@@ -339,6 +340,54 @@ defmodule MusicLibrary.CollectionTest do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "deduplicates records with same musicbrainz_id and merges formats" do
|
||||||
|
mbid = Ecto.UUID.generate()
|
||||||
|
|
||||||
|
record_with_artist("AC/DC", %{
|
||||||
|
title: "Highway to Hell",
|
||||||
|
format: :cd,
|
||||||
|
type: :album,
|
||||||
|
genres: ["hard rock", "rock"],
|
||||||
|
release_date: "1979-07-27",
|
||||||
|
musicbrainz_id: mbid,
|
||||||
|
purchased_at: ~U[2024-01-01 00:00:00Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
record_with_artist("AC/DC", %{
|
||||||
|
title: "Highway to Hell",
|
||||||
|
format: :vinyl,
|
||||||
|
type: :album,
|
||||||
|
genres: ["hard rock", "rock"],
|
||||||
|
release_date: "1979-07-27",
|
||||||
|
musicbrainz_id: mbid,
|
||||||
|
purchased_at: ~U[2024-01-02 00:00:00Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
{summary, record_count} = Collection.collection_summary()
|
||||||
|
lines = String.split(summary, "\n")
|
||||||
|
|
||||||
|
assert record_count == 2
|
||||||
|
assert length(lines) == 1
|
||||||
|
assert hd(lines) =~ "cd/vinyl"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "caps genres to 3 per record" do
|
||||||
|
record_with_artist("ABC", %{
|
||||||
|
title: "The Lexicon of Love",
|
||||||
|
format: :vinyl,
|
||||||
|
type: :album,
|
||||||
|
genres: ~w[dance dance-pop disco electronic new-wave pop synth-pop],
|
||||||
|
release_date: "1982-06-21",
|
||||||
|
purchased_at: ~U[2024-01-01 00:00:00Z]
|
||||||
|
})
|
||||||
|
|
||||||
|
{summary, _count} = Collection.collection_summary()
|
||||||
|
[genres_str] = Regex.run(~r/\[(.+)\]/, summary, capture: :all_but_first)
|
||||||
|
genre_count = genres_str |> String.split(", ") |> length()
|
||||||
|
|
||||||
|
assert genre_count == 3
|
||||||
|
end
|
||||||
|
|
||||||
test "omits genre brackets when genres are empty" do
|
test "omits genre brackets when genres are empty" do
|
||||||
record_with_artist("Radiohead", %{
|
record_with_artist("Radiohead", %{
|
||||||
title: "OK Computer",
|
title: "OK Computer",
|
||||||
@@ -349,7 +398,7 @@ defmodule MusicLibrary.CollectionTest do
|
|||||||
purchased_at: ~U[2024-01-01 00:00:00Z]
|
purchased_at: ~U[2024-01-01 00:00:00Z]
|
||||||
})
|
})
|
||||||
|
|
||||||
summary = Collection.collection_summary()
|
{summary, _count} = Collection.collection_summary()
|
||||||
refute summary =~ "["
|
refute summary =~ "["
|
||||||
refute summary =~ "]"
|
refute summary =~ "]"
|
||||||
end
|
end
|
||||||
@@ -365,7 +414,8 @@ defmodule MusicLibrary.CollectionTest do
|
|||||||
purchased_at: nil
|
purchased_at: nil
|
||||||
})
|
})
|
||||||
|
|
||||||
summary = Collection.collection_summary()
|
{summary, record_count} = Collection.collection_summary()
|
||||||
|
assert record_count == 1
|
||||||
assert summary =~ "OK Computer"
|
assert summary =~ "OK Computer"
|
||||||
refute summary =~ "Wish You Were Here"
|
refute summary =~ "Wish You Were Here"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user