feat(bm25): configurable native language + opt-in pgroonga backend - #1538
Merged
Conversation
nicoloboschi
marked this pull request as draft
May 8, 2026 10:28
nicoloboschi
force-pushed
the
multilingual-bm25
branch
from
May 26, 2026 08:13
ee3b17b to
347e9fd
Compare
Adds two new env-level config knobs and a new opt-in BM25 backend so users
can serve non-English banks (especially CJK) out of the box.
- HINDSIGHT_API_BM25_LANGUAGE drives the PostgreSQL text search dictionary
used by the native tsvector backend (default: english). Validated as a
PG identifier so it can be safely embedded in to_tsvector('<lang>', ...).
- HINDSIGHT_API_RETAIN_OUTPUT_LANGUAGE forces the fact extractor to emit
facts in the specified language regardless of source content's language.
Independent from bm25_language so users can mix indexing/extraction
languages deliberately.
- New 'pgroonga' option for HINDSIGHT_API_TEXT_SEARCH_EXTENSION. Uses
TokenBigram + NormalizerNFKC150 — single polyglot index handles English,
CJK, etc. simultaneously. Ships with a docker-compose recipe.
To support a per-deployment language, the GENERATED ALWAYS expression on
memory_units.search_vector (and reflections.search_vector) is dropped via
new alembic migration p4q5r6s7t8u9. The application now populates these
columns at INSERT time using the configured bm25_language.
…ontent to dedicated page - Rename HINDSIGHT_API_BM25_LANGUAGE → HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGE. The setting only applies to the "native" backend (vchord/pg_textsearch/pgroonga use their own tokenizers), so the env var name now reflects that scope. Field renamed to text_search_extension_native_language. - Trim configuration.md back to a brief env-var table + link. The expanded multilingual / CJK / pgroonga content moves to the dedicated multilingual.md page, alongside the existing LLM / embedding / reranker multilingual guidance.
…lidation + reflect
Renames HINDSIGHT_API_RETAIN_OUTPUT_LANGUAGE → HINDSIGHT_API_LLM_OUTPUT_LANGUAGE
(field llm_output_language) and applies the same "respond exclusively in {lang}"
directive across every LLM-generated artifact:
- retain (fact extraction) — already wired, just renamed.
- consolidation (observations / mental models) — appended to the batch
consolidation prompt via a new llm_output_language parameter.
- reflect (response synthesis) — appended to the final-system prompt via a
new parameter threaded through run_reflect_agent and memory_engine.
The shared directive lives in engine/prompt_utils.output_language_directive
so all three pipelines build the same instruction from a single source.
nicoloboschi
force-pushed
the
multilingual-bm25
branch
from
May 26, 2026 10:16
412fdc0 to
4170ca9
Compare
nicoloboschi
marked this pull request as ready for review
May 26, 2026 12:23
nicoloboschi
added a commit
that referenced
this pull request
May 26, 2026
Rebasing onto main pulled in hindsight-docs/ changes from #1704 (Codex OAuth embeddings) and #1538 (pgroonga). Re-run the generate-docs-skill.sh generator so the cached skills/hindsight-docs/references/developer/configuration.md mirror matches the current developer docs and verify-generated-files passes.
nicoloboschi
added a commit
that referenced
this pull request
May 26, 2026
…1755) * feat(api): add ParadeDB pg_search as Citus-compatible BM25 backend Adds a fourth value (`pg_search`) for `HINDSIGHT_API_TEXT_SEARCH_EXTENSION` alongside the existing `native`, `vchord`, and `pg_textsearch`. ParadeDB pg_search is the only true-BM25 backend that works on a Citus distributed Postgres cluster, so this unblocks horizontally scaled deployments. The retrieval arm builds the @@@ predicate via paradedb.boolean(should => ARRAY[paradedb.match('text', $4), ...]) since @@@ on the key_field requires field-qualified terms; this preserves multi-field coverage (text + context + text_signals) without needing query string interpolation. Includes a docker-compose example under docker/docker-compose/pg_search/ based on the official paradedb/paradedb:latest-pg17 image. Closes #1754 * fix: accept pgroonga in n9i0 migration; clarify consolidator search_vector comment - n9i0 (learnings + pinned_reflections) validation now permits 'pgroonga', treating it as native at this migration stage. ensure_text_search_extension() at startup converts the reflections table (renamed from pinned_reflections in p1k2l3m4n5o6) to pgroonga structures; the learnings table is dropped in the same later migration so its transient native column never reaches steady state. Without this, pgroonga users hit ValueError on a fresh install. - consolidator.py single-observation INSERT: the previous comment claimed search_vector was GENERATED ALWAYS, but migration p4q5r6s7t8u9 dropped that expression. Updated to reflect current behavior and flag the resulting gap for native (observations land with NULL search_vector and are not BM25- searchable until reflected/re-ingested) so a follow-up can address it. * chore: regenerate hindsight-docs skill after rebase Rebasing onto main pulled in hindsight-docs/ changes from #1704 (Codex OAuth embeddings) and #1538 (pgroonga). Re-run the generate-docs-skill.sh generator so the cached skills/hindsight-docs/references/developer/configuration.md mirror matches the current developer docs and verify-generated-files passes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes Hindsight's BM25 indexing language configurable, adds
pgroongaas a fourth opt-in text-search backend, and adds a single knob to force the LLM output language across every LLM-driven pipeline. The goal is to serve non-English banks (especially CJK) out of the box.Three new env vars
HINDSIGHT_API_TEXT_SEARCH_EXTENSION_NATIVE_LANGUAGEenglishnativebackend into_tsvector/to_tsquery. Validated as a PG identifier so it can be embedded safely in raw SQL. Users withzhparseretc. can point to custom dictionaries.HINDSIGHT_API_TEXT_SEARCH_EXTENSIONnativepgroonga(fourth backend). pgroonga'sTokenBigrampolyglot tokenizer +NormalizerNFKC150Unicode normalization handles English, French, Japanese, Chinese, etc. in a single index. Docker recipe atdocker/docker-compose/pgroonga/.HINDSIGHT_API_LLM_OUTPUT_LANGUAGEThe two language knobs are deliberately independent — search tokenization and LLM output language are separate concerns. Common patterns are documented on the Multilingual Support page.
Schema change
New migration
p4q5r6s7t8u9_configurable_bm25_languagedrops theGENERATED ALWAYSexpression onmemory_units.search_vector(andreflections.search_vector), turning them into regular tsvector columns. The application now populates them at INSERT time viato_tsvector(:lang, ...)using the configured native language. Existing rows retain their English-derived lexemes — switching languages only affects newly-written rows. A backfill SQL snippet is documented for users who need to re-index existing data.The initial migration's
_detect_text_search_extensionwas extended to acceptpgroonga(returningnativeso the migration still creates valid columns);ensure_text_search_extension()at startup converts the schema to pgroonga's structure on first boot, installing the extension if necessary.LLM output language wiring
A single shared helper
engine.prompt_utils.output_language_directive(language)is consumed by all three pipelines so the directive is identical everywhere:_build_extraction_prompt_and_schema.llm_output_languageparam onbuild_batch_consolidation_prompt.llm_output_languageparam onbuild_final_system_prompt, threaded throughrun_reflect_agentandMemoryEngine.Documentation
The multilingual story is consolidated on
hindsight-docs/docs/developer/multilingual.mdalongside the existing LLM / embedding / reranker guidance.configuration.mdis a brief env-var table that links out. Backend-choice patterns ("CJK-out-of-the-box → pgroonga", "single-language bank → native + language", "cross-lingual unification → llm_output_language=English") are written up there.Test plan
tests/test_config_validation.py— defaults, lowercasing, PG-identifier rejection, pgroonga accepted,llm_output_languageempty-string handling.tests/test_db_abstraction.py— native arm honours configured language, pgroonga branch uses&@~+pgroonga_score, pgroonga ignores the native language knob.tests/test_multilingual_bm25.py—output_language_directivehelper, per-pipeline directive injection (retain, consolidation, reflect),str.formatsafety for the consolidation prompt, migration shape regression.tests/test_migration_shape.py,tests/test_alembic_dag.py,tests/test_admin_backup_restore.py)../scripts/hooks/lint.shclean.CI failures unrelated to this PR
The current CI run has 5 failures that all reproduce on a clean
origin/maincheckout:test-api→test_hierarchical_fields_categorizationcount assertion (35 vs 36) — pre-existing breakage from feat(api): add targeted consolidation by observation scopes #1746 which addedconsolidation_max_memories_per_roundto_CONFIGURABLE_FIELDSwithout updating the count.test-rust-cli/test-embed-windows/test-doc-examples (cli)— all the same Rust compilation error (E0061: this method takes 3 arguments but 2 arguments were supplied) inhindsight-cli. I touched no Rust / API / OpenAPI files.LLM acceptance (vertexai)—test_llm_api_methods: known Vertex/Gemini flake (LLM occasionally doesn't emit the expected tool call).