Skip to content

Fix: Complete Multi-Language Support for React-to-Me (#104) - #140

Closed
bleedblack1 wants to merge 1 commit into
reactome:mainfrom
bleedblack1:multi_language
Closed

Fix: Complete Multi-Language Support for React-to-Me (#104)#140
bleedblack1 wants to merge 1 commit into
reactome:mainfrom
bleedblack1:multi_language

Conversation

@bleedblack1

Copy link
Copy Markdown

This PR resolves an issue where the React-to-Me chatbot always responded in English, even when users submitted questions in other languages.

The root cause was that the detected language from the language detection pipeline was never passed to the final generation step. As a result, the LLM only received English inputs and produced English outputs.

This change ensures the chatbot:

  • Detects the user's language
  • Uses English queries for retrieval
  • Generates the final response in the user’s detected language

The fix maintains retrieval quality while enabling full multilingual response support.


Problem

The chatbot pipeline already contained language detection and query rephrasing, but the information flow stopped before the generation stage.

Current behavior:

  1. User question is processed
  2. Language detection stores the detected language
  3. Query is translated to English for retrieval
  4. The generation step ignores the detected language

Because the model receives English queries and English context, responses are always generated in English.


Solution

The fix introduces an English-for-Search, Native-for-Response strategy.

Pipeline after this change:

User Input (any language)
      ↓
Language Detection
      ↓
Translate query to English
      ↓
Hybrid Retrieval (English index)
      ↓
LLM generates response in detected language

Retrieval continues to use English queries for optimal embedding similarity, while the response language follows the detected language.


Implementation Details

1. React-to-Me Profile Fix

File updated:

src/agent/profiles/react_to_me.py

The generation step now retrieves the detected language and injects a response instruction when the language is not English.

Example logic:

query = state["rephrased_input"]
detected_language = state.get("detected_language", "English")

if detected_language.lower() != "english":
    query = f"{query}\n\n[CRITICAL INSTRUCTION: respond in {detected_language}]"

This instruction is appended to the query before invoking the RAG chain.

This approach avoids modifying the RAG chain architecture while ensuring the LLM follows the correct output language.


2. Prompt Reinforcement

File updated:

src/retrievers/reactome/prompt.py

The system prompt now explicitly acknowledges that language instructions may appear in the query and must be followed.

This acts as a secondary safeguard to ensure consistent multilingual responses.


3. Rephrase Task Clarification

File updated:

src/agent/tasks/rephrase.py

The prompt documentation now explains the rationale behind always returning English queries.

This clarification helps future contributors understand that English queries are required because:

  • embeddings are English
  • the document index is English
  • retrieval quality depends on English inputs

4. Cross-Database Summarization Improvements

File updated:

src/agent/tasks/cross_database/summarize_reactome_uniprot.py

Updates include:

  • Typo corrections
  • Improved language instruction clarity
  • Corrected instruction numbering

The updated prompt enforces consistent language output while preserving scientific terminology.


Scientific Terminology Preservation

The LLM is instructed not to translate scientific identifiers, including:

  • gene symbols
  • protein names
  • Reactome pathway IDs
  • database URLs

Examples remain unchanged:

TP53
R-HSA-109581
https://reactome.org

This ensures scientific accuracy across languages.


Impact

English users

  • No change in behavior.

Non-English users

  • Responses are now generated in their native language.

Retrieval system

  • No changes to embeddings or document index.

Streaming responses

  • Unaffected.

Result

This change enables true multilingual responses in the React-to-Me chatbot while preserving the existing RAG retrieval architecture and maintaining search accuracy.

Fix: #104

r2m

adamjohnwright added a commit that referenced this pull request Sep 10, 2026
Adam pushed back that 0/10 looked too low, and he was right.

That number came from running BM25 directly on the polluted string.
BM25 never sees that string: HybridRetriever expands the query into
four LLM-generated alternates and appends the original last, so four of
five queries reach BM25 clean and the fusion recovers most of the
damage. Measured through the whole retriever, 20 of 40 and 21 of 40
documents survive.

Half the retrieved context silently differing for non-English users is
still reason enough to reject appending the instruction to `input`. But
the honest number is half, not all, and the spec now argues from it.

The review that produced the wrong number checked whether the claim was
true without checking whether the test measured the product -- the same
failure evaluator.py had four days ago. Recorded in the checklist,
because an adversarial review has to attack the measurement as well as
the claim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamjohnwright

Copy link
Copy Markdown
Contributor

Thank you @bleedblack1 — and sorry this sat so long.

This is implemented in #205, and the instruction wording is yours, kept nearly verbatim in src/agent/tasks/language_instruction.py.

You got the hard part right, and it is the part that is easy to miss: the retrieved context is English and must stay English, and scientific nomenclature inside the answer must not be translated either. SET, MAX and CAT are gene symbols and also ordinary English words; R-HSA-9612973 means nothing translated. Both are now protected because you spotted it.

You also picked the right profile. React-to-Me was the one with the gap — Cross-Database already worked, which made it easy to assume the feature existed.

What I changed, and why — with the number

The one thing I did not take is where the instruction goes. Your version appends it to input:

query = f"{query}\n\n[CRITICAL INSTRUCTION: ...]"
result = await self.reactome_rag.ainvoke({"input": query, ...})

create_retrieval_chain passes input straight to the retriever:

retrieval_docs = (lambda x: x["input"]) | retriever

So those 61 words become part of the BM25 query and the embedded vector, and they also reach the query expansion that runs in front of both.

I measured it rather than assuming. Through the whole retriever, appending the block to an English question leaves 20 of 40 and 21 of 40 documents surviving — about half the retrieved context silently changes, for exactly the users the feature is meant to help.

(My first measurement said 0 of 10, which was wrong: that tested BM25 directly, and BM25 never sees the raw string because the expander rewrites the query first. Adam pushed back that the number looked too low, and he was right. Recording it because you might reasonably have wondered where a dramatic figure came from.)

In #205 the language is a separate prompt variable, so input is untouched and the retrieval query is byte-identical across languages — there is a test asserting exactly that.

The full reasoning is in specs/004-answer-in-user-language/spec.md. Closing as implemented — thank you for finding it, and for the nomenclature rule especially.

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.

RAG only responds in English

2 participants