Backend: thread on_fallback callback through analysis chain (gemini_service → agents → analysis_service → handlers). The handler sends a 'model_fallback' WebSocket message exactly once per analysis when the primary model is unavailable. Frontend: handle 'model_fallback' WS message and show a dismissible yellow toast at the bottom of the screen with an 8-second auto-dismiss. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
181 lines
9.3 KiB
Python
Executable file
181 lines
9.3 KiB
Python
Executable file
from typing import List, Optional, Tuple
|
|
|
|
from app.agents.base_agent import BaseAgent
|
|
from app.models.schemas import PreviousReviewContext, SubReview
|
|
from app.services.gemini_service import GeminiService
|
|
from app.services.reference_docs import ReferenceDocsService
|
|
|
|
|
|
class LegalAgent(BaseAgent):
|
|
"""Legal Agent - analyzes proofs for legal compliance using Gemini."""
|
|
|
|
name = "Legal Agent"
|
|
|
|
def __init__(self, gemini_service: GeminiService, reference_docs: ReferenceDocsService):
|
|
"""
|
|
Initialize the Legal Agent.
|
|
|
|
Args:
|
|
gemini_service: Service for making Gemini API calls
|
|
reference_docs: Service for loading reference documents
|
|
"""
|
|
self.gemini = gemini_service
|
|
self.reference_docs = reference_docs
|
|
|
|
def _build_revision_context(self, previous_review: PreviousReviewContext) -> str:
|
|
"""Build prompt section for revision-aware analysis."""
|
|
issues_list = "\n".join(f" - {issue}" for issue in previous_review.issues) if previous_review.issues else " (No issues)"
|
|
return f"""
|
|
---
|
|
|
|
**REVISION CONTEXT**
|
|
|
|
This is a revision of a previously reviewed proof. The previous version (Version {previous_review.version}) had the following legal review:
|
|
|
|
- RAG Status: {previous_review.ragStatus}
|
|
- Feedback: {previous_review.feedback}
|
|
- Issues identified:
|
|
{issues_list}
|
|
|
|
When analyzing this revision, you MUST:
|
|
1. Compare against the previous issues and determine which have been RESOLVED
|
|
2. Identify which previous issues are still OUTSTANDING (not fixed)
|
|
3. Identify any NEW issues introduced in this revision
|
|
|
|
Your response MUST include:
|
|
- resolvedIssues: Array of issues from the previous version that have been fixed
|
|
- outstandingIssues: Array of issues from the previous version that remain unfixed
|
|
- newIssues: Array of new issues not present in the previous version
|
|
|
|
---
|
|
"""
|
|
|
|
async def analyze(
|
|
self,
|
|
images: List[Tuple[bytes, str]],
|
|
previous_review: Optional[PreviousReviewContext] = None,
|
|
channel: Optional[str] = None,
|
|
sub_channel: Optional[str] = None,
|
|
proof_type: Optional[str] = None,
|
|
on_fallback=None,
|
|
) -> SubReview:
|
|
"""
|
|
Analyze the proof for legal compliance.
|
|
|
|
Args:
|
|
images: List of (file_data, mime_type) tuples representing the proof
|
|
previous_review: Optional context from previous version for revision-aware analysis
|
|
channel: Target channel (e.g. "Social", "Digital")
|
|
sub_channel: Target sub-channel (e.g. "Meta", "Google")
|
|
proof_type: Proof format type (e.g. "In-feed 1x1", "Banner")
|
|
|
|
Returns:
|
|
SubReview with legal compliance assessment
|
|
"""
|
|
# Get the legal specification
|
|
legal_context = self.reference_docs.get_legal_spec()
|
|
|
|
# Build revision context if available
|
|
revision_context = ""
|
|
if previous_review:
|
|
revision_context = self._build_revision_context(previous_review)
|
|
|
|
prompt = f"""You are a legal compliance specialist for Barclays Bank. Your role is to analyze marketing proofs for legal compliance, advertising standards, and regulatory requirements.
|
|
|
|
Here are the legal guidelines to use for your analysis:
|
|
|
|
{legal_context}
|
|
{revision_context}
|
|
---
|
|
|
|
**PROOF METADATA**
|
|
- Channel: {channel or "Not specified"}
|
|
- Sub-Channel: {sub_channel or "Not specified"}
|
|
- Proof Type: {proof_type or "Not specified"}
|
|
|
|
Use this metadata to focus your analysis on the specific legal requirements for this channel and format.
|
|
---
|
|
|
|
Analyze the uploaded proof for legal compliance, checking:
|
|
|
|
1. **Financial Promotion Detection**:
|
|
- Does this marketing material qualify as a financial promotion?
|
|
- A financial promotion is any communication that invites or induces a person to engage in investment activity or relates to financial products/services
|
|
- Look for: interest rates, APR, loan offers, investment products, credit cards with financial terms, savings rates, etc.
|
|
- If detected, set isFinancialPromotion to true and provide a clear reason
|
|
|
|
2. **Advertising Standards**:
|
|
- Are all claims substantiated and not misleading?
|
|
- Are comparisons fair and verifiable?
|
|
- Are there any potentially deceptive practices?
|
|
|
|
3. **Required Disclaimers**:
|
|
- Are all necessary disclaimers present?
|
|
- Are disclaimers legible and appropriately placed?
|
|
- Do disclaimers meet minimum size requirements?
|
|
|
|
4. **Regulatory Compliance**:
|
|
- Does the content comply with FCA regulations (if applicable)?
|
|
- Are there any ASA/CAP code issues?
|
|
- Is representative APR shown where required?
|
|
|
|
5. **Terms and Conditions**:
|
|
- Are T&Cs referenced where necessary?
|
|
- Is the qualifying text clear and not hidden?
|
|
- Are important limitations disclosed?
|
|
|
|
6. **Third-Party Content**:
|
|
- Are proper permissions/attributions in place for third-party content?
|
|
- Are testimonials genuine and substantiated?
|
|
- Are celebrity/influencer relationships disclosed?
|
|
|
|
IMPORTANT: You must include the following fields in your JSON response:
|
|
- ragStatus: "Green", "Amber", or "Red"
|
|
- feedback: Your detailed analysis
|
|
- issues: Array of specific issues found
|
|
- isFinancialPromotion: true or false
|
|
- financialPromotionReason: String explaining why it is/isn't a financial promotion (required if isFinancialPromotion is true)
|
|
|
|
RAG Status Guidelines:
|
|
- **Green**: Fully compliant with legal requirements, no issues
|
|
- **Amber**: Minor issues that need attention but are not critical
|
|
- **Red**: Significant legal or regulatory issues that must be addressed
|
|
|
|
If the proof is nonsensical, not a marketing material, or cannot be analyzed, set analysisStatus to 'low_confidence'.
|
|
|
|
**Response Format:**
|
|
- Keep feedback brief and scannable
|
|
- Use bullet points for each finding
|
|
- Structure each feedback bullet as two clearly labelled parts separated by a line break:
|
|
**Issue:** [Clear description of what's wrong]
|
|
**Recommendation:** [Actionable fix — what to do and how]
|
|
- Always capitalise "Issue:" and "Recommendation:" and bold them with double asterisks (**)
|
|
- Always place the Recommendation on a new line after the Issue (not on the same line)
|
|
- Example:
|
|
"• **Issue:** The 'save more' claim is a comparative claim without a defined baseline.
|
|
**Recommendation:** Clarify what the customer is saving against (e.g. standard interest rates)."
|
|
- Do NOT include page numbers, document names, or source citations (e.g., no "Page 10", "per the Legal Guidelines"). Feedback must be self-contained and actionable.
|
|
- IMPORTANT: Use British English spelling throughout all output (e.g. "authorised" not "authorized", "colour" not "color", "capitalise" not "capitalize", "organised" not "organized", "centre" not "center", "analysed" not "analyzed").
|
|
- IMPORTANT: Never use the words "violation", "violates", or "violated" in your output. Use constructive alternatives such as "issue", "doesn't align with", "doesn't meet", or "conflicts with".
|
|
- IMPORTANT: Use Plain English throughout. Choose simple, clear words over complex vocabulary. Prefer: "add" over "incorporate/integrate", "about" over "regarding", "qualifies as" over "constitutes", "use" over "utilise", "before" over "prior to", "to" over "in order to", "try" over "endeavour", "then" over "subsequently", "put in place" over "implement", "keep/contain" over "constrain", "standard interest rate" over "reversion rate". Avoid unnecessary jargon (e.g. use "exaggerated claim" instead of "puffery"). Feedback should be easy to understand for all users.
|
|
- IMPORTANT: Apply consistent punctuation and capitalisation throughout:
|
|
(a) Always capitalise the first word after a full stop, including labels like "Recommendation:" and "Issue:".
|
|
(b) End every bullet point with a full stop if it is a complete sentence. If bullets are short fragments, omit the full stop — but be consistent within the same output.
|
|
(c) Write "e.g." with no comma after it (e.g. "Apply rotation" not "e.g., Apply rotation").
|
|
- IMPORTANT: When providing example corrections in recommendations, always show the example in the format you are recommending. If recommending sentence case, write the example in sentence case (e.g. "Apply now" not "Apply Now"). If quoting the user's original error, show it first, then the corrected version: "Change 'Apply Now' to 'Apply now' (sentence case)."
|
|
- IMPORTANT: Always spell out acronyms in full on first use, with the abbreviation in parentheses. Use the short form only for subsequent mentions within the same output. Common acronyms to expand include: WCAG (Web Content Accessibility Guidelines), FSCS (Financial Services Compensation Scheme), GDE (Global Digital Expression), APR (Annual Percentage Rate), CTA (Call-to-Action), FCA (Financial Conduct Authority), PRA (Prudential Regulation Authority), T&Cs (Terms and Conditions). Apply this rule to any acronym, not only those listed here.
|
|
"""
|
|
|
|
# Determine if revision fields should be included
|
|
include_revision_fields = previous_review is not None
|
|
|
|
# Use single-image or multi-image analysis depending on input
|
|
if len(images) == 1:
|
|
file_data, file_type = images[0]
|
|
return await self.gemini.analyze_with_image(
|
|
prompt, file_data, file_type, include_revision_fields=include_revision_fields, on_fallback=on_fallback
|
|
)
|
|
else:
|
|
return await self.gemini.analyze_with_images(
|
|
prompt, images, include_revision_fields=include_revision_fields, on_fallback=on_fallback
|
|
)
|