43 lines
1.2 KiB
Elixir
43 lines
1.2 KiB
Elixir
defmodule BDS.AI.JsonContent do
|
|
@moduledoc """
|
|
Decodes JSON object payloads from model responses, tolerating the markdown
|
|
code fences and surrounding prose that smaller (local) models often emit
|
|
instead of bare JSON.
|
|
"""
|
|
|
|
@fence_pattern ~r/```(?:json)?\s*\n?(.*?)```/is
|
|
|
|
@spec decode(term()) :: map() | nil
|
|
def decode(content) when is_binary(content) do
|
|
decode_strict(content) || decode_fenced(content) || decode_embedded_object(content)
|
|
end
|
|
|
|
def decode(_content), do: nil
|
|
|
|
defp decode_strict(content) do
|
|
case Jason.decode(content) do
|
|
{:ok, decoded} when is_map(decoded) -> decoded
|
|
_other -> nil
|
|
end
|
|
end
|
|
|
|
defp decode_fenced(content) do
|
|
case Regex.run(@fence_pattern, content, capture: :all_but_first) do
|
|
[inner] -> decode_strict(String.trim(inner)) || decode_embedded_object(inner)
|
|
_no_fence -> nil
|
|
end
|
|
end
|
|
|
|
defp decode_embedded_object(content) do
|
|
with {start, _length} <- :binary.match(content, "{"),
|
|
[{last, _} | _] <- content |> :binary.matches("}") |> Enum.take(-1),
|
|
true <- last > start do
|
|
content
|
|
|> binary_part(start, last - start + 1)
|
|
|> decode_strict()
|
|
else
|
|
_no_object -> nil
|
|
end
|
|
end
|
|
end
|