Skip to content

🌐 feat: Translate Docs With Claude Haiku 5.5 and Batch Prefetch - #836

Merged
berry-13 merged 14 commits into
mainfrom
berry-13/feat-model
Oct 18, 2026
Merged

berry-13 merged 14 commits into
mainfrom
berry-13/feat-model

Conversation

@berry-13

@berry-13 berry-13 commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Switches the docs translation model from Gemini 3.1 Flash Lite to Claude Haiku 5.5. At current OpenRouter pricing Haiku is cheaper on both input and output. Thinking is turned off and temperature is no longer sent, because Haiku 5.5 rejects non-default sampling values.

Also adds a --batch mode, which the workflow now uses. A dry pass collects the uncached strings and buys one half-price batch answer per string per locale. Every occurrence of that string uses the answer on its first attempt; validation retries and failed batches run live. OpenRouter cannot cancel a batch, so one still running after the 180-minute wait is not paid for twice. Its strings stay in English for that run, and content/.i18n-cache/batches.json (persisted with the translation cache) lets the next run collect the answers. PROMPT_VERSION is unchanged, so existing translations stay cached.

Change Type

  • New feature (non-breaking change which adds functionality)

Testing

  • pnpm test: 434/434 passed. Batch tests cover once-per-string purchase across files and contexts, retries going live, deferral and collection on the next run, a ledger that survives a crash mid-wait, stalled requests, 403/404 polls, scoped runs, and model overrides.
  • pnpm lint, pnpm lint:prettier, pnpm typecheck: clean.
  • npx tsx scripts/translate.ts --dry-run --locales=de: no skips.
  • Not yet run against the live OpenRouter batch API; the first scheduled workflow run is the check. Its log should show batch: N occurrence(s) answered, and zh/ja/ko output should be spot checked before the translations branch is merged.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code in hard-to-understand areas
  • My changes generate no new warnings
  • I have added tests that prove my feature works

@vercel

vercel Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
librechat-ai Ready Ready Preview Oct 18, 2026 10:30am UTC

Request Review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T10:10:12.043784Z 488e4d3 New commits
🔒 Security Review ✅ Completed 2026-10-17T20:50:44.713669Z be56f38 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be56f38ebb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/i18n/batch.ts Outdated
// Key order matters: OpenRouter rejects a body whose `requests` comes first.
const body = {
endpoint: '/v1/chat/completions',
model: `${opts.model}:batch`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Submit the base model slug to the Batch API

When the workflow uses --batch, this sends anthropic/claude-haiku-5.5:batch as the batch-level model. OpenRouter's Batch API documentation specifies the ordinary model slug here and selects its eligible :batch endpoint internally; supplying the variant suffix causes submission to be rejected. Because the surrounding catch silently falls back to live requests, scheduled runs will lose the intended discount and can spend the remaining job time translating the entire corpus live.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in a8b57ef. The batch-level model is now the plain slug (opts.model), which matches the batch quickstart; OpenRouter picks the :batch endpoint itself. The submit test asserts body.model === 'anthropic/claude-haiku-5.5' and that requests is serialized last.

Comment thread lib/i18n/run.ts
Comment on lines 222 to 225
if (opts.dryRun) {
opts.onMiss?.({ text, locale, kind, context })
stats.translatedBlocks++
return text

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stage dry-run misses before collecting later requests

When one file repeats the same source text in different contexts or kinds, this dry-run branch calls onMiss for every occurrence because it returns without adding a placeholder to ctx.staged. Those requests have different system prompts or context and therefore survive requestKey deduplication, but the real run stages the first translation by hashText(text) and never makes the later model calls. The extra batch rows are still processed and billed even though their answers cannot be consumed, which can multiply translation costs on repetitive pages.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, finally in b901749. A dry-run dedupe alone (a8b57ef) still left two files processed concurrently each sending the same text with a different context. prefetchMisses now buys one answer per string per locale, keyed by locale and text (b894c50), and gives every occurrence its own copy, the same way the TM already shares translations by text. The test buys each string once per locale and serves every occurrence, so nothing runs live uses a frontmatter description that repeats a paragraph, plus a second page sharing that paragraph. It asserts one submitted request per distinct string and zero live calls.

Switch the default translation model from gemini-3.1-flash-lite to
anthropic/claude-haiku-5.5, which lists at $0.10/$0.50 per million tokens.
Drop the Google AI Studio provider pin and flex service tier, which no
Anthropic endpoint can satisfy, stop sending temperature (Haiku 5.5 rejects
non-default sampling values), and turn reasoning off so thinking tokens are
not billed on every block.
Add a --batch mode to the translate script. A silent dry run collects every
request the real run would send, those go to the model's :batch endpoint at
half the live price, and the real run consumes each answer once before
falling back to the live model for validation retries, truncation, and
anything a batch did not answer within TRANSLATE_BATCH_WAIT_MINUTES.
Batch-side failures never fail the run; they only cost the live price.
The translate workflow now runs in batch mode.
…y-run misses

OpenRouter selects the :batch endpoint from the plain model slug and rejects the suffixed one, which silently sent every request live. A dry run also reported a repeated source text once per occurrence, so batch rows were billed for repeats the real run serves from the staged map. Batch requests now carry the shared OpenRouter attribution headers.
OpenRouter cannot cancel a batch, so running its requests live after the wait billed them twice. Batches still running are now kept in a ledger in the translation cache; their blocks stay in English and uncached for this run, and the next run collects the answers instead of resubmitting them.
…s they are submitted

Files processed concurrently can miss the same text before either commits it, with requests that differ only by context; the batch now buys one answer per string and serves a copy to every occurrence, as the TM already shares translations by text. The ledger is saved after each submit so a run that dies during the wait still hands its batches on.
… settings

A retry now carries its attempt number, so the prefetched model serves only a block's first request and never hands back another copy of an answer that just failed validation. Batch size, wait, and poll interval fall back to their defaults unless positive, so a bad value cannot loop forever or poll without pause.
…them

The ledger remembered each batch by the request of the string's first occurrence, whose context can differ from run to run, so a later run could resubmit a string a running batch was already answering.
…re no longer needed

A run that died after collecting a batch but before caching its answers left nothing for the next run to find, so it paid for those strings again.
A submit or status call that never answered kept the wait loop from reaching its deadline.
…y the remaining wait

A run scoped with --only or --locales dropped batches it did not need, so a later full run paid for them again; such entries now stay in the ledger past the 24h completion window. Each batch request is also capped by the time left in the wait so stalled calls cannot add up past it.
… be read

Running with a model override wiped the ledger entries of the default model, and a batch whose status returned a permanent 4xx such as 403 stayed pending forever, keeping its strings untranslated on every run.
@berry-13
berry-13 force-pushed the berry-13/feat-model branch from be56f38 to 488e4d3 Compare October 18, 2026 10:07
@github-actions

Copy link
Copy Markdown
Contributor

📦 Next.js Bundle Analysis for librechat.ai

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

@berry-13
berry-13 merged commit c35ca29 into main Oct 18, 2026
5 checks passed
@berry-13
berry-13 deleted the berry-13/feat-model branch October 18, 2026 12:27

This branch was successfully deployed

1 active deployment
Preview — 488e4d34 Deployed Oct 18, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant