Skip to content

BookInsight: "auto" language follows the book's own language#73

Closed
vitofico wants to merge 2 commits into
mainfrom
claude/stoic-mayer-55ba6f
Closed

BookInsight: "auto" language follows the book's own language#73
vitofico wants to merge 2 commits into
mainfrom
claude/stoic-mayer-55ba6f

Conversation

@vitofico

Copy link
Copy Markdown
Owner

What

The AI-insight language style already defaulted to auto, but auto emitted no language clause — the model just picked (effectively English) even for a non-English book. This makes auto write the insight in the book's own language.

How

Prompt resolution (core/ai/prompts.py)

  • auto resolves to the book's metadata language (bundle.language), normalized to ISO 639-1 — folds en-US/engen, and validates two-letter codes against the canonical ISO_639_1_CODES set so bogus tags never reach the model.
  • Explicit user codes still win; auto with no usable book language falls back to the prior language-neutral prompt.
  • Cache-key invariant: the resolved language steers only the prompt body. The book_insights row is still keyed on the style value (auto) — matching what the Android client caches under — so client and server never desync. (Regression-tested.)
  • PROMPT_VERSION bumped 5 → 6 to regenerate stale auto rows (they were generated with no language clause).

Scanned-book backfill (core/ai/retrieval.py, core/ai/service.py)

  • New lookup_book_language(isbn) uses OpenLibrary's edition-level Books API (jscmd=details). Its details.languages is edition-specific — unlike /search.json's language, which is work-level and aggregates every translation a work ever had (verified against the live API), so it can't identify the scanned edition.
  • The server backfills bundle.language only when the bundle has an ISBN but no language and OpenLibrary is enabled, on cache-miss only. It's cached (positive + confirmed-negative, but not transient 429/5xx failures), best-effort, and never fails generation. It never overrides a client-provided language.
  • Library EPUBs already carry <dc:language>, so this is a no-op for them and no Android change is needed — the in-flight ISBN-scan feature (Book scan: camera/ISBN capture → reader-affinity → optional insight (Android) #71) benefits for free since it already sends the ISBN.

Operational note

Bumping PROMPT_VERSION invalidates all cached insights (including explicit-language ones that are technically unaffected), so they regenerate on next open — a one-time generation-quota cost. It's the only cache-bust lever available.

No DB migration: the language column already exists; only its prompt semantics changed.

Tests

348 unit tests pass (new coverage for auto-resolution, ISO normalization/validation, the edition-level lookup, transient-failure non-caching, and the cache-key invariant) plus the AI integration suite. ruff clean.

vitofico added 2 commits May 30, 2026 21:35
The AI insight `language` style already defaulted to `auto`, but `auto`
emitted no language clause at all — the model just picked (effectively
English) even for a non-English book. Make `auto` write the insight in
the book's own language.

Server-side, in the prompt builder:
- `auto` now resolves to the book's metadata language (`bundle.language`),
  normalized to ISO 639-1 (folds `en-US`/`eng` → `en`, validates two-letter
  codes against the canonical set). Explicit user codes still win; `auto`
  with no usable book language falls back to the old language-neutral prompt.
- The resolved language steers ONLY the prompt body. The `book_insights`
  cache row is still keyed on the style value (`auto`), matching what the
  Android client caches under — so client and server never desync.
- Bump PROMPT_VERSION 5 → 6 to regenerate stale `auto` rows.

For scanned books (ISBN, no embedded language) the server backfills the
language from OpenLibrary's edition-level Books API (`jscmd=details`,
whose `details.languages` is edition-specific — unlike `/search.json`'s
work-level `language`, which aggregates every translation a work ever had).
The lookup is cached, best-effort, and never fails generation; it fires
only when the bundle has an ISBN but no language and OpenLibrary is enabled,
and never overrides a client-provided language. Library EPUBs already carry
`<dc:language>`, so this is a no-op for them and no Android change is needed.
- retrieval: don't cache transient OpenLibrary failures (429/5xx) as a null
  language — only cache confirmed 200 responses, so a temporary outage no
  longer suppresses backfill for the whole TTL.
- prompts: validate two-letter book-language tags against ISO_639_1_CODES so
  bogus metadata (e.g. "zz") can't become a prompt instruction.
- service: pass `style or AiStyle()` when composing so a None style (which
  the cache key already treats as "auto") also gets auto-language resolution.
- tests: assert the resolved book language never leaks into the cache key
  (row stays "auto"); cover transient-failure non-caching and bogus 2-letter
  rejection; give the shared FakeRetriever the new protocol method.
- update stale "auto emits no clause" comments; bump the prompt-version
  regression pin 5 -> 6; apply ruff format.
@vitofico vitofico closed this May 30, 2026
@vitofico
vitofico deleted the claude/stoic-mayer-55ba6f branch May 30, 2026 19:45
@vitofico

Copy link
Copy Markdown
Owner Author

Superseded by #74 — this PR auto-closed when the branch was renamed from claude/stoic-mayer-55ba6f to stoic-mayer-55ba6f. Same work continues there.

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