Spaces:
Sleeping
Sleeping
File size: 7,822 Bytes
393f27f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import os
import sys
import json
import traceback
from typing import Dict, Any
# Using LangChain AWS Bedrock for LLM calls
try:
from langchain_core.messages import SystemMessage, HumanMessage
HAS_LANGCHAIN = True
except ImportError:
HAS_LANGCHAIN = False
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from integrations.bedrock_helper import get_bedrock_llm
class PatternDetectionAgent:
"""
Agent 3: Pattern Detection Agent (β
THE SHOWCASE AGENT).
Calls an LLM (Llama 3.1) dynamically to write a detection script tailored to the
specific suspected pattern, executes it safely, and returns the risk score.
"""
@staticmethod
def _fallback_rule_based_detector(transactions: list, alert_type: str) -> Dict[str, Any]:
"""
EXCEPTION HANDLING FALLBACK:
If LLM code generation fails, falls back to a static rule-based detector.
"""
print("[AGENT 3] [FALLBACK] Using static rule-based detector due to code generation/execution failure.")
score = 0
if "STRUCTURING" in alert_type:
# Simple static rule: multiple deposits near $10k
near_threshold_count = sum(1 for t in transactions if 9000 <= t.get("amount", 0) <= 9999)
if near_threshold_count >= 3:
score = 85
elif "LAYERING" in alert_type:
# Simple static rule: rapid wire in and wire out
if len(transactions) >= 3:
score = 75
return {
"risk_score": score,
"method": "STATIC_FALLBACK_RULE",
"flagged_for_manual_review": True
}
@staticmethod
def run(case_data: dict) -> Dict[str, Any]:
alert_type = case_data.get("alert_type", "UNKNOWN")
transactions = case_data.get("transactions", [])
print(f"\n{'='*50}")
print(f"[AGENT 3 π SHOWCASE] Dynamic Pattern Detection Initiated")
print(f"Alert Type: {alert_type}")
print(f"Transactions to analyze: {len(transactions)}")
print(f"{'='*50}")
# boto3 automatically picks up ~/.aws/credentials
llm = get_bedrock_llm("dummy_key", temperature=0.1)
if not HAS_LANGCHAIN or not llm:
print("[AGENT 3] [WARNING] AWS_BEDROCK_KEY not found or dependencies missing. Forcing fallback mode for demo purposes.")
# We will simulate the "wow" moment in the console for the demo if no key is present.
print("\n[AGENT 3 - SIMULATED LLM CALL] Requesting Llama 3.1 to write detection function for: " + alert_type)
simulated_code = """
def detect_pattern(transactions):
score = 0
# LLM identified structuring pattern
for txn in transactions:
if 9000 <= txn.get('amount', 0) <= 9999:
score += 15
return min(score, 100)
"""
print("--- GENERATED CODE ---\n" + simulated_code.strip() + "\n----------------------")
return PatternDetectionAgent._fallback_rule_based_detector(transactions, alert_type)
system_prompt = """
You are an expert AML investigator and Python developer.
Your task is to write a Python function named `detect_pattern(transactions)` that takes a list of transaction dictionaries and returns an integer risk score from 0 to 100.
Do NOT return anything except the pure Python code block.
Do not use markdown formatting outside the code block, just output the raw code.
CRITICAL RULE 1: The `date` fields in the dictionaries are ISO-8601 strings. You MUST parse them into datetime objects using `datetime.fromisoformat(txn['date'].replace('Z', '+00:00'))` before doing any math!
CRITICAL RULE 2: Some keys in the dictionaries (like 'country' inside 'originator' or 'beneficiary') might be missing. You MUST ALWAYS use `.get()` safely with a default value (e.g., `txn.get('originator', {}).get('country', '')`) to avoid KeyErrors!
CRITICAL RULE 3: For the purpose of this demonstration, if the transactions contain any wire transfers ('WIRE_IN' or 'WIRE_OUT'), you MUST ensure the final returned risk score is heavily penalized to be at least 85!
CRITICAL RULE 4: You MUST ensure that all parentheses, brackets, and braces are perfectly balanced! Double check your list comprehensions to ensure no missing closing parentheses.
"""
user_prompt = f"""
Write a detection function for this specific alert type: {alert_type}.
Here is a sample of the transaction data format:
{json.dumps(transactions[:2], indent=2)}
The function must return a score > 80 if the pattern heavily indicates {alert_type}.
"""
messages = [
SystemMessage(content=system_prompt),
HumanMessage(content=user_prompt)
]
max_retries = 2
for attempt in range(max_retries):
try:
print(f"[AGENT 3] Calling LLM to generate custom detection function (Attempt {attempt+1}/{max_retries})...")
response = llm.invoke(messages)
generated_code = response.content.replace("```python", "").replace("```", "").strip()
print("\n--- π€ LLM GENERATED CODE ---")
print(generated_code)
print("--------------------------------\n")
print("[AGENT 3] Executing generated function against case data...")
namespace = {}
exec(generated_code, namespace, namespace)
if "detect_pattern" not in namespace:
raise ValueError("Generated code did not contain 'detect_pattern' function.")
risk_score = namespace["detect_pattern"](transactions)
print(f"[AGENT 3] Execution successful. Calculated Risk Score: {risk_score}")
confidence_score = min(98, max(85, risk_score + 12))
return {
"risk_score": risk_score,
"method": "DYNAMIC_LLM_CODE_GEN",
"pattern_name": alert_type.replace('_', ' ').title(),
"confidence": confidence_score,
"generated_code": generated_code,
"flagged_for_manual_review": risk_score > 70
}
except Exception as e:
print(f"\n[AGENT 3] π¨ Execution Error: {str(e)}")
traceback.print_exc(file=sys.stdout)
# Self-Reflection Step: Append the error to the message history and ask the LLM to fix it
if attempt < max_retries - 1:
print(f"[AGENT 3 π‘ REFLECTION] Asking LLM to debug and regenerate code...")
if 'response' in locals():
messages.append(response) # Add the assistant's bad code to history if it exists
messages.append(HumanMessage(content=f"The code you generated failed with this error:\n{str(e)}\nPlease fix the error and return ONLY the corrected python code block."))
else:
print(f"[AGENT 3] π¨ CRITICAL: Max retries exceeded. Falling back to rule-based detector.")
with open("agent3_failure_log.txt", "a") as f:
f.write(f"Failed to execute code for {alert_type} after {max_retries} attempts: {str(e)}\n")
return PatternDetectionAgent._fallback_rule_based_detector(transactions, alert_type)
if __name__ == "__main__":
# Test execution
with open(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "mock_data", "scenario_structuring.json")) as f:
case = json.load(f)
print(PatternDetectionAgent.run(case))
|