| | """ |
| | Letter Generator Module |
| | Combines templates with user data to produce final letters. |
| | """ |
| |
|
| | import logging |
| | from typing import Dict, Any, Optional |
| | from .template_loader import TemplateLoader |
| | from .llm_client import MistralClient |
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| | class LetterGenerator: |
| | """ |
| | Handles the generation of letters from templates and user data. |
| | """ |
| | |
| | def __init__(self): |
| | self.loader = TemplateLoader() |
| | try: |
| | self.llm = MistralClient() |
| | except Exception as e: |
| | logger.warning(f"LLM Client could not be initialized: {e}. LLM features will be disabled.") |
| | self.llm = None |
| |
|
| | def generate_letter(self, template_name: str, user_data: Dict[str, str]) -> str: |
| | """ |
| | Generate a letter by filling in a template with user data. |
| | Performs simple substitution. |
| | """ |
| | template_text = self.loader.load_template(template_name) |
| | |
| | |
| | |
| | |
| | |
| | generated_text = template_text |
| | |
| | for key, value in user_data.items(): |
| | |
| | generated_text = generated_text.replace(f"[{key}]", str(value)) |
| | |
| | generated_text = generated_text.replace(f"{{{{{key}}}}}", str(value)) |
| | |
| | generated_text = generated_text.replace(f"<{key}>", str(value)) |
| | |
| | generated_text = generated_text.replace(f"{{{key}}}", str(value)) |
| | |
| | return generated_text |
| |
|
| | def refine_with_llm(self, draft_letter: str, instructions: str = "") -> str: |
| | """ |
| | Use LLM to polish or refine the letter. |
| | """ |
| | if not self.llm: |
| | logger.warning("LLM not available for refinement.") |
| | return draft_letter |
| | |
| | prompt = f""" |
| | You are a helpful legal assistant for Nepal. |
| | Please refine the following letter to be more professional and grammatically correct. |
| | Ensure it remains factual to the original content. |
| | Do not add any fake information. |
| | |
| | Instructions: {instructions} |
| | |
| | Draft Letter: |
| | {draft_letter} |
| | |
| | Refined Letter: |
| | """ |
| | return self.llm.generate_response(prompt) |
| |
|
| | def analyze_requirements(self, description: str) -> Dict[str, Any]: |
| | """ |
| | Analyzes the user description against the best matching template |
| | to identify missing information. |
| | """ |
| | if not self.llm: |
| | raise RuntimeError("LLM required for analysis.") |
| | |
| | from .retriever import TemplateRetriever |
| | |
| | retriever = TemplateRetriever() |
| | retrieved_templates = retriever.retrieve_templates(description, k=1) |
| | |
| | if not retrieved_templates: |
| | return {"success": False, "error": "No relevant template found."} |
| | |
| | best_template = retrieved_templates[0] |
| | template_content = best_template['content'] |
| | template_name = best_template['filename'] |
| | |
| | |
| | placeholders = self.loader.extract_placeholders(template_content) |
| | |
| | if not placeholders: |
| | return { |
| | "success": True, |
| | "template_name": template_name, |
| | "detected_placeholders": [], |
| | "missing_fields": [] |
| | } |
| |
|
| | |
| | prompt = f""" |
| | You are an intelligent assistant. |
| | I have a letter template with the following required placeholders: {list(placeholders)} |
| | |
| | The user provided this description: "{description}" |
| | |
| | Identify which placeholders are MISSING or cannot be inferred from the description. |
| | Return ONLY a comma-separated list of missing placeholders. If none are missing, return "None". |
| | |
| | Missing Placeholders: |
| | """ |
| | response = self.llm.generate_response(prompt, temperature=0.0) |
| | |
| | missing_fields = [] |
| | if "None" not in response: |
| | |
| | cleaned = response.replace("\n", "").strip() |
| | if cleaned: |
| | missing_fields = [f.strip() for f in cleaned.split(",") if f.strip()] |
| | |
| | return { |
| | "success": True, |
| | "template_name": template_name, |
| | "detected_placeholders": list(placeholders), |
| | "missing_fields": missing_fields |
| | } |
| |
|
| | def generate_from_description(self, description: str, additional_data: Dict[str, str] = None, template_name: str = None) -> Dict[str, Any]: |
| | """ |
| | RAG-Based Generation: |
| | 1. Retrieve relevant template based on description (OR use provided template_name). |
| | 2. Use LLM to fill/adapt the retrieved template, incorporating additional data. |
| | """ |
| | if not self.llm: |
| | raise RuntimeError("LLM required for smart generation.") |
| | |
| | best_template = None |
| | retrieval_score = 1.0 |
| | |
| | if template_name: |
| | |
| | try: |
| | content = self.loader.load_template(template_name) |
| | best_template = { |
| | "filename": template_name, |
| | "content": content, |
| | "score": 1.0 |
| | } |
| | logger.info(f"Using specified template: {template_name}") |
| | except Exception as e: |
| | return {"success": False, "error": f"Template '{template_name}' not found: {e}"} |
| | else: |
| | |
| | from .retriever import TemplateRetriever |
| | retriever = TemplateRetriever() |
| | retrieved_templates = retriever.retrieve_templates(description, k=1) |
| | |
| | if not retrieved_templates: |
| | return { |
| | "success": False, |
| | "error": "No relevant template found." |
| | } |
| | best_template = retrieved_templates[0] |
| | retrieval_score = best_template['score'] |
| | |
| | template_content = best_template['content'] |
| | template_name = best_template['filename'] |
| | |
| | logger.info(f"Selected template: {template_name}") |
| | |
| | |
| | additional_info_str = "" |
| | if additional_data: |
| | additional_info_str = "\nAdditional User Details:\n" + "\n".join(f"- {k}: {v}" for k, v in additional_data.items()) |
| | |
| | |
| | prompt = f""" |
| | You are a helpful legal assistant for Nepal. |
| | Your task is to write a formal letter based on the user's description, using the provided template as a strict guide. |
| | |
| | User Description: "{description}" |
| | {additional_info_str} |
| | |
| | Selected Template ({template_name}): |
| | {template_content} |
| | |
| | Instructions: |
| | 1. Use the structure and formal language of the Selected Template. |
| | 2. Fill in the placeholders (like [Name], {{Date}}) with information from the User Description and Additional Details. |
| | 3. If information is still missing, use a generic placeholder like "[Insert Name]". |
| | 4. Output ONLY the final letter in Nepali (or English if the template is English). Do not add conversational text. |
| | |
| | Final Letter: |
| | """ |
| | generated_letter = self.llm.generate_response(prompt, temperature=0.3) |
| | |
| | return { |
| | "success": True, |
| | "letter": generated_letter, |
| | "template_used": template_name, |
| | "retrieval_score": retrieval_score |
| | } |
| |
|