Skip to content

Build1 publisher3 min readPublished

LectuLibre's max_tokens=4096 forces at least 25 calls to translate one novel

A dev.to writeup of the LectuLibre book translation pipeline sets chunks at 3,000 tokens with 500 of overlap and keeps a glossary in Postgres. An output ceiling sets the shape of the job, and it holds however big the input window gets.

The Engineer · Build desk

Illustration accompanying LectuLibre's max_tokens=4096 forces at least 25 calls to translate one novel

What happened

  • A dev.to post documents LectuLibre's book translation pipeline, which parses EPUB or PDF into text with chapter metadata and then translates the result in overlapping chunks with Claude at temperature 0.3.
  • The team's stated reasons for not sending a whole book in one request were cost, models losing attention to early chapters, rate limits and timeouts, and the difficulty of resuming a failed run.
  • Their first splitter, LangChain's RecursiveCharacterTextSplitter, counts characters, and some chunks came out over the model's token limit, so they moved to tiktoken for exact counts.
  • A later post-processing pass moves chunk boundaries to the nearest paragraph break inside the token window, a change the post credits with better translation quality.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint With the per-response cap set at 4,096 tokens, no increase in the input window reduces the request count; how much translated text has to come out sets it.
  • cost Anyone copying this pattern pays for a fifth of the book twice through the overlap, plus two auxiliary model calls per chunk, and gets glossary consistency and restartability for that money.
  • capability Keeping glossary and summary in Postgres makes the job restartable at chunk granularity, so a timeout costs one 3,000-token call rather than a book-length request.
  • contradiction Cost heads the post's list of reasons to chunk, and the token accounting runs the other way: chunking bills more input, so any saving has to come from long calls that would have failed.

Call `translate_chunk` and the model receives a system prompt holding the language pair, a summary of previous chapters, and a glossary rendered as `source -> translation` lines [16]. The chunk text itself goes in the user message [5]. The glossary block instructs the model to "use exactly these translations" [11]. A single 150,000-token prompt carries no equivalent instruction. That line is what holds a character's name steady on page 300.

The same function sets `max_tokens=4096` [10]. A 100,000-token book needs roughly 100,000 tokens of translated output, assuming the target language tokenizes at a similar rate, so at 4,096 tokens per response the job takes at least 25 calls [4]. The advertised 200,000-token window is an input budget [2].

Now the overlap. Chunks are 3,000 tokens with 500 tokens of overlap, so the window advances 2,500 tokens per call [6][1]. At the top of the post's own range for a novel, 150,000 tokens [3], that is 60 chunks, and 60 chunks at 3,000 tokens is 180,000 tokens of input for a 150,000-token book, a fifth of it paid for twice [2][3]. Each chunk also triggers a term-extraction call and a summary call on top of the translation [15]. That is up to 180 requests for one book [5].

The post lists cost first among its reasons not to send a whole book in one call [4], and it does not give a per-book figure for either approach. On tokens alone the chunked run bills more. For it to bill less, the price per input token would have to climb with prompt length, or the single long call would have to fail often enough that repaid inputs swamp the overlap. The post does report rate limits and timeouts as problems [4], and the retry policy is three attempts with exponential backoff capped at ten seconds [13]. Three attempts at 150,000 input tokens is 450,000 tokens for one book [6].

Sizing runs on tiktoken's `cl100k_base`, an encoding built for a different vendor's models, and the code comment says it "approximates well enough for chunk sizing" [8]. At 3,000 tokens against a 200,000-token limit, it does. Approximation bites at the response instead: 4,096 output tokens against a 3,000-token input is 1.36x of headroom [7], which is comfortable when source and target tokenize at similar rates and tight when the target expands.

The part I would keep in any version of this is the state layout. The glossary is a dict stored as JSONB in PostgreSQL and updated after every chunk, and the summary of each translated chunk is stored for the next one [12][15]. A crash costs one chunk. It also serialises the job, because chunk n+1's prompt depends on chunk n's output, so those 60 calls run one at a time; routing simpler passages to DeepSeek [14] changes the price of a call, and the order they have to happen in stays fixed.

What to watch

  • Whether the pipeline publishes per-book cost and quality numbers for the chunked run against a single long-context baseline.
  • Whether prompt caching of the glossary and prior-chapter summary prefix changes the token bill, since that prefix is resent on every chunk.
  • Whether the routing rule that sends simpler passages to DeepSeek is documented, since a second model brings a second tokenizer and its own glossary rendering.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories