OPEN SOURCE
software/src/evaluation/evaluator.py
このサイトで読み、必要なファイルを保存できます。
日常訳はAIを用いて作成された翻訳です。人間の聖書学・原語の専門家による全訳の検証は確認されていません。既存の翻訳や原語資料と照らし合わせてお読みください。
1"""Translation evaluator using LLM-based assessment."""23from __future__ import annotations45import json6from datetime import datetime7from pathlib import Path8from typing import TYPE_CHECKING, Any910from src.models.evaluation import (11DIMENSIONS,12ChapterEvaluation,13EvaluationScore,14VerseEvaluation,15)1617if TYPE_CHECKING:18from src.context.builder import TranslationContext192021def _load_style_config(data_dir: Path | str = "data") -> dict[str, Any]:22"""Load style configuration from JSON file.2324Args:25data_dir: Path to data directory.2627Returns:28Style configuration dictionary.29"""30style_path = Path(data_dir) / "config" / "style.json"31if style_path.exists():32with style_path.open("r", encoding="utf-8") as f:33return json.load(f)34return {}353637def _build_style_criteria(style_config: dict[str, Any]) -> str:38"""Build style-based evaluation criteria from config.3940Args:41style_config: Style configuration dictionary.4243Returns:44Formatted style criteria text for the evaluation prompt.45"""46target_style = style_config.get("target_style_reference", "現代のウェブ記事・ナレーションのような平易な日本語")47formality = style_config.get("formality_level", "standard")48avoid_archaic = style_config.get("avoid_archaic", ["なり", "たり", "候"])49equivalence = style_config.get("equivalence_approach", "dynamic")5051# Map formality level to description52formality_desc = {53"casual": "casual, conversational Japanese",54"standard": "standard, neutral Japanese appropriate for general audiences",55"polite": "warm, friendly です/ます narration (リビングバイブル-like)",56"formal": "formal, elevated Japanese with honorific language",57"liturgical": "liturgical, solemn Japanese suitable for worship",58}.get(formality, "standard Japanese")5960# Map equivalence approach to description61equivalence_desc = {62"formal": "formal equivalence (word-for-word, preserving source structure)",63"dynamic": "dynamic equivalence (thought-for-thought, natural target language)",64"balanced": "balanced approach between formal and dynamic equivalence",65}.get(equivalence, "dynamic equivalence")6667archaic_list = ", ".join(avoid_archaic) if avoid_archaic else "none specified"6869readability = style_config.get("readability_level", "web_native")70audience_line = ""71if readability == "web_native":72audience_line = (73"\n Would a Japanese reader in their 20s who mostly reads online"74" media (news apps, blogs, popular creators) find it effortless —"75" short sentences, everyday vocabulary, no stiff literary wording?"76)7778return f"""7. STYLE ADHERENCE (style_adherence)79Does it match the target style reference: {target_style}?80Does it follow {equivalence_desc}?{audience_line}81828. FORMALITY LEVEL (formality_level)83Does it use {formality_desc}?84Is the register appropriate and consistent?85869. ARCHAIC AVOIDANCE (archaic_avoidance)87Does it avoid archaic forms? (avoid: {archaic_list})88Is modern Japanese used appropriately?"""899091EVALUATION_SYSTEM_PROMPT = """You are an expert evaluator of Japanese Bible translations.92You will assess translations across multiple quality dimensions.9394For each dimension, provide:951. A score from 0-100 (percentage)962. A confidence level from 0-100 (how certain you are)973. A brief reasoning (1-2 sentences)9899Be rigorous but fair. A score of 100 means perfect, 80-90 is excellent,10070-80 is good, 60-70 is acceptable, below 60 needs improvement."""101102103def build_evaluation_prompt(104translation: str,105greek_text: str | None,106kjv_text: str | None,107reference_japanese: str | None,108glossary_terms: list[str] | None = None,109style_config: dict[str, Any] | None = None,110hebrew_text: str | None = None,111surrounding_context: str | None = None,112) -> str:113"""Build the evaluation prompt for a verse.114115Args:116translation: The Japanese translation to evaluate.117greek_text: Original Greek source text (NT).118kjv_text: KJV English reference translation.119reference_japanese: Reference Japanese translation (e.g., Kougo).120glossary_terms: Glossary guidance entries (term → Japanese, with notes).121style_config: Style configuration from style.json.122hebrew_text: Original Hebrew source text (OT).123surrounding_context: Neighboring verses, for judging cohesion.124125Returns:126Formatted evaluation prompt string.127"""128sections = []129130if greek_text:131sections.append(f"ORIGINAL TEXT (Greek):\n{greek_text}")132elif hebrew_text:133sections.append(f"ORIGINAL TEXT (Hebrew):\n{hebrew_text}")134135sections.append(f"TRANSLATION TO EVALUATE:\n{translation}")136137if kjv_text:138sections.append(f"REFERENCE (KJV English):\n{kjv_text}")139140if reference_japanese:141sections.append(f"REFERENCE (Japanese Kougo-yaku):\n{reference_japanese}")142143if surrounding_context:144sections.append(145"SURROUNDING PASSAGE (judge whether the translation reads as a natural,"146" register-consistent continuation of this passage. Lines marked"147" 'reference version' are an older translation shown for flow only —"148" do NOT penalize terminology differences from them; the glossary"149" governs terminology):\n"150f"{surrounding_context}"151)152153if glossary_terms:154sections.append(155"GLOSSARY GUIDANCE (contextual notes take precedence over the bare"156" default rendering):\n" + "\n".join(f"- {g}" for g in glossary_terms)157)158159# Build style criteria section160style_criteria = _build_style_criteria(style_config or {})161162sections.append(f"""163EVALUATE on these dimensions:1641651. THEOLOGICAL ACCURACY (theological_accuracy)166Is the theological meaning preserved without error?167Are doctrinal concepts accurately conveyed?1681692. SOURCE FIDELITY (source_fidelity)170How faithfully does it follow the original source text (Greek/Hebrew)?171Are key words and phrases properly translated?1721733. NATURALNESS (naturalness)174Does it sound like natural modern Japanese?175Would a native speaker find it comfortable to read?176Does it read as a smooth continuation of the surrounding passage?1771784. READABILITY (readability)179Is it easy to understand for contemporary readers?180Is the sentence structure clear?1811825. GLOSSARY CONSISTENCY (glossary_consistency)183Are standard theological terms used correctly?184Is terminology consistent with the glossary?1851866. LITERARY QUALITY (literary_quality)187Is the prose/poetic quality preserved?188Does it maintain appropriate tone and style?189190{style_criteria}191192Respond ONLY with valid JSON in this exact format:193{{194"theological_accuracy": {{"score": N, "confidence": N, "reasoning": "..."}},195"source_fidelity": {{"score": N, "confidence": N, "reasoning": "..."}},196"naturalness": {{"score": N, "confidence": N, "reasoning": "..."}},197"readability": {{"score": N, "confidence": N, "reasoning": "..."}},198"glossary_consistency": {{"score": N, "confidence": N, "reasoning": "..."}},199"literary_quality": {{"score": N, "confidence": N, "reasoning": "..."}},200"style_adherence": {{"score": N, "confidence": N, "reasoning": "..."}},201"formality_level": {{"score": N, "confidence": N, "reasoning": "..."}},202"archaic_avoidance": {{"score": N, "confidence": N, "reasoning": "..."}}203}}""")204205return "\n\n".join(sections)206207208def parse_evaluation_response(response: str) -> dict[str, EvaluationScore]:209"""Parse LLM evaluation response into EvaluationScore objects.210211Args:212response: JSON string from LLM evaluation.213214Returns:215Dictionary mapping dimension names to EvaluationScore objects.216217Raises:218ValueError: If response cannot be parsed.219"""220# Try to extract JSON from response (handle markdown code blocks)221json_str = response.strip()222if "```json" in json_str:223json_str = json_str.split("```json")[1].split("```")[0].strip()224elif "```" in json_str:225json_str = json_str.split("```")[1].split("```")[0].strip()226227try:228data = json.loads(json_str)229except json.JSONDecodeError as e:230raise ValueError(f"Failed to parse evaluation response as JSON: {e}") from e231232scores = {}233for dimension in DIMENSIONS:234if dimension not in data:235raise ValueError(f"Missing dimension in response: {dimension}")236237dim_data = data[dimension]238scores[dimension] = EvaluationScore(239dimension=dimension,240score=float(dim_data["score"]),241confidence=float(dim_data["confidence"]),242reasoning=str(dim_data["reasoning"]),243)244245return scores246247248class TranslationEvaluator:249"""Evaluates translations using LLM-based assessment.250251This class builds evaluation prompts and processes responses.252The actual LLM calls can be made externally (e.g., via Claude API253or directly in conversation with Claude).254"""255256def __init__(257self,258model_name: str = "claude-opus-4-8",259data_dir: Path | str = "data",260) -> None:261"""Initialize the evaluator.262263Args:264model_name: Name of the model used for evaluation.265data_dir: Path to data directory for loading style config.266"""267self.model_name = model_name268self.data_dir = Path(data_dir)269self._style_config: dict[str, Any] | None = None270271@property272def style_config(self) -> dict[str, Any]:273"""Get the style configuration, loading if needed."""274if self._style_config is None:275self._style_config = _load_style_config(self.data_dir)276return self._style_config277278def build_verse_prompt(279self,280translation: str,281context: TranslationContext,282) -> str:283"""Build evaluation prompt for a verse with full context.284285Args:286translation: The Japanese translation to evaluate.287context: TranslationContext with source texts and glossary.288289Returns:290Formatted evaluation prompt.291"""292glossary_terms = []293for term in context.glossary_terms:294entry = f"{term.original} → {term.japanese}"295if term.notes:296entry += f" ({term.notes})"297glossary_terms.append(entry)298299surrounding = self._format_surrounding_context(context)300301return build_evaluation_prompt(302translation=translation,303greek_text=context.target_verse.greek_text,304kjv_text=context.target_verse.english_kjv,305reference_japanese=(306context.target_verse.japanese_kougo307or context.target_verse.japanese_taisho308),309glossary_terms=glossary_terms if glossary_terms else None,310style_config=self.style_config,311hebrew_text=context.target_verse.hebrew_text,312surrounding_context=surrounding,313)314315def _format_surrounding_context(self, context: TranslationContext) -> str | None:316"""Format neighboring verses for cohesion judging.317318Prefers this project's own saved translations of the neighbors (so319internal consistency is judged against the translation under test,320not against a reference version); falls back to Kougo/Taisho/KJV.321"""322from src.data.storage import Storage323324storage = Storage(self.data_dir)325lines = []326for label, verses in (327("before", context.preceding_verses),328("after", context.following_verses),329):330for v in verses:331own = storage.load_translation(v.reference)332if own and own.text:333lines.append(334f"[{label} {v.reference.verse} — this translation] {own.text}"335)336continue337text = v.japanese_kougo or v.japanese_taisho or v.english_kjv338if text:339lines.append(340f"[{label} {v.reference.verse} — reference version] {text}"341)342return "\n".join(lines) if lines else None343344def create_verse_evaluation(345self,346verse_number: int,347scores: dict[str, EvaluationScore],348notes: str | None = None,349) -> VerseEvaluation:350"""Create a VerseEvaluation from scores.351352Args:353verse_number: The verse number.354scores: Dictionary of dimension scores.355notes: Optional notes.356357Returns:358VerseEvaluation object with computed flags.359"""360evaluation = VerseEvaluation(361verse_number=verse_number,362evaluator_model=self.model_name,363timestamp=datetime.utcnow(),364scores=scores,365flags=[],366notes=notes,367)368# Compute and set flags369evaluation.flags = evaluation.compute_flags()370return evaluation371372def create_chapter_evaluation(373self,374book: str,375chapter: int,376verse_evaluations: dict[int, VerseEvaluation],377) -> ChapterEvaluation:378"""Create a ChapterEvaluation from verse evaluations.379380Args:381book: Book name.382chapter: Chapter number.383verse_evaluations: Dictionary mapping verse numbers to evaluations.384385Returns:386ChapterEvaluation object with aggregated scores.387"""388return ChapterEvaluation(389book=book,390chapter=chapter,391evaluator_model=self.model_name,392evaluated_at=datetime.utcnow(),393verses=verse_evaluations,394)395