|
|
""" |
|
|
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 |
|
|
} |
|
|
|