muhammadbinmurtza
Restructure: clauseguard as package subfolder, app_file: clauseguard/app.py
913a064 | """Agent 4: Translator — writes plain English explanations and negotiation support.""" | |
| import json | |
| import logging | |
| from typing import List | |
| from clauseguard.config.prompts import TRANSLATOR_SYSTEM_PROMPT | |
| from clauseguard.models.clause import Clause, ClauseType | |
| from clauseguard.models.findings import RiskFinding, ScoredClause, Severity | |
| from clauseguard.services.model_service import call_model, clean_json_response | |
| logger = logging.getLogger(__name__) | |
| MAX_RETRIES = 1 | |
| async def run_translator(scored_clauses: List[ScoredClause]) -> List[ScoredClause]: | |
| """Translate legal clauses into plain English and write actionable recommendations. | |
| Args: | |
| scored_clauses: A list of ScoredClause objects from the Risk Scorer. | |
| Returns: | |
| Updated ScoredClause list with plain_english and recommended_action filled in. | |
| """ | |
| scored_json = [ | |
| { | |
| "clause": sc.clause.model_dump(), | |
| "finding": sc.finding.model_dump(), | |
| } | |
| for sc in scored_clauses | |
| ] | |
| input_json = json.dumps(scored_json, indent=2) | |
| content = await call_model( | |
| system_prompt=TRANSLATOR_SYSTEM_PROMPT, | |
| user_prompt=f"Translate these clauses into plain English:\n{input_json}", | |
| agent_name="Translator", | |
| max_retries=MAX_RETRIES, | |
| ) | |
| if content is None: | |
| logger.warning("Translator produced no valid output, returning original clauses") | |
| return scored_clauses | |
| return _parse_response(content, scored_clauses) | |
| def _parse_response(content: str, original: List[ScoredClause]) -> List[ScoredClause]: | |
| """Parse translator response and merge plain_english + actions into originals.""" | |
| cleaned = clean_json_response(content) | |
| data = json.loads(cleaned) | |
| items = data if isinstance(data, list) else [data] | |
| result: List[ScoredClause] = [] | |
| for i, item in enumerate(items): | |
| clause_data = item.get("clause", {}) | |
| finding_data = item.get("finding", {}) | |
| plain_english = clause_data.get("plain_english") | |
| recommended_action = finding_data.get("recommended_action", "") | |
| negotiation_tip = finding_data.get("negotiation_tip", "") | |
| safer_clause_version = finding_data.get("safer_clause_version", "") | |
| negotiation_message = finding_data.get("negotiation_message", "") | |
| impact_scenarios = finding_data.get("impact_scenarios", []) | |
| if i < len(original): | |
| orig = original[i] | |
| clause = orig.clause.model_copy(update={"plain_english": plain_english}) | |
| finding_updates = {"recommended_action": recommended_action} | |
| if negotiation_tip: | |
| finding_updates["negotiation_tip"] = negotiation_tip | |
| if safer_clause_version: | |
| finding_updates["safer_clause_version"] = safer_clause_version | |
| if negotiation_message: | |
| finding_updates["negotiation_message"] = negotiation_message | |
| if impact_scenarios: | |
| finding_updates["impact_scenarios"] = impact_scenarios | |
| finding = orig.finding.model_copy(update=finding_updates) | |
| result.append(ScoredClause(clause=clause, finding=finding)) | |
| else: | |
| result.append(_build_scored_clause_from_data(clause_data, finding_data)) | |
| return result | |
| def _build_scored_clause_from_data(clause_data: dict, finding_data: dict) -> ScoredClause: | |
| """Build a ScoredClause from raw LLM response data.""" | |
| clause_type_raw = clause_data.get("clause_type", "OTHER") | |
| try: | |
| clause_type = ClauseType(clause_type_raw) | |
| except ValueError: | |
| clause_type = ClauseType.OTHER | |
| severity_raw = finding_data.get("severity", "INFO") | |
| try: | |
| severity = Severity(severity_raw) | |
| except ValueError: | |
| severity = Severity.INFO | |
| clause = Clause( | |
| id=clause_data.get("id", 0), | |
| raw_text=clause_data.get("raw_text", ""), | |
| plain_english=clause_data.get("plain_english"), | |
| clause_type=clause_type, | |
| section_heading=clause_data.get("section_heading"), | |
| position=clause_data.get("position", 0), | |
| confidence_score=clause_data.get("confidence_score"), | |
| ) | |
| finding = RiskFinding( | |
| clause_id=finding_data.get("clause_id", clause.id), | |
| severity=severity, | |
| risk_title=finding_data.get("risk_title", "Risk Identified"), | |
| risk_reason=finding_data.get("risk_reason", ""), | |
| recommended_action=finding_data.get("recommended_action", ""), | |
| negotiation_tip=finding_data.get("negotiation_tip", ""), | |
| safer_clause_version=finding_data.get("safer_clause_version", ""), | |
| negotiation_message=finding_data.get("negotiation_message", ""), | |
| impact_scenarios=finding_data.get("impact_scenarios", []), | |
| ) | |
| return ScoredClause(clause=clause, finding=finding) | |