SOURCE EXPLORER / EVIDENCE BROWSER
prompt.py
Inspect the implementation and evidence, line by line.
Nichijoyaku was created using AI. A complete review by human biblical-language and biblical-studies specialists has not been established. Read alongside established translations and original-language resources.
1"""Prompt construction for Bible translation."""23from __future__ import annotations45from typing import TYPE_CHECKING67import os89from src.context.glossary import GlossaryManager10from src.models.pericope import ChunkTranslationContext11from src.models.style import TranslationStyle, build_system_prompt12from src.models.verse import TranslationContext, Verse, VerseReference13from src.pipeline.morphology import select_morphology_notes1415# Morphology exposure measured as quality-neutral in two A/Bs (famous and16# hard-passage sets) — default off to save prompt tokens; enable to experiment.17MORPHOLOGY_ENABLED = os.environ.get("TRANSLATE_BIBLE_MORPH", "0") == "1"181920def _morphology_notes_for(verse: Verse, limit: int = 6) -> list[str]:21"""Morphology notes for a verse, respecting the feature flag."""22if not MORPHOLOGY_ENABLED:23return []24if verse.greek_words:25return select_morphology_notes(verse.greek_words, "greek", limit=limit)26if verse.hebrew_words:27return select_morphology_notes(verse.hebrew_words, "hebrew", limit=limit)28return []2930if TYPE_CHECKING:31from src.context.translation_memory import TranslationMemoryManager32from src.models.translation_memory import TMMatch3334# Default system prompt (for backward compatibility)35SYSTEM_PROMPT = """You are an expert Bible translator creating a modern Japanese translation.3637=== TRANSLATION PHILOSOPHY ===3839EQUIVALENCE APPROACH: Dynamic/Functional40- Prioritize thought-for-thought translation over word-for-word literalism41- Ask "how would a native Japanese speaker naturally express this idea?"42- Preserve the meaning and impact, not necessarily the exact syntax43- Restructure sentences for natural Japanese flow when needed4445GENDER LANGUAGE: Moderate Inclusive46- Use gender-inclusive language for clearly generic references47- ἀδελφοί (adelphoi): render as "兄弟姉妹" when context indicates mixed group48- ἄνθρωπος (anthropos): use "人" or "人々" for generic humanity, not just "男"49- Preserve gender-specific language where the original clearly indicates it5051READABILITY: Web-native (young adults, online-media register)52- Target readers in their 20s whose daily reading is online media53- Short sentences: one idea per sentence54- Everyday vocabulary only; prefer 和語 over stiff literary 漢語55- Keep every concrete content word — modernize the wording, never the content5657THEOLOGICAL TRADITION: Protestant Evangelical58- Follow Protestant canon and interpretive tradition59- Translate key theological terms in line with evangelical understanding60- Examples: δικαιοσύνη as "義" (righteousness), σωτηρία as "救い" (salvation)6162TEXTUAL BASIS: MorphGNT/SBLGNT63- Follow the provided source text without incorporating textual variants64- Do not emend or correct based on other manuscript traditions6566TERM CONSISTENCY: Balanced67- Maintain consistent translation for key theological terms (provided in glossary)68- Allow contextual variation for common words where meaning shifts69- Prioritize natural reading over rigid concordance for non-technical vocabulary7071=== TARGET AUDIENCE ===72The translation should be accessible to young adult Japanese nonbelievers who are not familiar with Christianity.73- Use vocabulary and expressions that require no prior church background74- Explain concepts through natural phrasing rather than relying on traditional Christian terminology75- Avoid insider language that would confuse those unfamiliar with the faith76- HOWEVER: No compromise should be made in conveying the true Biblical meaning of the text77- Accuracy to the original meaning takes absolute priority over accessibility78- When tension exists between accessibility and faithfulness, choose faithfulness7980=== TARGET STYLE ===81Modern natural Japanese (現代のウェブ記事・ナレーションのような平易な日本語)82- Use contemporary vocabulary and grammar83- Avoid archaic expressions (なり、たり、候)84- Narrative, exposition, and poetry: plain form (である体/だ体), as in 新共同訳 and 口語訳85- Polite form (です/ます) ONLY inside quoted speech, when natural for the speaker86- Keep the same register consistently across every verse of a passage87- Orthography: first-person singular is わたし (hiragana, not 私); keep orthography consistent88- Prioritize clarity and accessibility8990=== LITERARY QUALITY (by genre) ===91Match the literary character of the source genre:92- Hebrew poetry: preserve parallel line structure, concrete imagery, quiet rhythm93- Narrative: clear flowing prose; preserve deliberate repetition and refrains94- Epistles: keep logical connectives and argument flow; no fragmenting of one thought95- Sayings/beatitudes: preserve the parallel formula across the whole series9697=== VERSE COHESION ===98A verse is often part of a longer sentence. Use the preceding and following verses99in CONTEXT to make your translation read as a smooth continuation of the passage.100101=== INPUT FORMAT ===102You will receive:1031. Source text (Greek for NT, Hebrew for OT)1042. Reference translations (historical Japanese and English)1053. Surrounding context (preceding and following verses)1064. Glossary terms to use consistently107108=== OUTPUT CONTRACT (strict) ===109Output ONLY the final Japanese translation of the target verse.110- No explanations, notes, romanization, or commentary111- No alternative renderings, no self-corrections, no English of any kind112- Decide on your final wording BEFORE writing; output exactly one rendering"""113114115class PromptBuilder:116"""Builds prompts for translation."""117118def __init__(119self,120glossary: GlossaryManager,121style: TranslationStyle | None = None,122translation_memory: TranslationMemoryManager | None = None,123) -> None:124"""Initialize the prompt builder.125126Args:127glossary: Glossary manager for term formatting.128style: Translation style configuration. If None, uses default.129translation_memory: Translation memory manager for TM matches.130"""131self.glossary = glossary132self._style = style133self._translation_memory = translation_memory134self._system_prompt: str | None = None135136@property137def style(self) -> TranslationStyle:138"""Get the current translation style."""139if self._style is None:140self._style = TranslationStyle()141return self._style142143@style.setter144def style(self, value: TranslationStyle) -> None:145"""Set the translation style and invalidate cached prompt."""146self._style = value147self._system_prompt = None148149def build_prompt(150self,151context: TranslationContext,152tm_matches: list[TMMatch] | None = None,153) -> str:154"""Build a translation prompt from context.155156Args:157context: Full translation context.158tm_matches: Optional list of translation memory matches.159160Returns:161Formatted prompt string for the LLM.162"""163parts = []164165# Source texts section166parts.append(self._format_source_texts(context.target_verse))167168# Selective morphology (key verb forms)169morph_notes = _morphology_notes_for(context.target_verse)170if morph_notes:171parts.append(172"\nMORPHOLOGY (parsing of key verb forms — use to disambiguate"173" tense/aspect, voice, and participles):\n"174+ "\n".join(f" {n}" for n in morph_notes)175)176177# Reference translations section178parts.append(self._format_reference_translations(context.target_verse))179180# Context section181parts.append(self._format_context(context))182183# Translation Memory section (if matches provided)184if tm_matches:185parts.append(self._format_tm_matches(tm_matches))186187# Glossary section188if context.glossary_terms:189parts.append(self._format_glossary(context))190191# Translation instruction192ref = context.target_verse.reference193parts.append(f"\nTRANSLATE: {ref.book.title()} {ref.chapter}:{ref.verse}")194parts.append("Provide only the Japanese translation:")195196return "\n".join(parts)197198def _format_tm_matches(self, matches: list[TMMatch]) -> str:199"""Format translation memory matches for prompt.200201Args:202matches: List of TM matches.203204Returns:205Formatted string for prompt inclusion.206"""207if not matches:208return ""209210lines = ["\nTRANSLATION MEMORY (similar past translations for reference):"]211for match in matches[:3]: # Limit to top 3212similarity_pct = int(match.similarity * 100)213seg = match.segment214lines.append(f" [{similarity_pct}%] {seg.reference}: {seg.target_text}")215216return "\n".join(lines)217218def _format_source_texts(self, verse: Verse) -> str:219"""Format source texts section."""220lines = ["SOURCE TEXT:"]221222if verse.greek_text:223lines.append(f"Greek: {verse.greek_text}")224if verse.greek_translit:225lines.append(f"Romanization: {verse.greek_translit}")226elif verse.hebrew_text:227lines.append(f"Hebrew: {verse.hebrew_text}")228if verse.hebrew_translit:229lines.append(f"Romanization: {verse.hebrew_translit}")230231return "\n".join(lines)232233def _format_reference_translations(self, verse: Verse) -> str:234"""Format reference translations section."""235lines = ["\nREFERENCE TRANSLATIONS:"]236237if verse.japanese_meiji:238lines.append(f"- 明治元訳 (1887): {verse.japanese_meiji}")239if verse.japanese_taisho:240lines.append(f"- 大正改訳 (1917): {verse.japanese_taisho}")241if verse.japanese_kougo:242lines.append(f"- 口語訳 (1955): {verse.japanese_kougo}")243if verse.english_kjv:244lines.append(f"- KJV: {verse.english_kjv}")245if verse.english_asv:246lines.append(f"- ASV: {verse.english_asv}")247248return "\n".join(lines)249250def _format_context(self, context: TranslationContext) -> str:251"""Format context section."""252lines = ["\nCONTEXT:"]253254# Book context255if context.book_metadata:256meta = context.book_metadata257lines.append(258f"Book: {meta.name_english} ({meta.name_japanese}) - {meta.description}"259)260if meta.themes:261lines.append(f"Themes: {', '.join(meta.themes)}")262263# Chapter summary264if context.chapter_summary:265lines.append(f"\nChapter Summary: {context.chapter_summary.summary}")266267# Preceding verses268if context.preceding_verses:269lines.append("\nPreceding verses:")270for v in context.preceding_verses:271text = self._get_best_text(v)272lines.append(f" {v.reference.verse}: {text}")273274# Following verses275if context.following_verses:276lines.append("\nFollowing verses:")277for v in context.following_verses:278text = self._get_best_text(v)279lines.append(f" {v.reference.verse}: {text}")280281return "\n".join(lines)282283def _format_glossary(self, context: TranslationContext) -> str:284"""Format glossary section."""285formatted = self.glossary.format_for_prompt(context.glossary_terms)286return f"\nGLOSSARY (use these terms consistently):\n{formatted}"287288def _get_best_text(self, verse: Verse) -> str:289"""Get the best available text representation for context."""290# Prefer English for context (easier for the model to understand)291if verse.english_kjv:292return verse.english_kjv293if verse.english_asv:294return verse.english_asv295if verse.japanese_kougo:296return verse.japanese_kougo297if verse.japanese_taisho:298return verse.japanese_taisho299if verse.japanese_meiji:300return verse.japanese_meiji301if verse.greek_text:302return verse.greek_text303if verse.hebrew_text:304return verse.hebrew_text305return "[text not available]"306307def get_system_prompt(self) -> str:308"""Get the system prompt for translation.309310Returns system prompt based on the configured style.311Uses cached version if style hasn't changed.312313Returns:314System prompt string.315"""316if self._style is None:317return SYSTEM_PROMPT318319if self._system_prompt is None:320self._system_prompt = build_system_prompt(self._style)321322return self._system_prompt323324def build_system_prompt(self) -> str:325"""Build system prompt from current style configuration.326327This always regenerates the prompt (no caching).328329Returns:330System prompt string.331"""332return build_system_prompt(self.style)333334def build_chunk_prompt(335self,336context: ChunkTranslationContext,337tm_matches: list[TMMatch] | None = None,338preceding_translations: list[tuple[VerseReference, str]] | None = None,339) -> str:340"""Build a chunk/pericope translation prompt.341342Args:343context: Full chunk translation context.344tm_matches: Optional list of translation memory matches.345preceding_translations: This project's own translations of the346immediately preceding verses, as (reference, text) pairs —347the new chunk must continue them seamlessly.348349Returns:350Formatted prompt string for translating an entire pericope.351"""352parts = []353354# Section header355pericope = context.pericope356title = pericope.title or pericope.get_display_range()357parts.append(f"=== PERICOPE: {context.book.title()} {title} ===")358if pericope.theme:359parts.append(f"Theme: {pericope.theme}")360parts.append("")361362# Source texts section (all verses)363parts.append(self._format_chunk_source_texts(context))364365# Selective morphology per verse (key verb forms)366chunk_morph_lines = []367for verse in context.verses:368notes = _morphology_notes_for(verse, limit=3)369if notes:370ref = f"{verse.reference.chapter}:{verse.reference.verse}"371chunk_morph_lines.append(f" [{ref}] " + "; ".join(notes))372if chunk_morph_lines:373parts.append(374"\nMORPHOLOGY (parsing of key verb forms — use to disambiguate"375" tense/aspect, voice, and participles):\n"376+ "\n".join(chunk_morph_lines)377)378379# Reference translations for each verse380parts.append(self._format_chunk_reference_translations(context))381382# Context section383parts.append(self._format_chunk_context(context))384385# Seam continuity: our own translation of the preceding passage386if preceding_translations:387seam_lines = [388"\nPRECEDING TEXT AS ALREADY TRANSLATED (your translation must"389" read as a seamless continuation of this — match its register,"390" terminology, and flow):"391]392for ref, text in preceding_translations:393seam_lines.append(f" [{ref.chapter}:{ref.verse}] {text}")394parts.append("\n".join(seam_lines))395396# Translation Memory section (if matches provided)397if tm_matches:398parts.append(self._format_tm_matches(tm_matches))399400# Glossary section401if context.glossary_terms:402parts.append(self._format_chunk_glossary(context))403404# Translation instruction with JSON output format405parts.append(self._build_chunk_instruction(context))406407return "\n".join(parts)408409def _format_chunk_source_texts(self, context: ChunkTranslationContext) -> str:410"""Format source texts for all verses in chunk."""411lines = ["SOURCE TEXTS:"]412413for verse in context.verses:414ref = f"{verse.reference.chapter}:{verse.reference.verse}"415if verse.greek_text:416lines.append(f"[{ref}] {verse.greek_text}")417elif verse.hebrew_text:418lines.append(f"[{ref}] {verse.hebrew_text}")419420return "\n".join(lines)421422def _format_chunk_reference_translations(423self, context: ChunkTranslationContext424) -> str:425"""Format reference translations for each verse in chunk."""426lines = ["\nREFERENCE TRANSLATIONS:"]427428for verse in context.verses:429ref = f"{verse.reference.chapter}:{verse.reference.verse}"430lines.append(f"\n[{ref}]")431432if verse.japanese_meiji:433lines.append(f" 明治元訳: {verse.japanese_meiji}")434if verse.japanese_taisho:435lines.append(f" 大正改訳: {verse.japanese_taisho}")436if verse.english_kjv:437lines.append(f" KJV: {verse.english_kjv}")438439return "\n".join(lines)440441def _format_chunk_context(self, context: ChunkTranslationContext) -> str:442"""Format context section for chunk translation."""443lines = ["\nCONTEXT:"]444445# Book context446if context.book_metadata:447meta = context.book_metadata448lines.append(449f"Book: {meta.name_english} ({meta.name_japanese}) - {meta.description}"450)451452# Chapter summary453if context.chapter_summary:454lines.append(f"Chapter Summary: {context.chapter_summary.summary}")455456# Preceding context457if context.preceding_verses:458lines.append("\nPreceding context:")459for v in context.preceding_verses:460ref = f"{v.reference.chapter}:{v.reference.verse}"461text = self._get_best_text(v)462lines.append(f" [{ref}] {text}")463464# Following context465if context.following_verses:466lines.append("\nFollowing context:")467for v in context.following_verses:468ref = f"{v.reference.chapter}:{v.reference.verse}"469text = self._get_best_text(v)470lines.append(f" [{ref}] {text}")471472return "\n".join(lines)473474def _format_chunk_glossary(self, context: ChunkTranslationContext) -> str:475"""Format glossary section for chunk translation."""476formatted = self.glossary.format_for_prompt(context.glossary_terms)477return f"\nGLOSSARY (use these terms consistently):\n{formatted}"478479def _build_chunk_instruction(self, context: ChunkTranslationContext) -> str:480"""Build the translation instruction with JSON output format."""481verse_refs = context.pericope.verses482verse_list = ", ".join(f'"{ref}"' for ref in verse_refs)483484return f'''485=== TRANSLATION INSTRUCTIONS ===486487Translate all {len(verse_refs)} verses as a COHESIVE UNIT:488- Read the entire pericope first to understand the flow489- Ensure smooth transitions between verses490- Maintain consistent terminology throughout491- Preserve narrative/argument coherence492493OUTPUT FORMAT (JSON):494Return ONLY a JSON object mapping verse references to translations.495No markdown code blocks, no explanations, just the JSON object.496497Example format:498{{499"1:1": "初めに言葉があった。...",500"1:2": "この言葉は..."501}}502503Translate verses: [{verse_list}]504505Respond with ONLY the JSON object:'''506507def get_chunk_system_prompt(self) -> str:508"""Get system prompt for chunk/pericope translation.509510Uses base style settings but adds chunk-specific instructions.511512Returns:513System prompt string for chunk translation.514"""515base_prompt = self.get_system_prompt()516517chunk_addition = """518519=== CHUNK TRANSLATION MODE ===520You are translating a PERICOPE (cohesive text unit) rather than individual verses.521522Key principles for pericope translation:5231. Translate with narrative/discourse flow in mind5242. Use transitional words naturally (そして、それで、しかし、etc.)5253. Ensure pronouns have clear antecedents across verses5264. Maintain consistent verb tenses within the unit5275. Consider the entire pericope's theme when choosing vocabulary5286. If a "PRECEDING TEXT AS ALREADY TRANSLATED" section is present, your first529verse must read as a seamless continuation of it — same register, same530terminology, natural transition531532Output: JSON object with verse-aligned translations for easy verse lookup.533Each verse should stand alone but flow naturally with adjacent verses."""534535return base_prompt + chunk_addition536