wanton7

wanton7

Optimal Azure AI translator API call batching

Can someone give ideas how could I create optimal batches for Azure AI translator calls?

Limits are max 50000 characters max per batch and max 1000 strings per batch. Then there can be x amount destination languages that multiplies characters per language. So string with 100 characters will take space of 200 characters example if you use two destination languages. If you have one string with length 50000 you can only have destination language. These destination languages are defined per batch. So example if I had 6 destination languages I could split them to two batches of 3 languages each and so on.

Marked As Solved

dimitarvp

dimitarvp

So maybe this?

defmodule AzureTranslate do
  def demo() do
    data = [
      {"text 1", 16_000, ~w(en ja za ru)},
      {"text 2", 25_000, ~w(de es)},
      {"text 3", 10_000, ~w(bg ru de sp es za)}
    ]

    requests_for_multiple_texts(50_000, data)
  end

  def requests_for_multiple_texts(max_request_length, texts) do
    texts
    |> Enum.map(fn {text_key, text_length, languages} ->
      {text_key, requests_for_single_text_via_reduce(max_request_length, text_length, languages)}
    end)
  end

  def requests_for_single_text_via_chunk(max_request_length, text_length, languages) do
    languages
    |> Enum.chunk_while(
      {max_request_length, []},
      fn lang, {remaining_bytes, chunk} ->
        if remaining_bytes >= text_length do
          {:cont, {remaining_bytes - text_length, [{text_length, lang} | chunk]}}
        else
          {:cont, Enum.reverse(chunk), {max_request_length - text_length, [{text_length, lang}]}}
        end
      end,
      fn {_remaining_bytes, list} ->
        {:cont, Enum.reverse(list), {}}
      end
    )
  end

  def requests_for_single_text_via_reduce(max_request_length, text_length, languages) do
    {_remaining_bytes, chunk, final_result} =
      languages
      |> Enum.reduce(
        {max_request_length, [], []},
        fn lang, {remaining_bytes, chunk, final_result} ->
          if remaining_bytes >= text_length do
            {remaining_bytes - text_length, [{text_length, lang} | chunk], final_result}
          else
            {max_request_length - text_length, [{text_length, lang}],
             [Enum.reverse(chunk) | final_result]}
          end
        end
      )

    Enum.reverse([Enum.reverse(chunk) | final_result])
  end

  def benchmark() do
    Benchee.run(%{
      "Enum.chunk_while" => fn ->
        requests_for_single_text_via_chunk(50_000, 16_000, ~w(de fr es it pt))
      end,
      "Enum.reduce" => fn ->
        requests_for_single_text_via_reduce(50_000, 16_000, ~w(de fr es it pt))
      end
    })
  end
end

Included:

  • Two alternative implementations of an algorithm to get a singular text and break it apart on several requests;
  • One function that uses the faster of both implementations (requests_for_multiple_texts);
  • Related to above: run the benchmark function (make a small new Elixir project and just include benchee in it, or use Mix.install in a single .exs file ) and you’ll see for yourself which of both is faster. SPOILERS: it’s the one using Enum.reduce. I could probably make an even faster one but wasn’t in the mood to make one that uses only pure recursion and nothing else;
  • Demo data + demo function that, ahem, demonstrates its correctness with an example.

The output might be slightly cryptic, so explaining it:

You get a list of tuples: first element is the text key (or the text itself), the second one is a list of lists of tuples.

The list of tuples represent a single request which might have e.g. same text with 5 languages. Each text here is represented by its length, not the key / text itself. Got tired and didn’t want to bloat the functions further with one more piece of data. :person_shrugging:

The list that encompasses that list of tuples is the list of requests that must be made for this single text to get fully translated.

Not sure if the code is good but it gets the job done. With the demo data above the result is:

[
  {"text 1", [[{16000, "en"}, {16000, "ja"}, {16000, "za"}], [{16000, "ru"}]]},
  {"text 2", [[{25000, "de"}, {25000, "es"}]]},
  {"text 3",
   [
     [{10000, "bg"}, {10000, "ru"}, {10000, "de"}, {10000, "sp"}, {10000, "es"}],
     [{10000, "za"}]
   ]}
]

…which means:

  • "text 1" gets to do 2 requests: one with 3 languages and one with 1 language;
  • "text 2" gets to do 1 request with 2 languages;
  • "text 3" gets to do 2 requests: one with 5 languages and one with 1 language.

Where Next?

Popular in Questions Top

dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
logicmason
Hi there, I'm working through my first release with elixir/phoenix. I've built a release with distillery and found that it crashes when I...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
Tee
can someone please explain to me how Enum.reduce works with maps
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New

We're in Beta

About us Mission Statement