anil988's picture
Include agents code without heavy binaries
393f27f
Raw History Blame Contribute Delete
7.82 kB
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))